git 过滤器分支和 git 属性

git filter-branch and gitattributes

我想使用 git filter-branch 将企业 git 存储库历史中的 Windows/mixed 行结尾转换为 Unix 行结尾。由于存储库包含一些特定于域的二进制文件类型,.gitattributes 文件非常详细,因此我宁愿使用 git 自己的 EOL 转换机制,而不是 dos2unix 脚本,如 here.

我设法使用我在 https://github.com/cnaj/demo-crlf-rewrite/tree/so-question 中描述的过程进行转换,即做一个 tree-filter 添加 .gitattributes 和然后执行 git reset。如果没有重置,历史将保持不变。但是,显然索引在过滤操作期间仍处于 HEAD 修订版,因此仅识别相对于 HEAD 更改的文件,并且仅根据 .gitattributes 转换这些文件(因此 "NUKE"在演示中提交)。

我的问题:

  1. 此程序使用安全吗,还是只是未记录(或误解)行为的意外结果?
  2. 树过滤器操作期间git的索引是什么?可以在 tree-filter 中使用更改索引的命令(我没有找到关于此主题的明确文档)吗?
  3. 额外问题:究竟是什么导致 git 在此设置中选择行结束转换? (我很难理解发生了什么......)

这个:

However, apparently the index is still at the HEAD revision during the filter operations, so only files that are changed with respect to HEAD are recognized ...

大错特错,可惜真相很复杂。

让我们从基本事实开始,即 git filter-branch 通过 复制 每个过滤的提交(和注释标签,如果使用 --tag-name-filter)。如果生成的副本与原始提交逐位相同,则副本最终具有相同的哈希 ID,因此 原始的,在非常真实的意义上。否则它是一个新的提交并且是原始提交的替换。

Git 的索引扮演着几个不同的角色,尽管主要角色是 "to build the next commit"。因此,索引确实发生了变化,因为 git filter-branch 遍历每个原始提交以复制它。然而...

namely to do a tree-filter that adds the .gitattributes and then performs a git reset.

...这里有问题。如果您查看 the filter-branch code,您会看到它 运行s git update-index 在 运行 树过滤器的末尾。它不会 运行 git add,因此它取决于早期 git diff-indexgit ls-files 命令的输出。所有这些都在覆盖正常工作树的临时目录中完成。

这里我们运行进入我个人的边缘Git-fu :-)因为git add之类调用属性代码(在松鼠路径中,通过函数 unpack_trees,直接从 builtin/reset.c 调用,从 diff_cache 间接调用,从 run_diff_indexindex_differs_from 调用(对于其他各种调用)以尊重.gitattributes 工作树中的文件。然而,git update-index 似乎没有(这是基于您自己的观察,您的属性未被应用)。

幸运的是,有一个可能的解决方法。如果您明确地 git add .gitattributes 自己(而不是 运行ning git reset 或另一个 git checkout-index,如果有必要,则应该将新文件放入索引中。然后,如果 git update-index 使用索引版本而不是临时树文件(根据来源似乎很可能),它将使用您打算使用的版本。

最后一个:

What exactly causes git to pick up the line ending conversions in this setting?

这没有很好的记录,这里的代码路径甚至更复杂。

基本上,所有过滤器,包括文本/CRLF 转换,都应用于两点:将文件 "out" 从索引移动到工作树时,或者将文件 "in" 从索引的工作树。

索引 ,但是,包含一组 stat 数据和标志,因此 Git 甚至只会查看(更不用说复制in or out) 树中的文件可能与索引版本中的文件不同。

索引中有一个单独的标志来标记文件"dirty"。当过滤器在结帐期间修改文件时,会设置此标志。由于 initial 结帐中不存在 .gitattributes,因此不会在此处设置脏标志。 (但是这个标志的存在使得任何 的文件被过滤,使用起来要慢得多。因此使用很多属性会使 Git 慢得多,通过击败聪明的索引缓存。)

根据我收到后的经验:

Is this procedure safe to use, or is it just an accidental outcome of undocumented (or misunderstood) behavior?

在区分大小写的 POSIX 兼容文件系统上使用此过程应该是安全的。但是,它可以变得更简单(参见 https://github.com/cnaj/demo-crlf-rewrite)。

What is git's index during the tree-filter operation? Can commands that alter the index be used in tree-filter (I didn't find explicit documentation on this topic)?

是的。在每一步中,tree-filter 都会将索引设置为当前修订的内容并将其检出到一个临时目录中。过滤器为运行后,索引与工作副本的差异被提升到索引中,然后成为新的修订。

Bonus question: What exactly causes git to pick up the line ending conversions in this setting? (I'm having a hard time understanding what's going on...)

虽然 filter-branch 为每个处理的修订设置索引,HEAD 停留在 filter-branch 所在的原始修订开始了。这样,git reset HEAD 命令具有从索引中删除每个文件的效果。之后所有文件都被视为已更改并相应更新索引,在此过程中触发EOL转换。