繁体   English   中英

将 mergetool 设置为仅打开某些扩展名并为其他人使用本地文件

[英]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 --cachedgit 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 会执行以下操作:

  • Git 将基本版本放入此文件名的“暂存槽 1”中的索引中;
  • Git 将--ours版本放入此文件名的“插槽 2”中的索引中;
  • Git 将--theirs版本放入此文件名的“插槽 3”中的索引中。

结果是索引现在有三个文件,分别命名为README.txtfile.pyimage.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 checkoutgit restore的一些特殊怪异之处。


1从技术上讲,索引中的内容实际上是一个 blob hash ID:索引包含预压缩、预去重的文件“副本”,如果它们与任何现有提交的文件完全相同,则不占用空间。 但是您可以将它们视为文件的实际副本:除了不使用磁盘空间外,它们的行为类似于文件副本。

2请注意,对于git cherry-pick ,我们和他们的名称仍然有意义。 合并基础版本是“他们的”提交的父级。 由于git rebase通过反复挑选工作,这也是有道理的,除了git rebase检查“他们的”分支提示提交开始。 然后它在每个“我们的”提交上使用git cherry-pick 这导致我们和他们的关系发生变化。 不过,这是一个单独讨论的问题。


Mergetool 通过读取索引来工作

在运行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.jpgfoo.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 checkoutgit 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 restoregit checkout更灵活,因为它可以单独写入 Git 的索引您的工作树或两者 如果从提交中提取,旧的、更令人困惑的git checkout命令写入 Git 的索引,并且总是写入你的工作树。

最后,如果您想将文件“取消标记”为已解决——即,将其返回到其未解决的git checkoutgit restore都有一个选项-m来执行此操作。 请注意,如果您处于“合并模式”,它会破坏您所做的所有工作树工作,并且-m意味着与git checkout不同的东西(对我来说,这是避免使用旧的git checkout命令的另一个原因,使用git switchgit restore )。 同样,我不会在这里介绍任何细节,因为这已经够长了。


3 git mergetool究竟何时以及是否运行此git add有点棘手,因为 Git 并不真正知道您的合并工具是否已完成合并。 您可以配置几个旋钮来告诉git mergetool如何解释合并工具的结果。 但是,如果您使用的是已知的合并工具,该工具已经正确配置,那么这一切都是不可见的。 当你想使用自己的合并工具时,你必须知道如何配置它。 我们不会在这里介绍这些细节。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM