English 中文(简体)
git rebase interactive: squash merge commits together
原标题:

I wanted to have a simple solution to squash two merge commits together during an interactive rebase.

My repository looks like:

   X --- Y --------- M1 -------- M2 (my-feature)
  /                 /           /
 /                 /           /
a --- b --- c --- d --- e --- f (stable)

That is, I have a my-feature branch that has been merged twice recently, with no real commits in between. I don t just want to rebase the my-feature branch since it is a published branch of its own, I just want to squash together the last two merge commits into one (haven t published those commits yet)

   X --- Y ---- M (my-feature)
  /            /
 /            /
a --- ... -- f (stable)

I tried:

git rebase -p -i M1^

But I got:

Refusing to squash a merge: M2

What I finally did is:

git checkout my-feature
git reset --soft HEAD^  # remove the last commit (M2) but keep the changes in the index
git commit -m toto      # redo the commit M2, this time it is not a merge commit
git rebase -p -i M1^    # do the rebase and squash the last commit
git diff M2 HEAD        # test the commits are the same

Now, the new merge commit is not considered a merge commit anymore (it only kept the first parent). So:

git reset --soft HEAD^               # get ready to modify the commit
git stash                            # put away the index
git merge -s ours --no-commit stable # regenerate merge information (the second parent)
git stash apply                      # get the index back with the real merge in it
git commit -a                        # commit your merge
git diff M2 HEAD                     # test that you have the same commit again

But this can get complicated if I have many commits, do you have a better solution ? Thanks.

Mildred

问题回答

This is an old topic, but I just ran across it while looking for similar information.

A trick similar to the one described in Subtree octopus merge is a really good solution to this type of problem:

git checkout my-feature
git reset --soft Y
git rev-parse f > .git/MERGE_HEAD
git commit

That will take the index as it exists at the tip of my-feature, and use it to create a new commit off of Y, with f as a second parent. The result is the same as if you d never performed M1, but gone straight to performing M2.

if you haven t published the last two merge commits, you could do a reset and a simple merge.

git reset --hard Y
git merge stable

I came to this topic wanting to squash a single merge commit; so my answer is not that useful to the original question.

               X
                   
                   
a --- b --- c --- M1 (subtree merge)

What I wanted was to rebase the M1 merge and squash everything as a single commit on top of b.

a --- b --- S (include the changes from c, X and M1)

I tried all kinds of different combinations but this is what worked:

git checkout -b rebase b (checkout a working branch at point b)
git merge --squash M1

This will apply the changes into the index where they can be committed git commit

Using the tree object of the original merge commit will ensure the content is left unchanged. commit-tree can be used to make a new commit with the desired parents and the same content. But, for fmt-merge-msg to produce a normal merge message, you ll need to first soft reset back to Y. Here is everything packaged up with a generic recipe:

parent2=$(git rev-parse f)
parent1=Y
merge_branch=stable
tree=$(git rev-parse HEAD^{tree})
git reset --soft $parent1
commit=$(echo $parent2$ 		 "branch $merge_branch" | git fmt-merge-msg | git commit-tree -p $parent1 -p $parent2 -F - $tree)
git reset --hard $commit

Here is an alias that can be put in your ~/.gitconfig:

[alias]
    remerge = "!f() { p1=$1; p2=`git rev-parse $2`; t=`git rev-parse HEAD^{tree}`; git reset --soft $p1; git reset --hard `echo $p2$ 		 "branch ${3:-$2}" | git fmt-merge-msg | git commit-tree -p $p1 -p $p2 -F - $t`; }; f"

To enable:

git remerge <parent1-rev> <parent2-rev> [<parent2-branch>]

None of the mentioned methods works for me with a recent git version. In my case the following did the trick:

git reset --soft Y
git reset --hard $(git commit-tree $(git write-tree) -p HEAD -p stable < commit_msg)

You ll have to write the commit message to the file commit_msg first, though.

In my opinion, the best method is to place yourself on top of the merge-commits and undo the top commit and amend the changes to the previous merge-commit.

You will undo a git-commit with the following command:

    git reset HEAD~1

or

    git reset HEAD^

Then use:

    git add . && git commit --amend

Then check the results with:

    git log

You should see that the first merge-commit now has both changes included.

This method can be used for any such needs for all type of commits, or when

    git rebase -i HEAD~10

... cannot be used.





相关问题
git confusion - cloning a repo is returning a past version

Im having some confusion with my git usage. I cloned a repo from one comp to the other, and the new clone is the state of the original that was active some time ago. So its cloning a past version. ...

Appropriate strategy for tagging and hotfixing with git

I was wondering if the strategy I m using for tagging and hotfixing tags (which then I use for deploying rails applications) with git is appropriate. For tagging I just tag a commit of the master ...

Tips on upgrading CVS to git/hg?

We still use CVS, I use git and hg for my personal use though I m still a novice at both, but I realize they re much more modern and better, faster, distributed, etc. It s just everyone is so ...

Using Git in a TFS shop

Using Git at home has spoiled me - I now find using TFS at work to be a bit of a drag and want to explore the possibility of using Git locally and syncing somehow with TFS. I figure there are a few ...

热门标签