简体   繁体   English

合并基于另一个功能分支的功能分支

[英]Merging a feature branch that is based off another feature branch

A few days ago I had a master branch with a completely linear history. 几天前,我有一个完全线性历史的master分支。 Then I created a feature branch, which we'll call feat/feature-a . 然后我创建了一个功能分支,我们称之为feat/feature-a I worked on that branch, then submitted it for code review to be merged into master . 我在那个分支上工作,然后将其提交给代码审查以合并到master

While feat/feature-a was being reviewed, I wanted to work on another feature that relied on some code introduced by feat/feature-a . 虽然正在审查feat/feature-a ,但我还想开发另一个依赖于feat/feature-a引入的代码的feat/feature-a So I created a feat/feature-b branch from the feat/feature-a branch. 所以,我创建了一个feat/feature-b从分支feat/feature-a分支。

While I was working on feat/feature-b , feat/feature-a got merged into master. 当我正在研究feat/feature-bfeat/feature-a合并为master。 So now master has the code introduced by feat/feature-a . 所以现在master拥有feat/feature-a引入的代码。 I now want to merge feat/feature-b into master, but I get a lot of merge conflicts that look like this: 我现在想将feat/feature-b合并到master中,但是我得到了很多看起来像这样的合并冲突:

<<<<<<< HEAD
=======
    // Some code that was introduced by feat/feature-b
>>>>>>> c948094... My commit message from feat/feature-b

My guess is that because I took feat/feature-a changes into my feat/feature-b branch, I'm now trying to "duplicate" those changes which is ending in merge conflicts. 我的猜测是因为我将feat/feature-a更改为我的feat/feature-b分支,我现在正试图“复制”那些以合并冲突结束的更改。

I can resolve these manually, but they exist multiple times over tens of files, so I'd like to know a better solution if there is one. 我可以手动解决这些问题,但它们在数十个文件中存在多次,所以如果有的话,我想知道更好的解决方案。

Summary: use git rebase --onto <target> <limit> 摘要:使用git rebase --onto <target> <limit>

As Useless suggested in a comment , if you had a real merge, this should not happen. 正如评论中提到的无用 ,如果你有一个真正的合并,这不应该发生。 Here's what I mean by a "real merge", along with a diagram of how the branching looks if you draw the graph of the commits in question. 这就是我所说的“真正的合并”,以及如果你绘制相关提交图形时分支的外观图。 We start with something like this: 我们从这样的事情开始:

...--E---H         <-- master
      \
       F--G        <-- feat/feature-a
           \
            I--J   <-- feat/feature-b

Here there are two commits (though the exact number does not matter) that are only on feat/feature-b , called I and J here; 这里有两个提交(虽然确切的数字并不重要) 只有feat/feature-b ,在这里称为IJ ; there are two commits that are on both feature branches, called F and G ; 两个特征分支上有两个提交,称为FG ; and there is one commit that is only on master , called H . 并且有一个提交master ,称为H (Commits E and earlier are on all three branches.) (提交E和早期的所有三个分支。)

Suppose we make a real merge on master to bring in F and G . 假设我们在master进行真正的合并以引入FG That looks like this, in graph form: 看起来像这样,以图表形式:

...--E---H--K      <-- master
      \    /
       F--G        <-- feat/feature-a
           \
            I--J   <-- feat/feature-b

Note that real merge K has, as its parent commit history pointers, both commit H (on master ) and G (on feat/feature-a ). 注意,真正的合并K作为其父提交历史指针,具有提交H (在master )和G (在feat/feature-a )。 Git therefore knows, later, that merging J means "start with G ". 因此,Git后来知道合并J意味着“从G开始”。 (More precisely, commit G will be the merge base for this later merge.) (更确切地说,提交G将是后来合并的合并基础 。)

That merge would just work. 合并就行了。 But that's not what happened before: instead, whoever did the merge used the so-called "squash merge" feature. 但这不是之前发生的事情:相反,合并的人使用了所谓的“壁球合并”功能。 While squash-merge brings in the same changes that an actual merge would, it doesn't produce a merge at all. 虽然squash-merge带来了与实际合并相同的更改 ,但它根本不会产生合并。 Instead, it produces a single commit that duplicates the work of the however-many-it-was commits that got merged. 相反,它会产生一个单独的提交,它复制了已合并的许多提交的工作。 In our case, it duplicates the work from F and G , so it looks like this: 在我们的例子中,它重复了FG的工作,所以它看起来像这样:

...--E---H--K      <-- master
      \
       F--G        <-- feat/feature-a
           \
            I--J   <-- feat/feature-b

Note the lack of a back-pointer from K to G . 注意缺少从KG的后向指针。

Hence, when you go to merge (real or squash-not-really-a-"merge") feat/feature-b , Git thinks it should start with E . 因此,当你去合并(真实壁球 - 不是真正的“合并”) feat/feature-b ,Git认为它应该从E开始。 (Technically, E is the merge base, rather than G as in the earlier real merge case.) This, as you saw, winds up giving you a merge conflict. (从技术上讲, E是合并基础,而不是早期真正合并案例中的G )正如您所看到的,这最终会给您一个合并冲突。 (Often it still "just works" anyway, but sometimes—as in this case—it doesn't.) (无论如何它通常仍然“正常工作”,但有时 - 就像在这种情况下 - 它没有。)

That's fine for the future, perhaps, but now the question is how to fix it. 或许这对未来很好,但现在的问题是如何解决它。

What you want to do here is to copy the exclusively- feat/feature-b commits to new commits, that come after K . 你想要做的是 feat/feature-b提交复制K之后的提交中。 That is, we want the picture to look like this: 也就是说,我们希望图片看起来像这样:

              I'-J'  <-- feat/feature-b
             /
...--E---H--K        <-- master
      \
       F--G          <-- feat/feature-a
           \
            I--J     [no longer needed]

The easiest way to do this is to rebase these commits, since rebase means copy. 要做到这一点,最简单的办法就是变基这些提交,因为底垫的副本。 The problem is that a simple git checkout feat/feature-b; git rebase master 问题是一个简单的git checkout feat/feature-b; git rebase master git checkout feat/feature-b; git rebase master will copy too many commits. git checkout feat/feature-b; git rebase master会复制太多提交。

The solution is to tell git rebase which commits to copy . 解决方案是告诉git rebase 提交复制 You do this by changing the argument from master to feat/feature-a (or the raw hash ID of commit G —basically, anything that identifies the first 1 commit not to copy). 您可以通过将参数从master更改为feat/feature-a (或提交G的原始哈希ID - 基本上,标识前1个提交复制的任何内容)来执行此操作。 But that tells git rebase to copy them to where they already are; 但这告诉git rebase将它们复制到已经存在的位置; so that's no good. 所以这不好。 So the solution for the new problem is to add --onto , which lets you split the "where the copies go" part from the "what to copy" part: 因此, 问题的解决方案是添加--onto ,它允许您从“要复制的内容”部分拆分“副本去哪里”部分:

git checkout feat/feature-b
git rebase --onto master feat/feature-a

(this assumes you still have the name feat/feature-a pointing to commit G ; if not, you'll have to find some other way to name commit G —you may wish to draw your own graph and/or or look closely at git log output, to find the commit hash). (这假设您仍然有名称feat/feature-a指向提交G ;如果没有,您将必须找到一些其他方式来命名提交G - 您可能希望绘制自己的图形和/或仔细观察git log输出,找到提交哈希)。


1 "First" in Git-style backwards fashion, that is. 以Git风格向后的方式获得1个 “第一”,即。 We start at the most recent commits, and follow the connections backwards to older commits. 我们从最近的提交开始,然后按照连接向后提交旧提交。 Git does everything backwards, so it helps to think backwards here. Git向后做所有事情,所以在这里思考倒退会很有帮助。 :-) :-)

The simple model looks like this: 简单模型如下所示:

X  -> MA  <master
  \  /
   A      <feature-a

here, feature-a may have been squashed via rebase into a single commit, but the merge is still a real merge. 在这里,feature-a 可能已经通过rebase压缩成单个提交,但合并仍然是真正的合并。 Then, you have 然后,你有

X -> MA -> MB  <master
 \  /     /
  A ---> B     <feature-b

where feature-b is based on feature-a after any squashing, and also merged normally. 其中feature-b基于feature-a之后的任何压缩,并且正常合并。 This case should just work , because git can see that A is an ancestor of B and that you already merged it. 这种情况应该可行 ,因为git可以看到AB的祖先并且你已经合并了它。

For comparison, this won't work cleanly: 为了比较,这不会干净利落:

X -> MA -> ...                  <master
|\  /      
| As                            <feature-a
|  |
|  ^--squash------<--
 \                   \
  A0 -> A1 -> ... -> An -> B    <feature-b

you squashed A0..n into As before merging feature-a, but feature-b was branched from An. 你在合并feature-a之前将A0..n压缩为As,但是feature-b是从An分支出来的。

Now git has no idea how As and A0..n are related, so neither merging nor (simple) rebasing will work automatically. 现在git不知道As和A0..n是如何相关的,所以合并和(简单)变基都不会自动运行。 See torek's excellent answer if you want to use rebase --onto to fix this situation. 如果你想使用rebase --onto来修复这种情况,请参阅torek的优秀答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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