#!/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 sys import urllib.parse 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") sys.exit(1) def get_config_path(): ret = f"{get_topdir()}/.gitreview" return ret if os.path.exists(ret) else None def exit_gerrit_host_project_unknown(): print("ERROR: failed to get gerrit host and project") print("Consider adding a .gitreview file:") print(" [gerrit]") print(" host=gerrit.osmocom.org") print(" project=…") sys.exit(1) def get_config_from_origin_url(): url = subprocess.run( ["git", "config", "--get", "remote.origin.url"], check=True, capture_output=True, encoding="UTF-8", ).stdout.rstrip() if not url.startswith("https://gerrit.osmocom.org/"): exit_gerrit_host_project_unknown() host = url.split("/")[2] project = "/".join(url.split("/")[3:]) return host, project def get_config(): config_path = get_config_path() if not config_path: return get_config_from_origin_url() 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: sys.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: sys.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: sys.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)