为什么我在git稀疏检出中排除的文件会不断重新出现?

13

我使用GCC git镜像,因为我只使用C和C++前端,所以我使用git的稀疏检出功能来排除我不需要的数百个文件:

$ git config core.sparseCheckout
true
$ cat .git/info/sparse-checkout 
/*
!gnattools/
!libada/
!libgfortran/
!libgo/
!libjava/
!libobjc/
!libquadmath/
!gcc/ada/
!gcc/fortran/
!gcc/go/
!gcc/java/
!gcc/objc/
!gcc/objcp/
!gcc/testsuite/ada/
!gcc/testsuite/gfortran.dg/
!gcc/testsuite/gfortran.fortran-torture/
!gcc/testsuite/gnat.dg/
!gcc/testsuite/go.dg/
!gcc/testsuite/go.go-torture/
!gcc/testsuite/go.test/
!gcc/testsuite/objc/
!gcc/testsuite/objc.dg/
!gcc/testsuite/obj-c++.dg/
!gcc/testsuite/objc-obj-c++-shared/

这个方法一段时间内有效,但是偶尔我会发现一些被排除的文件重新出现了,有时候数量还很多:

$ ls gnattools/
ChangeLog  configure  configure.ac  Makefile.in
$ ls  gcc/fortran/ | wc -l 
86

我不确定文件会在什么时候重新出现,因为我经常切换不同的分支(包括远程跟踪和本地分支),而且这是一个非常繁忙的版本库,所以经常有新的更改需要拉取。

作为一个相对新手的git用户,我不知道如何“重置”我的工作树来再次删除那些文件。

作为一个实验,我尝试禁用稀疏检出并进行拉取,想着之后可以再次启用稀疏检出来更新树,但是这个方法并不太有效:

$ git config core.sparseCheckout false
$ git config core.sparseCheckout 
false
$ git pull
remote: Counting objects: 276, done.
remote: Compressing objects: 100% (115/115), done.
remote: Total 117 (delta 98), reused 0 (delta 0)
Receiving objects: 100% (117/117), 64.05 KiB, done.
Resolving deltas: 100% (98/98), completed with 64 local objects.
From git://gcc.gnu.org/git/gcc
   7618909..0984ea0  gcc-4_5-branch -> origin/gcc-4_5-branch
   b96fd63..bb95412  gcc-4_6-branch -> origin/gcc-4_6-branch
   d2cdd74..2e8ef12  gcc-4_7-branch -> origin/gcc-4_7-branch
   c62ec2b..fd9cb2c  master     -> origin/master
   2e2713b..29daec8  melt-branch -> origin/melt-branch
   c62ec2b..fd9cb2c  trunk      -> origin/trunk
Updating c62ec2b..fd9cb2c
error: Your local changes to the following files would be overwritten by merge:
        gcc/fortran/ChangeLog
        gcc/fortran/iresolve.c
        libgfortran/ChangeLog
        libgfortran/io/intrinsics.c
Please, commit your changes or stash them before you can merge.
Aborting

看起来我有一些本地修改的文件,但这些文件我从未请求过,也没有碰过!

但是git status并没有显示这些更改:

$ git st
# On branch master
# Your branch is behind 'origin/master' by 9 commits, and can be fast-forwarded.
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       libstdc++-v3/53270.txt
#       libstdc++-v3/TODO

我尝试了git read-tree -m -u HEAD,但它并没有起作用。
所以我的问题是:
  • 为什么这些文件会重新出现?
  • 如何使它们再次消失?
  • 如何防止它们再次出现?
  • 这是否与我的.git/info/exclude文件中包含对应于sparse-checkout文件中应该被排除的目录中的文件(即以!命名)的引用有关?我按照指示忽略与SVN相同的文件

    $ git svn show-ignore >> .git/info/exclude

因此,我的exclude文件包括以下路径:
# /gcc/fortran/
/gcc/fortran/TAGS
/gcc/fortran/TAGS.sub
/gcc/fortran/gfortran.info*

这将位于文件中命名的一个目录之下:

!gcc/fortran/

我曾尝试使用测试存储库来复制这个问题,我克隆了几个副本并编辑每一个,创建/切换/删除分支并在它们之间合并更改,但在我的玩具测试中从未出现过错误。 GCC 存储库有点大(超过 2GB),而“故障”之间的时间(一两周左右)太长,无法期望人们精确地重现问题。我还没有尝试将相同的路径放在“sparse-checkout”和“exclude”中,因为今天我才想到可能会有冲突。
我几周前在 freenode 上的 #git 上询问了这个问题,并被告知 "这很可能是一个 bug,没有人使用稀疏检出",但我希望能得到更好的答案;-)
更新:
我最近一次看到的实际上发生了问题(即文件不存在,然后在单个命令之后出现了)是从源头上进行拉取时:
   bac6f1f..6c760a6  master     -> origin/master

这些更名变化如下所示:

 create mode 100644 libgo/go/crypto/x509/root.go
 rename libgo/go/crypto/{tls => x509}/root_darwin.go (90%)
 rename libgo/go/crypto/{tls => x509}/root_stub.go (51%)
 rename libgo/go/crypto/{tls => x509}/root_unix.go (76%)
 create mode 100644 libgo/go/crypto/x509/root_windows.go

在拉取之前,libgo目录不存在,这是期望的。 在拉取后,该目录存在,并且以下文件(而不是其他文件)位于其中:

$ ls libgo/go/crypto/x509/root_<TAB>
root_darwin.go  root_stub.go    root_unix.go    

我不知道重命名的文件是否丢失了skip-worktree标记,如何检查?

我相当确定问题并不总是在重命名时发生,因为例如上面示例中显示的libgfortran/ChangeLog文件不是新文件或最近重命名的。


1
这些文件可能是生成的吗?例如在某些配置或特定构建目标期间?这种情况经常发生在“ChangeLog”中。你尝试过删除它们并继续工作,但这一次无论你做什么,都要检查这些文件是否再次出现了吗?我猜测是 git 不能与它们一起工作,这就是为什么它在 git status 中也不显示它们的原因。 - Shahbaz
不,它们是被跟踪的文件,而不是生成的文件。GCC的ChangeLog文件不是生成的,它们是手动编辑和提交的。请查看我的新编辑,展示我观察到在运行命令后发生的问题的示例。 - Jonathan Wakely
你有没有尝试查看文件内容?有时候一些Makefile会通过touch命令来标记它们的构建过程,从而更新ChangeLog文件。如果你的gcc/fortran/ChangeLog文件是空的,这可能就是原因。此外,也有可能是某个人不小心将这些文件添加到了代码库中。 - Shahbaz
1
我总是在源代码目录的外面构建GCC,所以Makefile永远不会触及源代码树(这是有意设计的,这样你可以从只读介质构建源代码)。 - Jonathan Wakely
1
我在git 1.8.3.msysgit.0 (windows)上遇到了这个问题。它不是间歇性的 - 只有在某些情况下(不幸的是,只有在一个有很多文件的repo中)才会每次发生。此外,那些“留在原地”的文件具有不确定的git状态:它们既不是“未跟踪”的,也不会(当我删除或编辑它们时)显示为已被跟踪和修改。 - GreenAsJade
显示剩余5条评论
3个回答

4
跳过工作树位可以通过 git update-index --skip-worktree 修改。当你注意到文件存在时,你可以检查 git ls-files -v |grep ^S(S 表示标记为跳过工作树的文件)。
但是,正如 #git 的人所说,如果你看到奇怪的行为,那很可能是 git 中的一个 bug。毕竟,这是相当深奥的功能。你应该向 git 邮件列表报告你的发现。 编辑: 另外,如果你正在使用 git 1.7.7.6,我强烈建议升级。1.7.10 版本已经更新了很多,我认为它有很大的机会解决你的问题。

太好了,感谢这些命令。我现在将自动化检查不需要的文件,以便我可以准确地知道它们何时出现。如果没有更多关于如何重现它的信息,我怀疑错误报告不会引起太多注意,因此在报告之前我会继续调查。 - Jonathan Wakely
我最近使用git 1.7.10.5没有遇到任何问题,但由于它只是偶尔出现,所以我还不愿意说升级一定解决了这个问题。 - Jonathan Wakely
1
仍然没有问题,所以我认为最近版本中修复了一个错误。虽然现在已经太晚给你赏金了,但你可以得到我的第一个SO勾 - 谢谢! - Jonathan Wakely

1
在我的情况下,我正在使用稀疏检出对存储库执行一些单元测试。 我的一个测试用例创建了包含未包含在我的稀疏检出子树列表中的文件的提交。
当我尝试使用git reset --hard 123456时,我收到以下错误:
error: Entry 'a.c' not uptodate. Cannot update sparse checkout.
fatal: Could not reset index file to revision '123456'.

解决方案是通过重新应用稀疏检出规则来删除我的工作树中的文件:
git read-tree -mu HEAD

我遇到另一种情况,使用 git 版本 2.6.2 时,稀疏检出文件根本不起作用。 删除存储库后,克隆它,将完全相同的稀疏检出文件放回去,一切都正常。 我猜可能是我在 git 中遇到了一个错误。 - Jake88

1

请检查问题是否在最新的Git 2.13(2017年第二季度,5年后)中仍然存在。
任何skip-worktree文件都不应该在稀疏检出期间被修改或查看,因为:

preload-index代码已经学会不再处理未由“稀疏检出”检出的路径索引条目。

请参见提交e596acc(2017年2月10日)由Jeff Hostetler (jeffhostetler)
(由Junio C Hamano -- gitster --提交c7e234f中合并,2017年2月27日)

preload-index:避免对skip-worktree项进行lstat

教导preload-index避免对设置了skip-worktree位的索引条目进行lstat()调用。
这是一种性能优化。
在稀疏检出期间,对于未填充且因此不存在于工作树中的项目,会在其上设置skip-worktree位。
每个线程的preload-index循环尝试将工作树版本与索引进行比较并将它们标记为最新状态,这个补丁可以省略这个步骤。
在一个Windows 10系统上,针对一个非常大的仓库(450MB的索引)和不同级别的稀疏性,对于各种命令,采用{preloadindex=true, fscache=false}的情况下性能提高了80%,采用{preloadindex=true, fscache=true}的情况下性能提高了20%。
在 Git 2.27(2020 年第二季度)中,“sparse-checkout”以不同的方式管理跳过工作树。

请查看提交 5644ca2, 提交 681c637, 提交 ebb568b, 提交 22ab0b3, 提交 6271d77, 提交 1ac83f4, 提交 cd002c1, 提交 4ee5d50, 提交 f56f31a, 提交 7af7a25, 提交 30e89c1, 提交 3cc7c50, 提交 b0a5a12, 提交 72064ee, 提交 fa0bde4, 提交 d61633a, 提交 d7dc1e1, 提交 031ba55 (于2020年3月27日由Elijah Newren (newren)提交)
(由Junio C Hamano -- gitster --提交 48eee46中合并,于2020年4月29日)

unpack-trees: 无法设置 SKIP_WORKTREE 位始终只是警告

审核者:Derrick Stolee
签署者:Elijah Newren

设置和清除SKIP_WORKTREE位不仅在用户运行'sparse-checkout'时执行;其他命令如'checkout'也会通过unpack_trees()运行,该函数具有处理此特殊位的逻辑。因此,我们需要考虑它们如何处理特殊情况。
一些比较点应该有助于解释更改unpack_trees()如何处理这些位的基本原理:
- 现在忽略稀疏检出,如果您正在切换分支并且有脏更改,则只有当脏文件恰好是具有不同内容的路径之一时,才被视为错误,这将阻止分支切换成功。 - SKIP_WORKTREE一直被认为是建议性的;例如,如果rebase或merge需要或甚至希望将路径实现为其工作的一部分,则始终允许这样做,无论SKIP_WORKTREE设置如何。这已用于未合并的路径,但通常也用于不需要的路径,只是因为它使代码更简单。这是最佳努力考虑,当它实现与SKIP_WORKTREE设置相反的路径时,甚至不需要打印警告消息。
过去,如果您尝试运行例如'git checkout',并且:
1. 您有一个被实现并且具有一些脏更改的路径; 2. 该路径在$GITDIR/info/sparse-checkout中列出; 3. 此路径在当前分支和目标分支之间没有区别;
那么尽管上述比较点,无法设置SKIP_WORKTREE被视为硬性错误,将中止checkout操作。
这与其他地方处理SKIP_WORKTREE的方式完全不一致,并且对用户而言非常恼人,因为在工作副本中保留路径(带有简单警告)应该没有任何问题。
将任何无法切换SKIP_WORKTREE位的错误降级为警告,并允许操作继续进行。
所以这条消息不再是:

error: The following untracked working tree files would be overwritten by checkout:

但是:

warning: The following paths were already present and thus not updated despite sparse patterns:

在 Git 2.28(2020 年第三季度)中,"sparse-checkout" 在状态 "git clone --no-checkout" 下的行为在 2.27 中意外更改,已被纠正。

参见 提交 b5bfc08(由 Elijah Newren (newren) 于 2020 年 6 月 5 日提交)。
(由 Junio C Hamano -- gitster -- 合并于 提交 a554228,2020 年 6 月 18 日)

sparse-checkout: 避免将所有文件删除进行暂存

Signed-off-by: Elijah Newren

sparse-checkout 的目的是将工作树更新为反映受跟踪文件的子集。因此,它不应该切换分支、进行提交、下载或上传数据,也不应该暂存或取消暂存更改。除了更新工作树之外,sparse-checkout 唯一需要操作的就是索引中的 SKIP_WORKTREE 位。特别地,这设置了一个很好的不变式:运行 sparse-checkout 不会更改 git status 中任何文件的状态(反映了我们仅在文件安全删除时设置 SKIP_WORKTREE 位,即如果文件未修改,则文件是安全删除的)。传统上,我们在这个目标方面做得非常糟糕。sparse-checkout 的前身涉及手动编辑 .git/info/sparse-checkout 并运行 git read-tree -mu HEAD。那个命令会暂存和取消暂存更改,并覆盖工作树中的脏更改。sparse-checkout 命令的最初实现也不是更好;它只是作为子进程调用 git read-tree -mu HEAD,并具有相同的注意事项,尽管在合并该功能之前,这个问题在审查注释中反复出现,并且针对这些问题制定了解决方法 [1、2、3、4、5、6;特别是参见 4 和 6]。然而,这些解决方法除了在许多重要情况下禁用该功能之外,还错过了一个特殊情况。稍后我会回到它。在 2.27.0 周期中,通过最终替换内部等效于 git read-tree -mu HEAD 的函数来解除了该功能的禁用:unpack-trees.c 中的新 update_sparsity() 函数,它只更新索引中的 SKIP_WORKTREE 位,并更新工作树以匹配。这个新函数处理了旧实现中有问题的所有情况,除了它破坏了避免旧实现的解决方法的同一个特殊情况,但以不同的方式破坏了它。所以......这就是特殊情况:使用 --no-checkout 执行的 git clone。根据标志的含义,--no-checkout 不检出任何分支,意味着您没有在分支上,并且需要在克隆之后切换到某个分支。在实现上,HEAD 仍然设置(因此从某种意义上说,您部分地处于分支上),但是:索引是“未出生的”(不存在);工作树中没有文件(除了 .git/);下一次运行 git switch(或 git checkout)时,它将使用 initial_checkout 标志运行 unpack_trees。直到您运行,例如 git switch <somebranch>,索引才会被写入并填充工作树中的文件。对于这种特殊的 --no-checkout 情况,传统的 read-tree -mu HEAD 行为将执行相当于像 checkout 一样切换到默认分支(HEAD),写出与 HEAD 匹配的索引,并更新工作树以匹配。这个特殊情况在原始的 sparse-checkout 命令中通过避免进行更改检查而滑过去了,因此继续存在。在引入并使用 update_sparsity() 之后(参见 commit f56f31af03 ("sparse-checkout: use new update_sparsity() function", 2020-03-27, Git v2.27.0-rc0 -- merge listed in batch #5)),--no-checkout 情况的行为发

随着 Git 2.35(2022年第一季度)的推出, "git reset"(man) 的各种操作模式已经被改进,以更好地与稀疏索引配合使用。

请查看提交 f2a454e, 提交 4d1cfc1, 提交 20ec2d0, 提交 c01b1cb, 提交 291d77e (2021年11月29日), 提交 86609db, 提交 71471b2 (2021年10月27日), 和提交 1f86b7c,作者为Victoria Dye (vdye)
(由Junio C Hamano -- gitster --提交 f085087合并, 2021年12月10日)

sparse-index: 更新命令以进行展开/折叠测试

协助者: Derrick Stolee
签署者: Victoria Dye

在期待git reset --hard(man) 能够在不扩展稀疏索引的情况下使用时,将sparse-index is expanded and converted back中的命令替换为git reset -- folder1/a(man)。此命令需要扩展索引以正常工作,即使在将reset的其余部分与稀疏索引集成后也是如此。
注意:Git 2.36(2022年第二季度)修复了在2.35中引入的unpack-trees中的一个错误。

查看 提交 99430aa, 提交 bfc763d, 提交 c3a9cec (2022年3月17日) 由 Victoria Dye (vdye) 提交。
(合并于 提交 d629667,2022年3月29日,由 Junio C Hamano -- gitster -- 进行)

撤销 "unpack-trees":优化next_cache_entry的性能

签署者:Victoria Dye

这是一个还原操作 commit f2a454e (unpack-trees: improve performance of next_cache_entry, 2021-11-29, Git v2.35.0-rc0 -- merge listed in batch #2) (unpack-trees: improve performance of next_cache_entry, 2021-11-29)。

"hint" 值最初是为了在使用稀疏索引时,通过 'cache_bottom' 落后其正确值时,提高 'git reset -- <pathspec>'(man) 的性能。
现在 'cache_bottom' 跟踪已经被纠正,不再需要额外的 "pseudo-cache_bottom" 跟踪变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接