我们不会从未合并的文件列表中删除文件

嗨,我需要合并这样的两个分支。

这仅仅是一个例子,我正在处理数百个需要解析的文件。

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 ,只有'我们'分支有正确的内容,当我保存它时,文件从未合并列表中消失。 但由于我有这样的数百个文件,这不是一个选项。

我认为这种方法会将我带到我想要的地方 - 轻松地说出我想保留哪个分支的哪个文件。

但我想我误解了合并后git checkout --ours/theirs的概念git checkout --ours/theirs命令。

你能否给我提供一些信息,如何处理这种情况? 我使用git 1.7.1


这主要是git checkout在内部工作的一个怪癖。 Git人员倾向于让实现指定接口。

最终的结果是,在用--ours或者 - --theirs git checkout之后,如果你想解决冲突,你还必须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的路径名和一个选项或分支机构名称区分举例来说,如果你有一个文件命名。 --theirs ,它看起来像一个选项,但--会告诉不要,这真是一个路径名。)

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

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

当你运行:

$ git merge commit-or-branch

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

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

对于你做出改变但没有改变的文件,Git可以只取你的版本。

对于他们做出改变而你没有改变的文件,Git可以采用他们的版本。

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

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


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

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

在某些情况下,有多个合适的合并基础。 该流程取决于您的合并策略。 默认的递归策略将合并多个合并基础以产生“虚拟合并基础”。 这很罕见,您现在可以忽略它。


合并,第2部分:停止冲突,Git的“索引”

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

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

插槽零用于解析的文件。 当你使用Git而不是合并时,只有零槽被使用。 当你在工作树中编辑一个文件时,它有“非持续更改”,然后你git add文件,并将更改写入存储库,更新插槽零; 您的更改现在已“上演”。

插槽1-3用于未解析的文件。 当git merge必须以合并冲突结束时,它会使插槽零为空,并将所有内容写入插槽1,插槽2和插槽3.合并基本版本的文件记录在插槽1中,-- --ours版本记录在插槽2,并且 - --theirs版本记录在插槽3中。这些非零插槽条目是Git如何知道文件未解析。

当你解析文件时,你可以git add它们,这会删除所有的插槽1-3项,并写入一个零插槽的提交阶段提交条目。 这就是Git知道文件已被解析并准备好进行新的提交。 (或者,在某些情况下,您将文件git rm到文件中,在这种情况下,Git会向槽0写入一个特殊的“已删除”值,并再次删除槽1-3)。


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

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


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

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

commit命令的作用与以往一样:它将索引内容转换为树对象并写入新的提交。 合并提交的唯一特别之处在于它有多个父提交ID.3额外的父项来自git merge留下的文件。 默认的合并信息也来自一个文件(实际上是一个单独的文件,尽管原则上它们可能已经合并)。

请注意,在所有情况下,新提交的内容都由索引的内容决定。 而且,一旦新的提交完成,索引仍然是满的:它仍然包含相同的内容。 默认情况下, git commit在此时不会进行另一个新的提交,因为它会发现索引与HEAD提交匹配。 它将此称为“空”,并要求--allow-empty进行额外的提交,但索引不是空的。 它仍然非常完整 - 它与HEAD提交完全相同。


3这假定你正在做一个真正的合并,而不是一个壁球合并。 在进行壁球合并时, git merge有意不会将额外的父ID写入额外的文件,这样新的合并提交只有一个父节点。 (出于某种原因, git merge --squash也会禁止自动提交,就好像它也包含--no-commit标志一样。不清楚为什么,因为你可以运行git merge --squash --no-commit如果你想禁止自动提交。)

壁球合并不记录其他父母。 这意味着如果我们再次合并,一段时间后,Git将不知道从哪里开始差异。 这意味着如果您打算放弃其他分支,通常应该只进行压缩合并。 (有一些棘手的方法可以将压缩合并和真正的合并结合起来,但它们远远超出了这个答案的范围。)


git checkout branch如何使用索引

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

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

比方说,只是为了具体,你是master ,你在做git checkout branch 。 Git会比较每个当前索引条目和索引条目,它将需要在分支branch的提示最多的提交上。 也就是说,对于文件README.txtmaster内容是否与branch相同?还是它们不同?

如果内容相同,Git可以简单地转到下一个文件。 如果内容不同,Git必须对索引条目做些什么。 (Git检查工作树文件是否与索引条目不同也是在这一点上。)

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

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

一旦所有的测试都通过了,Git认为可以从master切换到branch - 或者如果你指定了--force - git checkout实际上用所有更改(或删除)的文件更新索引,并更新工作树匹配。

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

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

$ git log
... peruse log output ...
$ git checkout f17c393 # let's see what's in this commit

这里的动作与检出分支相同,不同之处在于不使用分支的提示提交,Git检出任意提交。 现在不用“在”新的分支,你现在不在分支上:5 Git给你一个“分离的HEAD”。 要重新连接你的头,你必须将git checkout mastergit checkout branch到分支上。


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

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

git status命令会告诉你你是否处于分离的HEAD模式。 经常使用它。如果你的Git比较旧(OP的版本是1.7.1,现在已经很老了),那么git status并不像现代版本的Git那样有用,但它还是比没有好。

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


检出特定的文件,以及为什么它有时会解决合并冲突

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

现在,不是改变分支,而是告诉Git从某个地方获取一些特定的文件,然后将它们放到工作树中,如果有的话覆盖任何地方。 棘手的问题是:Git在哪里获取这些文件?

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

  • 在提交中; 8
  • 在指数中;
  • 并在工作树中。
  • checkout命令可以从前两个位置读取,并始终将结果写入工作树。

    git checkout从提交中获取文件时,它首先将其复制到索引。 每当它做到这一点,它将文件写入零槽。 写入插槽零将抹掉插槽1-3,如果它们被占用。 当git checkout从索引获取文件时,它不必将其复制到索引。 (当然不是:它已经在那里了!)当你不在合并中时,这就是git checkout工作方式:你可以通过git checkout -- path/to/file来获得索引版本。

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

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

    git checkout --theirs :它从索引(插槽3)获取文件,并且不解决任何问题。

    但是 :如果你git checkout HEAD -- path/to/file ,你告诉git checkoutHEAD提交中提取。 由于这是一个提交,所以Git通过将文件内容写入索引开始。 这写入插槽0并擦除1-3。 现在该文件已解决!

    因为在冲突的合并过程中,Git会在MERGE_HEAD记录正在合并的提交的ID,您也可以通过git checkout MERGE_HEAD -- path/to/file来从另一个提交中获取文件。 这也从提交中提取,所以它写入索引,解析文件。


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

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

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

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

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

    9 git checkout -- path用例git checkout -- path是这样的:假设你是否合并,对文件做了一些修改,测试,发现这些修改有效,然后在文件上运行git add 。 然后你决定做更多的改变,但还没有再次运行git add 。 您测试第二组更改并发现它们是错误的。 如果只有你可以将文件的工作树版本设置回git addgit add的版本.... aha,你可以:你git checkout -- path和Git复制索引版本,从插槽零,回到工作树。


    微妙的行为警告

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

    但是如果在我们合并的提交中, doc.txt没有被重命名? 在这种情况下,我们应该能够通过git checkout --theirs -- Documentation/doc.txt他们的git checkout --theirs -- Documentation/doc.txt doc.txt从索引中获取他们的doc.txt 。 但是,如果我们试图git checkout MERGE_HEAD -- Documentation/doc.txt ,那么Git将无法找到该文件:它不在MERGE_HEAD提交中的Documentation中。 我们必须git checkout MERGE_HEAD -- doc.txt以获取他们的文件...并且不能解决Documentation/doc.txt 。 实际上,它只会创建./doc.txt (如果它被重命名,几乎肯定没有./doc.txt ,因此“创建”比“覆盖”更好)。

    因为合并使用HEAD的名字,所以它通常足够安全,可以通过git checkout HEAD -- path一步完成提取和解析的git checkout HEAD -- path 。 如果您正在解析文件并且正在运行git status ,则应该知道它们是否有重命名的文件,因此是否可以安全地进行git checkout MERGE_HEAD -- path通过放弃您的操作,一步完成提取和解析的git checkout MERGE_HEAD -- path自己的变化。 但是你仍然应该意识到这一点,并且知道如果需要重新命名,该怎么办。


    10我在这里说“应该”,而不是“可以”,因为Git目前很快就忘记了重命名。 所以如果使用--theirs来获取在HEAD重命名的文件,则必须在这里使用旧名称,然后在工作树中重命名该文件。

    链接地址: http://www.djcxy.com/p/7825.html

    上一篇: ours does not remove files from unmerged files list

    下一篇: Resolve conflicts using remote changes when pulling from Git remote