我在使用git clean和exclude选项时遇到了问题。
我想从仓库中清除所有未提交的文件,不包括vendor/bundle
目录。我的测试仓库像这样:
debugg-dir/
.git/
file.txt
not-commited-file
not-commited-folder
another-not-commited-file
vendor/
bundle/
another-not-commited-file
使用以下内容复制测试存储库:
git init debugg-dir && cd debugg-dir
touch file.txt && g add . && git commit -m "Commit"
mkdir -p not-commited-folder && touch not-commited-folder/another-not-commited-file
mkdir -p vendor/bundle && touch vendor/bundle/another-not-commited-file && touch not-commited-file
Git clean命令:
git clean -d -x -n -e vendor/bundle
清洁后预期有:
debugg-dir/
.git/
file.txt
vendor/
bundle/
another-not-commited-file
有什么方法可以从git clean命令中排除嵌套目录吗?
#编辑:
说明:
对于这种情况没有“干净”的解决方案。Git clean排除了git clean -d -x -n -e dir_name
的目录,但这不适用于嵌套的目录。这是git中的bug,还是有什么好的理由?更多的信息为什么这不工作,你可以在源代码中找到。长话短说,exclude模式只适用于字符串,直到模式中的第一个'/'。
我的解决方案:
cd vendor && git clean -dxf -e bundle && cd ..
git clean -dxf -e vendor
有了这个我设法只保留嵌套的目录和它的内容。
4条答案
按热度按时间dgenwo3n1#
这是因为
vendor
是一个未跟踪的目录,并且您正在使用选项- d
。the manual说:
除了删除未跟踪的文件外,还删除未跟踪的目录。如果一个未被跟踪的目录由另一个git仓库管理,默认情况下不会被删除。如果你真的想删除这样的目录,请使用-f选项两次。
我可以使用以下命令获得所需的输出:
这在真实的中行得通吗?如果没有,您可能希望在
vendor/bundle
中提交其他文件,然后查看。23c0lvtd2#
这是我用来清理git仓库的代码,同时排除了
venv/
目录和子目录:对于您的情况,第一个排除就足够了:
第二个排除项
\!venv/**
用于.gitignore中的其他规则,这些规则可能适用于供应商内部的文件或文件夹。例如:k0pti3hp3#
根据
git clean --help
git-clean -从工作树中删除未跟踪的文件
如果你加上弗洛伊德Pink关于
-d
的解释(简单地说,该选项允许删除未跟踪的目录,而不仅仅是文件),那么这就是为什么你也会删除vendor
。现在,假设你只想删除
not-commited-file
(所以,既不是任何未跟踪的目录,也不是another-not-commited-file
),我认为你应该git clean
交互模式,所以这将询问你如何处理每个未跟踪的文件(只有文件,添加
-d
,如果你想要求的目录太)。OP问题编辑后EDIT:你也想删除目录,所以运行
编辑2:由于我从手册中不清楚
-e
的含义,我在谷歌上搜索了一下,找到了this。我建议阅读对话,因为它解释了-e
的真实的含义,这不是OP的意图(或者可以从手册中理解)EDIT 3,更多关于
-e
交换机。在 edit 2 中找到的链接之后,我决定尝试一下。这里的结果,我希望这将有助于您了解-e
..gitignore
的内容,所以我不提交临时文件:我下达了命令:
其结果是,所有文件与tmp扩展名被删除(由于
-X
),但sample.tmp
。所以,总而言之,-e
真正做的,在我的理解中,如果我错了,请纠正我,不是从清洁过程中排除模式,从清理规则中排除模式(在我的例子中,规则是删除所有.tmp文件,我手动排除了sample.tmp)。
bvn4nwqk4#
Git 2.24(2019年第4季度)使**
git clean
**在嵌套的Git * 存储库 *(不仅仅是文件夹)方面更加强大参见commit 69f272b(2019年10月1日)和Elijah Newren (
newren
)的commit 902b90c、commit ca8b539、commit 09487f2、commit e86bbcf、commit 3aca580、commit 29b577b、commit 89a1f4a、commit a3d89d8、commit 404ebce、commit a5e916c、commit bbbb6b0、commit 7541cc5(2019年9月17日)。(由Junio C Hamano --
gitster
--合并于commit aafb754,2019年10月11日)clean
:避免删除嵌套Git存储库中未跟踪的文件用户希望嵌套的git仓库中的文件保持独立,除非足够强制(使用两个
-f
)。不幸的是,在某些情况下,git会同时删除嵌套仓库中被跟踪的(可能是脏的)文件和未被跟踪的文件。
为了解释这是如何发生的,让我们对比几个案例。
首先,以下面的示例设置为例(假设我们已经在git repo中):
在这个设置中,一切都按预期工作;运行'
git clean -fd
'将导致fill_directory()
返回以下路径:然后
correct_untracked_entries()
会注意到这可以被压缩为:然后,由于“
nested/
”是一个目录,我们将调用remove_dirs("nested/", ...)
,它将检查is_nonbare_repository_dir()
,然后决定跳过它。但是,如果有人也创建了一个忽略的文件:
那么运行'
git clean -fd
'会导致fill_directory()
返回相同的路径:但
correct_untracked_entries()
会注意到我们忽略了nested/下的条目,因此将此列表简化为因为这些不是目录,所以我们没有
call remove_dirs()
,因为call remove_dirs()
是唯一进行is_nonbare_repository_dir()
安全检查的地方--导致我们删除了未跟踪的文件和被跟踪的(可能是脏的)文件。解决这个问题的一个可能的方法是遍历每个路径的父目录,检查它们是否代表非空存储库,但这将是一种浪费。
即使我们添加了某种缓存,它仍然是一种浪费,因为我们应该能够检查“nested/”表示一个非空的存储库,甚至在第一次进入它之前。
在
dir_struct.flags
中添加一个DIR_SKIP_NESTED_GIT
标志,并使用它来防止fill_directory()
和朋友下降到嵌套的git repos中。通过此更改,我们还修改了commit 91479b9中添加的两个回归测试(“
t7300
:add tests to document behavior of clean and nested git”,2015-06-15,Git v2.6.0-rc0).那次提交、它的系列以及邮件列表上该系列的前六次迭代都讨论了为什么这些测试要对它们所做的期望进行编码。
事实上,他们的目的似乎只是测试 * 现有 * 行为,以确保性能变化不会改变行为。
然而,这两个测试直接反驳了manpage的声明,即需要两个
-f
来删除嵌套的git仓库下的文件/目录。虽然有人可能会争辩说,用户给出了一个明确的路径,该路径与嵌套存储库中的文件/目录相匹配,但一旦你沿着这条路线走下去,用户就很难理解这条滑坡(例如如果他们指定“
git clean -f -d '*.c'
“呢)也很难解释确切的行为是什么;通过使其变得非常简单来避免此类问题。
最后,
-ffd
仍然有一些bug没有清理干净(例如:缺少嵌套的.git
)和-ffdX
可能清理错误的文件(注意外部.gitignore
而不是内部)。这个补丁根本不解决这些情况(也不改变与这些标志相关的行为),它只修复了在给定单个
-f
时的处理。有关
-ffd[X?]
错误的更多讨论,请参阅this thread。使用Git 2.25.1(Feb. 2020),“
git clean
”中的一个角落案例错误,该错误源于目录枚举API中的笨拙调用约定(出于性能原因)。参见commit 0cbb605,commit ad6f215(2020年1月16日)by Jeff King (
peff
)。参见commit 2270533(2020年1月16日),作者Elijah Newren (
newren
)。参见commit f365bf4(2020年1月16日),作者Derrick Stolee (
derrickstolee
)。(由Junio C Hamano --
gitster
--合并至commit 7ab963e,2020年2月5日)dir
:treat_leading_path()
和read_directory_recursive()
,第2轮签字人:伊莱贾·纽伦
我想把这个标题命名为“
dir
:treat_leading_path()
和read_directory_recursive()
的更多同步”,向commit 777b42034764(“dir
:synchronizetreat_leading_path()
andread_directory_recursive()
“,2019-12-19,Git v2.25.0-rc 0--merge),但标题太长。不管怎样,首先是背景故事…
fill_directory()
总是有一个稍微容易出错的接口:它返回一个路径的子集,这些路径 * 可能 * 匹配指定的路径规范;它的目的是修剪掉一些不匹配指定路径规范的路径,并至少保留所有匹配它的路径。给定这个接口,调用者负责对结果进行后处理,并检查每个结果是否实际匹配路径规范。
builtin/clean.c
做了这个。它将首先修剪掉重复项(例如如果返回“
dir
“以及“dir/
“下的所有文件,则它会将其简化为“dir
“),并且在修剪重复项之后,它会将剩余路径与指定的路径规范进行比较。但是,这种后处理本身可能会遇到问题,如commit 404ebceda01c(“
dir
:also check directories for matching pathspecs”,2019-09-17,Git v2.24.0-rc0 -- merge listed in batch #8):对于
git clean
和一组路径规范“dir/file
”和“more
”的情况,这导致了一个问题,因为我们最终会得到两个目录项:“dir
”和“dir/file
”然后,
correct_untracked_entries()
会尝试删除“dir/file
”,因为它在“dir
”下,为我们删除重复项,留下“dir
”。由于原始的路径规范只有“
dir/file
”,所以剩下的唯一条目不匹配,没有留下任何要删除的内容。(Note如果仅指定一个路径规范,例如,只有“
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-rc 0--merge),我们注意到common_prefix_len
没有做适当的检查,让各种东西通过,导致递归到.git/目录和其他疯狂的事情。所以它开始锁定并检查代码路径中的路径名。
commit 777b42034764(“
dir
:synchronizetreat_leading_path()
andread_directory_recursive()
“,2019-12-19,Git v2.25.0-rc 0--merge),其中指出:我们的优化是避免在所有路径规范都有一个公共的引导目录时调用
read_directory_recursive()
,这意味着我们需要匹配read_directory_recursive()
在从根目录调用时所使用的逻辑。由于它不仅仅是调用
treat_path()
,因此我们需要复制相同的逻辑。......然后,它更有力地解决了这个问题,用这个奇妙的讽刺声明:
需要像这样复制逻辑意味着它保证有人最终需要做进一步的更改,忘记更新两个位置。
为了避免这样的bug并简化代码,直接修改
leading_directory
的特殊 shell 是很有诱惑力的,但是unpack_trees
的verify_clean_subdirectory()
也调用read_directory()
,并且使用非空的前导路径,所以我不确定是否要进一步重构。在
treat_leading_path()
和read_directory_recursive()
中添加令人讨厌的警告,以尝试警告人们此类问题。你可能会认为,有了这样一个措辞强烈的描述,它的作者实际上已经确保了
treat_leading_path()
和read_directory_recursive()
中的逻辑确实匹配,并且在写这一段的时候,所需要的一切至少已经被复制了。但你错了,我弄错了部分逻辑。