[英]Set mergetool to only open certain extensions and use local files for others
我正在尝试合并两个具有.py
和.png
文件的分支。 有没有办法将所有.png
文件设置为本地文件,并且只使用 mergetool 打开.py
文件? 我已经执行了git checkout --ours
/ --theirs
以选择要保存的正确.png
文件,但每次我打开合并工具时,这些文件都会不断弹出。 我在这里错过了什么?
git mergetool
命令默认在未解析的文件上运行,所以最简单的方法是首先将.png
文件标记为“正确解析”。 有关详细信息,请参阅下面较长的“详细信息”部分中的注释。
也就是说,您还可以提供git mergetool
的路径名,这样您就可以准确列出那些未解析的.py
文件。 这只是 shell 脚本的一个小问题:
git ls-files --unmerged
生成此类文件的列表(唉,还有所有--stage
数据),然后您可以使用例如awk
进行过滤:
git ls-files --unmerged | awk '$4 ~ /\.py$/ { print $4 } ' | uniq
(绝对可以将uniq
放入awk
代码中,但仅运行uniq
更简单)。 验证这会生成正确的文件列表; 然后将 shell 扩展到位就很简单了:
git mergetool $(git ls-files --unmerged | awk '$4 ~ /\.py$/ { print $4 } ' | uniq)
当您运行git merge
— 或者实际上是使用 Git 合并引擎的各种命令中的任何一个,例如git cherry-pick
— 并且存在冲突时,Git 会在 Git 调用的索引或暂存区中留下额外的信息。 这是同一事物的两个名称,还有第三个名称,现在基本上已过时,但它仍然出现在标志中:缓存,如git rm --cached
或git diff --cached
。 (一些命令,包括git diff
,接受--staged
作为同义词,但git rm
仍然没有 Git 2.39。)
通常——当不在合并冲突的中间时——Git 的索引又名暂存区包含每个文件的完整副本,如果你现在运行 `git commit ,它将具有的形式。 1但合并涉及读取每个文件的三个版本:
--ours
版本是我们开始git merge
时“我们”提交的版本; 和--theirs
版本是我们开始git merge
时“他们”在“他们的”提交中的版本。 2个Git 首先查看所有这三个版本。 如果三个都匹配,没有问题,合并结果是三个版本中的任何一个。 如果两个匹配而一个不同,则再次没有问题。 当三个都不同时,通常会出现问题。 (也可能存在一些其他问题,例如,当“双方”都创建了一个新的但不同的同名文件时,但为了简单起见,我们将忽略这些情况。)
当三个文件都不同时,Git 确实需要合并一些工作。 对于纯文本文件,Git 对基本版本与每个分支提示版本进行逐行比较。 这导致两个不同的差异。 Git 然后尝试组合两个差异,将两组更改应用到基本版本。
如果一切顺利——如果 Git 认为它成功地将“我们的更改”与“他们的更改”组合在一起,换句话说——Git 会将生成的合并文件视为正确的合并结果,并将该文件写入你的工作树,以便你可以看到它,以及它自己的索引,准备提交。 当事情 go 不太好时,您会遇到合并冲突。
同样,请记住 Git 的索引/暂存区通常包含每个文件的副本。 因此,如果 Git 能够将两组更改合并到README.txt
,Git 会将合并后的README.txt
版本放入其索引(准备提交)和您的工作树(以便您可以看到Git 做了)。 索引副本处于 Git 所说的“零阶段”。 但如果合并出错,Git 会执行以下操作:
--ours
版本放入此文件名的“插槽 2”中的索引中; 和--theirs
版本放入此文件名的“插槽 3”中的索引中。 结果是索引现在有三个文件,分别命名为README.txt
或file.py
或image.jpg
。
如果文件是文本文件并且 Git尝试合并,则 Git 会在文件名( README.txt
或其他名称)下将其最佳合并尝试和冲突标记放入您的工作树中。 如果文件不是文本,例如image.jpg
, Git 会在您的工作树中留下一些版本。 Git 没有放入冲突标记,因为二进制文件首先没有“行”。
在所有这些情况下,您的工作就是解决冲突。 为此,您可以挑选出正确的合并结果并将其填充到 Git 索引的“槽零”处,同时擦除三个冲突的版本。 例如,对于file.py
类的文件,您可以在编辑器中打开工作树副本,然后对其进行编辑并手动解决冲突。 然后你可以写出更新后的file.py
并运行:
git add file.py
这告诉 Git:擦除三个非零槽条目,并将file.py
的工作树版本复制到槽零处的索引中。 该文件现在已“解决”。
Git 允许合并完成——也就是说,你将运行git merge --continue
或者简单地运行git commit
commit——一旦 Git 索引中的所有文件都回到“槽零”。 在那之前,任何非零槽号的文件都是“未解析的”。 在大多数情况下,如果“slot 1”中有文件,则“slot 2”和“slot 3”中也会有同名文件。 (这不是真的情况是我们在这里没有涉及的情况。)
它是git add
命令,或者——如果你想删除文件git rm
命令解析文件,通过擦除非零槽并将正确的待提交文件写入 Git 索引的槽零。 在你git commit
之前,你可以用另一个(也是 slot-zero)文件覆盖slot-zero 文件,所以你可以在解析时做第一遍,然后git add
然后测试,如果你愿意:没有必要保留文件未解决。 但是有些人确实喜欢让它们直到最后才解决,所以如果你是这些人中的一员,请记住它是如何工作的:插槽 0 中的任何内容都是提交的,而git commit
不会让你提交直到一切Git 索引中的那个在槽零处。
暂存区的意义部分是为了允许这些非零槽,部分是为了让您改变对槽零中的文件的看法。 您使用git add
将内容复制到 Git 的索引中(始终位于零槽),它们现在已准备好提交,但它们只是坐在那里,准备就绪,直到您实际运行git commit
。 如果你git add
,你将一些文件替换为较新的版本; 如果您在git commit
之前执行此操作,则您替换的版本永远不会提交。
考虑到这一点,我们现在可以查看git mergetool
,以及git checkout
和git restore
的一些特殊怪异之处。
1从技术上讲,索引中的内容实际上是一个 blob hash ID:索引包含预压缩、预去重的文件“副本”,如果它们与任何现有提交的文件完全相同,则不占用空间。 但是您可以将它们视为文件的实际副本:除了不使用磁盘空间外,它们的行为类似于文件副本。
2请注意,对于git cherry-pick
,我们和他们的名称仍然有意义。 合并基础版本是“他们的”提交的父级。 由于git rebase
通过反复挑选工作,这也是有道理的,除了git rebase
从检查“他们的”分支提示提交开始。 然后它在每个“我们的”提交上使用git cherry-pick
。 这导致我们和他们的关系发生变化。 不过,这是一个单独讨论的问题。
在运行git merge
后运行git mergetool
并得到合并冲突。 git mergetool
所做的是在Git 的索引中查找那些非零暂存槽条目。 暂存号非零的文件是未解析的。
如果您运行git mergetool
并解析一些文件,和/或使用git add
解析一些文件,然后中断git mergetool
运行并启动另一个git mergetool
运行,Git 重新开始,列出未合并的文件。 如果该文件列表现在变小了,那么只有这些文件还没有合并。
因此,如果您有一些可以解析的*.jpg
文件,您可以先这样做:
git checkout --ours foo.jpg
git checkout --theirs bar.jpg
git add foo.jpg bar.jpg # these two files are now resolved
此时运行git mergetool
将不会尝试合并bar.jpg
和foo.jpg
,因为它们不再是未解析的。
当git mergetool
启动您的实际合并工具(无论可能是什么)时,您应该解决该合并工具中的冲突,然后退出合并工具以告诉git mergetool
它已完成。 然后git mergetool
命令将在该文件上为您运行git add
。 3这就是git mergetool
稍后可以在您中断后从您离开的地方继续执行的方式。
这给我们带来了一些奇怪的git checkout
,其中一些在git restore
中被清理。 checkout 和 restore 命令有标志:
--ours
表示从插槽 2 获取文件。--theirs
表示从插槽 3 获取文件。--base
来表示slot 1 ,但它不在那里。 这些选项告诉git checkout
和git restore
从给定的槽中读取索引副本,并将其写入该文件的工作树副本。 这些选项对索引本身没有任何作用,因此文件仍未解析。
但是,您也可以运行git checkout commit -- path
。 此选项告诉git checkout
到达指定的commit
并修改指定path
的已提交副本,而git checkout
通过首先将文件写入索引的零槽来执行此操作。 此操作会删除插槽 1、2 和 3。因此,这种git checkout
出标记文件已解决!
使用git restore
,您可以做同样的事情,但git restore
写入的位置由--worktree
(或-W
)和--staged
(或-S
)标志指定。 所以:
git restore -SW -s HEAD -- path/to/file
告诉git restore
提取文件的 ( HEAD
) 版本并将其写入索引 ( -S
) 和您的工作树 ( -W
)。 所以这也解析了文件,比如git checkout HEAD -- path/to/file
会。 省略-S
意味着git restore
不会将文件标记为已解决。
您可能想知道为什么这一切都如此复杂。 部分答案是“因为 Git 只是随着时间的推移而增长”,也就是说,这实际上并不是计划好的,只是错误地发生了。 它也部分是“因为 Git 命令试图成为灵活的工具”:特别是git restore
比git checkout
更灵活,因为它可以单独写入 Git 的索引或您的工作树或两者。 如果从提交中提取,旧的、更令人困惑的git checkout
命令写入 Git 的索引,并且总是写入你的工作树。
最后,如果您想将文件“取消标记”为已解决——即,将其返回到其未解决的git checkout
和git restore
都有一个选项-m
来执行此操作。 请注意,如果您未处于“合并模式”,它会破坏您所做的所有工作树工作,并且-m
意味着与git checkout
不同的东西(对我来说,这是避免使用旧的git checkout
命令的另一个原因,使用git switch
和git restore
)。 同样,我不会在这里介绍任何细节,因为这已经够长了。
3 git mergetool
究竟何时以及是否运行此git add
有点棘手,因为 Git 并不真正知道您的合并工具是否已完成合并。 您可以配置几个旋钮来告诉git mergetool
如何解释合并工具的结果。 但是,如果您使用的是已知的合并工具,该工具已经正确配置,那么这一切都是不可见的。 当你想使用自己的合并工具时,你必须知道如何配置它。 我们不会在这里介绍这些细节。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.