From 03d7758ada7017b44e243a5e008ed89f3d9f1a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Sedl=C3=A1k?= <psedlak@redhat.com> Date: Mon, 18 Feb 2013 11:14:00 +0100 Subject: [PATCH] Compare different Patch Sets of Review Adds ability to fetch/checkout branches and launch git diff for different review patchsets with one command. Change-Id: I5815eb0cb6025978a39d74ddb31eb179f06154de --- README.md | 4 +++ git-review | 90 ++++++++++++++++++++++++++++++++++++++++++++++++---- git-review.1 | 45 ++++++++++++++++++++++++-- 3 files changed, 129 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3f35064..538a872 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,10 @@ If you want to download patchset 4 for change 781 from gerrit to review it:: git review -d 781,4 +If you want to compare patchset 4 with patchset 10 of change 781 from gerrit:: + + git review -m 781,4-10 + If you just want to do the commit message and remote setup steps:: git review -s diff --git a/git-review b/git-review index 05a1bea..8e758ee 100755 --- a/git-review +++ b/git-review @@ -92,6 +92,19 @@ class ChangeSetException(GitReviewException): return self.__doc__ % self.e +def parse_review_number(review): + parts = review.split(',') + if len(parts) < 2: + parts.append(None) + return parts + + +def build_review_number(review, patchset): + if patchset is not None: + return '%s,%s' % (review, patchset) + return review + + def run_command_status(*argv, **env): if VERBOSE: print(datetime.datetime.now(), "Running:", " ".join(argv)) @@ -442,18 +455,23 @@ def check_remote(branch, remote, hostname, port, project): raise -def rebase_changes(branch, remote): +def rebase_changes(branch, remote, interactive=True): remote_branch = "remotes/%s/%s" % (remote, branch) if not update_remote(remote): return False - cmd = "git rebase -i %s" % remote_branch + if interactive: + cmd = "git rebase -i %s" % remote_branch + else: + cmd = "git rebase %s" % remote_branch + (status, output) = run_command_status(cmd, GIT_EDITOR='true') if status != 0: print("Errors running %s" % cmd) - print(output) + if interactive: + print(output) return False return True @@ -716,10 +734,10 @@ def fetch_review(review, masterbranch, remote): userhost = "%s@%s" % (username, hostname) review_arg = review - patchset_number = None patchset_opt = '--current-patch-set' - if ',' in review: - review, patchset_number = review.split(',') + + review, patchset_number = parse_review_number(review) + if patchset_number is not None: patchset_opt = '--patch-sets' review_info = None @@ -823,6 +841,52 @@ class DeleteBranchFailed(CommandFailed): EXIT_CODE = 68 +class InvalidPatchsetsToCompare(GitReviewException): + def __init__(self, patchsetA, patchsetB): + Exception.__init__( + self, + "Invalid patchsets for comparison specified (old=%s,new=%s)" % ( + patchsetA, + patchsetB)) + EXIT_CODE = 39 + + +def compare_review(review_spec, branch, remote, rebase=False): + new_ps = None # none means latest + + if '-' in review_spec: + review_spec, new_ps = review_spec.split('-') + review, old_ps = parse_review_number(review_spec) + + if old_ps is None or old_ps == new_ps: + raise InvalidPatchsetsToCompare(old_ps, new_ps) + + old_review = build_review_number(review, old_ps) + new_review = build_review_number(review, new_ps) + + old_branch = fetch_review(old_review, branch, remote) + checkout_review(old_branch) + + if rebase: + print('Rebasing %s' % old_branch) + rebase = rebase_changes(branch, remote, False) + if not rebase: + print('Skipping rebase because of conflicts') + run_command_exc(CommandFailed, 'git', 'rebase', '--abort') + + new_branch = fetch_review(new_review, branch, remote) + checkout_review(new_branch) + + if rebase: + print('Rebasing also %s' % new_branch) + if not rebase_changes(branch, remote, False): + print("Rebasing of the new branch failed, " + "diff can be messed up (use -R to not rebase at all)!") + run_command_exc(CommandFailed, 'git', 'rebase', '--abort') + + subprocess.check_call(['git', 'diff', old_branch]) + + def finish_branch(target_branch): local_branch = get_branch_name(target_branch) if VERBOSE: @@ -896,7 +960,7 @@ def main(): help="Force rebase even when not needed.") fetch = parser.add_mutually_exclusive_group() - fetch.set_defaults(download=False, cherrypickcommit=False, + fetch.set_defaults(download=False, compare=False, cherrypickcommit=False, cherrypickindicate=False, cherrypickonly=False) fetch.add_argument("-d", "--download", dest="changeidentifier", action=DownloadFlag, metavar="CHANGE", @@ -922,6 +986,14 @@ def main(): help="Apply the contents of an existing gerrit " "review to the working directory and prepare " "for commit") + fetch.add_argument("-m", "--compare", dest="changeidentifier", + action=DownloadFlag, metavar="CHANGE,PS[-NEW_PS]", + const="compare", + help="Download specified and latest (or NEW_PS) " + "patchsets of an existing gerrit review into " + "a branches, rebase on master " + "(skipped on conflicts or when -R is specified) " + "and show their differences") parser.add_argument("-u", "--update", dest="update", action="store_true", help="Force updates from remote locations") @@ -977,6 +1049,10 @@ def main(): config['hostname'], config['port'], config['project']) if options.changeidentifier: + if options.compare: + compare_review(options.changeidentifier, + branch, remote, options.rebase) + return local_branch = fetch_review(options.changeidentifier, branch, remote) if options.download: checkout_review(local_branch) diff --git a/git-review.1 b/git-review.1 index b2d381d..0a1e8b5 100644 --- a/git-review.1 +++ b/git-review.1 @@ -28,6 +28,12 @@ .Op Ar branch .Nm .Op Fl r Ar remote +.Op Fl uv +.Fl m +.Ar change-ps-range +.Op Ar branch +.Nm +.Op Fl r Ar remote .Op Fl fnuv .Fl s .Op Ar branch @@ -47,10 +53,14 @@ users that have recently switched to Git from another version control system. .Pp .Ar change -can be changeNumber as obtained using +can be +.Ar changeNumber +as obtained using .Fl --list -option, or it can be changeNumber,patchsetNumber for fetching exact patchset from the change. -In that case local branch will get -patch[patchsetNumber] suffix. +option, or it can be +.Ar changeNumber,patchsetNumber +for fetching exact patchset from the change. +In that case local branch name will have a -patch[patchsetNumber] suffix. .Pp The following options are available: .Bl -tag -width indent @@ -93,6 +103,28 @@ creating a local branch for it. .Pp If the current branch is different enough, the change may not apply at all or produce merge conflicts that need to be resolved by hand. +.It Fl m Ar change-ps-range , Fl -compare= Ns Ar change-ps-range +Download the specified patchsets for +.Ar change +from Gerrit, rebase both on master and display differences (git-diff). +.Pp +.Ar change-ps-range +can be specified as +.Ar changeNumber, Ns Ar oldPatchSetNumber Ns Op Ns Ar -newPatchSetNumber +.Pp +.Ar oldPatchSetNumber +is mandatory, and if +.Ar newPatchSetNumber +is not specified, the latest patchset will be used. +.Pp +This makes it possible to easily compare what has changed from last time you +reviewed the proposed change. +.Pp +If the master branch is different enough, the rebase can produce merge conflicts. +If that happens rebasing will be aborted and diff displayed for not-rebased branches. +You can also use +.Ar --no-rebase ( Ar -R ) +to always skip rebasing. .It Fl f , Fl -finish Close down the local branch and switch back to the target branch on successful submission. @@ -122,6 +154,10 @@ Do not automatically perform a rebase before submitting the change to Gerrit. .Pp When submitting a change for review, you will usually want it to be based on the tip of upstream branch in order to avoid possible conflicts. When amending a change and rebasing the new patchset, the Gerrit web interface will show a difference between the two patchsets which contains all commits in between. This may confuse many reviewers that would expect to see a much simpler difference. +.Pp +Also can be used for +.Fl --compare +to skip automatic rebase of fetched reviews. .It Fl -version Print the version number and exit. .El @@ -205,6 +241,8 @@ Changeset not found. Particular patchset cannot be fetched from the remote git repository. .It 38 Specified patchset number not found in the changeset. +.It 39 +Invalid patchsets for comparison. .It 64 Cannot checkout downloaded patchset into the new branch. .It 65 @@ -300,3 +338,4 @@ is maintained by This manpage has been enhanced by: .An "Antoine Musso" Aq hashar@free.fr .An "Marcin Cieslak" Aq saper@saper.info +.An "Pavel Sedlák" Aq psedlak@redhat.com