当分别合并到qc和master时,合并分支之间的冲突



我们从master分支(我们不使用dev分支,不要问为什么),然后当QA对分支提出合并请求时,我们将其合并到qc分支,然后当他们完成测试时,我们把它合并到master分支。我们从不将qc合并到master,也从不将分支从master重新调整到qc,尽管我们可以将分支快进到master(通过git merge--ff only master或git rebase-i),但通常只是跨域合并是可以的。

然而,分支(基于经理确定的任务)可以在不同的基础上启动,以不同的顺序合并到qc,然后以不同的次序合并回master。

问题是,我们经常不得不为不同的功能更改同一行代码(由于代码最初的结构方式和请求的更改,我们无法完全避免这种情况),所以我们几乎每周都会遇到合并冲突,当事情以不同的顺序合并到不同的分支时,我们最终可能不得不一次又一次地解决相同的冲突,或者将不同的更改合并到同一行(或者必须将不同分支中的新行扩展到不同的行)。Whew

为了消除相同的合并冲突修复,我们尝试将合并到QC中的分支重新定基(这可能是基于很久以前的主提交),但这在某种程度上复制了QC和master上的提交;我们可以将提交重新应用到master,而在qc上丢失提交,因为我们可以将其合并回qc(无论何时更新,我们都将master合并到qc)。

我们如何避免这些重复的提交和冲突,有没有更好的方法来管理这一点?

有人提出了一件事:git rebase --onto master qc branch对于需要从qc转到master以防止冗余冲突修复的冲突分支来说,这可行吗?

重新调整重复提交,因为这就是重新调整基础的意义:它是一系列自动的git cherry-pick操作,而git cherry-pick是一个提交复制器。

每个对象的标识都是其散列:例如251654c5f6f256fe6e23c2c85f1a70594aae00d4。哈希值是正在进行哈希处理的对象的内容的校验和。在提交对象的情况下,内容为:

  • 提交作者的姓名和电子邮件
  • 提交人的姓名和电子邮件
  • 提交的顶级源树的标识(即,与该提交相关联的每个文件的快照,由树对象的ID表示)
  • 父提交ID的列表(更多提交对象哈希);以及
  • 提交的日志消息

通常,选择提交的原因是制作一个稍微更改的版本——事实上,如果你制作一个完全相同的副本,你只会得到原始提交的ID,因为同一组输入位的哈希值是相同的。不过,对于精心挑选的提交,通常会有一个稍微不同的源树,并且几乎总是有一个不同的父ID。

考虑一下这个(稍微修改一下,以击败垃圾邮件扫描仪)示例commit:

tree 3b2ac3530e713fc93aa93525bed9623679f99173
parent d2628061aa3b38b9f5dbdcd9136711a5a4ac3a1a
author Chris Torek <chris.torek@somewhere.com> 1459821791 -0700
committer Chris Torek <chris.torek@somewhere.com> 1459821791 -0700
distributed: clarify GUID uniqueness
Add the phrase {Doppelg"anger commit} in a side-note.
Git and Mercurial allow them as long as they never meet.

我可以通过首先对其父提交执行git diff来复制此提交(以显示我更改了什么):

diff --git a/distributed.tex b/distributed.tex
index 40fb7fd..6d8854b 100644
--- a/distributed.tex
+++ b/distributed.tex
@@ -41,8 +41,18 @@ to discover and exchange commits
whenever you direct the system to synchronize your clone
with a peer.
In order to make this work correctly,
-these GUIDs really must be globally unique
-(across all repositories).
+these GUIDs really must be globally unique.sidenote
+{More specifically, they must be unique
+among all clones of a given repository,
+emph{including forks that may rejoin in the future}.
+This is a somewhat weaker requirement than true global uniqueness.
+For instance, if Alice makes a commit,
+but then destroys it without ever sharing it with anyone else,
+the destroyed commit is allowed to have the same GUID
+as some future commit,
+or a commit in an unrelated repository.
+You can think of this as allowing Doppelg"anger commits:
+they may share a GUID only as long as they never meet.}
It would not do
for Bob to create a emph{different} commit (in git)
or changeset (in mercurial)

现在我有了diff,我可以检查其他提交,将diff作为补丁应用,并根据结果重新提交,重新使用日志消息。我将获得一个新的提交,在文件distributed.tex上具有相同的日志消息和相同的效果,但(可能)不同的tree和(肯定)不同的parent,以及一个新提交器时间戳,因此是一个不同的哈希。

这是一个简单的选择:将提交与其父级进行比较(将其转换为变更集),将更改应用于其他地方,并根据结果进行新的提交。假设我们对提交的链重复此操作:

... <- B3 <- B4 <- B5   <-- branch-X

F6 <- F7 <- F8       <-- feature-Y

假设我们复制F6F8。调用F6F6'的副本,将其与原件区分开来。使F6'的父级为B5F7'的父级是F6'F8'的父级则为F7',从而得到:

F6' <- F7' <- F8'
/
... <- B3 <- B4 <- B5   <-- branch-X

F6 <- F7 <- F8       <-- feature-Y

现在让我们做最后一件事。让我们移除标签feature-Y,它当前被粘贴在提交F8上,并将其粘贴在F8'上:

F6' <- F7' <- F8'   <-- feature-Y
/
... <- B3 <- B4 <- B5   <-- branch-X

F6 <- F7 <- F8       [abandoned]

瞧,我们刚刚重新建立了feature-Y上的一些提交的基础,即复制了。新副本现在在分支feature-Y上(在复制过程中,它们在no分支上,或者更确切地说,在"分离的HEAD"模式下的特殊匿名分支上),原始副本是。。。好吧,还在那里。

我在这里标记了它们[放弃],但如果有其他分支或标记标签可以让您找到提交F8,您仍然可以看到所有原始提交。git rebase命令移动feature-Y标签,但不查看其他标签是否保持提交可见。


编辑:如果通过合并提交可以看到一些复制的然后放弃的提交,也是如此,例如:

F6' <- F7' <- F8'   <-- feature-Y
/
.... <- B3 <- B4 <- B5   <-- branch-X
  
  F6 <- F7 <- F8       [abandoned]
               
C2 <--- C3 <-- C4     <-- branch-C

这里,branch-C使提交C2C4可见,但C4是使F8可见的合并提交。因此,现在当您浏览存储库时,F8(通过branch-C中的C4)和F8'(通过feature-Y)都将显示。

这类事情控制了何时以及为什么你应该或不应该重新调整基准。由于rebase复制提交,您需要确定以下两件事之一:原始文件最终会被放弃,或者原始文件可以保存在其他地方。[结束编辑。]


git rebase为您自动化的另一件事是选择提交复制的。为什么我们决定复制,特别是F6F8?为什么把这三个都包括在内,而把其他的都排除在外?这就是git rebase的论点所在。现在让我们回到这个建议:

git rebase --onto master qc branch

这是git rebase的完全指定形式。最后一个参数,在本例中为branch,使git rebase从执行git checkout branch开始。也就是说,额外的参数只是传递给git checkout。在那之后,额外的论点就没有用了,所以我们可以这样做:

git checkout branch && git rebase --onto master gc

相反。

命令的--ontocommit部分指定副本的位置。更具体地说,我们将F6复制到F6',其新的父级是B5。如果使用--onto,则指定此目标提交。(使用分支名称,在本例中为master,只意味着在该分支上使用提示提交。)

剩下的一个论点,在我们的案例中是gc,是git rebase所说的<upstream>

如果没有指定--onto<upstream>参数将有两个角色:它将命名目标提交,它将为git rebase提供提交的标识,它应该特别而不是复制(然后根据该标识复制更多提交)。然而,当我们使用--onto时,我们已经指定了目标提交,所以剩下的参数只剩下一个角色:指定提交以避免复制。

这种"提交以避免复制"的工作方式可能有点棘手。我们指定了一个特定的提交,但rebase在这里使用了git自己一直使用的技巧。当我们将git指向某个提交时,我们可以要求它只查看提交:

$ git rev-parse c96a
c96af44caf036cf9f04d77c6146086a4ee422ceb

或者我们可以要求它查看该提交及其所有父提交(也称为其祖先):

$ git rev-list c96a
c96af44caf036cf9f04d77c6146086a4ee422ceb
bca8213e6fda6fdf92b3a5fbc4ee59f755e04a9c
86ac01b3e1d771033e93af93366ace1b586d7c74
bc3fa7a3571231e334f6f06e5610e04227ef1f0b

(默认情况下,rev-parse做一个提交的事情,而rev-list做祖先变体,尽管rev-list非常灵活,是许多git命令的核心,或者至少是肝脏或脾脏)。

Git的rebase选择其具有祖先的<upstream>参数来获得对exclude的提交,而选择具有祖先的当前分支(如果您给它一个额外的参数,则在检查它之后)来获得对include。排除排除集之后,剩下的就是要复制的提交。

使用--onto表单可以将<upstream>从其双重职责角色中释放出来,因此在选择哪些提交以避免复制时可以更有选择性。尽管如此,您仍将复制提交,包括所有这些内容。特别是,如果您使用rebase来复制其他人正在使用的提交(然后忘记的原始文件),他们还必须重新base他们所做的任何依赖于原始文件的工作。

最新更新