背景
我们使用基本的功能分支工作流程。功能分支是从 master 创建的,在它们存在期间定期更新 master 的合并,然后在拉取请求后合并回 master。
问题
我们当前的一个分支已经进入一种状态,在这种状态下,git 似乎认为分支的内容总是比 master 中的内容新。一些例子:
myConstant = 26
Run Code Online (Sandbox Code Playgroud)
到
myConstant = 27
Run Code Online (Sandbox Code Playgroud)
将master合并到分支后,PR显示合并[分支回master]将27[返回]变为26.
注意:这是一个运行时间相当长的功能分支,它有几个“子功能分支”来自它。至少有一个被合并回来。另一个仍然存在,并且已经将更新从主分支合并到功能分支,然后从功能分支合并到子功能分支。
怎么修?
我不确定我们是如何陷入这种困境的。我正在筛选几个月的提交,我看到一些可能不明智的合并可能已经完成了。例如,从 master 合并到 sub-feature-branch 到 feature-branch。但是,我还没有找到一个带有巨大文件列表的文件。如果有人对什么类型的行为让你陷入这种情况有更具体的想法,我很想听听。
即使我找到了确凿的证据,我也不确定哪种方法是解决这种情况的最佳方法。我目前的想法是覆盖(从 master 签出)我们知道在分支中没有明确更改的任何文件。显然,分支也有变化,这是比主更新,因此,即使是繁琐和危险的。欢迎提出建议:)
更新 1
在对@torek 的优秀答案中的一些工具进行了更多挖掘和使用之后,我发现了问题提交 - 或者至少是其中之一。提交图看起来像这样(我使用数字来避免与前面讨论中的字母混淆。s 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--- / -- / -----
\ / / /
----------------
Run Code Online (Sandbox Code Playgroud)
39结果证明提交是问题所在。提交者似乎已开始合并,清除所有挂起的更改(不中止合并),进行单个文件更改,然后提交。与来自父提交的比较38仅显示单个文件更改。但是,与 master 的父提交的比较显示了一系列意外更改。
我现在到了“该怎么做”阶段。我正在深入研究选项,包括更改提交39本身,进行新的提交71以扭转39.
更新 2
在阅读了一些书之后,似乎大多数人都在推荐相同的东西——这里概述得很好。我目前的想法是,从那时起进行一系列合并的 rebase 可能会变得丑陋(尽管我的 rebase Kung Fu 不强),所以我倾向于采用还原方法 ( git revert -m 2 <Commit_39_SHA>)。无论如何,Bisect 在这些分支上已经很好地被破坏了。
计划是将所有子功能分支合并到主功能分支并在那里运行恢复。如果我正确理解所有内容,我认为我不需要“还原还原”步骤。
从来都不是年龄的问题。对于合并,始终是合并基数的问题。
每个提交都有一个父级,或者在合并提交的情况下,有两个或多个父级。每个提交的真实名称是它的哈希 ID——由字母和数字组成的又大又丑的字符串,例如83232e38648b51abbcbdb56c94632b6906cc85a6. 这个真实的名字让 Git 找到实际的提交。提交本身存储其父项的哈希 ID,这让 Git 找到这些提交。它们依次存储其父母的哈希 ID,依此类推。结果是,对于大多数提交,有一个简单的线性向后链:
... <-F <-G <-H
Run Code Online (Sandbox Code Playgroud)
哪里H是某个提交哈希 ID,H其父是G,其父是F,依此类推。
这个向后指向的链,主要是线性的,就是历史。Git 中的历史只是一系列提交。像这样的分支名称master只包含一个哈希 ID:链中最后一次提交的 ID 。所以我们可以把上面的画成:
...--F--G--H <-- master
Run Code Online (Sandbox Code Playgroud)
每个提交都有所有文件的完整快照——嗯,所有文件在你提交的时候,因为一旦提交,每个提交都会及时冻结。
分支发生是因为两个不同的提交有一个共同的提交作为它们的(共享)父项:
I--J <-- branch1
/
...--F--G--H
\
K--L <-- branch2
Run Code Online (Sandbox Code Playgroud)
Git会开始在每个尖端commit-J和L,在这种情况下,和工作倒退。当从两个分支开始向后工作时,两个分支到达它们共享的公共 commit H。
如果你选择一个分支到git checkout,你会得到一个提交:
I--J <-- branch1 (HEAD)
/
...--F--G--H
\
K--L <-- branch2
Run Code Online (Sandbox Code Playgroud)
Git 已附加HEAD到 name branch1,这意味着 commitJ是您已检出的提交:branch1标识 commit J。
现在你跑git merge branch2。Git 找到 commit L,因为名称branch2指向L. 从J和 开始L,Git 向后工作,一次提交一次,以查找共享提交H。那是合并基础。
Git 现在将 中的快照(H您在 上branch1的快照)与 中的快照进行比较J,您可能也制作了并且现在正在使用。无论这里有什么不同,那都是你改变的。如果您确实更改myConstant = 26为myConstant = 27,那就是您对该特定文件的更改。如果你根本没有改变myConstant,你就没有改变。
Git 还比较了H其中的快照——这是他们branch2开始的最好的快照,你也开始了(提交F并且G也可以工作,但它们显然没有那么好)——与他们的提交L,看看他们改变了什么。
在完成了从HtoJ和从Hto的比较之后L,Git 现在知道你改变了什么,以及他们改变了什么。没关系,当你改变了一些事情,或者当他们改变了一些事情。重要的是是否有人改变了什么。在任何情况下,Git 都会从H(not Jnor L) 中提取所有文件,对它们应用两组更改,并且——如果没有冲突——提交结果:
I--J
/ \
...--F--G--H M <-- branch1 (HEAD)
\ /
K--L <-- branch2
Run Code Online (Sandbox Code Playgroud)
因为你在branch1,新的提交M继续branch1。因为这是最后一次提交,Git 更新名称branch1以标识 commit M。同时,commitM没有一个而是两个父提交:J,您之前的提示,和L,这仍然是他们的分支提示。
现在假设您和他们都对myConstant. 然后,当尝试合并您的两个更改时,Git 将声明合并冲突。对于正在运行git merge修复的人来说,它会在工作树(和索引)中留下一团糟。然后由人类M通过编辑冲突文件并修复该行来确定将什么放入 commit 。无论人类输入什么,Git 都假定这是正确的结果。假设人类选择 26 而不是 27,并提交。Commit Mnow 说myConstant = 26,并且Git 确定这是正确的,即使不是。或者,我们可以说人类选择了 27 个而不是 26 个,然后提交;现在 Git 确定 27 是正确的。
假设你和他们继续进行更多的提交:
I--J
/ \
...--F--G--H M--N--O <-- branch1
\ /
K--L--P--Q--R <-- branch2
Run Code Online (Sandbox Code Playgroud)
如果您现在选择这些分支之一进行检查,您将获得 commitO或 commit R。假设您再次选择O并运行git merge branch2。
Git的现在开始于O和逆向运作:O,N,M,磁盘后J-和- L......它开始于R和逆向运作:R,Q,P,L,。注意,这些都到达提交L。CommitL是合并基础——不是M, L。因此,Git 会M根据O您的更改进行比较。这可能包括将 26 更改为 27,或者——如果M它本身有错误的常数——什么都不做。然后,它比较L对R,看看他们改变什么。
和以前一样,Git 合并了这些更改,如果您和他们都接触到同一文件的相同行,则会发生合并冲突。您清理它并自己进行合并提交。如果没有,并且 Git 自己成功地合并了所有内容,Git 会立即进行合并提交。无论哪种方式,您现在都拥有:
I--J
/ \
...--F--G--H M--N--O--S <-- branch1 (HEAD)
\ / /
K--L--P--Q--R <-- branch2
Run Code Online (Sandbox Code Playgroud)
S你的合并结果在哪里。该文件中的常量是根据是否存在冲突和/或您和/或他们在比较L(合并基础)与O和R(两个合并的边)。
Git 有一些工具,您可以通过这些工具尝试找出谁设置myConstant了任何值。两个大的是git log和git blame。两者都从某个提交开始,以查看该提交中的内容,然后向后工作,一次提交一个。通过比较父提交中的内容与子提交中的内容,Git 可以查看是否提交N并O在那里有不同的行。如果是这样,Git 会告诉你是谁O修改了那行。
但是,当Git是通过像合并工作向后S或者M,其家长应Git的比较S? Git对此的回答(复数)非常棘手。有些命令根本不需要比较——例如,这就是比较git log -p。其他人选择一个父级并继续向下合并,即从 开始S,返回到 onlyR或 only O。一些,例如git show,可以向您显示组合的 diff,它通常只向您显示存在合并冲突的位置(组合的 diff 会忽略所有“双方”都没有贡献的文件)。
由于此类问题通常在合并冲突中引入,因此组合差异通常对于查找它的来源有些有用。但这不一定很有用:用于git bisect自动查找好的和坏的提交更有用。你声明一个提交“好”(代码工作/正确)和稍后提交“坏”(代码失败/错误),并让 Git 自动在这两个提交之间来回搜索,处理分支 -和合并提交图中的结构,所有这些都是自动的。
有时使用git log -m -p. 这样做是拆分每个合并。它没有将S和M视为一个提交,而是假装git diff为了生成补丁的目的,假设有一个S1与 parent的提交O和第二个S2与 parent 的提交R。然后,在到达时M,它假装有一次M1与 parent的提交J,以及与 parent的第二次M2提交L。这些拆分提交中的每一个都有一个简单的、普通的、非组合的 diff 与其(现在是唯一的)父级的差异,向您展示合并结果与该父级的不同之处。
所有这些都是查看发生了什么的有用工具。但是,它们不会防止将来发生错误合并:Git 所能做的就是将基础与两个技巧进行比较,以查看您更改的内容与他们更改的内容。什么时候无关紧要:只有改变了才重要。
编辑:格式化
| 归档时间: |
|
| 查看次数: |
326 次 |
| 最近记录: |