git checkout --ours 不会从未合并的文件列表中删除文件
git checkout --ours does not remove files from unmerged files list
嗨,我需要像这样合并两个分支。
这只是一个例子,我处理了数百个需要解析的文件。
git merge branch1
...conflicts...
git status
....
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both added: file1
# both added: file2
# both added: file3
# both added: file4
git checkout --ours file1
git chechout --theirs file2
git checkout --ours file3
git chechout --theirs file4
git commit -a -m "this should work"
U file1
fatal: 'commit' is not possible because you have unmerged files.
Please, fix them up in the work tree, and then use 'git add/rm <file>' as
appropriate to mark resolution and make a commit, or use 'git commit -a'.
当我执行 git merge tool
时,'ours' 分支中有正确的内容,当我保存它时,该文件从未合并列表中消失。但是因为我有数百个这样的文件,所以这不是一个选择。
我认为这种方法会把我带到我想去的地方——轻松地说出我想保留哪个分支的哪个文件。
但我想我误解了合并后 git checkout --ours/theirs
命令的概念。
能否请您提供一些信息,如何处理这种情况?我用 git 1.7.1
这主要是 git checkout
内部工作方式的一个怪癖。 Git 人倾向于让实现决定接口。
最终的结果是git checkout
与--ours
或--theirs
之后,如果要解决冲突,也必须git add
相同的路径:
git checkout --ours -- path/to/file
git add path/to/file
但是不是其他形式的情况git checkout
:
git checkout HEAD -- path/to/file
或:
git checkout MERGE_HEAD -- path/to/file
(这些在多个方面略有不同)。在某些情况下,这意味着最快的方法是使用中间命令。 (顺便说一下,这里的 --
是为了确保 Git 可以区分路径名和选项或 b运行ch 名。例如,如果你有一个名为 --theirs
,它看起来像是一个选项,但 --
会告诉 Git 不,它实际上是一个路径名。)
要了解这一切在内部是如何工作的,以及为什么您需要单独的 git add
除非您不需要,请继续阅读。 :-) 首先,让我们快速回顾一下合并过程。
合并,第 1 部分:合并如何开始
当你 运行:
$git合并<em>commit-or-branch</em>
Git 做的第一件事是找到命名提交和当前 (HEAD
) 提交之间的合并基础。 (请注意,如果您在此处提供 b运行ch 名称,如 git merge otherbranch
、Git t运行 将其命名为 commit-ID,即b运行ch。它保存最终合并日志消息的 b运行ch 名称参数,但需要提交 ID 才能找到合并基础。)
找到合适的合并基地后,1 Git 会生成两个 git diff
列表:一个从合并基地到 HEAD
,一个从合并基础到您确定的提交。这得到 "what you changed" 和 "what they changed",Git 现在必须合并。
对于您进行了更改但没有更改的文件,Git 可以只采用您的版本。
对于他们进行了更改而您没有更改的文件,Git 可以采用他们的版本。
对于你们都进行了更改的文件,Git 必须进行一些真正的合并工作。它逐行比较更改,看是否可以将它们组合起来。如果它 可以 将它们结合起来,它就会这样做。如果合并看起来——再次基于纯粹的 line-by-line 比较——冲突,Git 为该文件声明一个 "merge conflict"(并继续尝试合并,但留下冲突标记到位)。
一旦 Git 合并了它所能合并的所有内容,它要么完成合并(因为没有冲突),要么因合并冲突而停止。
1如果绘制提交图,合并基础是显而易见的。不画图,有点神秘。这就是为什么我总是告诉人们绘制图形,或者至少尽可能多地画出有意义的图形。
技术上的定义是merge base是提交图中的"lowest common ancestor"(LCA)节点。用不太专业的术语来说,它是您当前的 b运行ch 与您正在合并的 b运行ch 合并的最新提交。也就是说,通过记录每个合并的父提交 ID,Git 能够找到两个 b运行ches 在一起的 last 时间,从而找出两者你做了什么,他们做了什么。但是,要使它完全起作用,Git 必须记录每次合并。具体来说,它必须将两个(或全部,对于 so-called "octopus" 合并)父 ID 写入新的合并提交。
在某些情况下,有不止一个合适的合并基础。然后,该过程取决于您的合并策略。默认的 recursive 策略将合并多个合并基础以产生 "virtual merge base"。这种情况很少见,您现在可以忽略它。
合并,第 2 部分:因冲突而停止,以及 Git 的 "index"
当Git确实以这种方式停止时,它需要给你一个机会来解决冲突。但这也意味着它需要记录冲突,这就是Git的"index"—也称为"the staging area",有时"the cache"——真是自得其乐。
对于您 work-tree 中的每个暂存文件,索引有 最多四个 条目,而不是只有一个条目。其中最多三个实际使用过,但有四个插槽,编号为 0
到 3
。
槽零用于解析文件。当您使用 Git 而不进行合并时,仅使用 插槽零。当你在工作树中编辑一个文件时,它有 "unstaged changes",然后你 git add
文件和更改被写入存储库,更新槽零;您的更改现在是 "staged".
插槽 1-3 用于未解析的文件。当 git merge
必须因合并冲突而停止时,它会将槽 0 留空,并将所有内容写入槽 1、2 和 3。文件的 merge base 版本是记录在插槽 1 中,--ours
版本记录在插槽 2 中,ad --theirs
版本记录在插槽 3 中。这些非零插槽条目是 Git 知道文件未解析的方式。2
解析文件时,您 git add
它们会擦除所有插槽 1-3 条目并写入 slot-zero、staged-for-commit 条目。这就是 Git 知道文件已解析并准备好进行新提交的方式。 (或者,在某些情况下,您 git rm
文件,在这种情况下 Git 将一个特殊的 "removed" 值写入槽零,再次擦除槽 1-3。)
2在少数情况下,这三个插槽之一也是空的。假设文件 new
在合并库中不存在,并且在我们的和他们的中都添加了。然后:1:<em>new</em>
留空,:2:<em>new</em>
和:3:<em>new</em>
记录了add/add冲突。或者,假设文件 f
确实存在于 base 中,在我们的 HEAD b运行ch 中被修改,并在它们的 b运行ch 中被删除.然后:1:<em>f</em>
记录基础文件,:2:<em>f</em>
记录了我们的文件版本,而:3:<em>f</em>
为空,记录了modify/delete冲突。
对于modify/modify冲突,三个槽都被占用;只有当一个文件丢失时,这些插槽之一才会为空。逻辑上不可能有两个空位:没有 delete/delete 冲突,也没有 nocreate/add 冲突。但是 rename 冲突有些奇怪,我在这里省略了,因为这个答案足够长了!在任何情况下,正是插槽 1、2、and/or 3 中某些值的存在将文件标记为未解析。
合并,第 3 部分:完成合并
一旦所有文件都被解析——所有条目只在zero-numbered槽中——你可以git commit
合并结果。如果 git merge
能够在没有帮助的情况下进行合并,它通常会为您 运行s git commit
,但实际提交仍然由 运行ning git commit
完成.
commit 命令的工作方式与往常一样:它将索引内容转换为 tree 对象并写入新的提交。合并提交的唯一特殊之处在于它有多个父提交 ID。3 额外的父提交来自 git merge
留下的文件。默认的合并消息也来自一个文件(实际上是一个单独的文件,尽管原则上它们可以合并)。
请注意,在所有情况下,新提交的内容都由索引的内容决定。此外,一旦新的提交完成,索引仍然是满的:它仍然包含相同的内容。默认情况下,git commit
此时不会进行另一个新提交,因为它发现索引与 HEAD
提交匹配。它调用此 "empty" 并要求 --allow-empty
进行额外提交,但索引根本不是 empty。它仍然很满——它只是充满了 与 HEAD
提交相同的东西。
3这假设您正在进行真正的合并,而不是挤压合并。在进行压缩合并时,git merge
故意 不 将额外的父 ID 写入额外的文件,以便新的合并提交只有一个父。 (出于某种原因,git merge --squash
也抑制了自动提交,就好像它也包含 --no-commit
标志一样。不清楚为什么,因为你 可以 只是 运行 git merge --squash --no-commit
如果你想要 自动提交被抑制。)
壁球合并不记录其其他父项。这意味着如果我们再次合并 ,一段时间后,Git 将不知道 从哪里开始差异 。这意味着如果您打算放弃另一个 b运行ch,您通常应该只 squash-merge。 (有一些巧妙的方法可以结合挤压合并和真正的合并,但它们超出了这个答案的范围。)
如何git结帐<em>b运行ch</em>
使用索引
完成所有这些之后,我们还必须看看 git checkout
如何使用 Git 的索引。请记住,在正常使用中,只有零号槽被占用,并且索引对每个暂存文件都有一个条目。此外,该条目 与当前 (HEAD
) 提交 匹配,除非您修改了文件并 git add
-ed 结果。它还 匹配 work-tree 中的文件,除非您修改了该文件。4
如果你在一些 b运行ch 并且你 git checkout
一些 other b运行ch,Git 尝试切换到另一个 b运行ch。为了成功,Git 必须用另一个 b运行ch.
的条目替换每个文件的索引条目
为了具体起见,假设您在 master
并且正在做 git checkout branch
。 Git 将每个当前索引条目与它需要在 b运行ch branch
的 tip-most 提交上的索引条目进行比较。日问题是,文件 README.txt
的 master
内容 与 branch
的内容 相同,还是不同?
如果内容相同,Git可以放轻松,继续下一个文件。如果内容不同,Git 必须对索引条目做一些事情。 (正是在这一点附近,Git 检查 work-tree 文件是否也与索引条目不同。)
具体来说,如果 branch
的文件与 master
的不同,git checkout
必须 替换 索引条目使用来自 branch
的版本——或者,如果 README.txt
在 branch
的提示提交中 不存在 ,Git 必须删除 索引条目。而且,如果git checkout
要修改或删除索引项,还需要修改或删除work-tree文件。 Git 确保这是一件安全的事情,即 work-tree 文件与 master
提交的文件相匹配,然后它会让你切换 b运行ches.
换句话说,这就是 Git 确定是否可以更改 b运行ches 的方式(以及原因)——无论您是否有修改会因从 [=55 切换而被破坏=] 到 branch
。如果您在 work-tree、 中进行了修改,但 修改后的文件 在两个 b运行 中相同, Git 可以只保留索引和 work-tree 中的修改。它可以并且会提醒您这些修改过的文件 "carried over" 进入新的 b运行ch:很简单,因为无论如何它都必须检查这个。
一旦所有测试都通过并且 Git 决定可以从 master
切换到 branch
— 或者如果您指定 --force
— git checkout
实际上用所有更改(或删除)的文件更新索引,并更新 work-tree 以匹配。
请注意,所有此操作都使用了槽零。根本没有插槽 1-3 条目,因此 git checkout
不必删除任何此类内容。您不在有冲突的合并中,您 运行 git checkout branch
不只是检查 一个文件 ,而是检查整个 set 个文件并切换 b运行ches.
另请注意,您可以签出特定提交,而不是签出 b运行ch。例如,这就是您查看之前提交的方式:
$ git log
... peruse log output ...
$ git checkout f17c393 # let's see what's in this commit
此处的操作与检出 b运行ch 相同,只是不使用 b运行ch 的 tip 提交, Git 检查任意提交。你现在不是 "on" 新的 b运行ch,而是 no b运行ch:5Git给你一个"detached HEAD"。要重新装上你的头,你必须 git checkout master
或 git checkout branch
才能取回 "on" b运行ch。
4索引条目可能与 work-tree 版本不匹配,如果 Git 正在进行特殊的 CR-LF 结束修改,或应用污迹过滤器.这变得非常先进,最好的办法是暂时忽略这种情况。 :-)
5更准确地说,这使您进入一个 anonymous(未命名)b运行ch,它将从当前提交。如果您进行新的提交,您将保持分离的 HEAD 模式,一旦您 git checkout
其他提交或 b运行ch,您将切换到那里并且 Git 将 "abandon" 你所做的承诺。这种分离的 HEAD 模式的要点是让你环顾 和 让你做出新的提交,如果你不这样做, 将 消失不要采取特殊行动来拯救他们。但是,对于 Git 相对较新的任何人来说,提交 "just go away" 并不是一件好事——所以无论何时,请确保您知道自己处于这种 "detached HEAD" 模式。
git status
命令会告诉您是否处于分离 HEAD 模式。经常使用它。6 如果你的 Git 是旧的(OP 的是 1.7.1,现在已经很旧了),git status
没有它那么有用在 Git 的现代版本中,但总比没有好。
6一些程序员喜欢将密钥 git status
信息编码到每个 command-prompt 中。我个人不会走这么远,但不失为一个好主意。
签出特定文件,以及为什么它有时会解决合并冲突
不过,git checkout
命令还有其他操作模式。特别是,您可以 运行 git checkout [flags etc] -- path [path ...]
检出特定文件。这就是事情变得奇怪的地方。请注意,当您使用 this 形式的命令时,Git 不会 检查以确保您没有覆盖文件。7
现在,您不是更改 b运行ches,而是告诉 Git 从某处获取一些特定文件,并将它们放入 work-tree,覆盖无论那里有什么,如果有的话。棘手的问题是:Git 从哪里获取这些文件?
一般来说,Git保存文件的地方有3个:
- 在提交中;8
- 在索引中;
- 并在 work-tree.
checkout 命令可以从前两个地方读取,并且总是将结果写入work-tree。
当 git checkout
从提交中获取文件时,它首先 将其复制到索引 。每当它这样做时,它都会将文件写入槽零。如果插槽 1-3 已被占用,则写入插槽 0 会清除它们。 git checkout
从索引中获取文件时,不必将其复制到索引中。 (当然不是:它已经存在了!)这就是当您 不是 在合并过程中时 git checkout
的工作方式:您可以 git checkout -- path/to/file
获得返回索引版本。9
不过,假设您 处于冲突合并的中间,并且将要 git checkout
某些路径,可能 --ours
。 (如果你不在合并的中间,插槽 1-3 中没有任何内容,--ours
没有意义。)所以你 运行 git checkout --ours -- path/to/file
.
这个 git checkout
从索引中获取文件——在本例中,从索引槽 2 中获取文件。因为它已经在索引中,所以 Git 不会将 写入 的索引,刚好到work-tree。所以文件没有解决!
git checkout --theirs
也是如此:它从索引(插槽 3)获取文件,但不解析任何内容。
但是:如果你git checkout HEAD -- path/to/file
,你是在告诉git checkout
从HEAD
提交中提取。由于这是一个 commit,Git 首先将文件内容写入索引。这将写入插槽 0 并擦除 1-3。现在文件已解析!
因为在冲突合并期间,Git 将 being-merged 提交的 ID 记录在 MERGE_HEAD
中,您还可以 git checkout MERGE_HEAD -- path/to/file
从其他提交中获取文件。这也是从提交中提取的,因此它写入索引,解析文件。
7我经常希望 Git 为此使用不同的 front-end 命令,因为我们可以明确地说 git checkout 是安全的,它不会覆盖没有 --force
的文件。但是这种 git checkout
会 故意覆盖文件!
8这是一个谎言,或者至少是一个谎言:提交不直接包含文件。相反,提交包含一个指向 tree 对象的(单个)指针。此树对象包含其他树对象和 blob 对象的 ID。 blob 对象包含实际文件内容。
事实上,索引也是如此。每个索引槽包含的不是实际文件 内容,而是存储库中 blob 对象的哈希 ID。
不过,就我们的目的而言,这并不重要:我们只要求 Git 检索 commit:path
并找到树和我们的 blob ID。或者,我们要求 Git 检索 :n:path
,它会在 path
[=395 的索引条目中找到 blob ID =] 插槽 n
。然后它获取文件的内容,我们就可以开始了。
此 colon-and-number 语法适用于 Git,而 --ours
和 --theirs
标志仅适用于 git checkout
。 gitrevisions
.
中描述了有趣的冒号语法
9git checkout -- path
的use-case是这样的:假设,不管你是否合并,你对一个文件做了一些修改,测试,发现这些更改有效,然后 运行 git add
在文件上。然后你决定做更多的改变,但没有再运行 git add
。您测试第二组更改并发现它们是错误的。要是你能把文件的 work-tree 版本设置回你刚才 git add
编辑的版本就好了....啊哈,你 可以 : 你 git checkout -- path
和 Git 复制索引版本,从槽零,回到 work-tree.
细微行为警告
但是请注意,使用 --ours
或 --theirs
除了 "extract from index and therefore don't resolve" 行为之外还有另一个细微的差别。假设在我们的冲突合并中,Git 检测到某个文件被 重命名 。也就是说,在合并库中,我们有文件 doc.txt
,但现在在 HEAD
中我们有 Documentation/doc.txt
。 git checkout --ours
我们需要的路径是 Documentation/doc.txt
。这也是HEAD
提交中的路径,所以git checkout HEAD -- Documentation/doc.txt
.
就可以了
但是,如果在我们合并的提交中,doc.txt
没有 重命名怎么办?在这种情况下,我们应该10能够git checkout --theirs -- Documentation/doc.txt
从索引中得到他们的doc.txt
。但是如果我们尝试 git checkout MERGE_HEAD -- Documentation/doc.txt
,Git 将无法找到该文件:它不在 Documentation
中,在 MERGE_HEAD
提交中。我们必须 git checkout MERGE_HEAD -- doc.txt
才能获取他们的文件……而那 不会 解析 Documentation/doc.txt
。事实上,它只会创建 ./doc.txt
(如果它被重命名,几乎可以肯定没有 ./doc.txt
,因此 "create" 比 "overwrite" 更好。
因为合并使用了 HEAD
的名字,所以一步到 git checkout HEAD -- path
到 extract-and-resolve 通常是足够安全的。如果您正在处理文件并且已经 运行ninggit status
,你应该知道他们是否有一个重命名的文件,因此通过放弃你自己的更改一步到 git checkout MERGE_HEAD -- path
到 extract-and-resolve 是否安全。但是您仍然应该意识到这一点,并且知道如果 需要关注重命名该怎么做。
10我在这里说"should",而不是"can",因为Git目前忘记了重命名有点太早了。因此,如果使用 --theirs
获取您在 HEAD
中重命名的文件,您也必须在此处使用旧名称,然后在 work-tree.
中重命名该文件
嗨,我需要像这样合并两个分支。
这只是一个例子,我处理了数百个需要解析的文件。
git merge branch1
...conflicts...
git status
....
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both added: file1
# both added: file2
# both added: file3
# both added: file4
git checkout --ours file1
git chechout --theirs file2
git checkout --ours file3
git chechout --theirs file4
git commit -a -m "this should work"
U file1
fatal: 'commit' is not possible because you have unmerged files.
Please, fix them up in the work tree, and then use 'git add/rm <file>' as
appropriate to mark resolution and make a commit, or use 'git commit -a'.
当我执行 git merge tool
时,'ours' 分支中有正确的内容,当我保存它时,该文件从未合并列表中消失。但是因为我有数百个这样的文件,所以这不是一个选择。
我认为这种方法会把我带到我想去的地方——轻松地说出我想保留哪个分支的哪个文件。
但我想我误解了合并后 git checkout --ours/theirs
命令的概念。
能否请您提供一些信息,如何处理这种情况?我用 git 1.7.1
这主要是 git checkout
内部工作方式的一个怪癖。 Git 人倾向于让实现决定接口。
最终的结果是git checkout
与--ours
或--theirs
之后,如果要解决冲突,也必须git add
相同的路径:
git checkout --ours -- path/to/file
git add path/to/file
但是不是其他形式的情况git checkout
:
git checkout HEAD -- path/to/file
或:
git checkout MERGE_HEAD -- path/to/file
(这些在多个方面略有不同)。在某些情况下,这意味着最快的方法是使用中间命令。 (顺便说一下,这里的 --
是为了确保 Git 可以区分路径名和选项或 b运行ch 名。例如,如果你有一个名为 --theirs
,它看起来像是一个选项,但 --
会告诉 Git 不,它实际上是一个路径名。)
要了解这一切在内部是如何工作的,以及为什么您需要单独的 git add
除非您不需要,请继续阅读。 :-) 首先,让我们快速回顾一下合并过程。
合并,第 1 部分:合并如何开始
当你 运行:
$git合并<em>commit-or-branch</em>
Git 做的第一件事是找到命名提交和当前 (HEAD
) 提交之间的合并基础。 (请注意,如果您在此处提供 b运行ch 名称,如 git merge otherbranch
、Git t运行 将其命名为 commit-ID,即b运行ch。它保存最终合并日志消息的 b运行ch 名称参数,但需要提交 ID 才能找到合并基础。)
找到合适的合并基地后,1 Git 会生成两个 git diff
列表:一个从合并基地到 HEAD
,一个从合并基础到您确定的提交。这得到 "what you changed" 和 "what they changed",Git 现在必须合并。
对于您进行了更改但没有更改的文件,Git 可以只采用您的版本。
对于他们进行了更改而您没有更改的文件,Git 可以采用他们的版本。
对于你们都进行了更改的文件,Git 必须进行一些真正的合并工作。它逐行比较更改,看是否可以将它们组合起来。如果它 可以 将它们结合起来,它就会这样做。如果合并看起来——再次基于纯粹的 line-by-line 比较——冲突,Git 为该文件声明一个 "merge conflict"(并继续尝试合并,但留下冲突标记到位)。
一旦 Git 合并了它所能合并的所有内容,它要么完成合并(因为没有冲突),要么因合并冲突而停止。
1如果绘制提交图,合并基础是显而易见的。不画图,有点神秘。这就是为什么我总是告诉人们绘制图形,或者至少尽可能多地画出有意义的图形。
技术上的定义是merge base是提交图中的"lowest common ancestor"(LCA)节点。用不太专业的术语来说,它是您当前的 b运行ch 与您正在合并的 b运行ch 合并的最新提交。也就是说,通过记录每个合并的父提交 ID,Git 能够找到两个 b运行ches 在一起的 last 时间,从而找出两者你做了什么,他们做了什么。但是,要使它完全起作用,Git 必须记录每次合并。具体来说,它必须将两个(或全部,对于 so-called "octopus" 合并)父 ID 写入新的合并提交。
在某些情况下,有不止一个合适的合并基础。然后,该过程取决于您的合并策略。默认的 recursive 策略将合并多个合并基础以产生 "virtual merge base"。这种情况很少见,您现在可以忽略它。
合并,第 2 部分:因冲突而停止,以及 Git 的 "index"
当Git确实以这种方式停止时,它需要给你一个机会来解决冲突。但这也意味着它需要记录冲突,这就是Git的"index"—也称为"the staging area",有时"the cache"——真是自得其乐。
对于您 work-tree 中的每个暂存文件,索引有 最多四个 条目,而不是只有一个条目。其中最多三个实际使用过,但有四个插槽,编号为 0
到 3
。
槽零用于解析文件。当您使用 Git 而不进行合并时,仅使用 插槽零。当你在工作树中编辑一个文件时,它有 "unstaged changes",然后你 git add
文件和更改被写入存储库,更新槽零;您的更改现在是 "staged".
插槽 1-3 用于未解析的文件。当 git merge
必须因合并冲突而停止时,它会将槽 0 留空,并将所有内容写入槽 1、2 和 3。文件的 merge base 版本是记录在插槽 1 中,--ours
版本记录在插槽 2 中,ad --theirs
版本记录在插槽 3 中。这些非零插槽条目是 Git 知道文件未解析的方式。2
解析文件时,您 git add
它们会擦除所有插槽 1-3 条目并写入 slot-zero、staged-for-commit 条目。这就是 Git 知道文件已解析并准备好进行新提交的方式。 (或者,在某些情况下,您 git rm
文件,在这种情况下 Git 将一个特殊的 "removed" 值写入槽零,再次擦除槽 1-3。)
2在少数情况下,这三个插槽之一也是空的。假设文件 new
在合并库中不存在,并且在我们的和他们的中都添加了。然后:1:<em>new</em>
留空,:2:<em>new</em>
和:3:<em>new</em>
记录了add/add冲突。或者,假设文件 f
确实存在于 base 中,在我们的 HEAD b运行ch 中被修改,并在它们的 b运行ch 中被删除.然后:1:<em>f</em>
记录基础文件,:2:<em>f</em>
记录了我们的文件版本,而:3:<em>f</em>
为空,记录了modify/delete冲突。
对于modify/modify冲突,三个槽都被占用;只有当一个文件丢失时,这些插槽之一才会为空。逻辑上不可能有两个空位:没有 delete/delete 冲突,也没有 nocreate/add 冲突。但是 rename 冲突有些奇怪,我在这里省略了,因为这个答案足够长了!在任何情况下,正是插槽 1、2、and/or 3 中某些值的存在将文件标记为未解析。
合并,第 3 部分:完成合并
一旦所有文件都被解析——所有条目只在zero-numbered槽中——你可以git commit
合并结果。如果 git merge
能够在没有帮助的情况下进行合并,它通常会为您 运行s git commit
,但实际提交仍然由 运行ning git commit
完成.
commit 命令的工作方式与往常一样:它将索引内容转换为 tree 对象并写入新的提交。合并提交的唯一特殊之处在于它有多个父提交 ID。3 额外的父提交来自 git merge
留下的文件。默认的合并消息也来自一个文件(实际上是一个单独的文件,尽管原则上它们可以合并)。
请注意,在所有情况下,新提交的内容都由索引的内容决定。此外,一旦新的提交完成,索引仍然是满的:它仍然包含相同的内容。默认情况下,git commit
此时不会进行另一个新提交,因为它发现索引与 HEAD
提交匹配。它调用此 "empty" 并要求 --allow-empty
进行额外提交,但索引根本不是 empty。它仍然很满——它只是充满了 与 HEAD
提交相同的东西。
3这假设您正在进行真正的合并,而不是挤压合并。在进行压缩合并时,git merge
故意 不 将额外的父 ID 写入额外的文件,以便新的合并提交只有一个父。 (出于某种原因,git merge --squash
也抑制了自动提交,就好像它也包含 --no-commit
标志一样。不清楚为什么,因为你 可以 只是 运行 git merge --squash --no-commit
如果你想要 自动提交被抑制。)
壁球合并不记录其其他父项。这意味着如果我们再次合并 ,一段时间后,Git 将不知道 从哪里开始差异 。这意味着如果您打算放弃另一个 b运行ch,您通常应该只 squash-merge。 (有一些巧妙的方法可以结合挤压合并和真正的合并,但它们超出了这个答案的范围。)
如何git结帐<em>b运行ch</em>
使用索引
完成所有这些之后,我们还必须看看 git checkout
如何使用 Git 的索引。请记住,在正常使用中,只有零号槽被占用,并且索引对每个暂存文件都有一个条目。此外,该条目 与当前 (HEAD
) 提交 匹配,除非您修改了文件并 git add
-ed 结果。它还 匹配 work-tree 中的文件,除非您修改了该文件。4
如果你在一些 b运行ch 并且你 git checkout
一些 other b运行ch,Git 尝试切换到另一个 b运行ch。为了成功,Git 必须用另一个 b运行ch.
为了具体起见,假设您在 master
并且正在做 git checkout branch
。 Git 将每个当前索引条目与它需要在 b运行ch branch
的 tip-most 提交上的索引条目进行比较。日问题是,文件 README.txt
的 master
内容 与 branch
的内容 相同,还是不同?
如果内容相同,Git可以放轻松,继续下一个文件。如果内容不同,Git 必须对索引条目做一些事情。 (正是在这一点附近,Git 检查 work-tree 文件是否也与索引条目不同。)
具体来说,如果 branch
的文件与 master
的不同,git checkout
必须 替换 索引条目使用来自 branch
的版本——或者,如果 README.txt
在 branch
的提示提交中 不存在 ,Git 必须删除 索引条目。而且,如果git checkout
要修改或删除索引项,还需要修改或删除work-tree文件。 Git 确保这是一件安全的事情,即 work-tree 文件与 master
提交的文件相匹配,然后它会让你切换 b运行ches.
换句话说,这就是 Git 确定是否可以更改 b运行ches 的方式(以及原因)——无论您是否有修改会因从 [=55 切换而被破坏=] 到 branch
。如果您在 work-tree、 中进行了修改,但 修改后的文件 在两个 b运行 中相同, Git 可以只保留索引和 work-tree 中的修改。它可以并且会提醒您这些修改过的文件 "carried over" 进入新的 b运行ch:很简单,因为无论如何它都必须检查这个。
一旦所有测试都通过并且 Git 决定可以从 master
切换到 branch
— 或者如果您指定 --force
— git checkout
实际上用所有更改(或删除)的文件更新索引,并更新 work-tree 以匹配。
请注意,所有此操作都使用了槽零。根本没有插槽 1-3 条目,因此 git checkout
不必删除任何此类内容。您不在有冲突的合并中,您 运行 git checkout branch
不只是检查 一个文件 ,而是检查整个 set 个文件并切换 b运行ches.
另请注意,您可以签出特定提交,而不是签出 b运行ch。例如,这就是您查看之前提交的方式:
$ git log
... peruse log output ...
$ git checkout f17c393 # let's see what's in this commit
此处的操作与检出 b运行ch 相同,只是不使用 b运行ch 的 tip 提交, Git 检查任意提交。你现在不是 "on" 新的 b运行ch,而是 no b运行ch:5Git给你一个"detached HEAD"。要重新装上你的头,你必须 git checkout master
或 git checkout branch
才能取回 "on" b运行ch。
4索引条目可能与 work-tree 版本不匹配,如果 Git 正在进行特殊的 CR-LF 结束修改,或应用污迹过滤器.这变得非常先进,最好的办法是暂时忽略这种情况。 :-)
5更准确地说,这使您进入一个 anonymous(未命名)b运行ch,它将从当前提交。如果您进行新的提交,您将保持分离的 HEAD 模式,一旦您 git checkout
其他提交或 b运行ch,您将切换到那里并且 Git 将 "abandon" 你所做的承诺。这种分离的 HEAD 模式的要点是让你环顾 和 让你做出新的提交,如果你不这样做, 将 消失不要采取特殊行动来拯救他们。但是,对于 Git 相对较新的任何人来说,提交 "just go away" 并不是一件好事——所以无论何时,请确保您知道自己处于这种 "detached HEAD" 模式。
git status
命令会告诉您是否处于分离 HEAD 模式。经常使用它。6 如果你的 Git 是旧的(OP 的是 1.7.1,现在已经很旧了),git status
没有它那么有用在 Git 的现代版本中,但总比没有好。
6一些程序员喜欢将密钥 git status
信息编码到每个 command-prompt 中。我个人不会走这么远,但不失为一个好主意。
签出特定文件,以及为什么它有时会解决合并冲突
不过,git checkout
命令还有其他操作模式。特别是,您可以 运行 git checkout [flags etc] -- path [path ...]
检出特定文件。这就是事情变得奇怪的地方。请注意,当您使用 this 形式的命令时,Git 不会 检查以确保您没有覆盖文件。7
现在,您不是更改 b运行ches,而是告诉 Git 从某处获取一些特定文件,并将它们放入 work-tree,覆盖无论那里有什么,如果有的话。棘手的问题是:Git 从哪里获取这些文件?
一般来说,Git保存文件的地方有3个:
- 在提交中;8
- 在索引中;
- 并在 work-tree.
checkout 命令可以从前两个地方读取,并且总是将结果写入work-tree。
当 git checkout
从提交中获取文件时,它首先 将其复制到索引 。每当它这样做时,它都会将文件写入槽零。如果插槽 1-3 已被占用,则写入插槽 0 会清除它们。 git checkout
从索引中获取文件时,不必将其复制到索引中。 (当然不是:它已经存在了!)这就是当您 不是 在合并过程中时 git checkout
的工作方式:您可以 git checkout -- path/to/file
获得返回索引版本。9
不过,假设您 处于冲突合并的中间,并且将要 git checkout
某些路径,可能 --ours
。 (如果你不在合并的中间,插槽 1-3 中没有任何内容,--ours
没有意义。)所以你 运行 git checkout --ours -- path/to/file
.
这个 git checkout
从索引中获取文件——在本例中,从索引槽 2 中获取文件。因为它已经在索引中,所以 Git 不会将 写入 的索引,刚好到work-tree。所以文件没有解决!
git checkout --theirs
也是如此:它从索引(插槽 3)获取文件,但不解析任何内容。
但是:如果你git checkout HEAD -- path/to/file
,你是在告诉git checkout
从HEAD
提交中提取。由于这是一个 commit,Git 首先将文件内容写入索引。这将写入插槽 0 并擦除 1-3。现在文件已解析!
因为在冲突合并期间,Git 将 being-merged 提交的 ID 记录在 MERGE_HEAD
中,您还可以 git checkout MERGE_HEAD -- path/to/file
从其他提交中获取文件。这也是从提交中提取的,因此它写入索引,解析文件。
7我经常希望 Git 为此使用不同的 front-end 命令,因为我们可以明确地说 git checkout 是安全的,它不会覆盖没有 --force
的文件。但是这种 git checkout
会 故意覆盖文件!
8这是一个谎言,或者至少是一个谎言:提交不直接包含文件。相反,提交包含一个指向 tree 对象的(单个)指针。此树对象包含其他树对象和 blob 对象的 ID。 blob 对象包含实际文件内容。
事实上,索引也是如此。每个索引槽包含的不是实际文件 内容,而是存储库中 blob 对象的哈希 ID。
不过,就我们的目的而言,这并不重要:我们只要求 Git 检索 commit:path
并找到树和我们的 blob ID。或者,我们要求 Git 检索 :n:path
,它会在 path
[=395 的索引条目中找到 blob ID =] 插槽 n
。然后它获取文件的内容,我们就可以开始了。
此 colon-and-number 语法适用于 Git,而 --ours
和 --theirs
标志仅适用于 git checkout
。 gitrevisions
.
9git checkout -- path
的use-case是这样的:假设,不管你是否合并,你对一个文件做了一些修改,测试,发现这些更改有效,然后 运行 git add
在文件上。然后你决定做更多的改变,但没有再运行 git add
。您测试第二组更改并发现它们是错误的。要是你能把文件的 work-tree 版本设置回你刚才 git add
编辑的版本就好了....啊哈,你 可以 : 你 git checkout -- path
和 Git 复制索引版本,从槽零,回到 work-tree.
细微行为警告
但是请注意,使用 --ours
或 --theirs
除了 "extract from index and therefore don't resolve" 行为之外还有另一个细微的差别。假设在我们的冲突合并中,Git 检测到某个文件被 重命名 。也就是说,在合并库中,我们有文件 doc.txt
,但现在在 HEAD
中我们有 Documentation/doc.txt
。 git checkout --ours
我们需要的路径是 Documentation/doc.txt
。这也是HEAD
提交中的路径,所以git checkout HEAD -- Documentation/doc.txt
.
但是,如果在我们合并的提交中,doc.txt
没有 重命名怎么办?在这种情况下,我们应该10能够git checkout --theirs -- Documentation/doc.txt
从索引中得到他们的doc.txt
。但是如果我们尝试 git checkout MERGE_HEAD -- Documentation/doc.txt
,Git 将无法找到该文件:它不在 Documentation
中,在 MERGE_HEAD
提交中。我们必须 git checkout MERGE_HEAD -- doc.txt
才能获取他们的文件……而那 不会 解析 Documentation/doc.txt
。事实上,它只会创建 ./doc.txt
(如果它被重命名,几乎可以肯定没有 ./doc.txt
,因此 "create" 比 "overwrite" 更好。
因为合并使用了 HEAD
的名字,所以一步到 git checkout HEAD -- path
到 extract-and-resolve 通常是足够安全的。如果您正在处理文件并且已经 运行ninggit status
,你应该知道他们是否有一个重命名的文件,因此通过放弃你自己的更改一步到 git checkout MERGE_HEAD -- path
到 extract-and-resolve 是否安全。但是您仍然应该意识到这一点,并且知道如果 需要关注重命名该怎么做。
10我在这里说"should",而不是"can",因为Git目前忘记了重命名有点太早了。因此,如果使用 --theirs
获取您在 HEAD
中重命名的文件,您也必须在此处使用旧名称,然后在 work-tree.