且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

git checkout --ours不会从未合并的文件列表中删除文件

更新时间:2023-12-04 19:52:58

git checkout 可以在内部使用。最终的结果是,在 git checkout 之后的结果是 code> - 我们的或 - 他们的,如果您想解决冲突,您还必须 git add 相同的路径:

  git checkout --ours  -  path / to / file 
git add path / to / file

但这是 not 其他形式的 git checkout

  git checkout HEAD  -  -  path / to / file 

或者:

  git checkout MERGE_HEAD  -  path / to / file 

(这些以多种方式微妙地不同)。在某些情况下,这意味着最快的方法是使用中间命令。 (顺便提一句,这里的 - 是为了确保Git能够区分路径名和选项或分支名称。例如,如果你有一个名为 - 他们的,它看起来像一个选项,但 - 会告诉Git不,它真的是一个路径名。



要了解这一切在内部是如何工作的,以及为什么您需要单独的 git add ,除非您不需要,继续阅读。 :-)首先,让我们快速回顾合并过程。



合并,第1部分:如何合并开始



运行时:



$ git merge commit-or-branch

Git所做的第一件事是找到命名提交和当前( HEAD $)之间的合并基 c $ c>)提交。 (注意,如果你在这里提供了一个分支名称,比如在 git merge otherbranch 中,Git将它转换为一个提交ID,即分支的顶端,它保存分支最终合并日志消息的名称参数,但需要提交ID来查找合并基础。)



找到合适的合并基础 1 Git然后生成两个 git diff 列表:一个从合并基址到 HEAD ,另一个从merge base到您确定的提交。这会得到你改变了什么和他们改变了什么,Git现在必须合并。



对于你进行了更改而没有更改的文件,Git只需要你的版本。



对于他们进行了更改但没有更改的文件,Git可以只取其版本。



对于你们都做过修改的文件,Git必须做一些真正的合并工作。它逐行比较变化,看它是否可以合并它们。如果它可以组合它们,它就是这样做的。如果合并似乎是基于纯粹的逐行比较再次发生冲突,那么Git会为该文件声明一个合并冲突(并继续尝试合并,但留下冲突标记)。



一旦Git合并了它所能做的所有事情,它就会结束合并 - 因为没有冲突 - 或者因合并冲突而停止。






1 如果您绘制提交图形,则合并基数很明显。没有绘制图表,这有点神秘。这就是为什么我总是告诉人们绘制图表,或者至少是根据需要绘制图表来理解。

技术定义是合并基数是提交图中的最低共同祖先(LCA)节点。用不太专业的术语来说,这是当前分支与您正在合并的分支加入的最新提交。也就是说,通过记录每个合并的父提交ID,Git能够找到两个分支在一起的 last 时间,因此找出你做了什么以及他们做了什么。尽管如此,Git必须记录每个合并。具体来说,它必须将两个(或全部,为所谓的章鱼合并)父ID写入新的合并提交。



在某些情况下,一个合适的合并基地。该流程取决于您的合并策略。默认的递归策略将合并多个合并基础以产生虚拟合并基础。






合并,第2部分:停止冲突,以及Git的index



当Git以这种方式停止时,它需要给您一个解决冲突的机会。但是这也意味着它需要记录这些冲突,这就是Git的索引(也称为暂存区域),有时甚至是高速缓存 - 的地方,它实际上是它的存在。 p>

对于工作树中的每个分段文件,索引最多有4个条目,而不仅仅是一个条目。其中至多三个实际上正在使用,但有四个插槽,编号为 0 3

插槽零用于解析文件。当你使用Git而不进行合并时,仅使用零槽被使用。当你在工作树中编辑一个文件时,它有未分离的变化,然后你 git add 这个文件,并且这些变化被写入到存储库中,您的更改现在已上演。



插槽1-3用于未解析的文件。当 git merge 必须以合并冲突停止时,它会将槽0留空,并将所有内容写入槽1,槽2和槽3中。 merge base 版本的文件记录在插槽1中, - 我们的版本记录在插槽2中,而 - 它们的版本记录在插槽3中。这些非零插槽条目是Git如何知道该文件未解析。



解决文件,你 git add 它们,这将擦除所有的slot 1-3条目,并写入一个slot-zero,stage-for-commit条目。这就是Git知道文件已被解析并准备好进行新的提交。 (或者,在某些情况下,您的文件是 git rm ,在这种情况下,Git会在槽0中写入一个特殊的已删除值,再次删除槽1-3。)






2 有几种情况,其中三个插槽中的一个也是空的。假设文件 new 在合并库中不存在,并且在我们和他们的两个版本中都添加了。然后:1: new 留空,:2: new :3: new 记录添加/添加冲突。或者,假设文件 f 确实存在于基础中,在我们的HEAD分支中被修改,并且在其分支中被删除。然后:1: f 记录基本文件:2: f 记录我们的文件版本,并且:3: f 为空,记录修改/删除冲突。



对于修改/修改冲突,所有三个插槽都被占用;仅当缺少一个文件时,其中一个插槽为空。逻辑上不可能有两个空插槽:不存在删除/删除冲突,以及创建/添加冲突。但是有一些与重命名冲突有关的问题,我在这里省略了,因为这个答案足够长!在任何情况下,在第1,2和/或3号插槽中存在的某些值会将文件标记为未解决。




合并,第3部分:合并完成



一旦所有文件解析完成 - 所有条目只在零编号的位置上 - code> git commit 合并结果。如果 git merge 能够在没有帮助的情况下执行合并,它通常为您运行 git commit ,但实际提交仍然通过运行 git commit



完成commit命令的工作方式与以往一样:将索引内容放入 tree 对象中并写入新的提交。合并提交的唯一特殊之处在于它有多个父提交ID。 3 额外的父节点来自文件 git merge 离开背后。默认的合并信息也来自一个文件(实际上是一个单独的文件,虽然原则上它们可能已经合并了)。注意,在所有情况下,新提交的内容由索引的内容决定。而且,一旦新的提交完成,索引仍然是完整的:它仍然包含相同的内容。默认情况下, git commit 此时不会再创建一个新的提交,因为它看到索引匹配 HEAD 承诺。它调用这个空,并且要求 - allow-empty 来做额外的提交,但索引不是。它还是很完整的 - 它只是与 HEAD 提交完全一样。






3 这假定您正在进行真正的合并,而不是压缩合并。当进行压缩合并时, git merge 故意不会 将额外的父编号写入额外的文件,这样新的合并提交只有一个单身父母。 (出于某些原因, git merge --squash 也会禁止自动提交,就好像它包含了 - no-commit $ c $因为你可以运行 git merge --squash --no-commit ,如果你 >想要自动提交被抑制。)



一个壁球合并不会记录其他的父目录。这意味着,如果我们再次合并 ,一段时间后,Git将不知道从哪里开始比较

。这意味着如果您打算放弃其他分支,通常应该只进行压缩合并。 (有些棘手的方法可以将压缩合并和真正的合并结合起来,但它们远远超出了这个答案的范围。)






git checkout 分支 使用索引



那么我们必须看看 git checkout 如何使用Git的索引。请记住,在正常使用情况下,只有槽0被占用,并且索引对每个分阶段文件都有一个条目。此外,该条目与当前( HEAD )提交匹配,除非您修改了文件并且 git add - 结果。它也可以匹配工作树中的文件,除非您修改了文件。 4



如果你在某个分支上,而你 git checkout 某些其他分支,Git会尝试切换到另一个分支。为了成功,Git必须用每个文件的索引条目替换其他分支的条目。



比方说,仅仅为了具体性,继续 master ,你正在做 git checkout branch 。 Git会将每个当前索引条目与它需要在分支 branch 的提示最多提交时使用的索引条目进行比较。也就是说,对于文件 README.txt master 内容与 相同对于分支,或者它们是不同的?



如果内容相同, Git可以很容易地转移到下一个文件。如果内容不同,Git必须对索引条目执行一些操作。 (Git检查工作树文件是否与索引条目不同)

具体来说,在分支的文件不同于 master 的, git checkout 必须 README.txt doesn'将索引条目替换为分支$ c> t 存在分支的提示提交中,Git必须删除索引条目。此外,如果 git checkout 要修改或删除索引条目,它还需要修改或删除工作树文件。 Git确保这是一件安全的事情,也就是说,在它允许您切换分支之前,工作树文件与 master commit文件相匹配。



换句话说,这是Git如何(以及为什么)发现是否可以更改分支 - 无论您是否具有通过从 master切换而被破坏的修改分支。如果您的工作树中有修改,,两个分支中的修改后的文件都是相同的, Git可以在索引和工作树中保留修改。它可以也会提醒你这些修改过的文件结转到新的分支中:easy,因为它必须检查这个。

一旦所有的测试都有并且Git已经决定可以从 master 切换到分支 - 或者如果您指定了 - $ c> - git checkout 实际上会更新所有更改(或删除)文件的索引,并更新工作树以匹配。



请注意,所有这些操作都使用了零槽。根本没有插槽1-3条目,所以 git checkout 不必删除任何这样的内容。你并没有处于冲突的合并中,而是运行 git checkout branch 来检查一个文件,而是整个 set 文件和转换分支。



还要注意,您可以不检出分支,而是检出特定的提交。例如,你可能会看到以前的提交:

  $ git log 
... peruse log输出...
$ git checkout f17c393#让我们看看这个提交中有什么

这与检查分支是一样的,除了不使用分支的 tip 提交外,Git检出任意提交。现在不用在新的分支上,你现在在 no 分支上: 5 Git为你提供了一个分离的HEAD。要重新连接你的头,你必须 git checkout master git checkout branch 来取回分支。 / p>




4 如果Git执行特殊的CR,索引条目可能与工作树版本不匹配-LF结束修改或应用涂抹过滤器。这是相当先进的,***的情况是现在忽略这种情况。 : - )

5 更准确地说,这会让你进入一个匿名(未命名)分支,当前提交。如果你进行了新的提交,你将会保持独立的HEAD模式,并且只要你 git checkout 其他提交或分支,你就会在那里切换,Git将放弃您提交的提交。这种分离的HEAD模式的重点在于让你环顾,让你可以做出新的提交,如果你不采取特殊措施保存, 就会消失。他们。不过,对于Git相对较新的人来说,提交刚刚离开并不是那么好 - 所以确保你知道你处于这种分离的HEAD模式,无论你何时进入。



git status 命令会告诉您您是否处于分离的HEAD模式。 6 如果你的Git是旧的(OP的是1.7.1,现在已经很旧了), git status 不是有用,因为它是现代版本的Git,但它仍然比没有好。



6 一些程序员喜欢将 git status 编码到每个命令提示符中的信息。我个人不会走这么远,但可以是一个好主意。






检查特定文件,以及为什么有时解决合并冲突



然而, git checkout 命令有其他操作模式。特别是,你可以运行 git checkout [flags etc] - path [path ...] 来检出特定的文件。这是奇怪的地方。请注意,当您使用这个形式的命令时,Git 不会检查以确保您不覆盖文件。 7



现在,您不需要更改分支,而是让Git从某个地方获取某些特定文件,然后将它们放入工作树中,覆盖所有内容,如果任何东西。棘手的问题是: Git获取这些文件的位置是什么?



一般来说,Git有三个保存文件的地方:




  • 提交; 8

  • 在索引中;

  • 和工作树中。


结帐命令可以从前两个地方读取,总是将结果写入工作树。



> git checkout 从提交中获取文件时,它首先将其复制到索引。每当它做到这一点,它将文件写入零槽。写入插槽零将抹掉插槽1-3,如果它们被占用。当 git checkout 从索引获取文件时,它不必将其复制到索引。 (当然不是:它已经存在了!)当 在合并过程中时, git checkout code> git checkout - path / to / file 以获取索引版本。 9



假设你在冲突的合并过程中 git checkout 有一些路径,可能是 - 我们的。 (如果你不在合并的中间,slot 1-3没有任何内容,并且 - 我们的没有任何意义。)所以你运行 git checkout --ours - path / to / file



这个 git checkout 从索引中获取文件 - 在这种情况下,从索引插槽2获取文件。由于这已经在索引中,所以Git不会将索引写入,而是写入工作树。所以这个文件没有被解析!



对于 git checkout也是如此 - 他们的:它从索引(插槽3)获取文件,并且不解决任何问题。



但是:if git checkout HEAD - path / to / file ,告诉 git checkout HEAD 提交。由于这是一个 commit ,因此Git通过将文件内容写入索引开始。这写入插槽0并擦除1-3。由于在冲突合并期间,Git会在 MERGE_HEAD $ c $中记录正在合并的提交的ID c>,你也可以 git checkout MERGE_HEAD - path / to / file 从其他提交中获取文件。这也从提交中提取,所以它写入索引,解析文件。






7 我经常希望Git为此使用了一个不同的前端命令,因为我们可以毫不含糊地说git checkout是安全的,它不会覆盖没有的文件 - force 。但这种 git checkout 确实会覆盖文件!



8 这是一个谎言,或者至少是一段延伸:提交不直接包含文件。相反,提交包含指向对象的(单个)指针。此树对象包含其他树对象和 blob 对象的ID。 blob对象包含实际的文件内容。



事实上,索引也是如此。每个索引槽都包含,而不是实际的文件 contents ,而是存储库中blob对象的哈希ID。



,这并不重要:我们只是要求Git检索 commit:path ,它会为我们找到树和blob ID。或者,我们要求Git检索 :n:path ,并在索引条目中找到 路径 插槽 n 。然后它得到我们文件的内容,我们很好去。



这个冒号和数字语法在Git中无处不在,而 - 我们的 - 他们的标志只能在 git checkout 中工作。有趣的冒号语法在 gitrevisions



9 git checkout - 路径的用例是这样的:假设,不管你是否合并,你对文件进行了一些修改,测试,发现这些修改已经工作,然后运行 git add 在文件上。然后你决定进行更多的更改,但没有再次运行 git add 。您测试第二组更改并发现它们是错误的。如果只有你可以将文件的工作树版本设置回你刚才的 git add -ed的版本.... Aha,你可以:你 git checkout - path 并且Git将索引版本从零槽复制回工作树。






细微行为警告



请注意,使用 - -ours - 他们的除了从索引中提取并因此不解决行为之外,还有一些细微差别。假设在我们发生冲突的合并中,Git检测到某个文件被重命名了。也就是说,在合并基础中,我们有文件 doc.txt ,但现在在 HEAD 中,我们有文档/ doc.txt 。我们需要的路径 git checkout --ours Documentation / doc.txt 。这也是 HEAD 提交中的路径,所以 git checkout HEAD - Documentation / doc.txt