diff --git a/README.rst b/README.rst index b64b46b..f50b1cc 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,104 @@ A git command for fixing nit-picky changes on gerrit reviews. git-nit is a tool that helps grabbing existing reviews on gerrit and layering on a new patch to fix nits. +Installing +========== + +Install git-nit with pip:: + + $ pip install --user git-nit + +Using +===== + +To clone a patch to a local working directory, pass the URL of the +patch as the first argument. + +:: + + $ git-nit https://review.openstack.org/#/c/564559/ + release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it + Cloning openstack-infra/release-tools into ./release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it + git clone git://git.openstack.org/openstack-infra/release-tools release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it + Cloning into 'release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it'... + remote: Counting objects: 2320, done. + remote: Compressing objects: 100% (995/995), done. + remote: Total 2320 (delta 1491), reused 2109 (delta 1312) + Receiving objects: 100% (2320/2320), 2.72 MiB | 1.50 MiB/s, done. + Resolving deltas: 100% (1491/1491), done. + Checking connectivity... done. + + Configuring git-review + git review -s + Creating a git remote called 'gerrit' that maps to: + ssh://doug-hellmann@review.openstack.org:29418/openstack-infra/release-tools.git + + Downloading https://review.openstack.org/#/c/564559/ + git review -d 564559 + Downloading refs/changes/59/564559/2 from gerrit + Switched to branch "review/doug_hellmann/announce-script-fixes" + + Updating all remotes + git remote update + Fetching origin + remote: Counting objects: 1501, done. + remote: Compressing objects: 100% (659/659), done. + remote: Total 1501 (delta 842), reused 1501 (delta 842) + Receiving objects: 100% (1501/1501), 218.28 KiB | 0 bytes/s, done. + Resolving deltas: 100% (842/842), done. + From git://git.openstack.org/openstack-infra/release-tools + * [new ref] refs/notes/review -> refs/notes/review + Fetching gerrit + + Patch ready in ./release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it + +The URL argument can use the /#/c "fragment" form or it can use the +simplified form ``https://review.openstack.org/564559/``. + +It can also include a patchset number if the goal is to download a +draft older than the most recent patchset. + +:: + + $ git-nit https://review.openstack.org/#/c/564559/1/ + release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it + Cloning openstack-infra/release-tools into ./release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it + git clone git://git.openstack.org/openstack-infra/release-tools release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it + Cloning into 'release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it'... + remote: Counting objects: 2320, done. + remote: Compressing objects: 100% (991/991), done. + remote: Total 2320 (delta 1494), reused 2111 (delta 1316) + Receiving objects: 100% (2320/2320), 2.72 MiB | 2.23 MiB/s, done. + Resolving deltas: 100% (1494/1494), done. + Checking connectivity... done. + + Configuring git-review + git review -s + Creating a git remote called 'gerrit' that maps to: + ssh://doug-hellmann@review.openstack.org:29418/openstack-infra/release-tools.git + + Downloading https://review.openstack.org/#/c/564559/1/ + git review -d 564559,1 + Downloading refs/changes/59/564559/1 from gerrit + Switched to branch "review/doug_hellmann/announce-script-fixes-patch1" + + Updating all remotes + git remote update + Fetching origin + remote: Counting objects: 1501, done. + remote: Compressing objects: 100% (659/659), done. + remote: Total 1501 (delta 842), reused 1501 (delta 842) + Receiving objects: 100% (1501/1501), 218.18 KiB | 0 bytes/s, done. + Resolving deltas: 100% (842/842), done. + From git://git.openstack.org/openstack-infra/release-tools + * [new ref] refs/notes/review -> refs/notes/review + Fetching gerrit + + Patch ready in ./release-tools-564559-finish-moving-announce.sh-to-releases-repo-by-deleting-it + +Resources +========= + * Free software: Apache license * Documentation: http://docs.openstack.org/git-nit/latest/ * Source: https://git.openstack.org/cgit/openstack/git-nit diff --git a/git_nit/cmd.py b/git_nit/cmd.py index d56e14b..7eb78fa 100644 --- a/git_nit/cmd.py +++ b/git_nit/cmd.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,10 +15,14 @@ from __future__ import print_function import argparse +import json import os +import subprocess +import sys +import urllib import pkg_resources -from six.moves import urllib +import requests def get_version(): @@ -27,6 +31,19 @@ def get_version(): return provider.version +def decode_json(raw): + "Trap JSON decoding failures and provide more detailed errors" + + # Gerrit's REST API prepends a JSON-breaker to avoid XSS vulnerabilities + if raw.text.startswith(")]}'"): + trimmed = raw.text[4:] + else: + trimmed = raw.text + + decoded = json.loads(trimmed) + return decoded + + def parse_review_id(review_id): "Given a review URL or ID return the review number and PS number, if any." parsed = urllib.parse.urlparse(review_id) @@ -54,6 +71,16 @@ def parse_review_id(review_id): return (review, patchset) +def get_review_data(review_id): + "Return what gerrit knows about the review." + parsed = urllib.parse.urlparse(review_id) + gerrit_url = '{}://{}'.format(parsed.scheme, parsed.netloc) + review, patchset = parse_review_id(review_id) + change_url = '{}/changes/{}'.format(gerrit_url, review) + response = requests.get(change_url) + return decode_json(response) + + def main(): parser = argparse.ArgumentParser() parser.add_argument( @@ -74,8 +101,77 @@ def main(): ) args = parser.parse_args() + data = get_review_data(args.review) review, patchset = parse_review_id(args.review) - print(review, patchset) + + repo = data.get('project', '') + short_repo = repo.rsplit('/', 1)[-1] + if not repo: + raise ValueError('Could not determine the repository') + + subject = data.get('subject', '') + for old, new in [(' ', '-'), (':', ''), ("'", ''), ('"', '')]: + subject = subject.replace(old, new) + + clone_to = '{}-{}-{}'.format(short_repo, review, subject) + print(clone_to) + + output_dir = os.path.join(args.project_dir, clone_to) + if os.path.exists(output_dir): + sys.exit('{} already exists'.format(output_dir)) + + if not os.path.exists(args.project_dir): + print('Creating project directory {}'.format(args.project_dir)) + os.makedirs(args.project_dir) + + git_cmd = [ + 'git', + 'clone', + 'git://git.openstack.org/{}'.format(repo), + clone_to, + ] + if args.project_dir != '.': + cwd = args.project_dir + else: + cwd = None + print('Cloning {} into {}'.format(repo, output_dir)) + print(' '.join(git_cmd)) + subprocess.run(git_cmd, cwd=cwd, check=True) + + git_cmd = [ + 'git', + 'review', + '-s', + ] + print('\nConfiguring git-review') + print(' '.join(git_cmd)) + subprocess.run(git_cmd, cwd=output_dir, check=True) + + git_cmd = [ + 'git', + 'review', + '-d', + ] + if patchset is not None: + target = '{},{}'.format(review, patchset) + else: + target = review + git_cmd.append(target) + print('\nDownloading {}'.format(args.review)) + print(' '.join(git_cmd)) + subprocess.run(git_cmd, cwd=output_dir, check=True) + + git_cmd = [ + 'git', + 'remote', + 'update', + ] + print('\nUpdating all remotes') + print(' '.join(git_cmd)) + subprocess.run(git_cmd, cwd=output_dir, check=True) + + print('\nPatch ready in {}'.format(output_dir)) + if __name__ == '__main__': main()