Add --partial_ok to

This change lets succeed with a partial set of
cherry-picks. In our case, we sometimes get baacked up with several
commits needing to be cherry-picked. The first few cherry-pick fine, and
there's a problematic commit sometime down the line. By accepting the
first few that cherry-pick fine, someone resolving the conflict can
start at where that conflict begins, rather than having to wrangle more
commits unnecessarily.

I tested this with and without the flag, and confirmed that if the
first commit is problematic for cherry-picks, the command does

Change-Id: I2a8b34577f9cb74565adf90a2b7d5328bc555f85
Reviewed-by: Joe McDonnell <>
Tested-by: Philip Zeyliger <>


Branch: refs/heads/master
Commit: 242e822ae6819c5029270f58f771ee61c52b676e
Parents: 257ae0e
Author: Philip Zeyliger <>
Authored: Wed Apr 11 15:36:14 2018 -0700
Committer: Philip Zeyliger <>
Committed: Wed Apr 11 22:54:51 2018 +0000

 bin/ | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/bin/ b/bin/
index 0928751..8b54636 100755
--- a/bin/
+++ b/bin/
@@ -90,6 +90,8 @@ def create_parser():
   parser.add_argument('--cherry_pick', action='store_true', default=False,
       help='Cherry-pick mismatched commits to current branch. This ' +
         'must match (in the hash sense) the target branch.')
+  parser.add_argument('--partial_ok', action='store_true', default=False,
+      help='Exit with success if at least one cherrypick succeeded.')
   parser.add_argument('--source_branch', default='master')
   parser.add_argument('--target_branch', default='2.x')
   parser.add_argument('--source_remote_name', default='asf-gerrit',
@@ -159,7 +161,7 @@ def build_commit_map(branch, merge_base):
   logging.debug("Commit map for branch %s has size %d.", branch, len(result))
   return result
-def cherrypick(cherry_pick_hashes, full_target_branch_name):
+def cherrypick(cherry_pick_hashes, full_target_branch_name, partial_ok):
   """Cherrypicks the given commits.
   Also, asserts that full_target_branch_name matches the current HEAD.
@@ -167,6 +169,9 @@ def cherrypick(cherry_pick_hashes, full_target_branch_name):
   cherry_pick_hashes is a list of git hashes, in the order to
   be cherry-picked.
+  If partial_ok is true, return gracefully if at least one cherrypick
+  has succeeded.
   Note that this function does not push to the remote.
   print "Cherrypicking %d changes." % (len(cherry_pick_hashes),)
@@ -184,10 +189,16 @@ def cherrypick(cherry_pick_hashes, 
-  for cherry_pick_hash in cherry_pick_hashes:
-    subprocess.check_call(
+  for i, cherry_pick_hash in enumerate(cherry_pick_hashes):
+    ret =
         ['git', 'cherry-pick', '--keep-redundant-commits', cherry_pick_hash])
+    if ret != 0:
+      if partial_ok and i > 0:
+        subprocess.check_call(['git', 'cherry-pick', '--abort'])
+        print "Failed to cherry-pick %s; stopping picks." % (cherry_pick_hash,)
+        return
+      else:
+        raise Exception("Failed to cherry-pick: %s" % (cherry_pick_hash,))
 def main():
   parser = create_parser()
@@ -270,7 +281,7 @@ def main():
   if options.cherry_pick:
-    cherrypick(cherry_pick_hashes, full_target_branch_name)
+    cherrypick(cherry_pick_hashes, full_target_branch_name, options.partial_ok)
 if __name__ == '__main__':

Reply via email to