diff --git a/git-review b/git-review index 40ae4044..6d3289ec 100755 --- a/git-review +++ b/git-review @@ -664,7 +664,7 @@ class ResetHardFailed(CommandFailed): EXIT_CODE = 66 -def download_review(review, masterbranch, remote): +def fetch_review(review, masterbranch, remote): (hostname, username, port, project_name) = \ parse_git_show(remote, "Push") @@ -701,7 +701,6 @@ def download_review(review, masterbranch, remote): raise PatchSetInformationNotFound(review) try: - revision = review_info['currentPatchSet']['revision'] refspec = review_info['currentPatchSet']['ref'] except KeyError: raise PatchSetNotFound(review) @@ -718,14 +717,20 @@ def download_review(review, masterbranch, remote): author = 'unknown' branch_name = "review/%s/%s" % (author, topic) - print("Downloading %s from gerrit into %s" % (refspec, branch_name)) + print("Downloading %s from gerrit" % refspec) run_command_exc(PatchSetGitFetchFailed, "git", "fetch", remote, refspec) + return branch_name + + +def checkout_review(branch_name): + """Checkout a newly fetched (FETCH_HEAD) change + into a branch""" try: - output = run_command_exc(CheckoutNewBranchFailed, - "git", "checkout", "-b", - branch_name, "FETCH_HEAD") + run_command_exc(CheckoutNewBranchFailed, + "git", "checkout", "-b", + branch_name, "FETCH_HEAD") except CheckoutNewBranchFailed as e: if re.search("already exists\.?", e.output): @@ -737,7 +742,20 @@ def download_review(review, masterbranch, remote): else: raise - print("Switched to branch '%s'" % branch_name) + print("Switched to branch \"%s\"" % branch_name) + + +class PatchSetGitCherrypickFailed(CommandFailed): + "There was a problem applying changeset contents to the current branch." + EXIT_CODE = 69 + + +def cherrypick_review(option=None): + cmd = ["git", "cherry-pick"] + if option: + cmd.append(option) + cmd.append("FETCH_HEAD") + print(run_command_exc(PatchSetGitCherrypickFailed, *cmd)) class CheckoutBackExistingBranchFailed(CommandFailed): @@ -787,6 +805,14 @@ def main(): usage = "git review [OPTIONS] ... [BRANCH]" import argparse + + class DownloadFlag(argparse.Action): + """Additional option parsing: store value in 'dest', but + at the same time set one of the flag options to True""" + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + setattr(namespace, self.const, True) + parser = argparse.ArgumentParser(usage=usage, description=COPYRIGHT) parser.add_argument("-t", "--topic", dest="topic", @@ -811,9 +837,35 @@ def main(): parser.add_argument("-F", "--force-rebase", dest="force_rebase", action="store_true", help="Force rebase even when not needed.") - parser.add_argument("-d", "--download", dest="download", - help="Download the contents of an existing gerrit " + + fetch = parser.add_mutually_exclusive_group() + fetch.set_defaults(download=False, cherrypickcommit=False, + cherrypickindicate=False, cherrypickonly=False) + fetch.add_argument("-d", "--download", dest="changeidentifier", + action=DownloadFlag, metavar="CHANGE", + const="download", + help="Download the contents of an existing gerrit " "review into a branch") + fetch.add_argument("-x", "--cherrypick", dest="changeidentifier", + action=DownloadFlag, metavar="CHANGE", + const="cherrypickcommit", + help="Apply the contents of an existing gerrit " + "review onto the current branch and commit " + "(cherry pick; not recommended in most " + "situations)") + fetch.add_argument("-X", "--cherrypickindicate", dest="changeidentifier", + action=DownloadFlag, metavar="CHANGE", + const="cherrypickindicate", + help="Apply the contents of an existing gerrit " + "review onto the current branch and commit, " + "indicating its origin") + fetch.add_argument("-N", "--cherrypickonly", dest="changeidentifier", + action=DownloadFlag, metavar="CHANGE", + const="cherrypickonly", + help="Apply the contents of an existing gerrit " + "review to the working directory and prepare " + "for commit") + parser.add_argument("-u", "--update", dest="update", action="store_true", help="Force updates from remote locations") parser.add_argument("-s", "--setup", dest="setup", action="store_true", @@ -864,8 +916,17 @@ def main(): check_remote(branch, remote, config['hostname'], config['port'], config['project']) - if options.download is not None: - download_review(options.download, branch, remote) + if options.changeidentifier: + local_branch = fetch_review(options.changeidentifier, branch, remote) + if options.download: + checkout_review(local_branch) + else: + if options.cherrypickcommit: + cherrypick_review() + elif options.cherrypickonly: + cherrypick_review("-n") + if options.cherrypickindicate: + cherrypick_review("-x") return elif options.list: list_reviews(remote) diff --git a/git-review.1 b/git-review.1 index 8a0c26ce..94eea635 100644 --- a/git-review.1 +++ b/git-review.1 @@ -13,6 +13,21 @@ .Op Ar branch .Nm .Op Fl r Ar remote +.Op Fl uv +.Fl x Ar change +.Op Ar branch +.Nm +.Op Fl r Ar remote +.Op Fl uv +.Fl N Ar change +.Op Ar branch +.Nm +.Op Fl r Ar remote +.Op Fl uv +.Fl X Ar change +.Op Ar branch +.Nm +.Op Fl r Ar remote .Op Fl fnuv .Fl s .Op Ar branch @@ -39,6 +54,39 @@ Download from Gerrit into a local branch. The branch will be named after the patch author and the name of a topic. If the local branch already exists, it will attempt to update with the latest patchset for this change. +.It Fl x Ar change , Fl -cherrypick= Ns Ar change +Apply +.Ar change +from Gerrit and commit into the current local branch ("cherry pick"). +No additional branch is created. +.Pp +This makes it possible to review a change without creating a local branch for +it. On the other hand, be aware: if you are not careful, this can easily result +in additional patch sets for dependent changes. Also, 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 N Ar change , Fl -cherrypickonly= Ns Ar change +Apply +.Ar change +from Gerrit +into the current working directory, add it to the staging area ("git index"), but do not commit it. +.Pp +This makes it possible to review a change without creating a local commit for +it. Useful if you want to merge several commits into one that will be submitted for review. +.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 X Ar change , Fl -cherrypickindicate= Ns Ar change +Apply +.Ar change +from Gerrit and commit into the current local branch ("cherry pick"), +indicating which commit this change was cherry-picked from. +.Pp +This makes it possible to re-review a change for a different branch without +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 f , Fl -finish Close down the local branch and switch back to the target branch on successful submission. @@ -72,7 +120,7 @@ When submitting a change for review, you will usually want it to be based on the Print the version number and exit. .El .Sh FILES -To use +To use .Nm with your project, it is recommended that you create a file at the root of the repository named @@ -86,16 +134,16 @@ project=\fIproject name\fP defaultbranch=\fIbranch to work on\fP .Ed .Pp -It is also possible to specify optional default name for +It is also possible to specify optional default name for the Git remote using the .Cm defaultremote -configuration parameter. +configuration parameter. .Pp Setting .Cm defaultrebase to zero will make .Nm -not to rebase changes by default (same as the +not to rebase changes by default (same as the .Fl R command line option) .Bd -literal -offset indent @@ -141,6 +189,12 @@ Cannot switch to some other branch when trying to finish the current branch. .It 68 Cannot delete current branch. +.It 69 +Requested patchset cannot be fully applied to the current branch. This exit +status will be returned when there are merge conflicts with the current branch. +Possible reasons include an attempt to apply patchset from the different branch +or code. This exit status will also be returned if the patchset is already +applied to the current branch. .El .Pp Exit status larger than 31 indicates problem with @@ -176,7 +230,7 @@ $ git branch * master .Ed .Pp -An example +An example .Pa .gitreview configuration file for a project .Pa department/project