简体   繁体   中英

How to flatten two top-level merge commits using git

I've recently encountered a git scenario which I don't know how to handle. First let's set the scene.

Ideal Workflow

Suppose you are maintaining a fork of a large open-source project, where upstream is doing development using pull requests (PRs). Since upstream is doing PRs their git history has lots of merge commits.

Periodically you will have to sync your tree with upstream. Typically this goes like so:

  • git checkout -b sync
  • git pull upstream master
  • Fix merge conflicts, before git add ing the conflict files, then git commit .

At this point you have a neat little merge commit at the top of the tree. Nested inside are all of upstream's commits and merges. You run your continuous integration tests (CI), they pass and the branch is merged into your fork's master branch. Wonderful.

Problematic Scenario

  • git checkout -b sync
  • git pull upstream master
  • You fix merge conflicts, git add and git commit them.
  • You run your CI and tests fail because upstream broke something.
  • Two days later, upstream fixes the problem.
  • git pull upstream master
  • Fix merge conflicts again.

At this point your sync branch contains two merge commits authored by you. Each individual merge commit contains upstream's various commits and merges.

You could run CI and merge now, but you shouldn't, because your first merge commit will interfere with future git bisects . Ideally you'd have a single merge commit which is the sum of the two merge commits in sequence.

How does one flatten those two merge commits into one, but preserving upstream's git history?

Notes

git rebase --preserve-merges is a) deprecated, and b) all or nothing. You can't selectively preserve all of upstream's merges, but flatten yours.

I've tried using git rebase --rebase-merges -i origin/master and marking the second merge f for fixup, but this doesn't achieve the desired effect. I got merge commits on files I've never touched.

One solution would be to redo the entire merge from scratch now that upstream is fixed. The problem is, even with rerere this will be a lot of work. In these upstream syncs you end up fixing stuff which isn't a merge conflict, but is (eg) a compilation or logic error. Because these fixes are secondary to merge conflicts (separate commits), rerere doesn't record automatic resolutions for them.

If I understand correctly, you want a single merge commit: the upstream tree, including recent fixes, merged with your sync branch. So, that's what you should do: reset your sync branch to the commit prior to the original merge, then perform the merge again.

If you have made additional changes on your sync branch after the original merge but before the upstream fixes became available, you can rebase those commits onto the commit prior to the original merge first:

git checkout -b sync
git rebase --onto ${PREMERGECOMMIT} ${MERGECOMMIT}
git pull upstream master

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM