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:
83
git-review
83
git-review
@@ -664,7 +664,7 @@ class ResetHardFailed(CommandFailed):
|
|||||||
EXIT_CODE = 66
|
EXIT_CODE = 66
|
||||||
|
|
||||||
|
|
||||||
def download_review(review, masterbranch, remote):
|
def fetch_review(review, masterbranch, remote):
|
||||||
|
|
||||||
(hostname, username, port, project_name) = \
|
(hostname, username, port, project_name) = \
|
||||||
parse_git_show(remote, "Push")
|
parse_git_show(remote, "Push")
|
||||||
@@ -701,7 +701,6 @@ def download_review(review, masterbranch, remote):
|
|||||||
raise PatchSetInformationNotFound(review)
|
raise PatchSetInformationNotFound(review)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
revision = review_info['currentPatchSet']['revision']
|
|
||||||
refspec = review_info['currentPatchSet']['ref']
|
refspec = review_info['currentPatchSet']['ref']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise PatchSetNotFound(review)
|
raise PatchSetNotFound(review)
|
||||||
@@ -718,14 +717,20 @@ def download_review(review, masterbranch, remote):
|
|||||||
author = 'unknown'
|
author = 'unknown'
|
||||||
branch_name = "review/%s/%s" % (author, topic)
|
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,
|
run_command_exc(PatchSetGitFetchFailed,
|
||||||
"git", "fetch", remote, refspec)
|
"git", "fetch", remote, refspec)
|
||||||
|
return branch_name
|
||||||
|
|
||||||
|
|
||||||
|
def checkout_review(branch_name):
|
||||||
|
"""Checkout a newly fetched (FETCH_HEAD) change
|
||||||
|
into a branch"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
output = run_command_exc(CheckoutNewBranchFailed,
|
run_command_exc(CheckoutNewBranchFailed,
|
||||||
"git", "checkout", "-b",
|
"git", "checkout", "-b",
|
||||||
branch_name, "FETCH_HEAD")
|
branch_name, "FETCH_HEAD")
|
||||||
|
|
||||||
except CheckoutNewBranchFailed as e:
|
except CheckoutNewBranchFailed as e:
|
||||||
if re.search("already exists\.?", e.output):
|
if re.search("already exists\.?", e.output):
|
||||||
@@ -737,7 +742,20 @@ def download_review(review, masterbranch, remote):
|
|||||||
else:
|
else:
|
||||||
raise
|
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):
|
class CheckoutBackExistingBranchFailed(CommandFailed):
|
||||||
@@ -787,6 +805,14 @@ def main():
|
|||||||
usage = "git review [OPTIONS] ... [BRANCH]"
|
usage = "git review [OPTIONS] ... [BRANCH]"
|
||||||
|
|
||||||
import argparse
|
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 = argparse.ArgumentParser(usage=usage, description=COPYRIGHT)
|
||||||
|
|
||||||
parser.add_argument("-t", "--topic", dest="topic",
|
parser.add_argument("-t", "--topic", dest="topic",
|
||||||
@@ -811,9 +837,35 @@ def main():
|
|||||||
parser.add_argument("-F", "--force-rebase", dest="force_rebase",
|
parser.add_argument("-F", "--force-rebase", dest="force_rebase",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Force rebase even when not needed.")
|
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")
|
"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",
|
parser.add_argument("-u", "--update", dest="update", action="store_true",
|
||||||
help="Force updates from remote locations")
|
help="Force updates from remote locations")
|
||||||
parser.add_argument("-s", "--setup", dest="setup", action="store_true",
|
parser.add_argument("-s", "--setup", dest="setup", action="store_true",
|
||||||
@@ -864,8 +916,17 @@ def main():
|
|||||||
check_remote(branch, remote,
|
check_remote(branch, remote,
|
||||||
config['hostname'], config['port'], config['project'])
|
config['hostname'], config['port'], config['project'])
|
||||||
|
|
||||||
if options.download is not None:
|
if options.changeidentifier:
|
||||||
download_review(options.download, branch, remote)
|
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
|
return
|
||||||
elif options.list:
|
elif options.list:
|
||||||
list_reviews(remote)
|
list_reviews(remote)
|
||||||
|
|||||||
64
git-review.1
64
git-review.1
@@ -13,6 +13,21 @@
|
|||||||
.Op Ar branch
|
.Op Ar branch
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl r Ar remote
|
.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
|
.Op Fl fnuv
|
||||||
.Fl s
|
.Fl s
|
||||||
.Op Ar branch
|
.Op Ar branch
|
||||||
@@ -39,6 +54,39 @@ Download
|
|||||||
from Gerrit
|
from Gerrit
|
||||||
into a local branch. The branch will be named after the patch author and the name of a topic.
|
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.
|
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
|
.It Fl f , Fl -finish
|
||||||
Close down the local branch and switch back to the target branch on
|
Close down the local branch and switch back to the target branch on
|
||||||
successful submission.
|
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.
|
Print the version number and exit.
|
||||||
.El
|
.El
|
||||||
.Sh FILES
|
.Sh FILES
|
||||||
To use
|
To use
|
||||||
.Nm
|
.Nm
|
||||||
with your project, it is recommended that you create
|
with your project, it is recommended that you create
|
||||||
a file at the root of the repository named
|
a file at the root of the repository named
|
||||||
@@ -86,16 +134,16 @@ project=\fIproject name\fP
|
|||||||
defaultbranch=\fIbranch to work on\fP
|
defaultbranch=\fIbranch to work on\fP
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.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
|
the Git remote using the
|
||||||
.Cm defaultremote
|
.Cm defaultremote
|
||||||
configuration parameter.
|
configuration parameter.
|
||||||
.Pp
|
.Pp
|
||||||
Setting
|
Setting
|
||||||
.Cm defaultrebase
|
.Cm defaultrebase
|
||||||
to zero will make
|
to zero will make
|
||||||
.Nm
|
.Nm
|
||||||
not to rebase changes by default (same as the
|
not to rebase changes by default (same as the
|
||||||
.Fl R
|
.Fl R
|
||||||
command line option)
|
command line option)
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
@@ -141,6 +189,12 @@ Cannot switch to some other branch when trying to finish
|
|||||||
the current branch.
|
the current branch.
|
||||||
.It 68
|
.It 68
|
||||||
Cannot delete current branch.
|
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
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Exit status larger than 31 indicates problem with
|
Exit status larger than 31 indicates problem with
|
||||||
@@ -176,7 +230,7 @@ $ git branch
|
|||||||
* master
|
* master
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
An example
|
An example
|
||||||
.Pa .gitreview
|
.Pa .gitreview
|
||||||
configuration file for a project
|
configuration file for a project
|
||||||
.Pa department/project
|
.Pa department/project
|
||||||
|
|||||||
Reference in New Issue
Block a user