2

我正在尝试拥有一个特定于每个分支的文件。我不希望此文件在合并时被覆盖或更新。为什么这不起作用?

(我的尝试是基于如何防止跟踪的配置文件被 git 中的合并更改?但由于某种原因它不起作用。我还关注了该答案所基于的更详细的博客文章,但它也没有行为如博客文章中所述。因此,这似乎是 git 版本问题。我使用的是 2.7.4)

git init
echo "master">config
echo "config merge=alwaysours">.gitattributes
echo ".gitattributes merge=alwaysours">>.gitattributes
git config --local merge.alwaysours.driver true
git add -A
git commit -m 'Master'
git checkout -b feature
echo "feature">config
touch feature
echo ".gitattributes merge=alwaysours">.gitattributes
git add -A
git commit -m 'Add feature'
git checkout master
git merge feature 
cat config # PRINTS OUT feature INSTEAD OF master
4

1 回答 1

2

由于我对特定文件的 Git 合并策略的回答中概述的原因,整个合并驱动程序的想法注定要失败,具体取决于 rebase / merge1 有一种不同的可能性可以发挥作用,但它很难看。事实上,最后,它非常丑陋,而且可能仍然是个坏主意。相反,您最好的选择可能是使用 Git 钩子(post-checkout特别post-merge是 )来操作工作树中一些未跟踪和被忽略的文件。

(但是请注意,由于我不知道您真正想在文件中包含什么,因此我什至没有一个好的起点来提出这些作为解决方案。)

讨论

值得记住的是,在我们开始考虑拥有一个内容经过特殊处理的文件之前,Git是如何处理文件的。在 Git 中,文件并不是那么重要。在 Git 中重要的是commit。提交存储文件,因此文件随之而来,但提交本身才是关键——提交存储文件的方式有点特殊,这点开始变得重要。

提交存储文件的方式是构建然后引用一个对象。树对象本质上是 <mode, name, hash> 元组的列表:

$ git ls-tree HEAD
[lots of snippages here]
100644 blob acf853e0299463a12212e9ed5f35d7f4a9d289af    .gitattributes
040000 tree 7ba15927519648dbc42b15e61739cbf5aeebf48b    .github
100644 blob 0d77ea5894274c43c4b348c8b52b8e665a1a339e    .gitignore
...
100755 blob 54cbfecc5ab0531513ff9e069be55d74339ad427    git-bisect.sh
100644 blob 09b0102cae8c8c0e39dc239003ca599a896730cf    git-compat-util.h
100755 blob d13f02da95f3b9b3921c3ccff9e3b6a7511cd666    git-cvsexportcommit.perl
...
100644 blob 2d41fffd4c618b5d7b816146d9df684b195535e3    xdiff-interface.h
040000 tree 77abde3699bc6874e10f1c17f4b97c219492542f    xdiff
100644 blob d594cba3fc9d82d94b9277e886f2bee265e552f6    zlib.c

这里中间的字符串(blobor tree)来源于前面的模式:100644or100755是一个blob,040000是一棵树,还有一堆不常见的特殊情况。

文件并未完全存储在 Git 中。相反,文件的内容出现在blob 对象中列出的哈希 ID 处。我们可以直接看到那个 blob 对象:

$ git cat-file -p 54cbfecc5ab0531513ff9e069be55d74339ad427
#!/bin/sh

USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|view|replay|log|run]'
LONG_USAGE='git bisect help
    print this long help message.
... [lots more, snipped]

git cat-file -p命令提取对象,从 Git 的冻结内部压缩格式中取出并将其转换为可读文本。所以blob 对象具有git bisectshell 脚本的内容,而树对象告诉 Git 在这个特定的提交中,blob 对象应该扩展为有用的文本形式,在工作树中,名称为git-bisect.sh.

正是这种扩展为有用的文本表单过程,我们可以让一些有趣的事情发生。我们可以使用.gitattributes 过滤器驱动程序而不是合并驱动程序来做到这一点。在我们希望使用它的关键情况下,不使用合并驱动程序。将文件提取到工作树中时始终使用过滤器驱动程序。


1如果您通读链接问题的答案并推理出发生了什么,您会发现 Git 有可能使这种方法发挥作用,也许是通过具有另一个每个文件的属性,例如always-merge. 但至少今天 Git 没有这个。


过滤驱动程序

过滤器驱动有两种形式,Git 称之为smudge filtersclean filters。它们在工作树之间的接口上运行,您的文件具有对您和计算机有用的格式,以及索引,这是 Git 存储文件名和压缩的、准备好的哈希 ID 的地方-to-go 该文件的快照(始终为下一次提交做好准备,但最初与当前提交相同)。

涂抹过滤器的目的是获取已解压缩但尚未准备好使用的文件文本,并将其转换为准备使用的工作树形式。干净过滤器的目的是采用文件的工作树形式并删除任何特定于工作树的数据,以便文件准备好压缩为仅 Git 的内部形式。该git checkout命令以及其他一些可以将冻结的仅限 Git 对象的命令使用涂抹过滤器。该git add命令使用清洁过滤器去除污迹过滤器放入的“脏东西”。

所以,现在我们可以看到如何使某个文件的工作树副本依赖于当前分支:我们只需编写一个涂抹过滤器来执行此操作。我们可能还应该编写一个干净的过滤器来取出特定于分支的内容,这将使 Git 更好地压缩文件,但我将把它留给你。

要定义污迹过滤器,我们需要一些.gitconfig.git/config配置文件中的条目。例如,如果我们想通过某种漂亮的打印过滤器运行源代码:

[filter "pretty-printer-for-XYZ-language"]
    smudge = xyz prettyprint --stdin

(假设漂亮打印源文件的命令是xyz prettyprint并且它需要--stdin从标准输入中读取)。然后我们通过 告诉 Git.gitattributes将此过滤器应用于*.xyz文件:

*.xyz  filter=pretty-printer-for-XYZ-language

过滤器只需要读取标准输入并写入标准输出:Git 安排过滤器的标准输入来自未压缩但“干净”的文件内容,因为它出现在 blob 对象中,过滤器的标准输出将转到最后的临时文件这个过程,成为工作树中的适当文件。

例如,如果somefile.xyz树对象中有一些 blob 哈希,Git 将读取 blob,将内容写入过滤器的标准输入,读取过滤器的标准输出,并将这些内容写入somefile.xyz. 不过,这里有一些重要的事情需要意识到:

  1. 过滤器无法直接访问名称somefile.xyz。您可以通过指令告诉 Git生成名称作为参数%f,但请记住,过滤器仍必须读取标准输入并写入标准输出。(如果您将过滤器重写为“长时间运行的过滤器进程”以提高效率,则过滤器必须遵守文档中描述的数据包协议,该协议还提供了文件的路径名。)
  2. 涂抹过滤器在更新之前 运行。git checkoutHEAD 与第 1 点一样,涂抹过滤器无法直接访问正在发生的事情:例如,它们不知道 Git 位于 a 的中间git checkout otherbranch

这里的第 2 点用粗体表示,因为它是这里最大的绊脚石。可以使用当前进程树来查找调用过滤器的 Git 命令,并使用任何操作系统工具来查找命令行参数。如果 Git 在启动此类过滤器之前设置环境变量以指示正在发生的事情,这将非常有帮助:此过滤器是代表切换到新分支操作运行,还是由于git checkout -- path/to/filegit checkout --ours -- path/to/file索引提取而运行,例如? 但是,唉,Git 也不这样做。

于 2018-12-17T23:20:07.677 回答