src/grd: replace fetch-gerrit-patch.sh

As discussed with Neels, replace the previous version of the script that
didn't work anymore (probably due to api change from gerrit) with a new
one that works with the current api and also doesn't require unlocking
the ssh key.

Change-Id: Ie5d061323dce6843cafe49434250cc4780c8c832
diff --git a/.gitignore b/.gitignore
index 3b70a48..6aa1dd0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,8 +5,8 @@
 make-*
 make/*
 src/*
-!src/fetch-gerrit-patch.sh
 !src/gits
+!src/grd
 !src/osmo-add-gerrit-hooks.sh
 !src/README
 net/*
diff --git a/src/README b/src/README
index f066561..09b8054 100644
--- a/src/README
+++ b/src/README
@@ -7,7 +7,7 @@
 	gerrit commit-msg hook in each one. This requires an ~/.ssh/config
 	entry, see top comment in the script.
 
- fetch-gerrit-patch.sh
+ grd
 	Pass a patch number seen on gerrit to fetch the latest patch set into
 	your git clone. See top comment in the script.
 
@@ -34,22 +34,23 @@
 -----------------------------------------------------------------------------
 
 
-cd osmo-msc
-../fetch-gerrit-patch.sh 3787
-+ git fetch origin refs/changes/87/3787/2
-From ssh://go/osmo-msc
- * branch            refs/changes/87/3787/2 -> FETCH_HEAD
-+ git checkout -b 3787_2 FETCH_HEAD
-Switched to a new branch '3787_2'
+$ cd osmo-msc
+$ ../grd 3787
+Download https://gerrit.osmocom.org/changes/osmo-msc~3787/detail
++ git fetch https://gerrit.osmocom.org/osmo-msc refs/changes/87/3787/5
+From https://gerrit.osmocom.org/osmo-msc
+ * branch                refs/changes/87/3787/5 -> FETCH_HEAD
++ git checkout -B gerrit/3787_5 FETCH_HEAD
+Switched to a new branch 'gerrit/3787_5'
 
 
 # or if you want an earlier patch set
-../fetch-gerrit-patch.sh 3787/1
-From ssh://go/osmo-msc
- * branch            refs/changes/87/3787/1 -> FETCH_HEAD
-+ git checkout -b 3787_1 FETCH_HEAD
-Switched to a new branch '3787_1'
-
+$ ../grd 3787 -r1
++ git fetch https://gerrit.osmocom.org/osmo-msc refs/changes/87/3787/1
+From https://gerrit.osmocom.org/osmo-msc
+ * branch                refs/changes/87/3787/1 -> FETCH_HEAD
++ git checkout -B gerrit/3787_1 FETCH_HEAD
+Switched to a new branch 'gerrit/3787_1'
 
 -----------------------------------------------------------------------------
 
diff --git a/src/fetch-gerrit-patch.sh b/src/fetch-gerrit-patch.sh
deleted file mode 100755
index 0353461..0000000
--- a/src/fetch-gerrit-patch.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/sh
-# fetch gerrit patch into new branch named like the patch number.
-#
-# Usage: go to a git clone and pass a patch number:
-#
-#   cd osmo-msc
-#   P 973
-# or
-#   P 973/2
-#
-# Will create new local branches '973_4' (if 4 is the latest patch set)
-# or '973_2', respectively.
-
-patch="$1"
-
-if [ -z "$patch" ]; then
-  echo "Usage: $0 1234[/5]"
-  exit 1
-fi
-
-if [ -z "$(echo "$patch" | grep '/')" ]; then
-  patch="/$patch/"
-fi
-
-if [ -z "$(echo "$patch" | grep '^/')" ]; then
-  patch="/$patch"
-fi
-
-last_set="$(git ls-remote origin "changes/*" | grep "$patch" | sed 's#.*/\([^/]*\)$#\1 &#' | sort -n | tail -n 1)"
-if [ -z "$last_set" ]; then
-  echo "Not found: $patch"
-  exit 1
-fi
-
-change_name="$(echo "$last_set" | sed 's/.*\(refs.*\)/\1/')"
-branch_name="$(echo "$change_name" | sed 's#refs/changes/../\([0-9]*\)/\([0-9]*\)#\1_\2#')"
-
-set -x
-git fetch origin "$change_name"
-git checkout -b "$branch_name" FETCH_HEAD
-
diff --git a/src/grd b/src/grd
new file mode 100755
index 0000000..b1623ec
--- /dev/null
+++ b/src/grd
@@ -0,0 +1,100 @@
+#!/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_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("-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)
+git_checkout_fetch_head(args.patch_id, rev)