Rob Gilliam
Rob Gilliam

Reputation: 2950

How to merge all changes from one branch into another

(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

Answers (2)

Mark Adelsberger
Mark Adelsberger

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

LeGEC
LeGEC

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

Related Questions