gerrit: move the download-gerrit-topic.py script from internal
Change-Id: I6441d5250b261f11e873c88c6a73d7aaec188844
diff --git a/download-gerrit-topic.py b/download-gerrit-topic.py
new file mode 100755
index 0000000..52596a3
--- /dev/null
+++ b/download-gerrit-topic.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+"""Download from gerrit all the changes of the specified topic. The new
+branches will be called TOPIC. If a project has multiple changes, they must
+all form a parent-child chain, and only the last child will be downloaded.
+"""
+
+import argparse
+import http
+import logging
+import subprocess
+import sys
+import tempfile
+
+from louhi.common import gerrit
+from louhi.common.utils import run
+
+logger = logging.getLogger()
+logger.setLevel(logging.INFO)
+
+
+def get_parser():
+ """Constract the command line parser."""
+ parser = argparse.ArgumentParser(description=__doc__)
+
+ parser.add_argument('-b',
+ '--branch',
+ type=str,
+ help='branch name to use, instead of TOPIC.')
+
+ parser.add_argument('--force',
+ action='store_true',
+ help='delete the branch if it already exists.')
+
+ parser.add_argument(
+ '-s',
+ '--status',
+ type=str,
+ choices=[
+ 'abandoned', 'closed', 'merged', 'new', 'pending', 'reviewed',
+ 'open'
+ ],
+ default='open',
+ help='(default: open) limit to changes with matching status; '
+ 'see also --no-status.')
+
+ parser.add_argument(
+ '--no-status',
+ dest="status",
+ action='store_const',
+ const=None,
+ help='don\'t limit to changes by status; see also --status.')
+
+ parser.add_argument('--repo-path',
+ metavar='PATH',
+ type=str,
+ default='repo',
+ help='(default: repo) path to the repo executable.')
+
+ parser.add_argument('topic', metavar='TOPIC')
+
+ return parser
+
+
+def main():
+ """The main function."""
+ args = get_parser().parse_args()
+
+ branch = args.branch if args.branch else args.topic
+
+ try:
+ git_cookie_file = run(['git', 'config', 'http.cookieFile'],
+ verbose=False).read().rstrip()
+ except subprocess.CalledProcessError:
+ logging.error('The git option http.cookieFile is not set.')
+ return 1
+
+ cookies = http.cookiejar.MozillaCookieJar()
+ with tempfile.NamedTemporaryFile('w', encoding='ascii') as temp_file:
+ # NB: MozillaCookieJar requires the file to start with the
+ # following line, so we add it in a temp file.
+ temp_file.write('# Netscape HTTP Cookie File\n')
+ with open(git_cookie_file, 'r', encoding='ascii') as cookie_file:
+ temp_file.write(cookie_file.read())
+ temp_file.flush()
+ cookies.load(temp_file.name)
+
+ changes = gerrit.query_topic(args.topic,
+ args.status,
+ access_cookie=cookies)
+ if not changes:
+ logging.error('No changes to download.')
+ return 1
+
+ branch_exists = run([args.repo_path, 'forall'] + list(changes) + [
+ '--command', f'! git rev-parse --verify "{branch}" &>/dev/null '
+ '|| echo "$REPO_PROJECT"'
+ ],
+ verbose=False).read().split('\n')[:-1]
+ if branch_exists:
+ if not args.force:
+ logging.error(
+ 'The branch %s alread exists in %s (use --force to '
+ 'overwrite it.)', branch, branch_exists)
+ return 1
+
+ logging.info('Deleting branch %s from %s', branch, branch_exists)
+ run([args.repo_path, 'forall'] + branch_exists + [
+ '--command',
+ f'if git rev-parse --verify "{branch}" &>/dev/null; then'
+ ' git checkout --detach HEAD;'
+ f' git branch --delete "{branch}" --force;'
+ 'fi'
+ ])
+
+ logging.info('Downloading changes: %s', dict(changes))
+ run([args.repo_path, 'download', f'--branch={branch}', '--verbose'] +
+ [str(part) for change in changes.items() for part in change])
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())