#!/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 import urllib.parse 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): project_q = urllib.parse.quote(project, "") url = f"https://{host}/changes/{project_q}~{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)