为什么我的分支机构认为与主部门的更改是“旧的”?

时间:2019-05-15 23:39:21

标签: git

背景

我们使用基本的功能分支工作流程。功能分支是从母版创建的,在存在时会定期与母版合并更新,然后在请求请求后合并回母版。

问题

我们当前的一个分支已进入一种状态,其中git似乎认为该分支的内容总是比master中的新。一些例子:

  • 如果将master合并到分支中,则将忽略master新增的任何文件(在创建分支后添加);它们不会在合并过程中添加到分支中。我的假设是git会将它们视为在分支上被故意删除。
  • 自创建分支以来,master中已经修改了几个文件。当合并PR时,Bitbucket拉取请求将这些更改的反向显示为对主控的挂起更改。例如,假设主行中的行已从
myConstant = 26

myConstant = 27

将master合并到分支后,PR显示合并[将分支重新合并到master]将27 [back]更改为26

注意:这是一个运行时间很长的功能分支,它有多个“子功能分支”。至少有一个被合并回去。另一个仍然存在,并且更新已从主分支合并到功能分支,然后从功能分支合并到子功能分支。

如何解决?

我不确定我们是如何陷入困境的。我正在筛选价值几个月的提交,并且我发现了一些可能做得不好的建议。例如,从主合并到子功能分支再到功能分支。但是,我还没有找到一个文件列表很大的文件。如果有人对什么类型的动作让您陷入这种情况有更具体的想法,我很乐意听到。

即使我找到了吸烟枪,也不确定哪一种是纠正这种情况的最佳方法。我当前的想法是覆盖(知道是否从master检出)任何我们知道在分支中未明确更改的文件。显然,该分支还具有比master更新的更改,因此即使那样也是乏味且冒险的。欢迎提出建议:)

更新1

在进一步挖掘并使用了@torek出色答案中的某些工具之后,我发现了问题提交-或其中至少一个。提交图看起来像这样(我使用数字来避免与先前讨论的字母混淆。X表示一个或多个非合并提交。Master在底部;我没有在这里注意到单个提交。 ):

                                                                               X-------57-X--------61-X--------62--65
                                                                              /        /           /            \   \
                                                                           51-55-X-56- / -X-67-----73-------------63--66-71
                                                                          /       /  /     /      /                     /
    34-35-X----37-X-X-38-X-X-39------41-X-----42-X-----44-X-46---47-X-50-X-53---54--------60-----68                    /  
    /          /      /      /       / \      /        /        /    /          /                /                    /
32-33----36-X-X----- / ---- / ---X-40   ---- / --43-X---X---45-48-X-49-52---------58------------59-------69---72     /
/        /          /      /       /        /               /       /  /          /              \       /    /     /
-------------------------------------------------------------------------------------------       70--- / -- / -----
                                                                                           \      /    /    /
                                                                                            ----------------

提交39可能是问题所在。提交者似乎已开始合并,清除了所有未决的更改(不中止合并),进行了单个文件更改,然后提交。与来自38的父提交的比较仅显示单个文件更改。但是,与主提交与父提交的比较显示出意外更改的清单。

我现在进入“该做什么”阶段。我正在研究各种选项,包括更改提交39本身,从71开始进行新提交以撤消39中的更改,等等。

更新2

经过阅读后,似乎大多数人都推荐同样的东西-概述得很好here。我目前的想法是,在很远的基础上进行合并(此后进行大量合并)可能会很难看(尽管我的基础功夫不强),所以我倾向于采用还原方法(git revert -m 2 <Commit_39_SHA>)。无论如何,Bisect在这些分支上已经被很好地打破了。

计划是将所有子功能分支合并到主功能分支中,然后在其中运行还原。如果我正确地理解了所有内容,那么我认为不需要“还原还原”步骤。

1 个答案:

答案 0 :(得分:4)

这绝不是 age 的问题。对于合并,通常是合并基础

每个提交都有一个父级,如果是合并提交,则有两个或多个父级。每次提交的真实名称是其哈希ID,即丑陋的字母和数字字符串,例如83232e38648b51abbcbdb56c94632b6906cc85a6。该真实名称使Git可以找到实际的提交。提交本身存储其父代的哈希ID,这使Git可以找到那些提交。他们依次存储其父母的哈希ID,依此类推。结果是,对于大多数提交,都有一个简单的线性向后链:

... <-F <-G <-H

其中H是一些提交哈希ID,H的父是G,父是F,依此类推。

这个向后指向的链(大多是线性的)是历史。 Git中的历史只是一系列提交。像master这样的分支名称仅拥有一个哈希ID:链中 last 提交的ID。因此我们可以将以上内容绘制为:

...--F--G--H   <-- master

每个提交都具有所有文件的完整且完整的快照-好吧,截至提交时的所有文件,因为一旦提交,每个提交都会及时冻结。

发生分支是因为两个不同的提交具有一个共同的提交作为其(共享的)父提交:

             I--J   <-- branch1
            /
...--F--G--H
            \
             K--L   <-- branch2

Git将在每次提交(在这种情况下为JL)处开始,然后向后进行。从两个分支开始并向后工作时,两个分支到达其共享的公共提交H

如果您选择一个分支到git checkout,则会得到一次提交:

             I--J   <-- branch1 (HEAD)
            /
...--F--G--H
            \
             K--L   <-- branch2

Git已将HEAD附加到名称branch1上,这意味着提交J是您已检出的提交:branch1标识提交J。 / p>

现在您运行git merge branch2。 Git找到提交L,因为名称branch2指向L。从JL开始,Git向后工作,一次提交一次,以找到共享的提交H。这是合并基础

Git现在将H中的快照(您在branch1上开始的快照)与J中的快照(您可能也已经做了)进行比较,现在。这里的不同就是您您更改的。如果您没有myConstant = 26更改为myConstant = 27,那就是您对该特定文件的更改。如果您根本没有更改myConstant,则表示您没有进行更改。

Git还会比较H中的快照,这是快照在branch2上的最佳快照,也是您从({commit FG也可以使用,但显然效果不佳)-提交L,以查看他们发生了什么变化。

在完成从HJ以及从HL的比较之后,Git现在知道您进行了哪些更改,以及发生了什么他们已更改。 何时更改内容或何时更改内容都没有关系。重要的是某人是否更改了某些内容。无论如何,Git都会从H中提取所有文件(而不是JL),对它们应用两组更改,并且-如果存在没有冲突-提交结果:

             I--J
            /    \
...--F--G--H      M   <-- branch1 (HEAD)
            \    /
             K--L   <-- branch2

由于您在branch1上,因此新提交Mbranch1上进行。因为这是 last 提交,所以Git更新了名称branch1以标识提交M。同时,尽管提交M没有一个,但是有两个 父提交:J是您的上一个技巧,L仍然是它们的分支技巧。 / p>

现在假设您他们对myConstant进行了一些更改。然后,当尝试合并您的两个更改时,Git将声明合并冲突。无论谁正在运行git merge进行修复,它都会在工作树(和索引)中留下混乱。然后由 human 决定通过编辑有冲突的文件并修复该行来确定提交M的内容。无论人类投入什么,Git都认为这是正确结果。假设人类选择了26个而不是27个,并提交了。提交M现在说myConstant = 26,并且 Git可以确定这是正确的,即使事实并非如此。或者,我们可以说人类选择了27个而不是26个,并提交了。现在,Git确信27是正确的。

假设您和他们继续进行更多提交:

             I--J
            /    \
...--F--G--H      M--N--O   <-- branch1
            \    /
             K--L--P--Q--R   <-- branch2

如果您现在选择这些分支之一进行检出,您将得到OR的提交。假设您选择O并再次运行git merge branch2

Git现在从O开始并向后工作:ONMJ。 ...并且它从L开始并向后工作:RRQP,...。请注意,它们都到达提交L。提交L是合并基础,而不是LM。因此,Git会将LM进行对比,以查看您所做的更改。这可能包括将26更改为27,或者-如果O本身的常量错误,则什么也不做。然后将ML进行比较,以了解它们的变化。

和以前一样,Git合并了这些更改,如果您和他们都触摸了同一文件的同一行,则会遇到合并冲突。您清理它并使合并自己提交。如果没有,并且Git成功地自行组合了所有内容,则Git立即进行合并提交。无论哪种方式,您现在都可以:

R

其中 I--J / \ ...--F--G--H M--N--O--S <-- branch1 (HEAD) \ / / K--L--P--Q--R <-- branch2 是您的合并结果。该文件中的常量已设置,但是您或Git会根据比较时是否存在冲突和/或您和/或他们是否对该行进行了任何更改来设置它(合并基础)与SL(合并的两侧)。

查找困难之处的人很容易,除非困难时

Git有一些工具,您可以通过这些工具找出谁将O设置为任何值。两个大的是RmyConstant。两者都从某个提交开始,以查看该提交中的内容,然后向后工作,一次提交一次。通过将 parent 提交中的内容与 child 中的内容进行比较,Git可以查看提交git loggit blame中是否有不同的行。如果是这样,Git可以告诉您,谁使N更改了该行。

但是当Git通过OO之类的合并向后工作时, Git应该与S比较哪个父级? Git的回答(复数)这很棘手。某些命令根本不需要打扰,例如M就是这样做。其他人则选择一位父母并继续进行合并,即从S开始,仅返回git log -p或仅返回S。诸如R之类的文件可以为您显示组合差异,该组合通常仅向您显示存在合并冲突的位置(组合差异忽略了两个文件都没有贡献的所有文件“边”)。

由于这类问题通常是在合并冲突时引入的,因此组合差异通常对于查找问题的来源很有用。不过,这不一定非常有用:使用O自动查找好的和不好的提交会更有用。您将一个提交声明为“好”(代码有效/正确),然后再声明为“坏”(代码失败/错误),并让Git自动在这两个提交之间来回搜索,从而处理分支-并自动在提交图中合并结构。

有时使用git show也很有用。这样做是分割每个合并。并非冒充git bisectgit log -m -p都是一次提交,而是为了S生成补丁的目的,假装有一个M与父项{ {1}}和第二个git diff与父级S1一起提交。然后,到达O时,它会假装与父级S2进行一次R提交,并与父级M进行第二次M1提交。这些拆分提交中的每一个都会对其(现在是孤单的)父级获得一个简单的,普通的, non 组合的差异,向您展示合并结果与该父级之间的差异。

所有这些都是查看发生情况的有用工具。但是,它们不会防止将来出现合并错误:Git所能做的就是将基准与这两个技巧进行比较,以了解您所更改的内容与所更改的内容。何时 无关紧要,只有发生什么变化重要。

编辑:格式化

相关问题