blob: a32fff53ed815a49c84edbb2a89496f0c0a0ca40 [file] [log] [blame]
#!/usr/bin/env python
# utils/update-checkout - Utility to update your local checkouts -*- python -*-
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
from __future__ import print_function
import argparse
import json
import os
import sys
from functools import reduce
sys.path.append(os.path.dirname(__file__))
from SwiftBuildSupport import (
SWIFT_SOURCE_ROOT,
) # noqa (E402 module level import not at top of file)
SCRIPT_FILE = os.path.abspath(__file__)
SCRIPT_DIR = os.path.dirname(SCRIPT_FILE)
sys.path.append(os.path.join(SCRIPT_DIR, 'swift_build_support'))
from swift_build_support import shell # noqa (E402)
def update_single_repository(repo_path, branch, reset_to_remote, should_clean):
if not os.path.isdir(repo_path):
return
print("--- Updating '" + repo_path + "' ---")
with shell.pushd(repo_path, dry_run=False, echo=False):
shell.call(["git", "fetch"], echo=True)
if should_clean:
shell.call(['git', 'clean', '-fdx'],
echo=True)
shell.call(['git', 'reset', '--hard', 'HEAD'],
echo=True)
if branch:
status = shell.capture(['git', 'status', '--porcelain', '-uno'],
echo=False)
if status:
print("Please, commit your changes.")
print(status)
exit(1)
shell.call(['git', 'checkout', branch], echo=True)
# If we were asked to reset to the specified branch, do the hard
# reset and return.
if reset_to_remote:
shell.call(['git', 'reset', '--hard', "origin/%s" % branch],
echo=True)
return
# Prior to Git 2.6, this is the way to do a "git pull
# --rebase" that respects rebase.autostash. See
# http://stackoverflow.com/a/30209750/125349
shell.call(["git", "rebase", "FETCH_HEAD"], echo=True)
shell.call(["git", "submodule", "update", "--recursive"],
echo=True)
def update_all_repositories(args, config, scheme_name):
repo_branch = scheme_name
for repo_name in config['repos'].keys():
if repo_name in args.skip_repository_list:
print("--- Skipping '" + repo_name + "' ---")
continue
if scheme_name:
# This loop is only correct, since we know that each alias set has
# unique contents. This is checked by verify config. Thus the first
# branch scheme data that has scheme_name as one of its aliases is
# the only possible correct answer.
for v in config['branch-schemes'].values():
if scheme_name not in v['aliases']:
continue
repo_branch = v['repos'][repo_name]
break
update_single_repository(os.path.join(SWIFT_SOURCE_ROOT, repo_name),
repo_branch,
args.reset_to_remote,
args.clean)
def obtain_additional_swift_sources(
config, with_ssh, scheme_name, skip_history, skip_repository_list):
with shell.pushd(SWIFT_SOURCE_ROOT, dry_run=False,
echo=False):
for repo_name, repo_info in config['repos'].items():
if repo_name in skip_repository_list:
print("--- Skipping '" + repo_name + "' ---")
continue
if os.path.isdir(os.path.join(repo_name, ".git")):
continue
print("--- Cloning '" + repo_name + "' ---")
# If we have a url override, use that url instead of
# interpolating.
remote_repo_info = repo_info['remote']
if 'url' in remote_repo_info:
remote = remote_repo_info['url']
else:
remote_repo_id = remote_repo_info['id']
if with_ssh is True or 'https-clone-pattern' not in config:
remote = config['ssh-clone-pattern'] % remote_repo_id
else:
remote = config['https-clone-pattern'] % remote_repo_id
if skip_history:
shell.call(['git', 'clone', '--recursive', '--depth', '1',
remote, repo_name], echo=True)
else:
shell.call(['git', 'clone', '--recursive', remote,
repo_name], echo=True)
if scheme_name:
for v in config['branch-schemes'].values():
if scheme_name not in v['aliases']:
continue
repo_branch = v['repos'][repo_name]
break
else:
repo_branch = scheme_name
src_path = os.path.join(SWIFT_SOURCE_ROOT, repo_name,
".git")
shell.call(['git', '--git-dir', src_path, '--work-tree',
os.path.join(SWIFT_SOURCE_ROOT, repo_name),
'checkout', repo_branch], echo=False)
with shell.pushd(os.path.join(SWIFT_SOURCE_ROOT, repo_name),
dry_run=False, echo=False):
shell.call(["git", "submodule", "update", "--recursive"],
echo=False)
def validate_config(config):
# Make sure that our branch-names are unique.
scheme_names = config['branch-schemes'].keys()
if len(scheme_names) != len(set(scheme_names)):
raise RuntimeError('Configuration file has duplicate schemes?!')
# Then make sure the alias names used by our branches are unique.
#
# We do this by constructing a list consisting of len(names),
# set(names). Then we reduce over that list summing the counts and taking
# the union of the sets. We have uniqueness if the length of the union
# equals the length of the sum of the counts.
data = [(len(v['aliases']), set(v['aliases']))
for v in config['branch-schemes'].values()]
result = reduce(lambda acc, x: (acc[0] + x[0], acc[1] | x[1]), data,
(0, set([])))
if result[0] == len(result[1]):
return
raise RuntimeError('Configuration file has schemes with duplicate '
'aliases?!')
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""
repositories.
By default, updates your checkouts of Swift, SourceKit, LLDB, and SwiftPM.""")
parser.add_argument(
"--clone",
help="Obtain Sources for Swift and Related Projects",
action="store_true")
parser.add_argument(
"--clone-with-ssh",
help="Obtain Sources for Swift and Related Projects via SSH",
action="store_true")
parser.add_argument(
"--skip-history",
help="Skip histories when obtaining sources",
action="store_true")
parser.add_argument(
"--skip-repository",
metavar="DIRECTORY",
default=[],
help="Skip the specified repository",
dest='skip_repository_list',
action="append")
parser.add_argument(
"--branch",
help='Obtain Sources for specific branch',
metavar='BRANCH',
dest='scheme')
parser.add_argument(
'--reset-to-remote',
help='Reset each branch to the remote state.',
action='store_true')
parser.add_argument(
'--clean',
help='Clean unrelated files from each repository.',
action='store_true')
parser.add_argument(
"--config",
default=os.path.join(SCRIPT_DIR, "update-checkout-config.json"),
help="Configuration file to use")
args = parser.parse_args()
clone = args.clone
clone_with_ssh = args.clone_with_ssh
skip_history = args.skip_history
scheme = args.scheme
with open(args.config) as f:
config = json.load(f)
validate_config(config)
if clone or clone_with_ssh:
# If branch is None, default to using the default branch alias
# specified by our configuration file.
if scheme is None:
scheme = config['default-branch-scheme']
obtain_additional_swift_sources(
config, clone_with_ssh, scheme, skip_history,
args.skip_repository_list)
update_all_repositories(args, config, scheme)
return 0
if __name__ == "__main__":
sys.exit(main())