| #!/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 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" |
| |
| |
| def _GetGClientRoot(repo_path): |
| """Finds the root of this gclient repo, or returns repo_path if failed.""" |
| current_path = os.path.normpath(repo_path) |
| while not os.access(os.path.join(current_path, ".gclient"), os.R_OK): |
| next_path = os.path.dirname(current_path) |
| if next_path == current_path: |
| return repo_path |
| current_path = next_path |
| return current_path |
| |
| |
| class SubprocessFailedException(Exception): |
| """Exception for non-zero subprocess exits.""" |
| |
| def __init__(self, command): |
| super(SubprocessFailedException, self).__init__() |
| 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 |
| p = subprocess.Popen(popen_args, stdout=subprocess.PIPE, **kwargs) |
| output = p.stdout.read().splitlines() |
| return p.wait(), output |
| |
| |
| def main(): |
| dev_null = open("/dev/null", "w") |
| arg_parser = argparse.ArgumentParser( |
| description="Syncs to a given Cobalt build id") |
| arg_parser.add_argument("buildid", nargs=1) |
| 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"]) |
| gclient_root = _GetGClientRoot(os.getcwd()) |
| |
| for relpath, rep_hash in hashes.iteritems(): |
| path = os.path.join(gclient_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 |
| |
| for relpath, rep_hash in hashes.iteritems(): |
| path = os.path.join(gclient_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) |