Reputation: 2950
(Apologies if this is a dupe - none of the answers that I've turned up in my search appear to relate to my exact situation, and/or the suggested moves aren't solving my problem.)
I have the following (approximate) structure in my Git repo. I've patched a defect and merged that patch into the Production
branch OK.
A--B--C--D--E--F--G [Master]
| |
| \--K--L--M [Release Candidate]
|
\--H--I--J--N--O--P [Production]
|
\--N--O--P [Patch]
Now I need to apply the exact same patch (commits N
, O
, and P
) to the Master
and Release Candidate
branches, both of which have moved on (a lot) since the Production
branch was taken, but there are no changes in the files I'll be affecting. Commits H
, I
, and J
need not to go into those branches, though.
Is there a way to do this without needing to do some juggling with commit IDs?
Ideally, I'm looking for something that I can just refer to the Patch
by its branch name. (After all, Git knows what was changed on the Patch
branch, doesn't it? I shouldn't have to tell it again!)
p.s. I tried git cherry-pick Patch
as that seemed like an obvious guess, but that only pulled in commit P
.
EDIT: the suggested solution from another question results in a ton of conflicts when I try to rebase, even though I know the changes I want to merge will not conflict with anything currently in the Master
and Production
branches.
Please note that, even though there only a few commits shown on each branch in the diagram, there are actually tens (possibly hundreds, I haven't counted them) on each branch except for Patch
, which is only three (this time). I'm ideally looking for a situation when the Patch
branch might also have a large number of commits, though.
Why is it so hard to tell git "use the commits that were made on this branch since it was taken"?
Upvotes: 1
Views: 2859
Reputation: 45649
UPDATE - As torek points out, I was being too optimistic about the consequences of running multiple rebases. Suggested procedures adjusted accordingly...
Some things to consider in choosing a solution, that may easily be overlooked:
Has any of this been published / shared with other developers? The less that has been shared, the more flexibility you have to rewrite history. I'm not the biggest advocate of history rewrites - I usually don't do it just to make a topology more linear - but in cases like this having the flexibility to do it can make your future life easier. (If you have a team that can coordinate to "cut over" to a rewritten repo, then you can do rewrites regardless of sharing; but as a rule of thumb, let's assume you can't do rewrites that discard commits you've already shared.)
How much trouble will it cause if you have multiple commits that perform the same changes on different branches? If these branches will eventually merge, then this sort of thing can cause unnecessary merge conflicts. So you might want to favor solutions that use a single commit for any given change.
What kind of branch topology do you want to end up with? Some people really don't like to see merges in their history. Different solutions will produce more, or fewer, merge commits.
Ok, so what of it? Well, you'll probably have to make some compromises among those three concerns. If you don't want any merges, and want only a single commit for any given change, then you'd have to rewrite pretty much everything. You can get by with only rewriting Patch
(and the application of Patch
to Production
), but it will require some merges. If even Patch
can't be rewritten, there's another way (but with some merges and a somewhat goofy branch topology in the end). Or you can get a no-merge situation without rewrites (except on a technicality), but multiple sets of commits representing the patch. Let's walk through some options...
First let's back up to before you'd merged patch
into production
. I'm doing this mostly because it's not clear to me exactly how the merge went down, so I'm starting from a more "clear" state. If need be, you could actually do this
git checkout Production
git reset --hard J
In the general case J
is a commit ID; but if it was a non-ff merge you could use HEAD^
.
A--B--C--D--E--F--G <--(Master)
\ \
\ K--L--M <--(Release Candidate)
\
H--I--J <--(Production)
\
N--O--P <--(Patch)
Anyway, if we're going to apply N
, O
, and P
to all of these branches without duplication of changes and without merges, then the first thing to do is rebase them to C
. (You might want to tag C
; of course getting the tag set up will require a little commit ID usage...)
git rebase --onto C Production Patch
N'--O'--P' <--(Patch)
/
A--B--C--D--E--F--G <--(Master)
\ \
\ K--L--M <--(Release Candidate)
\
H--I--J <--(Production)
Note that "prime" markers on the commits for the new patch; these are rewritten commits and are not equal to the originals, though they contain exactly the same changes (assuming Patch
was truly independent of anything on Production
).
If you're not worried about doing rewrites, then you can rebase the other branches onto Patch
. This is not quite as easy as I'd like because some commits are shared by multiple branches, and multiple rebase operations would create multiple rewrites (due to timestamps). So
git rebase --onto Patch Patch master
git rebase --onto Patch Patch rc
git rebase --onto Patch Patch Production
does not work. Instead you would have to do
git rebase --onto Patch Patch master
git rebase --onto Patch Patch Production
then identify E
', then
git rebase --onto E' Master rc
Alternately there's a game you could play where you merge (possibly octopus merge) the branches you want to rebase, then do a single rebase of that new merge using the --preserve-merges
option. It works sort of, but then you may have some manual work to move your original branch refs.
Well, if you do one of these things you'll get
(Patch)
|
A--B--C--N'--O'--P'--D'--E'--F'--G' <--(Master)
\ \
\ K'--L'--M' <--(Release Candidate)
\
H'--I'--J' <--(Production)
Of course now you've rewritten almost everything. Suppose Patch
is the only thing you can safely rewrite... so we go back to
N'--O'--P' <--(Patch)
/
A--B--C--D--E--F--G <--(Master)
\ \
\ K--L--M <--(Release Candidate)
\
H--I--J <--(Production)
and then
git checkout Master
git merge Patch
git checkout rc
git merge Patch
git checkout Production
git merge Patch
Note that this is not the same as leaving your original merge to production in place. By going back and re-merging with the re-written Patch
, you preserve the "one commit per change" situation.
But maybe you can't rewrite Patch
- or, more likely, maybe you can't rewrite the merge of Patch
into Production
. Unfortunately having already done that merge may have taken some options away from you. In that case, you have to make more compromises and honestly it becomes very hard not to have multiple sets of commits representing the Patch
. You probably just need to go with the answers already spelled out by others at that point.
As one last ditch, you might think "well, I could check out Patch
and use revert
to add to it commits that undo H
, I
, and J
, and then I'd have a patch I could merge into Master
and Release Candidate
"... Well, you could - I considered it - and it would address the immediate problem, but it would create a bigger problem down the line because now H
(as well as ~H
), I
(as well as ~I
), and J
(as well as ~J
) would already be in the histories of those branches, so a future merge form Production wouldn't automatically re-introduce those changes. So if there's any future state in which you'd want H
..J
to merge up, this last option is not so good.
Upvotes: 1
Reputation: 51850
If your patch really is 3 commits : git cherry-pick N O P
would do
Otherwise, you can rebase
an excerpt of a branch :
# starting from branch Patch
# create a fresh branch :
$ git checkout -b wip/patch-on-master
# tell rebase to rebase 'commits from (but not including) J' :
$ git rebase --onto master J wip/patch-on-master
Upvotes: 1