4

So I've performed a partial merge (via git merge --no-commit) to only bring in some changes from a featureA branch. Then merged in a featureB branch. And now I need the rest of the changes from featureA. Git says there's nothing to do if I merge featureA again, because Git thinks it's already been merged.

Below is an attempt to explain what I'm trying to do

       featureB ,-------.
               /         \(merge)
master----+---+-----+-----+------------+---  
           \       /(partial merge)   /(TODO: merge in rest of featureA)
   featureA `-----'..................'
                   (no changes here)

Is there a simple command to do this in git?

Sascha Wolf
  • 18,810
  • 4
  • 51
  • 73
gjcamann
  • 621
  • 4
  • 14

2 Answers2

3

The tl;dr for the text below is: master reflects accumulated differences since the incorrect merge of A. You want the correct merge result committed as the incorrect merge's child, so when you merge the correct result back into master and merge diffs the two tips, it sees and merges the differences between the incorrect merge and the correct one.

Here's how, with the narrative more or less reflecting how I got this result.


The merge base you've got now, the featureA tip, you don't want. There are no changes since then to merge with the changes since then on master. You want the merge history and result you would have gotten if you'd done the whole job in the original merge. A condensed version of your commit graph, for reference:

#            /-B.....-\         featureB
#   ---A0---B0---APM---BM---?   master [APM, featureA partial merge, is bad]
#       \-A...../??????????/    featureA [can keep history, merge rest of A?]

Step 1 is to get the merge result you wanted in the first place:

# redo the original merge to get the right parents and the right content
git checkout $APM^        # checkout the original merge parent
git merge $APM^2          # do the correct merge with its other parent
git checkout -b AM        # give this work a name

Now you have:

#            /-B......\
#   ---A0---B0---APM---BM---?
#       \     \-/-AM
#        \-A.../-/

where AM has the identical parents as $APM but the correct result. Whatever $APM did right is now in AM too: AM embodies the changes that need to be made to $APM to get the correct result. Everything in the $APM commit has been in effect (in literal effect) merged into AM.

git merge -s ours $APM   # $APM is correctly incorported in AM. Tell git.

And that makes the history correct: AM reflects one set of changes to $APM, and master reflects another. Time to merge them:

git checkout master
git merge AM

git branch -d AM and if I've got this right, you're done.


Just in case I've got something wrong, though, as a reminder or caution as the case may be, do the above in a scratch repo:

git clone -s . ../wip  # safety play: sandbox the changes
# do the above, and when you're satisfied you've got `master` correct,
cd ../$mainrepo
git checkout master    # okay, incorporate the results
git pull               # .
rm -rf ../wip          # (everything in wip is now also here)

Testing:

# drop this as file `script` in an empty directory and
# say `sh script` to recreate the described situation:

set -x
git init --template=
for f in {1..5}; do seq -ffile$f%4.0fx 10 >f$f; done
git add .
git commit -m'Initial commit'
git checkout -b featureA
sed -si s/4x/4-featureA/ f2 f3
git commit -am4featureA-f2f3
git checkout master
sed -si s/1x/1-master/ f1 f2 f3
git commit -am1master-f1f2f3
git checkout -b featureB
sed -si s/8x/8-featureB/ f3 f4
git commit -am8featureB-f3f4
git checkout master
git merge --no-commit featureA
git checkout HEAD f3
git commit -m'Merge branch featureA - less the f3 changes'
git tag APM
git merge --no-edit featureB

# and test the given solution:

git checkout -b AM APM^
git merge --no-edit featureA
git merge --no-edit -s ours APM
git checkout master
git merge --no-edit AM

# say `rm -rf f* .git` to cleanup
jthill
  • 55,082
  • 5
  • 77
  • 137
  • Great, let me give this a try. – gjcamann Dec 11 '14 at 21:24
  • I fixed the branch creation command, sorry for late edit on that. – jthill Dec 11 '14 at 21:36
  • Thank for the attempt, but this didn't work... Git is still too smart for us mortals to trick. I think the only choice is to go back in time and re-merge everything. – gjcamann Dec 11 '14 at 22:00
  • See the above test script please. Unless I've misread something it recreates your given commit graph and gives the requested result. – jthill Dec 12 '14 at 00:01
  • Oh yeah, it did work! I was trying it without tagging the partial merge and just using the SHA1 ID's. I must have screwed something up. Much easier with a tag Thanks loads!!! – gjcamann Dec 12 '14 at 13:57
1

IMO partial merges as you described them are evil for the reasons you experience right now and other things.

For example: any developer which works with your repository would expect master to contain all changes from featureA. This could lead to a lot of confusion.


Note: I'll assume that you are currently in the root of the repository and on the master branch.

You effectivly have 2 easy options:

  1. You use git checkout --merge featureA -- . to merge the remaining changes of featureA into master - in this case you won't have a merge commit

  2. You make an empty commit (git commit --allow-empty) on featureA and then merge again

As you can see both solutions are suboptimal which is a result of your partial merge.

The cleanest approach would be to revert your old merge, split featureA into multiple commits and only merge the necessary commits. Then you can later on merge the remaining ones.
This obviously is also the approach with the most work.

Personally I would suggest either option 1. or 2. and to avoid such merges in the future.


EDIT I've tried to manually create a merge commit using the method I described in this answer. But it seems like git recognizes the fact that featureA already was merged and throws away the second parent, which results in a simple and not a merge commit.

Community
  • 1
  • 1
Sascha Wolf
  • 18,810
  • 4
  • 51
  • 73