Git Cherry-Pick VS Rebase
Introduction
When we are working on some large projects in the company, it is often inevitable to have our CL to be dependent on others’ CLs which have not been merged. Although it is usually unambiguous what dependencies we want, running Git cooperatively sometimes is error-prone, causing push failures and even unwanted changes to CLs.
In this blog post, I would like quickly discuss how to run Git using cherry-pick
and rebase
in cooperative projects.
Cooperative Git
Here is a common scenario that we will encounter in a cooperative project.
Tom and Jerry are enabling two different features in their own CLs. It turns out that Jerry’s feature is dependent on Tom’s. Although Tom’s implementation of the new feature seems successful in his CL, it still has to go through the code review which can take a very long time. Jerry does not want to wait until Tom’s CL gets merged and decide to work based on Tom’s unmerged CL.
Suppose the commit history of Tom’s CL, Jerry’s CL and the main branch are as follows.
Tom’s CL: base commit 1 –> Tom’s commit
Jerry’s CL: base commit 2 –> Jerry’s commit
Main: base commit 1 –(X commits)–> base commit 2 –(Y commits)–> main tip commit
Now if Jerry wants to use Tom’s commit in Jerry’s CL, what should Jerry do? Suppose Jerry can push code to Tom’s CL (by --reset-author
).
Cherry-Pick
One of the common ways to incorporate others’ WIP CLs is cherry-pick
. We could have two methods to cherry-pick
for our problem.
Method 1
Checkout Jerry’s CL.
Rebase Jerry’s CL onto the main tip and resolve all conflicts if there is any so that
Jerry’s CL: main tip commit –> Jerry’s commitPush. This goes to Jerry’s CL.
Checkout Tom’s CL.
Rebase Tom’s CL onto the main tip and resolve all conflicts if there is any so that
Tom’s CL: main tip commit –> Tom’s commitPush (using
--reset-author
). This goes to Tom’s CL.(Now still in Tom’s CL branch) Cherry-pick Jerry’s CL and resolve all the conflicts between Tom’s CL and Jerry’s CL if there is any so that
Tom’s CL: main tip commit –> Tom’s commit –> Jerry’s commit
Push. This goes to Jerry’s CL.
Git will catch the relation chain:
Tom’s CL –> Jerry’s CLJerry’s CL cannot merge to the main branch until Tom’s CL gets merged.
Method 2
Checkout Tom’s CL.
Cherry-pick Jerry’s CL and resolve all the conflicts between Tom’s CL and Jerry’s CL if there is any so that
Tom’s CL: base commit 1 –> Tom’s commit –> Jerry’s commitPush. This goes to Jerry’s CL and this does not require changing Tom’s CL or reset the author for Tom’s CL.
Git will catch the relation chain:
Tom’s CL –> Jerry’s CLJerry’s CL cannot merge to the main branch until Tom’s CL gets merged.
If base commit 1 is a “good” base, i.e., there is no conflict to resolve when Tom’s commit is rebased on the main tip., this is a very straightforward way to work since we don’t have to push to Tom’s CL.
However, the major problem is Tom’s base commit 1 is much behind Jerry’s base commit 2 and therefore Jerry’s CL might not work (probably due to missing lots of code).
To make Jerry’s CL work, Jerry might still have to rebase Tom’s CL after checking out Tom’s CL so that
Tom’s CL: main tip commit –> Tom’s commitPush (using
--reset-author
). This goes to Tom’s CL.(Now still in Tom’s CL branch) Cherry-pick Jerry’s CL and resolve all the conflicts between Tom’s CL and Jerry’s CL if there is any so that
Tom’s CL: main tip commit –> Tom’s commit –> Jerry’s commitPush. This goes to Jerry’s CL.
Git will catch the relation chain:
Tom’s CL –> Jerry’s CLJerry’s CL cannot merge to the main branch until Tom’s CL gets merged.
Therefore, if base commit 1 is a “bad” base, i.e., there is conflict to resolve when Tom’s commit is rebased on the main tip., it makes almost no difference between method 1 and method 2.
Rebase
An alternative way to incorporate others’ WIP CLs is rebase
. It is different from cherry-pick
and in some scenarios can be more difficult to work with than cherry-pick
. We also could have two methods to rebase
for our problem.
Method 1
Checkout Tom’s CL.
Rebase Tom’s CL onto the main tip and resolve all conflicts if there is any so that
Tom’s CL: main tip commit –> Tom’s commitPush (using
--reset-author
). This goes to Tom’s CL.Checkout Jerry’s CL.
Rebase Jerry’s CL onto the main tip and resolve all conflicts if there is any so that
Jerry’s CL: main tip commit –> Jerry’s commit
Push. This goes to Jerry’s CL.
(Now still in Jerry’s CL branch) Rebase Jerry’s CL on top of Tom’s CL and resolve all the conflicts between Tom’s CL and Jerry’s CL if there is any so that
Jerry’s CL: main tip commit –> Tom’s commit –> Jerry’s commit
Push. This goes to Jerry’s CL.
Git will catch the relation chain:
Tom’s CL –> Jerry’s CLJerry’s CL cannot merge to the main branch until Tom’s CL gets merged.
Method 2
Checkout Jerry’s CL.
Rebase Jerry’s CL onto Tom’s CL so that
Jerry’s CL: base commit 1 –> Tom’s commit –(X commits)–> base commit 2 –> Jerry’s commit
Pushing Jerry’s CL is problematic, because we will push X commits, base commit 2, and Jerry’s commit but Jerry is (usually) not the author for the X commits and base commit 2. Unless Jerry is the administrator of Git, this push will be prevented.
So instead of rebase entirely, we could do rebase interactively which allows the user to pick the commits and reset the author for each commit. In this way, Jerry can become the author of the X commits and base commit 2, or skip rebasing those commits, and the push will be successful.
The major problem is that if X is very large, there will be too many steps for Jerry to rebase interactively. If this is the case, we would rather use the method 1 instead.
Conclusions
It seems that no matter whether we use cherry-pick
or rebase
, rebase
the two CLs to the main branch is very critical for setting up a correct relation chain and pushing.
References
Git Cherry-Pick VS Rebase