背景
我们使用基本的功能分支工作流程。功能分支是从母版创建的,在存在时会定期与母版合并更新,然后在请求请求后合并回母版。
问题
我们当前的一个分支已进入一种状态,其中git似乎认为该分支的内容总是比master中的新。一些例子:
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在这些分支上已经被很好地打破了。
计划是将所有子功能分支合并到主功能分支中,然后在其中运行还原。如果我正确地理解了所有内容,那么我认为不需要“还原还原”步骤。
答案 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将在每次提交(在这种情况下为J
和L
)处开始,然后向后进行。从两个分支开始并向后工作时,两个分支到达其共享的公共提交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
。从J
和L
开始,Git向后工作,一次提交一次,以找到共享的提交H
。这是合并基础。
Git现在将H
中的快照(您在branch1
上开始的快照)与J
中的快照(您可能也已经做了)进行比较,现在。这里的不同就是您您更改的。如果您没有将myConstant = 26
更改为myConstant = 27
,那就是您对该特定文件的更改。如果您根本没有更改myConstant
,则表示您没有进行更改。
Git还会比较H
中的快照,这是快照在branch2
上的最佳快照,也是您从({commit F
和G
也可以使用,但显然效果不佳)-提交L
,以查看他们发生了什么变化。
在完成从H
与J
以及从H
到L
的比较之后,Git现在知道您您进行了哪些更改,以及发生了什么他们已更改。 何时更改内容或何时更改内容都没有关系。重要的是某人是否更改了某些内容。无论如何,Git都会从H
中提取所有文件(而不是J
或L
),对它们应用两组更改,并且-如果存在没有冲突-提交结果:
I--J
/ \
...--F--G--H M <-- branch1 (HEAD)
\ /
K--L <-- branch2
由于您在branch1
上,因此新提交M
在branch1
上进行。因为这是 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
如果您现在选择这些分支之一进行检出,您将得到O
或R
的提交。假设您选择O
并再次运行git merge branch2
。
Git现在从O
开始并向后工作:O
和N
和M
和J
。 ...并且它从L
开始并向后工作:R
,R
,Q
,P
,...。请注意,它们都到达提交L
。提交L
是合并基础,而不是L
,M
。因此,Git会将L
与M
进行对比,以查看您所做的更改。这可能包括将26更改为27,或者-如果O
本身的常量错误,则什么也不做。然后将M
与L
进行比较,以了解它们的变化。
和以前一样,Git合并了这些更改,如果您和他们都触摸了同一文件的同一行,则会遇到合并冲突。您清理它并使合并自己提交。如果没有,并且Git成功地自行组合了所有内容,则Git立即进行合并提交。无论哪种方式,您现在都可以:
R
其中 I--J
/ \
...--F--G--H M--N--O--S <-- branch1 (HEAD)
\ / /
K--L--P--Q--R <-- branch2
是您的合并结果。该文件中的常量已设置,但是您或Git会根据比较时是否存在冲突和/或您和/或他们是否对该行进行了任何更改来设置它(合并基础)与S
和L
(合并的两侧)。
Git有一些工具,您可以通过这些工具找出谁将O
设置为任何值。两个大的是R
和myConstant
。两者都从某个提交开始,以查看该提交中的内容,然后向后工作,一次提交一次。通过将 parent 提交中的内容与 child 中的内容进行比较,Git可以查看提交git log
和git blame
中是否有不同的行。如果是这样,Git可以告诉您,谁使N
更改了该行。
但是当Git通过O
或O
之类的合并向后工作时, Git应该与S
比较哪个父级? Git的回答(复数)这很棘手。某些命令根本不需要打扰,例如M
就是这样做。其他人则选择一位父母并继续进行合并,即从S
开始,仅返回git log -p
或仅返回S
。诸如R
之类的文件可以为您显示组合差异,该组合通常仅向您显示存在合并冲突的位置(组合差异忽略了两个文件都没有贡献的所有文件“边”)。
由于这类问题通常是在合并冲突时引入的,因此组合差异通常对于查找问题的来源很有用。不过,这不一定非常有用:使用O
自动查找好的和不好的提交会更有用。您将一个提交声明为“好”(代码有效/正确),然后再声明为“坏”(代码失败/错误),并让Git自动在这两个提交之间来回搜索,从而处理分支-并自动在提交图中合并结构。
有时使用git show
也很有用。这样做是分割每个合并。并非冒充git bisect
和git log -m -p
都是一次提交,而是为了S
生成补丁的目的,假装有一个M
与父项{ {1}}和第二个git diff
与父级S1
一起提交。然后,到达O
时,它会假装与父级S2
进行一次R
提交,并与父级M
进行第二次M1
提交。这些拆分提交中的每一个都会对其(现在是孤单的)父级获得一个简单的,普通的, non 组合的差异,向您展示合并结果与该父级之间的差异。
所有这些都是查看发生情况的有用工具。但是,它们不会防止将来出现合并错误:Git所能做的就是将基准与这两个技巧进行比较,以了解您所更改的内容与所更改的内容。何时 无关紧要,只有发生什么变化重要。
编辑:格式化