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.
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 v2.0
via:
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 : )
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 v1.0
and 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 <1>
.
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 G
and 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 G
and 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
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.
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.
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 B
and 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.
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.