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