| #!/usr/bin/python2 |
| """Syncs to a given Cobalt build id. |
| |
| Syncs current gclient instance to a given build id, as |
| generated by "build_id.py" and stored on carbon-airlock-95823. |
| """ |
| |
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| |
| import argparse |
| import json |
| import os |
| import shutil |
| import subprocess |
| import sys |
| import requests |
| |
| _BUILD_ID_QUERY_URL = ( |
| "https://carbon-airlock-95823.appspot.com/build_version/search") |
| _BUILD_ID_QUERY_PARAMETER_NAME = "build_number" |
| |
| |
| class SubprocessFailedException(Exception): |
| """Exception for non-zero subprocess exits.""" |
| |
| def __init__(self, command): |
| super(SubprocessFailedException, self).__init__() # pylint: disable=super-with-arguments |
| self.command = command |
| |
| def __str__(self): |
| return "Subprocess failed '{0}'".format(self.command) |
| |
| |
| def _RunGitCommand(gitargs, **kwargs): |
| """Runs a git command with "gitargs", returning the output splitlines(). |
| |
| Args: |
| gitargs: Commandline args that follow 'git'. |
| **kwargs: Keyword args for Popen. |
| |
| Returns: |
| All of stdout, as an array of lines. |
| |
| Raises: |
| SubprocessFailedException: if the exit code is nonzero. |
| |
| """ |
| result_tuple = _RunGitCommandReturnExitCode(gitargs, **kwargs) |
| if result_tuple[0] != 0: |
| raise SubprocessFailedException(" ".join(["git"] + gitargs)) |
| return result_tuple[1] |
| |
| |
| def _RunGitCommandReturnExitCode(gitargs, **kwargs): |
| """Runs a git command with "gitargs", returning the exit code and output. |
| |
| Args: |
| gitargs: Commandline args that follow 'git'. |
| **kwargs: Keyword args for Popen. |
| |
| Returns: |
| Tuple of (exit code, all of stdout as an array of lines). |
| |
| """ |
| popen_args = ["git"] + gitargs |
| with subprocess.Popen(popen_args, stdout=subprocess.PIPE, **kwargs) as p: |
| output = p.stdout.read().splitlines() |
| return p.wait(), output |
| |
| |
| def main(): |
| dev_null = open(os.devnull, "w") # pylint: disable=consider-using-with |
| arg_parser = argparse.ArgumentParser( |
| description="Syncs to a given Cobalt build id") |
| arg_parser.add_argument("buildid", nargs=1) |
| arg_parser.add_argument( |
| "--force", |
| default=False, |
| action="store_true", |
| help="Deletes directories that don't match the requested format.") |
| args = arg_parser.parse_args() |
| r = requests.get( |
| _BUILD_ID_QUERY_URL, |
| params={_BUILD_ID_QUERY_PARAMETER_NAME: args.buildid[0]}) |
| if not r.ok: |
| print( |
| "HTTP request failed\n{0} {1}\n{2}".format(r.status_code, r.reason, |
| r.text), |
| file=sys.stderr) |
| return 1 |
| # The response starts with a security-related close expression line |
| outer_json = json.loads(r.text.splitlines()[1]) |
| hashes = json.loads(outer_json["deps"]) |
| git_root = os.getcwd() |
| |
| for relpath, rep_hash in hashes.items(): |
| path = os.path.normpath(os.path.join(git_root, relpath)) |
| if not os.path.exists(path): |
| # No warning in this case, we will attempt to clone the repository in |
| # the next pass through the repos. |
| continue |
| is_dirty = ( |
| bool( |
| _RunGitCommandReturnExitCode(["diff", "--no-ext-diff", "--quiet"], |
| cwd=path, |
| stderr=dev_null)[0]) or |
| bool( |
| _RunGitCommandReturnExitCode( |
| ["diff", "--no-ext-diff", "--quiet", "--cached"], |
| cwd=path, |
| stderr=dev_null)[0])) |
| |
| if is_dirty: |
| print("{0} is dirty, please resolve".format(relpath)) |
| return 1 |
| |
| (requested_repo, _) = rep_hash.split("@") |
| remote_url = _RunGitCommand(["config", "--get", "remote.origin.url"], |
| cwd=path)[0].strip().decode("utf-8") |
| if requested_repo.endswith(".git"): |
| if remote_url + ".git" == requested_repo: |
| print(("WARNING: You are syncing to {0} instead of {1}. While these " |
| "point to the same repo, the differing extension will cause " |
| "different build ids to be generated. If you need the same " |
| "id, you'll need to specifically clone {0} (note the .git " |
| "extension).").format(requested_repo, remote_url)) |
| remote_url += ".git" |
| |
| if remote_url != requested_repo: |
| if args.force and path != git_root: |
| shutil.rmtree(path) |
| else: |
| print(("{0} exists but does not point to the requested repo for that " |
| "path, {1}. Either replace that directory manually or run this " |
| "script with --force. --force will not try to remove the top " |
| "level repository.").format(path, requested_repo)) |
| return 1 |
| |
| for relpath, rep_hash in hashes.items(): |
| path = os.path.normpath(os.path.join(git_root, relpath)) |
| |
| # repo_hash has a repo path prefix like this: |
| # 'https://chromium.googlesource.com/chromium/llvm-project/libcxx.git |
| # @48198f9110397fff47fe7c37cbfa296be7d44d3d' |
| (requested_repo, requested_hash) = rep_hash.split("@") |
| |
| if not os.path.exists(path): |
| print("Missing path {0}, cloning from {1}.".format(path, requested_repo)) |
| try: |
| # The clone command will create all missing directories leading to the |
| # path. If the clone is successful, we continue on as usual and let |
| # the subsequent logic here checkout the appropriate git hash. |
| _RunGitCommand(["clone", "-q", requested_repo, path]) |
| except SubprocessFailedException: |
| print("There was an error cloning the repository.") |
| continue |
| |
| current_hash = _RunGitCommand(["rev-parse", "HEAD"], cwd=path)[0] |
| |
| if requested_hash == current_hash: |
| continue |
| symbolic_ref = None |
| try: |
| symbolic_ref = _RunGitCommand(["symbolic-ref", "--short", "-q", "HEAD"], |
| cwd=path, |
| stderr=dev_null)[0] |
| except SubprocessFailedException: |
| pass |
| |
| user_visible_commit = symbolic_ref if symbolic_ref else current_hash[0:7] |
| |
| print("{0} was at {1} now {2}".format(path, user_visible_commit, |
| requested_hash[0:7])) |
| |
| _RunGitCommand(["checkout", "-q", "--detach", requested_hash], cwd=path) |
| |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| try: |
| sys.exit(main()) |
| except SubprocessFailedException as ex: |
| print(str(ex), file=sys.stderr) |
| sys.exit(1) |