| #!/usr/bin/env python3 |
| # SPDX-License-Identifier: GPL-3.0-or-later |
| # "git review download", like git review -d, but without need for ssh key. Run |
| # this script inside a git repository with the gerrit review ID as argument to |
| # fetch and checkout the patch. |
| import argparse |
| import configparser |
| import json |
| import os |
| import subprocess |
| import urllib.request |
| |
| |
| def get_topdir(): |
| try: |
| return subprocess.run(["git", "rev-parse", "--show-toplevel"], |
| check=True, capture_output=True, |
| encoding="UTF-8").stdout.rstrip() |
| except subprocess.CalledProcessError: |
| print("ERROR: not running inside a git repository") |
| exit(1) |
| |
| |
| def get_config_path(): |
| ret = f"{get_topdir()}/.gitreview" |
| if not os.path.exists(ret): |
| print(f"ERROR: config not found: {ret}") |
| exit(1) |
| return ret |
| |
| |
| def get_config(): |
| config_path = get_config_path() |
| config = configparser.ConfigParser() |
| config.read(config_path) |
| return config["gerrit"]["host"], config["gerrit"]["project"] |
| |
| |
| def get_gerrit_details(host, project, patch_id, verbose): |
| url = f"https://{host}/changes/{project}~{args.patch_id}/detail" |
| print(f"Download {url}") |
| with urllib.request.urlopen(url) as response: |
| content = response.read().decode() |
| content = "{" + content.split("{", 1)[1] |
| ret = json.loads(content) |
| if args.verbose: |
| print(json.dumps(ret, indent=4)) |
| return ret |
| |
| |
| def get_highest_revision(details): |
| ret = 1 |
| for message in details["messages"]: |
| rev = message["_revision_number"] |
| if rev > ret: |
| ret = rev |
| return ret |
| |
| |
| def git_fetch(host, project, patch_id, rev): |
| last_digits = str(patch_id)[-2:] |
| url = f"https://{host}/{project}" |
| ref = f"refs/changes/{last_digits}/{patch_id}/{rev}" |
| cmd = ["git", "fetch", url, ref] |
| print(f"+ {' '.join(cmd)}") |
| |
| try: |
| return subprocess.run(cmd, check=True) |
| except subprocess.CalledProcessError: |
| exit(1) |
| |
| def git_cherry_pick_fetch_head(): |
| cmd = ["git", "cherry-pick", "FETCH_HEAD"]; |
| print(f"+ {' '.join(cmd)}") |
| |
| try: |
| return subprocess.run(cmd, check=True) |
| except subprocess.CalledProcessError: |
| exit(1) |
| |
| def git_checkout_fetch_head(patch_id, rev): |
| cmd = ["git", "checkout", "-B", f"gerrit/{patch_id}_{rev}", "FETCH_HEAD"] |
| print(f"+ {' '.join(cmd)}") |
| |
| try: |
| return subprocess.run(cmd, check=True) |
| except subprocess.CalledProcessError: |
| exit(1) |
| |
| |
| desc = "git review download: fetch and checkout a patch from gerrit" |
| parser = argparse.ArgumentParser(description=desc) |
| parser.add_argument("patch_id", type=int, |
| help="gerrit review ID") |
| parser.add_argument("-c", "--cherry-pick", action="store_true", |
| help="cherry-pick into current branch instead of " |
| "fetching into a branch") |
| parser.add_argument("-r", "--revision", type=int, |
| help="patchset revision, default is latest") |
| parser.add_argument("-v", "--verbose", action="store_true") |
| args = parser.parse_args() |
| |
| host, project = get_config() |
| rev = args.revision |
| |
| if not rev: |
| details = get_gerrit_details(host, project, args.patch_id, args.verbose) |
| rev = get_highest_revision(details) |
| |
| git_fetch(host, project, args.patch_id, rev) |
| if args.cherry_pick: |
| git_cherry_pick_fetch_head() |
| else: |
| git_checkout_fetch_head(args.patch_id, rev) |