当文件规范包括已删除的文件时,使用“git checkout --ours”。

9

当我们合并时,会保留本地版本的Maven pom.xml文件:

git merge origin/remote_branch
git checkout --ours **/pom.xml pom.xml
git add **/pom.xml pom.xml
git commit -m "Merge"

这很好用,但是如果本地分支中的pom.xml文件已被删除,则无法正常工作。运行上述第二个命令后,我们会收到错误提示:
d:\code>git checkout --ours **/pom.xml pom.xml
error: path 'blah/pom.xml' does not have our version

...在这个错误之后,下一条命令 #3 git add **/pom.xml pom.xml 实际上添加了远程的 pom.xml 文件 - 这正是我们不想要的。

我们该如何更新脚本来处理这个问题呢?

2个回答

18
如何解决运行命令git checkout --ours **/some_file2.xml some_file2.xml后出现的错误error: path 'some/file' does not have our version

1.A. 作为人类,以下是步骤

作为人类,您需要执行以下操作。假设您按照我在Who is "us"/"ours" and "them"/"theirs" according to Git?中的解释和建议运行了以下命令:

git checkout --ours -- path/to/some/dir

...而且它不起作用!它什么都没做。相反,它输出了以下错误信息:
error: path 'path/to/some/dir/file1.cpp' does not have our version
error: path 'path/to/some/dir/file2.cpp' does not have our version
error: path 'path/to/some/dir/file3.cpp' does not have our version
问题是这些是我们这边的已删除文件,所以我们必须手动从我们的工作树(工作文件系统)中逐个使用“git rm”命令将它们删除,以手动强制使我们的工作树与我们这边的文件保持一致。
git rm path/to/some/dir/file1.cpp
git rm path/to/some/dir/file2.cpp
git rm path/to/some/dir/file3.cpp

# OR (same thing)
git rm path/to/some/dir/file1.cpp path/to/some/dir/file2.cpp \
path/to/some/dir/file3.cpp

现在,重新运行你的checkout --ours命令,它会正常工作的!
git checkout --ours -- path/to/some/dir

搞定了!完成了。

1.B. 要编写上述过程的脚本,有点难,但是下面是方法

让我们来编写上面的东西。毫无疑问,有很多方法可以做到这一点,但是下面是我找到的最简单的方法:

# 1. attempt to run `git checkout --ours` the first time,
# collecting any filenames which errored out, if any, and 
# `git rm` them all.
git checkout --ours -- path/to/some/dir \
|& gawk '{ print $3 }' | xargs git rm

# 2. Now run it again. If it worked the first time above already, 
# no big deal--running it again causes no problems. If it failed
# above though, the above command just ran `git rm` on all those
# failed files, so now this time it will succeed!
git checkout --ours -- path/to/some/dir

完成!当然,你也可以将第一次尝试的输出存储到文件中,只有在第一次尝试失败(即输出不是空字符串)时才运行第二次尝试,但这取决于你。
示例输出:通过使用git rm命令删除文件,你将看到以下输出(这里的第一行包含了$字符后的实际命令):
$ git checkout --ours -- path/to/some/dir |& gawk '{ print $3 }' | xargs git rm
path/to/some/dir/file1.cpp: needs merge
path/to/some/dir/file2.cpp: needs merge
path/to/some/dir/file3.cpp: needs merge
rm 'path/to/some/dir/file1.cpp'
rm 'path/to/some/dir/file2.cpp'
rm 'path/to/some/dir/file3.cpp'

git checkout --ours -- path/to/some/dir |& gawk '{ print $3 }' | xargs git rm 的解释:

  1. git checkout --ours -- path/to/some/dir 接受来自 --ours 方的所有合并冲突(在这里阅读更多:根据 Git,"我们" 和 "他们" 是谁?)。
  2. |& 管道同时将 stderr 输出和 stdout 输出传递,因为 git 命令可能打印的错误消息位于 stderr,这就是我们需要传递的内容。
  3. gawk '{ print $3 }' 仅打印每行的第三个以空格分隔的字段,这意味着它捕获了 'path/to/some/dir/file1.cpp' 部分,例如 error: path 'path/to/some/dir/file1.cpp' does not have our version
  4. | xargs git rm 将所有这些文件传递给 git rm 命令以将它们从 Git 中移除。

2. 完成

现在您可以添加这些自动修复的文件并继续进行:

git add path/to/some/dir 
git status 

# Use the appropriate one of these based on whatever operation 
# you were in at the time when the conflicts happened.
git merge --continue 
git rebase --continue
git revert --continue
git cherry-pick --continue
# etc.

参考资料:

对于awk/gawk:
  1. 我的git-diffn.sh“带行号的git diff”脚本(我总是记不住awk的语法,所以我只能看以前的例子,包括我自己的)。
  2. https://en.wikipedia.org/wiki/AWK
  3. 官方GNU AWK用户指南
使用| xargs git rm如何批量删除文件? 使用|&来同时传递标准输出和标准错误输出:如何在bash中同时传递标准输出和标准错误输出? 为什么要使用'git rm'来删除文件,而不是使用'rm'命令?

2
运算符|&表示将_stderr_和_stdout_都传输到第二个命令。然而,并非所有的shell都支持它。在bash中,只有4+版本才支持它。对于旧的shell,请使用: git checkout --ours -- path/to/some/dir 2>&1 | gawk '{ print $3 }' | xargs git rm2>&1运算符意味着将_syserr_传输到相同的_stdout_,结果相同。 - jfaleiro
好的,rm 命令运行正常,但是接下来的 git checkout --ours path/to/my/file.file 命令给我留下了一个错误:error: pathspec 'path/to/my/file.file' did not match any file(s) known to git。明确一下,我已经在合并的目标分支中从本地删除了这个文件。编辑:看起来,如果我逐个文件处理,git rm 命令就足够了... 文件现在已经合并/冲突已解决。?? - undefined
@ruffin,error: pathspec...did not match any file(s) known to git 表示 git 在其 .git 文件夹中找不到该文件。首先尝试运行 git fetch 从远程下载所有本地不存在的更改。请参考这里。除此之外,我不清楚。我无法看到你的终端或你的屏幕上的确切情况和存储库。还要记住,rmgit rm 不完全相同。我认为 git rm file 等同于先运行 rm file,然后运行 git add file,这样会将删除操作暂存起来。在上面的命令中,我使用了 git rm - undefined
1
@GabrielStaples 谢谢回复 - 我觉得问题在于,由于我只剩下已删除的文件,一旦我“移除”它们,执行“现在,重新运行您的checkout --ours命令,它就会正常工作!”是多余的。再次强调,这只适用于我的情况。也就是说,我相信您的答案很好,但是对于其他人来说,根据您的用例,仅仅运行git rm命令来删除每个已删除的文件可能已经足够解决冲突。 - undefined

1

首先:

git merge origin/remote_branch

建议使用 git merge --no-commit 命令,以确保如果没有合并冲突,Git 不会提交这些更改,否则您的下一步将没有多大意义。请注意,如果 --theirs 提交已更改了一些 pom.xml 文件并且您没有更改它们,或者如果 Git 认为已成功合并了您和他们的更改,则根本不会出现合并冲突。(如果您想在其中一种情况下使用 theirs,那也有点棘手,但是您似乎总是想使用 --ours 版本。)

接下来:

git checkout --ours **/pom.xml pom.xml
这取决于您的shell(可能是或类似的),以您想要的方式展开**;您可能希望引用星号,并让Git进行全局扩展。不过这会影响到您的特定情况,而且我不确定Git在合并冲突期间如何处理此问题,因此在执行任何此类操作之前,您需要仔细实验。

This works great except if a pom.xml file has been removed in the local branch. After running command #2 above we get an error:

d:\code>git checkout --ours **/pom.xml pom.xml
error: path 'blah/pom.xml' does not have our version

对于这种情况,如果你想保留已删除的文件,你需要覆盖Git默认的操作,选择在索引和工作树中保留它们的版本。

让我们进入所有这些内容中与Git相关的部分,即索引。请记住,Git的索引是您构建下一个提交的地方。在合并期间,它也是您解决冲突的地方。

合并期间索引中的条目

在正常(非合并)情况下,索引为每个被跟踪的文件都有一个条目。如果文件F在当前(HEAD)提交和工作树中,则索引具有F的条目。最初,此索引条目版本与HEAD版本相匹配。您可以在工作树中修改文件,然后git add工作树版本以将其复制到索引中以替换HEAD版本;然后下一个git commit将保存索引版本。

在冲突合并期间,文件F存在冲突,索引会有最多三个条目,而不是通常的一个。这些条目将放置在1、2和3号槽中(0号槽保留用于正常、无冲突的条目)。第1号槽用于合并基础版本。第2号槽用于--ours,第3号槽用于--theirs,您可以直接使用这些名称来表示第2和第3号槽,但是第1号槽没有名称。

当发生合并冲突时:

  • 我们和他们相对于基础版本修改了同一行或几行代码(这是修改/修改冲突),或者
  • 没有基础版本,只有我们和他们(这是创建/创建冲突),或者
  • 我们删除了文件,他们更改了某些内容,即使只是文件名(这是删除/修改或删除/重命名冲突),或者
  • 他们删除了文件,我们更改了某些内容:这也是修改/删除或重命名/删除冲突,但是交换了伙伴关系。
对于修改/修改冲突,三个插槽都被填充了。对于另外三种类型的冲突,一个插槽为空:合并基础插槽为空(创建/创建),或者--ours为空(删除/X),或者--theirs为空(X/删除)。
--ours插槽为空时,git checkout --ours步骤将失败。当--ours插槽非空时,它会成功地将--ours版本提取到工作目录中。
Git在任何删除/X或X/删除冲突上的默认操作是保留工作目录中幸存的任一版本。也就是说,如果插槽3(theirs)为空,则工作目录文件与插槽2条目匹配,但如果插槽2(ours)为空,则工作目录文件与插槽3条目匹配。
您可以通过扫描空的“插槽2”并在此情况下使用git rm来处理此问题:
git ls-files --stage | fancy-script-or-program

如果你将其编写为Python程序,可以使用git ls-files -z --stage使其易于机器解析。你甚至可以完全停止使用git checkout --ours,停止依赖shell或Git globbing,并在脚本中编写用于解决pom.xml文件的规则。从根本上讲,你可以阅读整个索引,查找基本名称(最终/之后的所有内容)与pom.xml匹配的文件:
如果存在一个零阶段条目,Git认为它已经正确地解决了文件。请将哈希ID与HEAD提交中的哈希ID进行比较,因为Git实际上可能并没有正确地解决文件;在这种情况下,请使用HEAD提交中的哈希ID替换索引blob哈希。有关详细信息,请参见git update-index文档。您应该能够使用--cacheinfo,尽管我没有测试过未合并的索引条目。
否则,存在1、2或3个阶段的条目。如果存在第2阶段条目,请将其用作解决方案,即像上面那样将其提供给git update-index。如果没有第2阶段条目,请使用git update-index删除条目(使用0作为模式,使用任何内容,包括全零哈希,作为哈希;如果模式为0,则哈希无关紧要)。
一旦您完成了所有pom.xml路径的操作,任何剩余的非零阶段索引条目都表示合并冲突,您应将其传递回给用户。否则,您可能已经准备好提交。
(快速扫描http://gitpython.readthedocs.io/en/stable/reference.html#module-git.index.base表明这在GitPython中可以相当容易地完成,但我没有使用它的经验。)
最后的警告:我完全没有Maven的经验,但我知道pom.xml文件是控制各种东西的XML文件,而Git合并效果不佳(这对几乎所有XML文件都是正确的)。我并不清楚仅使用“我们”的版本是否正确。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接