Make it possible to cherry-pick a change

Option -d already makes it possible to get a change into a separate
branch, but for large projects and trivial changes, it makes sense to
also be able to just cherry-pick the change to the currently active
local branch.

Also add modes that invokes "git cherry-pick -n" or "git cherry-pick
-x".  Program exits with exit code 69 whenever "git cherry-pick" fails.

This feature was subsequently amended by Marcin Cieślak
<saper@saper.info>.

Change-Id: I0a30052fb2baf357ed81e3f1ef4618bf786cb7ee
This commit is contained in:
Miklos Vajna
2013-01-23 22:27:14 +01:00
parent b8f228118e
commit e1e9d2ccfa
2 changed files with 131 additions and 16 deletions

View File

@@ -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)

View File

@@ -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