Git 2.24(2019 年第四季度)使 git clean 在嵌套 Git 存储库(不仅仅是文件夹)方面更加强大
请参阅commit 69f272b(2019 年 10 月 1 日)和 commit 902b90c、commit ca8b539、commit 09487f2、commit e86bbcf、commit 3aca580、commit 29b577b、commit 89a1f4a、commit a3d89d8、@987 commit a5e916c、commit bbbb6b0、commit 7541cc5(2019 年 9 月 17 日)Elijah Newren (newren)。
(由 Junio C Hamano -- gitster -- 合并于 commit aafb754,2019 年 10 月 11 日)
clean:避免删除嵌套 Git 存储库中未跟踪的文件
用户希望嵌套 git 存储库中的文件被单独放置,除非被强制(有两个 -f's)。
不幸的是,在某些情况下,git 会删除嵌套存储库中跟踪的(可能是脏的)文件和未跟踪的文件。
为了解释这是如何发生的,让我们对比几个案例。
首先,采用以下示例设置(假设我们已经在一个 git repo 中):
git init nested
cd nested
>tracked
git add tracked
git commit -m init
>untracked
cd ..
在此设置中,一切都按预期工作;运行 'git clean -fd' 将导致 fill_directory() 返回以下路径:
nested/
nested/tracked
nested/untracked
然后correct_untracked_entries() 会注意到这可以压缩为:
nested/
然后由于“nested/”是一个目录,我们将调用remove_dirs("nested/", ...),它会检查is_nonbare_repository_dir(),然后决定跳过它。
但是,如果有人也创建了一个被忽略的文件:
>nested/ignored
然后运行 'git clean -fd' 将导致 fill_directory() 返回
相同的路径:
nested/
nested/tracked
nested/untracked
但correct_untracked_entries() 会注意到我们忽略了条目
在嵌套/下,因此将此列表简化为
nested/tracked
nested/untracked
由于这些不是目录,因此我们不使用 call remove_dirs(),这是唯一进行了 is_nonbare_repository_dir() 安全检查的地方 - 导致我们同时删除了未跟踪的文件和跟踪的(可能是脏的)文件。
解决此问题的一个可能方法是遍历每个路径的父目录并检查它们是否代表非裸存储库,但这会很浪费。
即使我们添加了某种类型的缓存,它仍然是一种浪费,因为我们应该能够在首先进入它之前检查“嵌套/”是否代表一个非裸存储库。
将DIR_SKIP_NESTED_GIT 标志添加到dir_struct.flags 并使用它来防止fill_directory() 和朋友下降到嵌套的git repos。
通过此更改,我们还修改了 commit 91479b9 中添加的两个回归测试(“t7300:添加测试以记录干净和嵌套 git 的行为”,2015-06-15,Git v2.6.0-rc0)。
那个提交,它的系列,以及邮件列表中该系列的六次迭代都讨论了为什么这些测试编码了他们所做的期望。
事实上,他们的目的似乎只是为了测试现有行为,以确保性能变化不会改变行为。
然而,这两个测试直接与手册页中关于删除嵌套 git 存储库下的文件/目录需要两个 -f 的说法相矛盾。
虽然有人可能会争辩说用户给出了一个明确的路径来匹配嵌套存储库中的文件/目录,但是一旦你沿着这条路线走下去,用户就很难理解(例如,如果他们指定了“@987654386”呢? @"?)
也很难解释确切的行为是什么;通过使其变得非常简单来避免此类问题。
最后,还有几个错误,-ffd 清理得不够充分(例如缺少嵌套的.git)和-ffdX 可能清理了错误的文件(注意外部.gitignore 而不是内部)。
此补丁根本没有解决这些情况(并且不会更改与这些标志相关的行为),它仅在给定单个 -f 时修复处理。
有关-ffd[X?] 错误的更多讨论,请参阅this thread。
在 Git 2.25.1(2020 年 2 月)中,“git clean”中的一个极端情况错误已得到纠正,该错误源于(出于性能原因)目录枚举 API 中的尴尬调用约定。
见commit 0cbb605、commit ad6f215(2020 年 1 月 16 日)Jeff King (peff)。
请参阅commit 2270533(2020 年 1 月 16 日)Elijah Newren (newren)。
请参阅 Derrick Stolee (derrickstolee) 的 commit f365bf4(2020 年 1 月 16 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 7ab963e,2020 年 2 月 5 日)
dir: treat_leading_path() 和 read_directory_recursive(),第 2 轮
签字人:Elijah Newren
我打算将这个标题命名为“dir:treat_leading_path() 和 read_directory_recursive() 的更多同步”,向 commit 777b42034764 致敬(“dir:同步 treat_leading_path() 和 read_directory_recursive()”,2019- 12-19,Git v2.25.0-rc0 -- merge),但是标题太长了。
不管怎样,首先是背景故事……
fill_directory() 总是有一个稍微容易出错的接口:它返回可能匹配指定路径规范的路径子集;它旨在修剪掉一些与指定路径规范不匹配的路径,并至少保留所有匹配它的路径。
给定这个接口,调用者负责对结果进行后处理并检查每个结果是否真正匹配路径规范。
builtin/clean.c 做到了这一点。
它将首先删除重复项(例如,如果返回“dir”以及“dir/”下的所有文件,那么它将简化为仅“dir”),然后在删除重复项之后会将剩余路径与指定的路径规范进行比较。
不过,此后处理本身可能会遇到问题,如 commit 404ebceda01c 中所述(“dir:还检查目录是否匹配路径规范”,2019-09-17,Git v2.24.0-rc0 -- @ 987654353@列在batch #8):
对于git clean 和一组“dir/file”和“more”的路径规范,这会导致问题,因为我们最终会得到两个目录条目:“dir”和"dir/file"
然后correct_untracked_entries() 会尝试通过删除“dir/file”来帮助我们修剪重复项,因为它位于“dir”下,而我们只剩下“dir”。
由于原始路径规范只有“dir/file”,因此剩下的唯一条目不匹配并且没有任何内容可以删除。
(请注意,如果只指定了一个路径规范,例如只指定了“dir/file”,那么fill_directory 中的common_prefix_len optimizations 将使我们绕过这个问题,使其出现在我们可以正确删除手动指定的路径规范的简单测试中。 )
该提交通过确保fill_directory() 不会在common_prefix_len 优化路径之外返回“dir”和“dir/file”,解决了这个问题——当指定多个路径规范时。
这就是开始变得有趣的地方。
在commit b9670c1f5e6b ("dir: fix checks on common prefix directory", 2019-12-19, Git v2.25.0-rc0 -- merge) 中,我们注意到common_prefix_len 不是进行适当的检查并让各种东西通过,导致递归到 .git/ 目录和其他疯狂。
因此它开始锁定并检查该代码路径中的路径名。
继续commit 777b42034764(“dir:同步treat_leading_path()和read_directory_recursive()”,2019-12-19,Git v2.25.0-rc0 -- merge),其中指出以下内容:
当所有路径规范都有一个共同的前导目录时,我们避免调用read_directory_recursive() 的优化意味着我们需要匹配read_directory_recursive() 将使用的逻辑,如果我们刚刚从根调用它。
由于它不仅仅是调用treat_path(),我们需要复制相同的逻辑。
...然后它用这个极具讽刺意味的声明更有力地解决了这个问题:
需要复制这样的逻辑意味着可以保证有人最终需要进行进一步的更改而忘记更新这两个位置。
很想只对 leading_directory 特殊外壳进行核对以避免此类错误并简化代码,但 unpack_trees' verify_clean_subdirectory() 也调用 read_directory() 并且使用非空前导路径这样做,所以我很犹豫尝试进一步重组。
向treat_leading_path() 和read_directory_recursive() 添加令人讨厌的警告,以尝试警告人们此类问题。
您会认为,如果使用如此措辞强硬的描述,其作者实际上会确保 treat_leading_path() 和 read_directory_recursive() 中的逻辑确实匹配,并且所需的一切都在在撰写本段时最少被复制。
但你错了,我因为遗漏了部分逻辑而搞砸了。