git: what is the correct merging or rebasing workflow to modify a maintenance branch and apply those patches to another branch?
Objective: I need to make custom patches to a prior release of an upstream project, and I want to be able to apply those patches to the later version.
Problem: Rebasing from the maintenance branch to a later version creates conflicts with files that have not been modified in the maintenance branch.
Suspicion: I am applying merging or rebasing incorrectly for what I’m trying to accomplish.
Example: This is a logical example of what I want to accomplish, but understand that the commit history between tagged release v1.0 and v2.0 can be hundreds of commits in between.
I fork an upstream repo with multiple tagged releases (v1.0 and v2.0) with a master commit history A thru D. B is the last commit at tag v1.0. C is the last commit at tag v2.0. D is ongoing development.
$ git clone git@server:repo A<--B(v1.0)<--C(v2.0)<--D Master
I branch off an earlier release tag (v1.0) to make a few custom modifications like so:
$ git checkout v1.0 -b v1.1 E<--G<--H v1.1 / A<--B<--C<--D Master \ F v2.1
What I now want to do is branch off a later release tag (v2.0) and apply the patch commits I made in the v1.1 branch (G’ and H’) to the v2.0 branch. I need to preserve the individual commits that I made in v1.0 in the v2.0 log.
E<--G<--H v1.1 / A<--B<--C<--D Master \ F<--G'<--H' v2.1
Question: What is the correct workflow to apply these changes and avoid conflicts? I have tried many combinations of merge and rebase, (including –onto) yet all fail. git rebase wants to default to a 3-way merge and conflict with unrelated files.
$ git rebase v2.1 v1.1 Falling back to patching base and 3-way merge ... Failed to merge in the changes. $ git checkout v2.1 $ git rebase v1.1 Falling back to patching base and 3-way merge ... Failed to merge in the changes.
2 Solutions collect form web for “git: what is the correct merging or rebasing workflow to modify a maintenance branch and apply those patches to another branch?”
I suggest you edit your question to include the exact command(s) with which you attempted to rebase –onto. That should be the way you accomplish what you are attempting but you may have run the command in such a way to trigger more rebasing than is actually necessary.
If your rebase commands rewrites everything between
v.2.0, then this might result in a lot of unnecessary pain if that history includes conflicts resolved through non-fast-forward merges.
In interest of clarity, I’ve moved the explanation about merge conflicts and rebasing to the bottom of this answer. However that section is simply speculation, it would be helpful to see an example of the
rebase --onto that you attempted. Not having that available now, I will provide what I think you should do. With that said, lets get started on your solution.
I like to read the arguments for –onto backward to understand the better. Read backward,
--onto <1> <2> <3> reads as – Take whatever commits are on
<3>, that are not on
<2>, and apply them to
<1>. The commits are not “moved”, the are “cloned”, so your old commits are still where they were – the rebase –onto simply creates copies of them and applies them after
Its important to know that after performing a
rebase --onto you may end up in a headless state. The new commits are applied as described above, but they do not immediately change the state of your branch. This adds an extra step in the process, but also give you the added security of knowing that the rebase can not break your branch – you will have a chance to review the changed history before applying tose changes to your branch.
Starting with this diagram.
E<--G<--H v1.1 / A<--B<--C<--D Master \ F v2.1
To get only
H to follow
F, without including
E, which seems to be the case according to your description, then you should try the following command.
git rebase --onto F G^ v1.1
I wrote the above assuming as little as I could about the reality if your situation.
This will take whatever commits exist on
v1.1 that do not exist on the commit immediately proceeding
G, and applies them after
F. Since the only commits actually being rewritten are
H, then there is no reason why you should get any conflicts unrelated to what those two commits changed.
As I described above, you may end up in a headless state. This means that you are not in your branch anymore. Your diagram at this point actually looks something like this…
E<--G<--H v1.1 / A<--B<--C<--D Master \ F v2.1 \ G'<--H' (currently checkout out headless state)
As you can see, branch v2.1 is still at
F, but you’ve created a new history of
A<--B<--C<--F<--G'<--H'. This is what you wanted, but its not in your
v2.1 branch. So now review your history and verify that its what you wanted. Even test it if you want. Once verified you just need to checkout v2.1 and run the following command…
git checkout v2.1 git merge H'
This assumes you have no new commits on v2.1 that are not in
H'. To ensure, you may want to use the
--ff-only flag on the merge so that it will reject the merge instead of creating a merge commit.
Like I said above, this is an extra step to be aware of, but as a result of this you can reset assured that the
git rebase --onto will not make a mess on your actual branch. If you find that the rebase did not work as intended – you can simply checkout
v2.1 and see that no harm as been done.
After your fast-forward merge compeltes, you will have a history that looks like this…
E<--G<--H v1.1 / A<--B<--C<--D Master \ F<--G'<--H' v2.1
Cherry-picking vs Rebase –onto
Wont go into detail about cherry picking, but I want to make clear that the following..
git checkout v2.1 git cherry-pick G^..H
Is completely equivalent too…
git rebase --onto v2.1 G^ H git checkout v2.1 git reset --hard <hash> <-- were hash is the commit the rebase kicks you into.
Cherry pick has fewer steps, the rebase can be done without checking out the “base”, which in both cases here is
v2.1. Also as explained bove rebase –onto doesn’t directly effect your branch making it easier to recover from if something goes wrong. Both “clone” the commits they bringing onto the base, leaving the originals untouched.
The above is a general explanation as to how to achieve what you are asking to do. Below is my suspicion as to why you had the problems you described.
Non-Fast-Forward Conflict Resolution & Rebasing
My guess is that between v1.0 and v2.0, you have some non-fast-forward merges that where used to resolve conflicts. When a conflict is resolved during a non-fast-forward merge, the resolution for that conflict is stored in the merge commit, rather than in the offending commits themselves. The merge commit occurs later in the history at the point of merge, rather than on the conflicting commits themselves.
When you rebase, git steps through each commit individually and recommits it – as a result you will relive all conflicts resulting from a non-fast-forward merge, but the resolution to that conflict is unavailable until later in the history when the merge occurred. Conflicts resolved with non-fast-forward merges are detrimental to your ability to rebase a branch in the future unless your willing to re-resolve all those conflicts one by one.
Your possible mistake
If my guess about your problem is correct, then you might have done the following…
git rebase --onto v1.1 F v1.1
This or some variation of this would result in taking all the commits in
F that are not on
v1.1 and appending them to the end of
v1.1. As explained above this would result in each commit between
F being re-committed one-by-one. If there are conflicts in there that were resolved with non-fast-forward merges – then you will relive each of those conflicts as the rebase steps though those commits.
Merging instead of Rebasing
Your question title suggests you may be open to simply merging these histories. If your not concerned with linear history, you may simply want to merge v1.1 into
F. This shouldn’t result in any strange conflicts but it will significantly muddy your history.
While it may not have the fine-grained control of a rebase, or the ease of a merge, passing a range of commits to
git cherry-pick seems better suited to taking individual changes on older branches and playing them onto the current branch.
So, if G and H are the last two commits in
v1.1 you should be able to cherry pick them into
git cherry-pick v1.1~1
(or manually providing the commit hashes)
If you’ve already tried this, and there are downsides, let me know. I’m still trying to perfect this sort of workflow myself : )