我还在学习git。
我有一个名为names.txt
的文件。有了这个文字。
这是我的提交历史
第一次提交添加了文件。第二次提交添加了第一行Mary。第三次提交添加了第二行John。
git show 7bdb5ef
git show 80384aa
我想重新定义并修改提交Mary以将文本更改为Mary Shelly
。
I do git rebase -i 4a5244b
接下来,我设置了Mary来编辑和运行rebase。
Rebase在这里停止。
我将它改为Mary Shelly
并进行演出。
我跑
git commit --amend
其次是
git rebase --continue
现在我得到了这个合并冲突。
我不明白为什么会这样。提交John
只会更改文件中的第二行。当我们编辑提交Mary
时,我们只更改文件的第一行。这是如何引发冲突的?
问题在于存在合并冲突,而chepner's comment是理解原因的关键。好吧,那个,以及提交图,加上git rebase
由重复的git cherry-pick
操作组成的事实。交互式rebase允许您在每个git cherry-pick
之间添加自己的命令,甚至可以将樱桃选择更改为其他内容。 (最初的命令表以all-pick
命令开头,每个命令都是做一个樱桃选择。)
您的提交历史记录是您的提交图的摘要 - 实质上是访问提交图中每个提交的结果,从某个特定的结束点(当前分支的尖端)开始并向后工作。如果你使用git log --graph
,你会得到一些在没有--graph
的情况下遗漏的重要信息,尽管在这种特殊情况下,很容易看出图形是线性的。所以你只有三个提交:
A <-B <-C <-- master (HEAD)
其中A
实际上是4a5244b
,B
代表7bdb5ef
,而C
代表80384aa
(如果我正确地转录了图像)。每个提交都有一个完整的完整副本文件names.txt
。在A
,B
和C
中,副本当然是不同的,因为在A
中,它是空的;在B
,它是一行读Mary
;在C
,它是两行读Mary
然后John
图表本身源于提交C
或80384aa
在B
本身中包含commit 7bdb5ef
或C
的哈希ID。这就是为什么我画出C
指向B
的箭头。 Git称之为C
的父提交。 Git在名称C
中记录master
的哈希ID,然后将特殊名称HEAD
附加到名称master
,以便它知道这是git log
应该从哪里开始,并且提交C
就是你已经拥有的,用于工作,马上。
当你运行git rebase -i 4a5244b
-选择提交A
作为新的基础时 - Git发现这意味着复制提交B
和C
,因此它将它们的哈希ID放入pick
命令列表中。然后它在命令表上打开您的编辑器。你将pick
更改为edit
,它告诉Git:在操作过程中执行樱桃选择,然后退出rebase。
你没有强迫rebase制作一个真正的副本。 (要做到这一点,使用-f
或--no-ff
或--force-rebase
-都意味着同样的事情。这在这里并不重要,在大多数情况下也是如此。)所以Git看到有一条指令,复制B
以便它出现在A
之后,并意识到:嘿,等等,B
已经在A
之后了。我会把它留在那里。 Git做到了并停了下来,让你处于这种状态:
A--B <-- HEAD
\
C <-- master
请注意,HEAD
不再附加到master
:它现在直接指向提交B
。提交C
仍然存在,master
仍指向它,但Git已停止在“超级HEAD”模式允许您进行编辑。
您对文件git add
和git commit --amend
进行了更改。这使得一个新的提交 - 我们可以称之为B'
或D
,通常我使用B'
,因为通常它很像B
,但这次它不同,所以让我们使用D
。新的提交将A
作为其父级 - 这就是--amend
所做的。 Git更新HEAD
以指向新提交。现有的提交B
保持不变。所以现在你有:
D <-- HEAD
/
A--B
\
C <-- master
names.txt
中的文件D
有新的单行读取Mary Shelly
。
你现在运行git rebase --continue
,所以Git继续说明表中剩下的内容。这包括pick <hash-of-C>
,这使得Git运行git cherry-pick
复制C
。此副本需要在当前提交后进行,D
。现有的提交C
没有,所以Git这次必须真正做好这项工作。
要执行合并操作 - 要合并,action-Git需要三个输入。这三个输入是合并基础提交,当前或--ours
提交(有时也称为本地,特别是git mergetool
),另一个或--theirs
提交(有时称为远程)。对于常规合并,基数通常有点遥远:它是两行提交分歧的地方。对于cherry-pick-for和revert,就此而言,基础就在提交旁边。这个操作的合并基础是C
的父提交B
!
合并的实际操作包括在整个提交上运行两个git diff
命令:
git diff --find-renames hash-of-base hash-of-ours
:我们改变了什么?git diff --find-renames hash-of-base hash-of-theirs
:他们改变了什么?所以Git现在差异提交B
,基础,vs提交D
,你当前/我们的提交。差异影响文件names.txt
并说:将一行说明玛丽改为两行:一行读玛丽雪莉,一读约翰。然后Git将B
与C
区分开来,看看他们(你,之前)做过什么。差异影响文件names.txt
并说:在读取Mary的行之后,在文件末尾添加读取John的行。
这就是Git在合并冲突部分向您展示的内容:一个文件说Mary和Mary Shelly取代,另一个文件说保留Mary并添加John。如果您愿意,可以告诉Git在合并冲突部分中保留更多信息。为此,请将diff.conflictStyle
设置为diff3
。 (默认情况下,如果未设置,则为merge
。)
使用diff3
设置,您将看到由|||||||
标记的基本内容是一行Mary
,并且来自冲突提交的两个文件分别用Mary Shelly
或Mary
+新行John
替换了该基础。我发现这种合并冲突更清晰,更容易手动合并。
在任何情况下,你的工作就是提出正确的结果 - 无论是什么 - 并将其写出并将其复制到索引槽零。通常,您只需编辑Git在工作树中留下的凌乱的names.txt
,将正确的内容放入其中,然后运行git add names.txt
。
解决了冲突后,运行git whatever --continue
以恢复任何停止的操作 - 在这种情况下,rebase,但这也发生在cherry-pick和merge中。 Git将使用您使用git add
更新的索引内容来生成C
副本的新提交:
D--C' <-- HEAD
/
A--B
\
C <-- master
到达命令表的末尾,git rebase
现在完成了将master
命名为C
并将其粘贴到C'
上,这是它制作的最后一个副本,然后重新附加HEAD
:
D--C' <-- master (HEAD)
/
A--B
\
C [abandoned]
文件级合并操作(即Git需要将两组更改协调到文件的操作)尝试允许您移动代码而不会导致太多冲突,因此为了尝试找到应用更改的正确位置,上下文 - 周围线的集合 - 也被考虑在内。
在这里,重新应用提交John
会导致麻烦:原始提交在John
行旁边添加了Mary
。现在Git正在尝试重新应用提交,但是该参考行说Mary
不再存在 - 所有这些都有一行说Mary Shelly
...请记住,Git不理解文件的目的和/或含义,因此,在这种情况下,它不会冒任何机会,并将此作为冲突呈现给您,以便您可以检查它。
再次尝试使用John
和Mary
之间的许多其他线路,你会保持不变 - 你会发现你不会发生冲突。
问题在于,在添加的相邻行上也可能需要对原始行进行的更改,以允许合并成功,而不会涉及人为判断。我使用的例子是
<<<<<<<<<<<<<
if ( g->tag == mark
|| g->tag == error ) {
||||||||||||||
if ( tag == mark
|| tag == error ) {
==============
if ( tag == mark
|| tag == release
|| tag == error ) {
>>>>>>>>>>>>>>
其中一个变化将g->
添加到一对线,另一个变化在中间添加了release
线。