【问题标题】:Git clean exclude nested sub directorygit clean 排除嵌套子目录
【发布时间】:2014-01-09 16:40:07
【问题描述】:

我在使用 git clean 和排除嵌套目录的选项时遇到问题。

我想从 repo 中清除所有未提交的文件,不包括 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 a . && git ci -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 清理命令:

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 中的错误还是有什么好的理由?更多信息为什么这不起作用你可以找到in source。长话短说,排除模式仅适用于字符串,直到模式中的第一个“/”。

我的解决方案:

cd vendor && git clean -dxf -e bundle && cd ..
git clean -dxf -e vendor

有了这个,我设法只保留嵌套的目录及其内容。

【问题讨论】:

    标签: git


    【解决方案1】:

    这是我用来清理我的 git 存储库同时排除 venv/ 目录和子目录的代码:

    git clean -nXd -e \!venv -e \!venv/**
    

    对于您的情况,第一次排除就足够了:

    git clean -nXd -e \!vendor
    

    第二个排除 \!venv/** 用于 .gitignore 中可能适用于供应商内部文件或文件夹的其他规则。例如:

    .gitignore    
    *.log
    
    vendor/
      bundle/
        another-not-commited-file.log
    

    【讨论】:

      【解决方案2】:

      根据git clean --help

      git-clean - 从工作树中删除未跟踪的文件

      如果您在此 Floyd Pink 对 -d 的解释中添加(不久,该选项还允许删除未跟踪的目录而不仅仅是文件),那么这就是为什么您也会删除 vendor
      现在,假设你只想删除not-commited-file(所以,既不是任何未跟踪的目录也不是another-not-commited-file我认为你应该git clean交互模式,所以p>

      git clean -i
      

      它将询问您对每个未跟踪的文件执行什么操作(仅限文件,如果您也想询问目录,请添加 -d)。

      EDIT 在 OP 的问题编辑之后: 你也想删除目录,所以运行

      git clean -i -d
      

      编辑 2:由于手册中 -e 的含义对我来说不是很清楚,所以我搜索了它并找到了 this。我建议阅读对话,因为它解释了 -e 的真正含义,这不是 OP 的意图(或者可以从手册中理解)

      EDIT 3,更多关于-e 开关。按照我在 edit 2 中找到的链接,我决定尝试一下。这里是结果,希望能帮助你理解-e
      .gitignore的内容,所以我不提交临时文件:

      *.tmp
      

      我发出了命令:

      echo "Temporary file" > sample.tmp
      git st //which of course shows *nothing to commit, working directory clean*
      git clean -fX -e \!sample.tmp
      

      结果是所有带有 tmp 扩展名的文件都被删除(由于-X)但sample.tmp。所以,总而言之,-e 在我的理解中真正做了什么,如果我错了,请纠正我,不是从清洁过程中排除模式,而是

      从清理规则中排除模式(在我的情况下,规则是删除所有 .tmp 文件,我手动从中排除了 sample.tmp)。

      【讨论】:

      • git clean 没有交互模式。是吗?
      • 它有。试试看(或阅读git clean --help)。免责声明:在我的笔记本电脑上,git --version 显示git version 1.8.5,它可能在早期版本中不存在
      • 看不到here,但更官方的here 确实有一个-i 开关。
      • @FloydPink,老实说,我经常发现 git --help 开关非常清晰,显然,完整。我建议将其用作第一个参考来源,然后使用其他来源作为示例或说明。然后,当然,也有例外(如-e,见EDIT 2)和用例可以更好地从网络上找到和理解(git-scm.com 通常是惊人的,很多例子,用例,特殊情况,.. .)
      • 谢谢。我确实经常使用--help,但现在我在 Windows 上,看起来我已经有一段时间没有更新 git 了。我在v1.8.1,现在已经是v1.8.5。从here 我意识到git cleanv1.8.4 中“学习”了交互模式。谢谢你,我今天学到了这一点。 :)
      【解决方案3】:

      这是因为vendor 是一个未跟踪的目录,而您正在使用选项- d

      正如the manual 所说:

      -d

      除了未跟踪的文件之外,还删除未跟踪的目录。如果未跟踪的目录由不同的 git 存储库管理,则默认情况下不会将其删除。如果您真的想删除这样的目录,请使用 -f 选项两次。

      我可以使用这个命令获得所需的输出:

      git clean -x -n
      

      这在真实场景中有效吗?如果没有,您可能需要在 vendor/bundle 中提交一些其他文件,然后查看。

      【讨论】:

      • 如果有未跟踪的目录是什么情况,例如。在项目的根目录中,如何清理它?
      • 您想探索选项-X 而不是-x?这允许只清理.gitignore - Read more中指示的那些文件
      • 我不能依赖 .gitignore,我需要更通用的解决方案。更新的问题,现在更现实了。
      【解决方案4】:

      Git 2.24(2019 年第四季度)使 git clean 在嵌套 Git 存储库(不仅仅是文件夹)方面更加强大

      请参阅commit 69f272b(2019 年 10 月 1 日)和 commit 902b90ccommit ca8b539commit 09487f2commit e86bbcfcommit 3aca580commit 29b577bcommit 89a1f4acommit a3d89d8、@987 commit a5e916ccommit bbbb6b0commit 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 0cbb605commit 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

      我打算将这个标题命名为“dirtreat_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() 中的逻辑确实匹配,并且所需的一切都在在撰写本段时最少被复制。

      但你错了,我因为遗漏了部分逻辑而搞砸了。

      【讨论】:

        猜你喜欢
        • 2017-04-22
        • 2017-06-25
        • 2015-06-17
        • 2016-02-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-23
        相关资源
        最近更新 更多