2

I have a topic branch that looks like this. (the branch topic currently points at 'a0a0')

  • a0a0 Merge branch 'Master' into topic
  • b1b1 Merge Branch 'Master' into topic
  • c2c2 Merge commit 'something from master' into topic
  • d3d3 Merge Branch 'Master' into topic
  • e4e4 Merge Branch 'Master' into topic
  • f5f5 Merge Branch 'Master' into topic
  • 6666 an actual commit
  • [lots of history on the topic branch]
  • 9999 original divergence point

How do I turn a0-f5 into a single merge commit?

I've tried: git rebase -i f5f5 with the intention of telling it to squash those 5 commits, but instead it just gave every commit on the master branch between f5f5 and a0a0. So I turned all but the first and last from pick into squash, but that went crazy and rebased every single commit from master into a giant commit in place of all the merges, losing the fact that it was a 'merge'. That isn't what I expected!

I can do:

  $ git reset 6666
  $ git merge Master

but that requires me to re-do any merge commits.

So how do I compress those different merges into a single merge commit, that knows it's a merge, and also retains all the merge-conflict stuff?

Pod
  • 3,938
  • 2
  • 37
  • 45
  • A `git rebase -p -i` (`-p` standing for "preserve merge") is supposed to do that... but I have some doubt: see comments in http://stackoverflow.com/a/12967782/6309 – VonC Oct 23 '12 at 12:41
  • Thanks. I'll give it a try at some point, though I had to wait ages as it applied hundreds of patches, occasionally tripping up and requiring my input (never actually a conflict, I would just have to git add -u, git rebase --continue) – Pod Oct 23 '12 at 15:05

3 Answers3

5

This will do it:

git reset --hard $(git commit-tree -p f5f5 -p Master -m "Merge hurricane" a0a0:)

To understand how it works, first remember that there is absolutely nothing magical about a merge commit. It can be hard to create, but once created, it's embarrassingly easy to recreate exactly. This is because git doesn't record changesets, it records entire trees. Tools like git show display commits as diffs, and merge commits as very fancy diffs, but in reality a commit is the pointer to a full-fledged tree. Squashing some commits is a simple matter of recreating a new commit that contains the same tree, giving it a different parent and commit message.

The plumbing command git commit-tree does this last part. Given a parent commit (f5f5), a commit message, and a tree ID (easily referenced with COMMIT:), it creates a new commit that contains the specified tree. If the commit has a single parent, it will be a regular commit. If it has multiple parents, it will be a merge commit. Once the squashed merge commit is created, what remains is to point the current branch to the new commit with git reset --hard.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • This is exactly what I wanted and was trying to do using the porcelain (except I used 6666 rather than f5f5, same thing). Thanks :) Note: I had to update a computer from the ancient 1.7.4 for this to work. Not sure which version it was added in, other than 'a year ago'. https://github.com/msysgit/git/commit/79a9312cc99f9c2f2949e1211c708896e6037f64. – Pod Oct 24 '12 at 08:56
  • I'm not sure if this can be done using porcelain. Possibly one could `git reset --hard 6666`, then `git merge -s ours Master` to create a dummy merge without conflicts, then `git checkout a0a0 -- .` at top-level to copy `a0a0`'s tree to the index, and finally `git commit --amend` to create the desired commit. Plumbing-free, but I find it easier to remember and reason about the variant that makes use of `git commit-tree`. – user4815162342 Oct 24 '12 at 17:59
  • 1
    Answer is coming in handy once again. I've taken to calling them Merge Hurricanes. – Pod Nov 02 '12 at 15:20
  • Glad to be of service, but if you're using this hack *too* often, it might be time to rethink your git workflow. :) – user4815162342 Nov 02 '12 at 15:32
  • It's a private topic branch that I need to keep updated. It's very quite old and full of commits that I don't want to be suddenly made context-less by rebasing. – Pod Jan 03 '13 at 10:49
1

Try this:

git checkout 6666 -b master_squashed
git merge --squash master
git commit -m 'My new feature'
git checkout master
git merge master_squashed
Greg H
  • 445
  • 4
  • 12
  • This raises all of the merge conflicts already resolved in a0,b1 etc. I'd prefer not to do that, otherwise I would just go with the `git reset 6666 && git merge master` method. – Pod Oct 23 '12 at 15:03
  • Hmmm -- well that's all I have for that one. Sorry I couldn't be of more help. – Greg H Oct 23 '12 at 18:34
1

A late answer:

git diff 6666 f5f5 > patch-f5f5.patch
git checkout 6666
git apply patch-f5f5.patch

As you prefer, you may also use git apply --cached or git apply --index which allow you to make further modification before commit.

cuter44
  • 160
  • 4
  • 12