当前位置: 首页 > 知识库问答 >
问题:

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

伍嘉
2023-03-14

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

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

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

我认为,这种方法将把我带到我想去的地方——很容易说出我想保留哪个分支中的哪个文件。

但我想我误解了git签出的概念——合并后我们/他们的命令。

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

共有1个答案

阎扬
2023-03-14

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

最终结果是,在git签出--our--his之后,如果要解决冲突,还必须git添加相同的路径:

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

但其他形式的git check out并非如此:

git checkout HEAD -- path/to/file

或:

git checkout MERGE_HEAD -- path/to/file

(这些在许多方面都有细微的不同)。在某些情况下,这意味着最快的方法是使用中间命令。(顺便说一句,这里的--是为了确保Git可以区分路径名和选项或分支名。例如,如果您有一个名为--their的文件,它看起来像一个选项,但--会告诉Git不,它实际上是一个路径名。)

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

运行时:

<代码>$git合并提交或分支

Git要做的第一件事是找到命名提交和当前(HEAD)提交之间的合并基。(请注意,如果在此处提供分支名称,如git merge otherbranch中所述,git会将其转换为提交ID,即分支的尖端。它会为最终的合并日志消息保存分支名称参数,但需要提交ID才能找到合并基。)

找到合适的合并基后,Git然后生成两个Git差异列表:一个从合并基到头部,另一个从合并基到您确定的提交。这将得到“你改变了什么”和“他们改变了什么”,Git现在必须将这两者结合起来。

对于您进行了更改但未进行更改的文件,Git可以只获取您的版本。

对于他们进行了更改而您没有进行更改的文件,Git可以采用他们的版本。

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

一旦Git尽其所能合并了所有内容,它要么因为没有冲突而完成合并,要么因为合并冲突而停止。

如果绘制提交图,则合并基础很明显。如果不画图表,那就有点神秘了。这就是为什么我总是告诉人们绘制图表,或者至少,根据需要绘制尽可能多的图表。

技术定义是,合并基是提交图中的“最低共同祖先”(LCA)节点。用不太专业的术语来说,这是当前分支与正在合并的分支的最新提交。也就是说,通过记录每个合并的父提交ID,Git能够找到上一次两个分支在一起的时间,从而了解您做了什么,以及它们做了什么。然而,要想让这一切都起作用,Git必须记录每次合并。具体来说,它必须将两个(或所有,对于所谓的“octopus”合并)父ID写入新的合并提交。

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

当Git以这种方式停止时,它需要给您一个解决冲突的机会。但这也意味着它需要记录冲突,而这正是Git的“索引”(也称为“暂存区”,有时也称为“缓存”)真正存在的地方。

对于工作树中的每个暂存文件,索引最多有四个条目,而不是只有一个条目。其中最多有三个实际使用中,但有四个插槽,编号为0到3。

插槽0用于已解析的文件。当您使用Git而不进行合并时,只使用插槽0。当您在工作树中编辑一个文件时,它有“未老化的更改”,然后您添加文件并将更改写入存储库,更新插槽0;您的更改现在已“暂存”。

插槽1-3用于未解析的文件。当git merge因合并冲突而停止时,它会将插槽0保留为空,并将所有内容写入插槽1、2和3。文件的合并基本版本记录在插槽1中,我们的版本记录在插槽2中,他们的版本记录在插槽3中。这些非零插槽条目是Git知道文件未解析的方式2

在解析文件时,您可以添加这些文件,这将擦除所有插槽1-3条目,并写入插槽0,暂存用于提交条目。这就是Git如何知道文件已被解析并准备好进行新的提交。(或者,在某些情况下,您可以使用git rm文件,在这种情况下,git会将一个特殊的“已删除”值写入插槽0,再次擦除插槽1-3。)

在少数情况下,这三个插槽中的一个也为空。假设文件new不存在于合并库中,并且添加到我们的和他们的合并库中。然后,1:new(新建)保留为空,2:new(新建)和3:new(新建)记录添加/添加冲突。或者,假设base中确实存在file,在我们的HEAD分支中修改,并在其分支中删除。然后,1:f记录基本文件,2:f记录我们的文件版本,3:f为空,记录修改/删除冲突。

对于修改/修改冲突,三个插槽都被占用;只有当一个文件丢失时,这些插槽中的一个才是空的。从逻辑上讲,不可能有两个空槽:不存在删除/删除冲突,也不存在nocreate/add冲突。但是重命名冲突有一些奇怪之处,我在这里省略了,因为这个答案足够长了!在任何情况下,正是插槽1、2和/或3中存在的某些值将文件标记为未解析。

解析完所有文件后,所有条目仅位于零编号的插槽中,您可以提交合并结果。如果git merge能够在没有帮助的情况下进行合并,它通常会为您运行git commit,但实际的提交仍然是通过运行git commit来完成的。

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

请注意,在所有情况下,新提交的内容都是由索引的内容决定的。此外,一旦完成新的提交,索引仍然是满的:它仍然包含相同的内容。默认情况下,git commit此时不会进行另一次新的提交,因为它会看到索引与提交匹配。它将其称为“empty”,并要求允许empty进行额外的提交,但索引根本不是空的。它仍然很满,只是充满了与提交相同的内容。

3这假设您正在进行真正的合并,而不是壁球合并。进行壁球合并时,git合并故意不将额外的父ID写入额外的文件,因此新的合并提交只有一个父级。(出于某种原因,git合并--Squash也抑制了自动提交,就好像它也包含了--no-提交标志一样。不清楚为什么,因为如果您希望抑制自动提交,您可以直接运行git合并--Squash--no-提交。)

挤压合并不会记录其其他父级。这意味着,如果我们在一段时间后再次合并,Git将不知道从哪里开始差异。这意味着,如果计划放弃另一个分支,通常只应挤压合并。(有一些巧妙的方法可以将挤压合并和真实合并结合起来,但它们远远超出了这个答案的范围。)

说完这些,我们还要看看git check out是如何使用Git的索引的。请记住,在正常使用中,只有零槽被占用,并且每个暂存文件的索引都有一个条目。此外,该条目与当前(HEAD)提交匹配,除非您修改了文件并git add-ed了结果。它还与工作树中的文件匹配,除非您修改了文件。4

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

假设,为了具体起见,您在master上并且正在执行git签出分支。Git会将每个当前索引条目与它需要在分支分支的最尖端提交上的索引条目进行比较。也就是说,对于文件README. txtmaster的内容与分支的内容相同,还是不同?

如果内容相同,Git就可以轻松地转到下一个文件。如果内容不同,Git必须对索引项进行一些处理。(Git也在这一点上检查工作树文件是否与索引项不同。)

具体而言,如果分支机构的文件与主文件不同,则git checkout必须用分支机构的版本替换索引项,或者如果是自述文件,则必须用自述文件替换索引项。txt在分支的tip commit中不存在,Git必须删除索引项。此外,如果git checkout要修改或删除索引项,它还需要修改或删除工作树文件。Git确保这是一件安全的事情,即工作树文件与主提交文件匹配,然后才允许切换分支。

换句话说,这就是Git发现更改分支是否可以的方式(以及原因)——您是否有修改会通过从master切换到分支而遭到重创。如果您在工作树中进行了修改,但修改后的文件在两个分支中是相同的,Git可以将修改保留在索引和工作树中。它可以并且将提醒您这些修改后的文件“携带”到新分支中:很容易,因为它无论如何都必须检查这一点。

一旦所有测试都通过并且Git决定可以从master切换到分支-或者如果您指定了--force-git签出实际上使用所有更改(或删除)的文件更新索引,并更新工作树以匹配。

请注意,所有这些操作都使用了插槽零。根本没有插槽1-3条目,因此git check out不必删除任何此类内容。您没有处于冲突合并的中间,并且您运行git check out分支不仅要签出一个文件,还要签出一整套文件并切换分支。

还请注意,您可以签出特定的提交,而不是签出分支。例如,您可以这样看待以前的提交:

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

这里的操作与签出分支的操作相同,只是Git没有使用分支的提示提交,而是签出了任意提交。您现在不是“在”新分支上,而是在没有分支上:5Git为您提供了一个“分离的HEAD”。要重新连接您的头部,您必须git check out mastergit check out分支才能回到“上”分支。

如果Git正在进行特殊的CR-LF结尾修改或应用污迹过滤器,则索引项可能与工作树版本不匹配。这一点非常先进,最好的办法是暂时忽略这个案例。:-)

更准确地说,这将使您进入一个匿名(未命名)分支,该分支将从当前提交扩展而来。如果您进行新的提交,您将保持分离头模式,并且一旦您进行其他提交或分支,您将在那里切换,git将“放弃”您所做的提交。这种分离头模式的目的是让您可以四处看看,也可以让您做出新的promise,如果您不采取特殊措施来保存这些promise,这些promise就会消失。不过,对于任何一个对Git相对陌生的人来说,提交“就这么走”并不太好,所以无论何时,都要确保自己处于这种“超然的头脑”模式

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

6一些程序员喜欢将keygit status信息编码到每个命令提示符中。我个人没有走到这一步,但可能是个好主意。

不过,git checkout命令还有其他操作模式。特别是,您可以运行git checkout[标志等]--path[路径…] 以签出特定文件。这就是事情变得奇怪的地方。请注意,当您使用这种形式的命令时,Git不会检查以确保您没有覆盖文件7

现在,您没有更改分支,而是告诉Git从某处获取一些特定的文件,并将它们放到工作树中,覆盖其中的任何内容(如果有的话)。棘手的问题是:Git从哪里获得这些文件?

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

  • 在提交中;8
  • 在索引中;
  • 并在工作树中。

checkout命令可以读取前两个位置中的任何一个,并始终将结果写入工作树。

当git checkout从提交中获取文件时,它首先将其复制到索引中。每当执行此操作时,它都会将文件写入插槽零。如果插槽1-3被占用,写入插槽0会擦除插槽1-3。当git checkout从索引中获取文件时,它不必将其复制到索引中。(当然不是:它已经在那里了!)这就是当您不在合并过程中时git签出的工作方式:您可以使用git签出-路径/到/文件来获取索引版本9

但是,假设您正处于一个冲突的合并过程中,并且要使用某个路径进行git签出,可能是我们的路径。(如果您不在合并过程中,插槽1-3中没有任何内容,我们的也没有意义。)所以您运行git checkout—我们的—路径/到/文件

此git签出从索引中获取文件,在本例中,从索引槽2获取。由于这已经在索引中,Git不会写入索引,只会写入工作树。因此文件未解析!

git签出也是如此——他们的签出:它从索引(插槽3)获取文件,不解析任何内容。

但是:如果您是git签出头--path/to/file,那么您是在告诉git签出头从提交头中提取。由于这是一个提交,Git从将文件内容写入索引开始。这将写入插槽0并擦除1-3。现在文件已解决!

由于在发生冲突的合并期间,Git会将被合并提交的ID记录在合并头中,因此您还可以通过Git checkout merge\u HEAD--path/to/file从其他提交中获取文件。这也是从提交中提取的,因此它会写入索引,解析文件。

我经常希望Git为此使用不同的前端命令,因为我们可以明确地说,Git签出是安全的,如果没有强制,它不会覆盖文件。但是这种git签出确实是故意覆盖文件的!

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

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

不过,就我们的目的而言,这并不重要:我们只需要让Git检索提交路径,它就会为我们找到树和blob ID。或者,我们要求Git检索:n:path,然后它在slotn的路径的索引项中找到blob ID。然后它会给我们文件的内容,我们就可以开始了。

这种冒号和数字语法在Git中随处可见,而我们的和他们的标志只在Git签出中起作用。gitrevisions中描述了有趣的冒号语法。

9git check out--path的用例是这样的:假设,无论您是否正在合并,您对文件进行了一些更改,经过测试,发现这些更改有效,然后在文件上运行git add。然后您决定进行更多更改,但没有再次运行git add。您测试第二组更改并发现它们是错误的。如果您能将文件的工作树版本设置回您刚才git add-ed的版本就好了......啊哈,您可以:您git check out--path和Git将索引版本从零槽复制回工作树。

不过,请注意,除了“从索引中提取,因此不解析”行为之外,使用我们的或他们的还有另一个细微的差别。假设在我们的冲突合并中,Git检测到某个文件被重命名。也就是说,在合并库中,我们有filedoc。txt,但现在在HEAD中,我们有文档/文档。txt。git签出所需的路径——我们的路径是文档/doc。txt。这也是提交头中的路径,因此可以使用git签出头——文档/文档。txt。

但是,如果在提交中,我们正在合并,doc。txt未重命名?在这种情况下,我们应该能够进行git签出——他们的——文档/文档。txt获取他们的文档。索引中的txt。但如果我们尝试git checkout MERGE\u HEAD——文档/文档。txt,Git将无法找到该文件:它不在文档中,也不在合并头中。我们必须git checkout MERGE\u HEAD--doc。txt获取他们的文件。。。这并不能解决文档/文档的问题。txt。事实上,它只会创建<代码>/文件编号:。txt(如果它被重命名,几乎可以肯定没有/doc.txt,因此“创建”是比“覆盖”更好的猜测)。

因为合并使用了头的名称,所以在一个步骤中提取和解析git checkout头-path通常足够安全。如果您正在解析文件,并且一直在运行git status,您应该知道它们是否有重命名的文件,因此是否可以通过放弃自己的更改一步提取和解析git checkout MERGE\u HEAD--path来安全。但您仍然应该意识到这一点,并且知道如果需要关注重命名,应该怎么做。

10我在这里说“应该”,而不是“可以”,因为Git目前忘记重命名有点太早了。因此,如果使用-他们的来获取您在HEAD中重命名的文件,您也必须在这里使用旧名称,然后在工作树中重命名文件。

 类似资料:
  • 问题内容: 我正在使用HTML5构建拖放式Web应用程序,并将文件拖放到div上,当然要获取dataTransfer对象,这给了我FileList。 现在,我想删除一些文件,但是我不知道怎么办,或者甚至可能。 最好是我只想从FileList中删除它们;我没有用。但是,如果那不可能,我是否应该在与FileList交互的代码中编写检查代码?那看起来很麻烦。 问题答案: 如果只想删除几个选定的文件,则不

  • 我有一个下拉列表,其中填充了使用下面列出的PHP从目录中提取的文件,我正试图找出如何在选中这些文件时使用表单中的删除按钮删除它们。 在运行时更新代码。 编辑和更新代码 现在说警告:取消链接(/myhome/root/public_html/users/addicient/)[function.unlink]:在/home/revo/public_html/evo/avdeleteprocess中没

  • 问题内容: 我需要使用sed命令使用bash脚本从html中删除所有标签。我尝试了这个 和这 但我仍然想念什么,有什么建议吗? 问题答案: 您可以使用许多HTML到文本转换器之一,可以使用Perl regex,或者必须使用 如果没有错误的余地,请改用HTML解析器。例如,当元素分布在两行上时 此正则表达式将不起作用。 这正则表达式由三个部分组成,, 寻找开放 后面跟着零个或多个字符(不是结尾) 是

  • 我有一个火花作业,它管理上的。 我的问题是它会生成很多文件,包括 95% 的空 avro 文件。我尝试使用合并来减少RDD上的分区数量,从而减少输出文件的数量,但它没有效果。 我想分区配置和分区之间丢失了一些东西,也许没有考虑到它,但我不确定。 我错过了什么吗? 有人能解释一下根据rdd分区调用时真正附加的内容吗?

  • 问题内容: 背景 我有一个由詹金斯触发的安装程序,内容如下: 通过phing,与git服务器通信并在单独的构建服务器中进行所需的git版本之间的准备来准备要部署的文件,而无需涉及AWS代码部署(据我所知)。phing版本由Jenkins触发。 我仅将要添加/修改的文件(基于修订的git差异)动态添加到appspec.yml文件。我只准备要添加/修改到路径的文件,并且在Jenkins项目的“高级项目