From f844901e9045f0a1f4970695abeeca7955b9f53d Mon Sep 17 00:00:00 2001 From: Simon Percivall Date: Mon, 24 Sep 2012 15:02:48 +0200 Subject: [PATCH 1/5] support ssh ports. --- git_change/git.py | 6 ++++-- git_change/git_change.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/git_change/git.py b/git_change/git.py index 179de0f..9d819aa 100644 --- a/git_change/git.py +++ b/git_change/git.py @@ -278,8 +278,10 @@ def search_gerrit(query): """ results = [] stats = None - response = run_command('ssh %s gerrit query --format=JSON %s' % - (FLAGS['gerrit-ssh-host'].value, query), trap_stdout=True) + ssh_host, _, ssh_port = FLAGS['gerrit-ssh-host'].value.partition(":") + ssh_port = (ssh_port and "-p %s " % ssh_port) or "" + response = run_command('ssh %s%s gerrit query --format=JSON %s' % + (ssh_port, ssh_host, query), trap_stdout=True) for line in response.split('\n'): if not line: continue diff --git a/git_change/git_change.py b/git_change/git_change.py index 9a12014..982ae41 100644 --- a/git_change/git_change.py +++ b/git_change/git_change.py @@ -604,8 +604,10 @@ def submit_change(): commit = git.run_command('git rev-parse --verify HEAD', trap_stdout=True) project = change['project'] - git.run_command_or_die('ssh %s gerrit review --project %s --submit %s' % - (FLAGS['gerrit-ssh-host'].value, project, commit)) + ssh_host, _, ssh_port = FLAGS['gerrit-ssh-host'].value.partition(':') + ssh_port = (ssh_port and "-p %s " % ssh_port) or "" + git.run_command_or_die('ssh %s%s gerrit review --project %s --submit %s' % + (ssh_port, ssh_host, project, commit)) def garbage_collect(): From 9143d4d313227e60420e79acc1da289e19099237 Mon Sep 17 00:00:00 2001 From: Simon Percivall Date: Fri, 26 Oct 2012 13:52:04 +0200 Subject: [PATCH 2/5] fix man page. --- git-change.1 | 236 +++++++++++++++++++++++++++++++++++++++++++++++++ git-change.rst | 2 +- 2 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 git-change.1 diff --git a/git-change.1 b/git-change.1 new file mode 100644 index 0000000..de59305 --- /dev/null +++ b/git-change.1 @@ -0,0 +1,236 @@ +.\" Man page generated from reStructuredText. +. +.TH GIT-CHANGE 1 "2012-09-24" "" "" +.SH NAME +git-change \- Git command to create and manage Gerrit changes +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.\" 0.1.0 +. +.SH SYNOPSIS +.nf +\fIgit change\fP [create] [] +\fIgit change\fP update [] +\fIgit change\fP rebase +\fIgit change\fP list +\fIgit change\fP submit +\fIgit change\fP gc +\fIgit change\fP print [] +.fi +.sp +.SH DESCRIPTION +.sp +Use git\-change to create and manage changes for the \fBGerrit Code +Review\fP [1] tool. The default behavior is to create a new +change. There are subcommands to manage the change at later stages, +including uploading a new patch set, rebasing, and garbage\-collecting +the temporary change branches this command creates. +.SH USAGE +.sp +create [\-r|\-\-reviewers=] [\-\-cc=] [\-b|\-\-bug=] [\-m|\-\-message=] [\-\-topic=] [\-\-skip=] [\-\-fetch] [\-\-switch] [\-\-chain] [\-\-use\-head\-commit] [\-\-merge\-commit] +.INDENT 0.0 +.INDENT 3.5 +Create a new change and upload to Gerrit. Creating a change is the +default operation, so omitting the subcommand causes \fIgit\-change\fP +to behave as if \fIcreate\fP had been specified. +.sp +The files that make up the change must be staged for commit. Those +staged changes will be committed in a new temporary branch meant +to exist locally and exclusively for this change. +.sp +The \fIcreate\fP subcommand performs the following operations: +.INDENT 0.0 +.INDENT 3.5 +.nf +1. Notes the current tracking branch (the "target" branch) +2. Creates a temporary, local branch (the "change" branch) +3. Commits the staged changes to the change branch +4. Creates a Gerrit code review destined for the target branch +.fi +.sp +.UNINDENT +.UNINDENT +.sp +If there are commits in the target branch not yet merged into its +remote branch prior to step 3 above, a warning will be issued to +explain that continuing would result in multiple new commits being +added to the temporary branch and pushed to Gerrit, resulting in +multiple code reviews, all of which share the reviewers, bug ID, +etc. +.sp +Change branches are named change\-, where ID is the change ID +generated by the commit\-msg hook that ships with Gerrit. That hook +must be enabled. Note that if the Change\-Id header is not injected +into the commit message for some reason, or if step 4 above fails, +the temporary change branch will be named tmp\-change\- where TS +is a timestamp of as a floating point number expressed in seconds +since the epoch. In this case the change branch must be manually +deleted and the change creation must be retried. +.UNINDENT +.UNINDENT +.sp +update [\-r|\-\-reviewers=] [\-\-cc=] [\-b|\-\-bug=] [\-\-skip=] +.INDENT 0.0 +.INDENT 3.5 +Update the existing Gerrit change with new changes. Staged changes +will be automatically committed by amending the HEAD commit. The +current branch must be a temporary change branch. +.UNINDENT +.UNINDENT +.sp +rebase +.INDENT 0.0 +.INDENT 3.5 +Rebase the target and temporary change branches. The current +branch must be a temporary change branch. +.sp +First the target branch (the branch from which the temporary +change branch was created) will be rebased, then the temporary +change branch will be rebased. This subcommand can be used to pull +upstream changes down to both branches to resolve a failed Gerrit +submission due to a path conflict. +.sp +If there are conflicts with either rebase operation, the process +terminates and it is up to the user to resolve the conflicts at +that point and retry. +.UNINDENT +.UNINDENT +.sp +list +.INDENT 0.0 +.INDENT 3.5 +List all temporary change branches and display a menu to check one +of them out. +.UNINDENT +.UNINDENT +.sp +submit +.INDENT 0.0 +.INDENT 3.5 +Submit the code review associated with the current change branch +to Gerrit. +.UNINDENT +.UNINDENT +.sp +gc +.INDENT 0.0 +.INDENT 3.5 +Remove temporary change branches which are fully merged. +.UNINDENT +.UNINDENT +.sp +print [\-r|\-\-reviewers=] [\-\-cc=] [\-b|\-\-bug=] +.INDENT 0.0 +.INDENT 3.5 +Print the command to push a change to Gerrit. This can be useful +if manaually creating a Gerrit code review is desired. +.UNINDENT +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.TP +.BI \-r \ , \ \-\-reviewers\fB= +Comma\-separated list of reviewers. +.TP +.BI \-\-cc\fB= +Comma\-separated list of addresses to copy on change notification +mails. +.TP +.BI \-b \ , \ \-\-bug\fB= +Bug ID to include in the commit message header. This +option causes \fIgit\-change\fP to set the BUG_ID environment +variable to the given ID before invoking \fIgit\-commit\fP so +that a Git hook can add it as a commit message header. +.TP +.BI \-m \ , \ \-\-message\fB= +Use the given message as the commit message. +.TP +.BI \-\-topic\fB= +Tag the change with the given topic name. +.TP +.BI \-\-skip\fB= +Comma\-separated list of pre\-commit checks to skip. Option +values: tests, whitespace, linelength, pep8, pyflakes, +jslint or all. This option assumes that a pre\-commit hook +runs the checks, and causes \fIgit\-change\fP to set the SKIP +environment variable to the given list of checks before +invoking \fIgit\-commit\fP so that the hook can skip them. +.TP +.B \-\-fetch +Run \fIgit\-fetch\fP so that remote branch is in sync with +the central repository. +.TP +.B \-\-switch +Switch to the temporary change branch after creating it. +.TP +.B \-\-chain +Chain with the previous Gerrit change. Use when this +change depends on the previous one. Current branch must be +a temporary change branch. Implies \-\-switch. +.TP +.B \-\-use\-head\-commit +Use the HEAD commit as the change to push rather than +committing staged changes. +.TP +.B \-\-merge\-commit +Create a change for a merge commit. Implies +\fI\-\-use\-head\-commit\fP. This flag assumes the current branch +is a tracking branch and that the HEAD commit is an +unreviewed merge commit for which a review is being +created. A change branch will be created and \fIgit\-commit +\-\-amend\fP invoked in order to have the commit\-msg hook add +a change ID header. The usual check for unmerged commits +is skipped, so be sure all of the commits being merged +have change ID headers to avoid having Gerrit create a +review for each one. Finally, note that the HEAD (merge) +commit in the original tracking branch is removed after +the change branch is created. +.TP +.BI \-\-remote\fB= +Name of the remote repository to fetch from and push to. +Defaults to the \fIgit\-change.remote\fP Git config option if +it is set, otherwise \(aqorigin\(aq. +.TP +.BI \-\-gerrit\-ssh\-host\fB= +Name of the Gerrit server hosting the Git repository. +Defaults to the \fIgit\-change.gerrit\-host\fP Git config +option if it is set. Required unless the config +option is set. +.UNINDENT +.SH SEE ALSO +.sp +git(1), git\-commit(1), git\-merge(1), git\-rebase(1), git\-fetch(1), git\-config(1), githooks(1) +.SH NOTES +.INDENT 0.0 +.IP 1. 3 +Gerrit Code Review: \fI\%http://code.google.com/p/gerrit/\fP +.UNINDENT +.SH AUTHOR +Jacob Hesch +.\" Generated by docutils manpage writer. +. diff --git a/git-change.rst b/git-change.rst index dc4701b..c6e2a95 100644 --- a/git-change.rst +++ b/git-change.rst @@ -7,7 +7,7 @@ Git command to create and manage Gerrit changes ----------------------------------------------- .. |date| date:: -.. include:: version.rst +.. 0.1.0 :Author: Jacob Hesch :Date: |date| From fd2813ed26e9b2ae55917c52d26b28d08d9eaf6c Mon Sep 17 00:00:00 2001 From: Simon Percivall Date: Thu, 8 Nov 2012 11:24:57 +0100 Subject: [PATCH 3/5] port download_review from git-review. --- git-change.1 | 10 +++++- git-change.rst | 5 +++ git_change/git_change.py | 77 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/git-change.1 b/git-change.1 index de59305..304263a 100644 --- a/git-change.1 +++ b/git-change.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH GIT-CHANGE 1 "2012-09-24" "" "" +.TH GIT-CHANGE 1 "2012-11-08" "" "" .SH NAME git-change \- Git command to create and manage Gerrit changes . @@ -39,6 +39,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] \fIgit change\fP rebase \fIgit change\fP list \fIgit change\fP submit +\fIgit change\fP download \fIgit change\fP gc \fIgit change\fP print [] .fi @@ -137,6 +138,13 @@ to Gerrit. .UNINDENT .UNINDENT .sp +download +.INDENT 0.0 +.INDENT 3.5 +Fetch an existing review from Gerrit to a change branch. +.UNINDENT +.UNINDENT +.sp gc .INDENT 0.0 .INDENT 3.5 diff --git a/git-change.rst b/git-change.rst index c6e2a95..d97ceed 100644 --- a/git-change.rst +++ b/git-change.rst @@ -22,6 +22,7 @@ SYNOPSIS | `git change` rebase | `git change` list | `git change` submit +| `git change` download | `git change` gc | `git change` print [] @@ -104,6 +105,10 @@ submit Submit the code review associated with the current change branch to Gerrit. +download + + Fetch an existing review from Gerrit to a change branch. + gc Remove temporary change branches which are fully merged. diff --git a/git_change/git_change.py b/git_change/git_change.py index 982ae41..cc331f6 100644 --- a/git_change/git_change.py +++ b/git_change/git_change.py @@ -23,10 +23,13 @@ See git-change(1) for full documentation. """ + __author__ = 'jacob@nextdoor.com (Jacob Hesch)' import sys import time +import json +import re import gflags @@ -83,6 +86,7 @@ def usage(include_flags=True): ' or: git change rebase\n' ' or: git change list\n' ' or: git change submit\n' + ' or: git change download \n' ' or: git change gc\n' '\n' ': [-r|--reviewers=] [--cc=] [-b|--bug=] [-m|--message=] ' @@ -644,6 +648,73 @@ def print_push_command(): print build_push_command(target_branch) +def download_review(review): + """Download existing review from Gerrit. + + Almost literally copied from git-review. + """ + hostname, _, port = FLAGS['gerrit-ssh-host'].value.partition(':') + + if port is not None: + port = "-p %s" % port + else: + port = "" + + + review_info = None + try: + (output, errput) = git.run_command(" ".join([ + "ssh", "-x", port, hostname, + "gerrit", "query", + "--format=JSON --current-patch-set change:%s" % review]), + trap_stderr=True, trap_stdout=True) + except git.CalledProcessError, e: + print ("Could not fetch review information from gerrit") + print e.stderr + sys.exit(1) + + review_jsons = output.split("\n") + found_review = False + for review_json in review_jsons: + try: + review_info = json.loads(review_json) + found_review = True + except: + pass + if found_review: + break + if not found_review: + print (output) + print ("Could not find a gerrit review with id: %s" % review) + sys.exit(1) + + change_id = review_info['id'] + branch_name = "change-%s" % change_id + refspec = review_info['currentPatchSet']['ref'] + + print ("Downloading %s from gerrit into %s" % (refspec, branch_name)) + (status, output) = git.run_command_or_die("git fetch %s %s" % ( + FLAGS.remote, refspec)) + + checkout_cmd = "git checkout -b %s FETCH_HEAD" % branch_name + + try: + (output, errput) = git.run_command(checkout_cmd, trap_stdout=True, trap_stderr=True) + except git.CalledProcessError, e: + if re.search("already exists\.?", output): + print ("Branch already exists - reusing") + checkout_cmd = "git checkout %s" % branch_name + git.run_command_or_die(checkout_cmd) + + reset_cmd = "git reset --hard FETCH_HEAD" + git.run_command_or_die(reset_cmd) + else: + print (e.stderr) + sys.exit(e.returncode) + + print("Switched to branch '%s'" % branch_name) + + def main(argv): if FLAGS['help-summary'].value: usage(include_flags=False) @@ -673,7 +744,9 @@ def main(argv): git.run_command_or_die('git status') argc = len(argv) - if argc > 2: + if argc > 2 and argv[1] == "download": + subcommand = argv[1] + elif argc > 2 or argv[1] == "download" and argc == 2: usage(include_flags=False) sys.exit(1) elif argc == 2: @@ -695,6 +768,8 @@ def main(argv): garbage_collect() elif subcommand == 'print': print_push_command() + elif subcommand == 'download': + download_review(argv[2]) else: exit_error('Unknown subcommand: %s.' % subcommand) From 20b4ce4a4d5edb15b496dfcf7d99dad6c3560308 Mon Sep 17 00:00:00 2001 From: Simon Percivall Date: Thu, 8 Nov 2012 18:49:43 +0100 Subject: [PATCH 4/5] fix bool logic miss. --- git_change/git_change.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_change/git_change.py b/git_change/git_change.py index cc331f6..8ebf4f6 100644 --- a/git_change/git_change.py +++ b/git_change/git_change.py @@ -746,7 +746,7 @@ def main(argv): argc = len(argv) if argc > 2 and argv[1] == "download": subcommand = argv[1] - elif argc > 2 or argv[1] == "download" and argc == 2: + elif argc > 2 or argc == 2 and argv[1] == "download": usage(include_flags=False) sys.exit(1) elif argc == 2: From 363fd81a217edaf44bbf61d38c461bbf25f0708e Mon Sep 17 00:00:00 2001 From: Simon Percivall Date: Mon, 26 Aug 2013 13:52:46 +0200 Subject: [PATCH 5/5] Allow creating draft changes. --- git-change.1 | 9 ++++++--- git-change.rst | 7 +++++-- git_change/git_change.py | 13 ++++++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/git-change.1 b/git-change.1 index 304263a..da5fbdd 100644 --- a/git-change.1 +++ b/git-change.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH GIT-CHANGE 1 "2012-11-08" "" "" +.TH GIT-CHANGE 1 "2013-08-26" "" "" .SH NAME git-change \- Git command to create and manage Gerrit changes . @@ -53,7 +53,7 @@ including uploading a new patch set, rebasing, and garbage\-collecting the temporary change branches this command creates. .SH USAGE .sp -create [\-r|\-\-reviewers=] [\-\-cc=] [\-b|\-\-bug=] [\-m|\-\-message=] [\-\-topic=] [\-\-skip=] [\-\-fetch] [\-\-switch] [\-\-chain] [\-\-use\-head\-commit] [\-\-merge\-commit] +create [\-r|\-\-reviewers=] [\-\-cc=] [\-b|\-\-bug=] [\-m|\-\-message=] [\-\-topic=] [\-\-skip=] [\-\-fetch] [\-\-switch] [\-\-chain] [\-\-use\-head\-commit] [\-\-merge\-commit] [\-\-draft] .INDENT 0.0 .INDENT 3.5 Create a new change and upload to Gerrit. Creating a change is the @@ -95,7 +95,7 @@ deleted and the change creation must be retried. .UNINDENT .UNINDENT .sp -update [\-r|\-\-reviewers=] [\-\-cc=] [\-b|\-\-bug=] [\-\-skip=] +update [\-r|\-\-reviewers=] [\-\-cc=] [\-b|\-\-bug=] [\-\-skip=] [\-\-draft] .INDENT 0.0 .INDENT 3.5 Update the existing Gerrit change with new changes. Staged changes @@ -219,6 +219,9 @@ review for each one. Finally, note that the HEAD (merge) commit in the original tracking branch is removed after the change branch is created. .TP +.B \-\-draft +Create a draft change rather than a public change. +.TP .BI \-\-remote\fB= Name of the remote repository to fetch from and push to. Defaults to the \fIgit\-change.remote\fP Git config option if diff --git a/git-change.rst b/git-change.rst index d97ceed..90cbe3c 100644 --- a/git-change.rst +++ b/git-change.rst @@ -40,7 +40,7 @@ the temporary change branches this command creates. USAGE ===== -create [-r|--reviewers=] [--cc=] [-b|--bug=] [-m|--message=] [--topic=] [--skip=] [--fetch] [--switch] [--chain] [--use-head-commit] [--merge-commit] +create [-r|--reviewers=] [--cc=] [-b|--bug=] [-m|--message=] [--topic=] [--skip=] [--fetch] [--switch] [--chain] [--use-head-commit] [--merge-commit] [--draft] Create a new change and upload to Gerrit. Creating a change is the default operation, so omitting the subcommand causes `git-change` @@ -73,7 +73,7 @@ create [-r|--reviewers=] [--cc=] [-b|--bug=] [-m|--message=] [--topic=] [--skip= since the epoch. In this case the change branch must be manually deleted and the change creation must be retried. -update [-r|--reviewers=] [--cc=] [-b|--bug=] [--skip=] +update [-r|--reviewers=] [--cc=] [-b|--bug=] [--skip=] [--draft] Update the existing Gerrit change with new changes. Staged changes will be automatically committed by amending the HEAD commit. The @@ -176,6 +176,9 @@ OPTIONS commit in the original tracking branch is removed after the change branch is created. +--draft + Create a draft change rather than a public change. + --remote= Name of the remote repository to fetch from and push to. Defaults to the `git-change.remote` Git config option if diff --git a/git_change/git_change.py b/git_change/git_change.py index 8ebf4f6..fd85aa3 100644 --- a/git_change/git_change.py +++ b/git_change/git_change.py @@ -71,6 +71,8 @@ 'tracking branch is removed after the change branch is created.') gflags.DEFINE_bool('fake-push', False, 'Do everything except for actually pushing the change to Gerrit.') +gflags.DEFINE_bool('draft', False, + 'Submit a draft change instead of a public change') FLAGS = gflags.FLAGS @@ -153,7 +155,7 @@ def get_change_id_from_head(): return None -def build_push_command(branch): +def build_push_command(branch, draft=False): """Builds a git push command string for pushing a Gerrit change. The command is built using the given branch and flag values to @@ -176,7 +178,8 @@ def build_push_command(branch): if receive_pack_args: command = '%s --receive-pack="git receive-pack %s"' % ( command, ' '.join(receive_pack_args)) - command = '%s HEAD:refs/for/%s' % (command, branch) + for_ = "drafts" if draft else "for" + command = '%s HEAD:refs/%s/%s' % (command, for_, branch) if FLAGS.topic: command = '%s/%s' % (command, FLAGS.topic) if FLAGS['fake-push'].value: @@ -292,7 +295,7 @@ def update_change(): commit_change(['--amend']) - command = build_push_command(change['branch']) + command = build_push_command(change['branch'], draft=change['status'] == "DRAFT") try: git.run_command(command) except git.CalledProcessError, e: @@ -480,7 +483,7 @@ def create_change(): git.run_command('git branch -m %s %s' % (tmp_branch, new_branch)) print '\nCreated branch: %s\n' % new_branch - command = build_push_command(target_branch) + command = build_push_command(target_branch, draft=FLAGS.draft) try: git.run_command(command) except git.CalledProcessError, e: @@ -645,7 +648,7 @@ def print_push_command(): target_branch = change['branch'] else: target_branch = git.get_branch() - print build_push_command(target_branch) + print build_push_command(target_branch, draft=FLAGS.draft) def download_review(review):