在合并到 master 时压缩第一个分支之后合并一个分支

下面是我在工作中经常处理的一个工作流程。

git checkout -b feature_branch
# Do some development
git add .
git commit
git push origin feature_branch

此时,特性分支正在等待我的同事的审查,但是我想继续开发依赖于 feature_branch的其他特性。因此,当 feature_branch在审查中时..。

git checkout feature_branch
git checkout -b dependent_branch
# Do some more development
git add .
git commit

现在我做一些更改以响应 Feature _ Branch 上的代码审查

git checkout feature_branch
# Do review fixes
git add .
git commit
git checkout dependent_branch
git merge feature_branch

这就是我们有问题的地方。我们在 master 上有一个 squash 策略,这意味着合并到 master 中的特性分支必须被压缩到一个单独的提交中。

git checkout feature_branch
git log # Look for hash at beginning of branch
git rebase -i  first_hash_of_branch # Squash feature_branch into a single commit
git merge master

一切都很酷,除了 dependent_branch。当我尝试将依赖分支重新基于 master 或尝试将 master 合并到其中时,git 会被重写/压缩的历史记录弄糊涂,并且基本上将 depedendent_branch中的每一个更改都标记为冲突。这是一个 PITA,通过和基本上重做或非冲突化的 dependent_branch的所有变化。有什么解决办法吗?有时候,我会手动创建一个补丁,然后在一个新的 master 分支上应用它,但是如果与之有任何真正的冲突,那么修复起来就更糟糕了。

git checkout dependent_branch
git diff > ~/Desktop/dependent_branch.diff
git checkout master
git checkout -b new_dependent_branch
patch -p1 < ~/Desktop/dependent_branch.diff
# Pray for a clean apply.

有什么想法吗?我知道这是因为壁球比赛期间重写了历史,但这是我不能改变的要求。最好的解决办法是什么?我能施魔法吗?还是有更快的方法来完成手动创建 diff 所涉及的所有步骤?

12404 次浏览

A little bit about why this happens:

I'll let O be "original master" and FB be "new master", after a feature branch has been merged in:

Say feature_branch looks like:

O - A - B - C

dependent_feature has a few extra commits on top of that:

O - A - B - C - D - E - F

You merge your original feature branch into master and squash it down, giving you:

O - FB

Now, when you try to rebase the dependent branch, git is going to try to figure out the common ancestor between those branches. While it originally would have been C, if you had not squashed the commits down, git instead finds O as the common ancestor. As a result, git is trying to replay A, B, and C which are already contained in FB, and you're going to get a bunch of conflicts.

For this reason, you can't really rely on a typical rebase command, and you have to be more explicit about it by supplying the --onto parameter:

git rebase --onto master HEAD~3  # instruct git to replay only the last
# 3 commits, D E and F, onto master.

Modify the HEAD~3 parameter as necessary for your branches, and you shouldn't have to deal with any redundant conflict resolution.

Some alternate syntax, if you don't like specifying ranges and you haven't deleted your original feature branch yet:

git rebase --onto master feature_branch dependent_feature


# replay all commits, starting at feature_branch
# exclusive, through dependent_feature inclusive
# onto master

I heartily disagree with the "mash every feature development down into a single commit" policy, but it's their call...

I'd keep the branches as-is, and create a mashed up commit just for publication, in a special branch. Being able to follow the development step by step is valuable, even if management doesn't believe in it. Mark the places of squashes by tags in the "real" branches, you could also add between-squash tags with messages pointing back to the real commits.

In this particular case it seems you "know" that only the squashed work of the branch you originally worked on has been put into master.

So you can happily merge by keeping your changes every time when there is a conflict. There is an option for that:

git merge -Xours master

See https://git-scm.com/docs/git-rebase for details more details.

In case your dependent branch has multiple non-trivial changes, rebasing like @joshtkling suggested, can become painful. I have often turned to an alternative strategy which might be easier. This doesn't mean using rebase is wrong or anything, I just want to offer an alternative. It uses the "ours" strategy, but in contrast to @nha's answer it also works if there is more work on the master.

Suppose the starting situation

O - A - B - C (feature_branch) - D - E - F (dependent_branch)
\
- D - Cs (squash merged feature branch) - G - H (master)

where D, G and H are work done by others, possibly squash merges from other branches not shown here.

Merging dependent_branch into master is likely to produce conflicts, because the changes from A, B and C are duplicated. In order to tell git that it does not need to consider these, we can introduce a new merge base that combines C and Cs, by creating a temporary branch and then merge that into our dependent branch. This way git has much more knowledge for merging.

git checkout -b temp Cs
git merge C --strategy ours
git checkout dependent_branch
git merge temp
git branch -d temp

The second merge might yield some conflicts, but in my experience they are fewer and simpler to solve than when using rebase. Afterwards merging dependent_branch and master should be a lot easier.