'git clean'默认删除忽略的文件?

qc6wkl3g  于 2023-01-28  发布在  Git
关注(0)|答案(5)|浏览(152)

根据帮助,没有-x选项git clean应该让忽略的文件,但它没有。

[il@reallin test]$ cat .gitignore
*.sar
[il@reallin test]$ mkdir -p conf/sar && touch conf/sar/aaa.sar
[il@reallin test]$ git status
# On branch master
nothing to commit, working directory clean
[il@reallin test]$ git clean -df
Removing conf/

conf/sar/aaa.sar已删除。这是一个错误吗?

bpsygsoo

bpsygsoo1#

根据man git clean

-d
    Remove untracked directories in addition to untracked files.

在您的例子中,目录conf/sar没有被跟踪--它不包含任何被git跟踪的文件。如果您没有gitignore规则并执行了git clean -fd,这个未被跟踪的目录的内容将被删除--正如文档中所说。
现在,如果您添加.gitignore与规则忽略*.sar文件,它不会改变基本的事实,即您的目录conf/sar/仍然是untracked,并有untracked文件aaa.sar,这是符合这个gitignore规则不应该突然使它不可移动的git clean -fd
但是,如果您在忽略的aaa.sar旁边添加任何跟踪文件,则此目录不会被删除,您的文件将保持不变。
换句话说,虽然看起来很混乱,但这并不是一个bug,git完全按照文档中的说明去做。

aurhwmvo

aurhwmvo2#

警告:此git clean行为在Git 2.14(Q3 2017)中会略有改变
"git clean -d"用于清理已忽略文件的目录,即使没有" -x "命令也不应丢失已忽略的文件。
"git status --ignored"未列出不带"-uall"的已忽略和未跟踪文件。
参见commit 6b1db43(2017年5月23日)和Samuel Lijin ( sxlijin )commit bbf504acommit fb89888commit df5bcdfcommit 0a81d4acommit b3487cc(2017年5月18日)。
(由Junio C Hamano -- gitster --合并至commit f4fd99b,2017年6月2日)

clean:示教clean -d保留忽略的路径

有一个隐含的假设,即只包含未跟踪和忽略路径的目录本身应该被认为是未跟踪的,这在我们询问目录是否应该被添加到git数据库的用例中是有意义的,但在我们询问目录是否可以安全地从工作树中删除时就没有意义了;结果,即使这样做也将移除被忽略的路径,clean -d也将假定包含被忽略的路径的"未被跟踪"目录可以被删除。
为了解决这个问题,我们教clean -d收集忽略的路径,并跳过未跟踪的目录,如果它包含一个忽略的路径,而不是仅仅删除其中未跟踪的内容。
为了实现这一点,cmd_clean()必须收集未跟踪目录的所有未跟踪内容,以及所有忽略的路径,以确定必须跳过哪些未跟踪目录(因为它们包含忽略的路径),以及不应该跳过哪些目录。
但是......从2017年开始,这种变化意味着**git status --ignored hangs indefinitely!
Martin Melka在本线程中所报告,并由SZEDER Gábor**进行分析:
如果目录深度为120,则需要6 * 10^23年才能完成
这是由commit df5bcdf造成的,它是修补程序系列的一部分,用于修复"git clean -d"删除未跟踪的目录,即使这些目录包含被忽略的文件。
所以...修复正在进行中,将于2020年晚些时候发布。
Git 2.24(2019年第四季度)说明了git clean行为更改引入了回归。
参见commit 502c386(2019年8月25日),作者为SZEDER Gábor ( szeder )
(由Junio C Hamano -- gitster --合并至commit 026428c,2019年9月30日)

t7300-clean:演示如何删除嵌套的存储库,忽略文件损坏

"git clean -fd"不得删除属于其他Git存储库或工作树的未跟踪目录。
不幸的是,如果外部存储库中的".gitignore"规则碰巧与嵌套存储库或工作树中的文件匹配,则会出现错误,"git clean -fd"会删除嵌套存储库工作树中的内容,但忽略的文件除外,这可能会导致数据丢失。
在"t7300-clean.sh"中添加一项测试,以证明该断裂。
此问题是6b1db43clean:教clean -d保留忽略的路径,2017 - 05 - 23,Git v2.13.2).
Git 2.24进一步阐明了git clean -d
请参见第一版19f1x(2019年10月1日)和第一版20f1x、第一版21f1x、第一版22f1x、第一版23f1x、第一版24f1x、第一版25f1x、第一版26f1x、第一版27f1x、第一版28f1x、第一版29f1x、第一版30f1x、第一版31f1x(2019年9月17日)。
(由Junio C Hamano -- gitster --合并至commit aafb754,2019年10月11日)

t7300:添加显示无法清除指定路径规范的测试用例

有人给我带来了一个测试用例,需要多次调用git-clean来清除不需要的文件:

mkdir d{1,2}
touch d{1,2}/ut
touch d1/t && git add d1/t

使用此设置时,用户需要运行

git clean -ffd */ut

两次以删除两个ut文件。
一个小测试显示了一些有趣的变体:

  • 如果这两个ut文件中只存在一个(任一个),那么只需要一个clean命令。
  • 如果两个目录都有被跟踪的文件,那么只需要一个git clean就可以清理两个文件。
  • 如果两个目录都没有被跟踪的文件,那么上面的clean命令将永远不会清除任何一个未被跟踪的文件,尽管pathspec显式地调用了这两个文件。

平分显示无法清除文件始于commit cf424f5("clean:使用"-d"遵守路径规范,2014年3月10日,Git v1.9.1)。
然而,这又引出了另一个问题:虽然"-d"标志是由向我显示此问题的原始用户使用的,但该标志应该与此问题无关。
在不使用"-d"标志的情况下再次进行测试,结果表明在不使用该标志的情况下存在相同的错误行为,并且实际上在cf424f5之前就存在。
因此:

clean:使用"-d"遵守路径规范

git-clean使用read_directory来填充struct dir中的潜在命中项。但是,read_directory实际上并不检查我们的路径规范。它使用的是简化版本,可能会出现误报。因此,我们需要检查任何命中项是否与我们的路径规范匹配。
对于非目录,我们这样做是可靠的。
对于目录,如果没有给出"-d",我们检查路径规范是否完全匹配(也就是说,我们更加严格,需要一个显式的"git clean foo"来清除"foo/");但是如果给出了"-d",我们就不检查路径规范,而不是放宽精确匹配以允许递归匹配。
这个回归是在113f10f中引入的(使git-clean成为一个内置函数,2007年11月11日,Git v1.5.4-rc0)。

dir:如果我们的路径规范可能与目录下的文件匹配,则递归到该目录

对于git clean,如果一个目录完全没有被跟踪,并且用户没有指定-d(对应于DIR_SHOW_IGNORED_TOO),那么我们通常不想删除该目录,因此不递归到它。
但是,如果用户手动指定了要删除的目录下的某个特定(甚至是globbed)路径,那么我们需要递归到该目录,以确保按照用户的请求删除该目录下的相关路径。
注意,这并不意味着递归到的目录将被添加到dir->entries以供稍后移除;在本系列前面的几个提交中,在决定将其添加到条目列表之前,在从递归目录返回之后运行另一个更严格的匹配检查。
因此,这将只会导致给定目录下与某个路径规范匹配的文件被添加到条目列表中。
以及:

dir:同时检查目录以查找匹配的路径规范

即使一个目录不匹配某个路径规范,根据精确的路径规范,它下面的某个文件也有可能匹配。
因此,我们对这种情况进行特殊处理并递归到目录中。
但是,我们以前总是将递归到的任何未跟踪目录添加到未跟踪路径列表中,而不管目录本身是否匹配路径规范。
对于git-clean和一组路径规范"dir/file"和"more"的情况,这会导致一个问题,因为我们最终会得到两个目录项:

"dir"
"dir/file"

然后,correct_untracked_entries()将尝试删除"dir/file"(因为它位于"dir"下),从而帮助我们删除重复项,

"dir"

由于原始路径规范只有"dir/file",因此剩下的唯一条目不匹配,没有留下任何要删除的内容。
(Note如果只指定了一个pathspec,例如只指定了"dir/file",那么fill_directory中的common_prefix_len优化将导致我们绕过这个问题,使得在简单的测试中看起来我们可以正确地手动删除指定的pathspec。)
通过实际检查我们将要添加到目录条目列表中的目录是否与路径规范匹配来解决这个问题;仅在我们已经从递归到目录中返回之后才执行该匹配检查。
结果是:

clean:消除-d定义的歧义

-d标志早于git-clean指定路径的能力。
因此,git-clean的默认设置是只删除当前目录中未跟踪的文件,而-d的存在允许它递归到子目录。
路径和-d选项之间的交互似乎没有得到仔细考虑,大量的bug和测试套件中覆盖此类配对的测试的缺乏就是明证。
这个定义很重要,所以让我们来看看解释-d选项的一些不同方式:
A)如果没有-d,只在包含被跟踪文件的子目录中查找;对于-d,还要在未跟踪的子目录中查找要清除的文件。
B)如果没有用户指定的路径让我们删除,我们需要有某种默认值,所以...没有-d,只在子目录下包含跟踪文件的子目录中查找;对于-d,还要在未跟踪的子目录中查找要清除的文件。
这里重要的区别是选项B表示如果指定了路径,则'-d'的存在与否是无关紧要的。
选项B背后的逻辑是,如果用户明确要求我们清除指定的路径规范,那么我们应该清除与该路径规范匹配的任何内容。
一些例子也许能说明问题。
应:

git clean -f untracked_dir/file

删除untracked_dir/文件或不?
不这样做似乎很疯狂,但严格解读选项A,它不应该被删除。
不如这样:

git clean -f untracked_dir/file1 tracked_dir/file2

git clean -f untracked_dir_1/file1 untracked_dir_2/file2


它应该删除这两个文件中的一个还是两个?
是否需要多次运行才能删除列出的两个文件?(如果这听起来像是一个疯狂的问题,请参阅提交消息"t7300:添加一些测试用例,显示无法清除指定的路径规范"(本修补程序系列前面已添加)。
如果使用-ffd而不是-f会怎么样--这是否允许删除它们?是否需要多次调用-ffd
如果使用glob(如'* tracked *')而不是拼写目录名会怎样?
如果文件名涉及全局变量,例如

git clean -f '*.o'

git clean -f '*/*.o'


当前的文档实际上提出了一个与选择A略有不同的定义,本系列之前的实现提供了与选择A或B完全不同的定义。
(The不过,实现显然有缺陷)。
也许还有其他的选择。
然而,对于我所能想到的几乎任何给定的-d定义选择,上面的一些示例对用户来说都显得有缺陷。

唯一没有负面意外的情况是选项B:将用户指定的路径视为清除所有与该路径规范匹配的未跟踪文件的请求,包括递归到任何未跟踪的目录。
更改文档和基本实现以使用此定义。
有两个回归测试间接依赖于当前的实现,但都不是关于子目录处理的。
这两个测试在提交5b7570c("git-clean:add tests for relative path ",2008年3月7日,Git v1.5.5-rc0),创建该文件的唯一目的是为提交fb328947c8e(" git-clean:正确打印相对路径",2008 - 03 - 07)。
这两个测试都指定了一个目录,该目录碰巧有一个未跟踪的子目录,但这两个测试都只是检查被删除文件的打印结果是否显示了相对路径。
适当更新这些测试。
最后,请参见"Git clean exclude nested sub directory"。
警告:目录遍历代码有冗余的递归调用,这使得其性能特征与树的深度成指数关系,Git 2.27(Q2 2020)对此进行了纠正。
这也会影响git clean
请参见第一版第40页、第一版第41页、第一版第42页、第一版第43页、第一版第44页、第一版第45页、第一版第46页、第一版第47页、第一版第48页、第一版第49页、第一版第50页(2020年4月1日)。
参见Derrick Stolee ( derrickstolee )commit 0bbd0e8(2020年4月1日)。
(由Junio C Hamano -- gitster --合并至commit 6eacc39,2020年4月29日)

dir:用线性算法代替指数算法

签署人:伊莱贾·纽伦
dir的read_directory_recursive()自然地递归操作,以便遍历目录树。
目录的处理有时很奇怪,因为关于如何处理目录有太多不同的排列。
一些例子:

  • "git ls-files -o --directory"只需要知道目录本身未被跟踪;它不需要递归到它里面去看它下面是什么。
  • "git status"需要递归到未跟踪的目录,但仅用于确定该目录是否为空。

如果下面没有文件,则目录本身将从输出中忽略。
如果不为空,则只列出目录。

  • 'git status --ignored'需要递归到未跟踪的目录,并报告所有忽略的条目,然后报告该目录为未跟踪--除非该目录下的所有条目都被忽略,在这种情况下,我们不打印该目录下的任何条目,而只报告该目录本身为忽略。

(Note虽然这会迫使我们遍历目录下所有未跟踪的文件,但我们会将它们从输出中剥离,除了'git clean'这样的用户也设置了DIR_KEEP_TRACKED_CONTENTS。)

  • 对于'git clean',我们可能需要递归到一个不匹配任何指定路径规范的目录,如果该目录下可能有一个条目可以匹配其中一个路径规范的话.

在这种情况下,我们需要小心地从路径列表中忽略目录本身(参见commit 404ebceda01c("dir:同时检查目录是否匹配路径规范",2019 - 09 - 17,Git v2.24.0-rc0))
上面提到的部分矛盾在于,对目录的处理可以根据其中的文件以及dir->flags中的各种设置而改变。
在阅读代码时要记住这一点,很容易想到"treat_directory()告诉我们如何处理目录,而read_directory_recursive()是递归的东西"。
但是,由于我们需要查看目录以了解如何处理它,因此通过添加read_directory_recursive()调用,很容易决定(也)从treat_directory()递归到目录。
添加这样一个调用实际上是很好的,如果我们确保read_directory_recursive()不会递归到同一个目录。
不幸的是,commit df5bcdf83aeb("dir:recurse into untracked dirs for ignored files ",2017 - 05 - 18,Git v2.14.0-rc0--merge listed in batch #5),在代码中添加了这样一个例子,这意味着我们将有两次调用read_directory_recursive()来获取一个未跟踪的目录。
如果我们有一个文件名为

one/two/three/four/five/somefile.txt

并且在one/中没有任何东西被跟踪,则"git status --ignored"将在目录"one/"上调用read_directory_recursive()两次,并且它们中的每一个将在目录"one/two/"上调用read_directory_recursive()两次,以此类推,直到read_directory_recursive()对于"one/two/three/four/five/"被调用2^5次。
通过将大量特殊逻辑移到treat_directory()中,避免每个级别调用read_directory_recursive()两次。
由于dir.c有些复杂,随着时间的推移,围绕它建立了额外的cruft。
在试图解开它的时候,我注意到有几个例子,第一次调用read_directory_recursive()会为某个目录返回例如path_untracked,而后来的调用会返回例如path_none,,尽管事实上该目录显然应该被认为是未跟踪的。
由于第一次调用将未跟踪条目添加到dir->entries;的副作用,代码碰巧工作,这允许它获得正确的输出,尽管后面的调用在返回值中假定覆盖。

我有点担心仍然有bug,甚至可能有错误期望的测试用例。
我已经尝试仔细地记录treat_directory(),因为它在此更改后变得更加复杂(尽管这种复杂性的大部分来自其他地方,可能值得更好的评论)。
然而,我的大部分工作感觉更像是一场试图使代码与现有回归测试相匹配的疯狂游戏,而不是试图创建一个与某些清晰设计相匹配的实现。
这在我看来似乎是错误的,但是现有行为的规则有太多的特殊情况,以至于我很难想出一些关于所有情况下什么是正确行为的总体规则,这迫使我希望回归测试是正确和充分的。
考虑到我在过去几个月里与dir.c相关的测试用例的经验,这样的希望似乎是没有根据的:
文档很难解析甚至是错误的示例:

  • 3aca58045f4fgit-clean.txt:不要声称我们会删除带有-n/--dry-run的文件,2019年9月17日,Git v2.24.0-rc0)
  • 09487f2cbad3clean:避免删除嵌套git存储库中未跟踪的文件,2019 - 09 - 17,v2.24.0-rc0)
  • e86bbcf987faclean:消除-d的定义歧义,2019 - 09 - 17)

测试用例被声明为错误并发生变更的示例:

  • 09487f2cbad3clean:避免删除嵌套git存储库中未跟踪的文件,2019 - 09 - 17,Git v2.24.0-rc0)
  • e86bbcf987faclean:消除-d的定义歧义,2019年9月17日,Git v2.24.0-rc0)
  • a2b13367fe55(将"dir.c:使'git-status --ignored'在前导目录中工作",2019 - 12 - 10,Git v2.25.0-rc0)

测试用例明显不足的示例:

  • 502c386ff944t7300-clean:演示删除嵌套存储库并忽略文件损坏,2019 - 08 - 25,Git v2.24.0-rc0)
  • 7541cc530239t7300:添加显示无法清理指定路径规范的测试用例,2019年9月17日,Git v2.24.0-rc0)
  • a5e916c7453bdir:修复了match_pathspec_item中的逐1错误,2019年9月17日,Git v2.24.0-rc0)
  • 404ebceda01cdir:另请检查目录是否匹配路径规范,2019年9月17日,Git v2.24.0-rc0)
  • 09487f2cbad3clean:避免删除嵌套git存储库中未跟踪的文件,2019 - 09 - 17,Git v2.24.0-rc0)
  • e86bbcf987faclean:消除-d的定义歧义,2019年9月17日,Git v2.24.0-rc0)
  • 452efd11fbf6t3011:演示目录遍历失败,2019年12月10日,Git v2.25.0-rc0)
  • b9670c1f5e6bdir:修复对公共前缀目录的检查,2019年12月19日,Git v2.25.0-rc0)

每个人都不清楚"正确行为"的例子:

其他值得注意的提交:

  • 902b90cf42bcclean:修复理论路径损坏,2019年9月17日,Git v2.24.0-rc0)

不过,从积极的方面来说,它确实使代码快得多。
对于空存储库中的以下简单shell循环:

for depth in $(seq 10 25)
do
  dirs=$(for i in $(seq 1 $depth) ; do printf 'dir/' ; done)
  rm -rf dir
  mkdir -p $dirs

$目录/未跟踪文件/usr/bin/时间--格式="$深度:% e "git状态--已忽略〉/dev/null已完成
我看到了以下计时,以秒为单位(注意,每次运行的数字都有点嘈杂,但每次运行的趋势都非常清楚):

10: 0.03
11: 0.05
12: 0.08
13: 0.19
14: 0.29
15: 0.50
16: 1.05
17: 2.11
18: 4.11
19: 8.60
20: 17.55
21: 33.87
22: 68.71
23: 140.05
24: 274.45
25: 551.15

对于上面的运行,使用strace,我可以查找打开的未跟踪目录的数量,并可以验证它是否与预期的2^($depth+1)-22^1 + 2^2 + 2^3 + ... + 2^$depth的总和)匹配。
修复之后,使用strace,我可以验证打开的未跟踪目录的数量是否下降到$depth,计时是否全部下降到0.00。
实际上,直到嵌套目录达到190个时,它才开始报告0.01秒的时间,并且直到嵌套目录达到240个时,才会一致地报告0.01秒。

17.55 * 2^220 / (60*60*24*365) = 9.4 * 10^59 YEARS

以完成240个嵌套目录的情况。
你很少能把速度提高到3 * 10^69倍。

vcirk6k6

vcirk6k63#

是的,git clean的行为似乎与文档相反,即使没有指定-x/-Xgit clean也会删除忽略的文件。
选项-d似乎覆盖了-x/-X的缺失。即,* git clean -df将删除未跟踪的目录,即使它们包含未跟踪但被忽略的文件 *。
我不知道这是疏忽还是故意的,但手册页在这方面显然是不完整的,您可以考虑将手册页的补丁发送到git邮件列表。
顺便说一句,同样的问题在问题How to preserve all ignored files in git clean -fd?中讨论过。注意git clean -df不会删除.gitignore中的目录。所以要保留您的conf/,您可以将其添加到.gitignore中。

juud5qan

juud5qan4#

要获得所需的行为,即保护未跟踪的目录免受git clean -d的影响,并选择性地删除这些未跟踪目录中的内容,在您的情况下,必须显式忽略整个最顶层的未跟踪目录

echo /conf/ >>.gitignore   # or .git/info/excludes if it's just you

现在,git clean不会递归到未跟踪的目录,但幸运的是,这是一个简单的handroll:

# recursive x-ray git clean with various options:

git ls-files --exclude-standard '-x!*/' -oz  | xargs -0 rm -f   #
git ls-files                            -oz  | xargs -0 rm -f   # -x
git ls-files --exclude-standard '-x!*/' -oiz | xargs -0 rm -f   # -X

(or使用单引号是因为!是交互式shell语法,用于拉入前面的命令行片段。
要清理空目录,您可以使用接近您所需的行为

find -depth -type d -empty -delete
# -delete is -exec rm -f '{}' ';' on non-GNU userlands

但这实际上属于makefile方法,后面跟着一批mkdir -p,用于重新创建您希望保留的任何结构(即使是空的),因为make是为管理构建/测试/安装产品等 transient 而构建的。

xzabzqsa

xzabzqsa5#

除了git clean修复I mentioned previously,Git 2.28(Q3 2020)对"git clean"的代码清理也修复了最近的性能回归。
参见Elijah Newren ( newren )commit 7233f17commit f7f5c6ccommit 351ea1ccommit e6c0be9(2020年6月11日)。
(由Junio C Hamano -- gitster --合并到commit 5367469,2020年6月25日)

clean:优化并记录递归到子目录的情况

报告人:布赖恩·马勒霍恩
签署人:伊莱贾·纽伦
提交6b1db43109("clean:teach clean-d to reserve ignored paths ",2017 - 05 - 23,Git v2.14.0-rc0--merge列于batch #5中)在git-clean中添加了以下代码块:

if (remove_directories)
    dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS;

使用这些标志的原因在提交消息中有很好的文档记录,但是仅仅通过查看代码并不明显。
在代码中添加一些解释,使其更加清晰。
此外,git-2.26似乎没有正确处理git clean中的标志组合。
有了这两个标志,并且没有设置DIR_SHOW_IGNORED_TOO_MODE_MATCHING,git应该递归到所有未跟踪和忽略的目录。
git-2.26.0显然没有这么做。
我不知道这其中的全部原因,也不知道git〈2.27.0是否因为这种错误行为而存在其他未知bug,因为我觉得不值得深入研究。
根据commit 8d92fb2927("dir:用线性算法替换指数算法",2020 - 04 - 01,Git v2.27.0-rc0--merge列在batch #5中),旧算法一团糟,被扔了出去。
我能说的是,git-2.27.0使用该组合正确地递归到未跟踪和忽略的目录。
然而,在clean的情况下,我们不需要递归到被忽略的目录;那纯粹是浪费时间。
因此,当git-2.27.0开始正确处理这些标志时,我们得到了一个性能回归报告。
与其依赖fill_directory()以前逻辑中的其他错误来提供跳过忽略目录的行为,不如利用commit eec0f7f2b7中专门添加的DIR_SHOW_IGNORED_TOO_MODE_MATCHING值("status:添加选项以不同方式显示忽略的文件",2017年10月30日,Git v2.16.0-rc0--merge列在batch #4中)。

相关问题