首页 > 解决方案 > 通过 git filter-branch 调用时反直觉的 git rm --cached 标志行为(追溯 .gitignore)

问题描述

问题

通过--cached运行时标志有什么区别?git rmgit filter-branch --index-filter

git filter-branch --index-filter 'git rm --cached *.ext'  
git filter-branch --index-filter 'git rm *.ext'

问题

当我运行时git rm --cached *.ext,所有*.ext文件都未跟踪但未从工作目录中删除(--cached据我所知,这是 flag 的目的)

但是,当我尝试追溯运行该命令时

git filter-branch --index-filter 'git rm --cached *.ext'

所有历史*.ext文件都未跟踪现有*.ext文件已从工作目录中删除。

为什么?

没有有什么区别--cached

理论

由于该命令在所有提交上运行,每次运行都没有“工作目录”,因此它还将 HEAD/当前工作目录视为没有工作目录的独立提交。

标签: git

解决方案


你的理论大部分是正确的,只是在最后有点偏离。 git filter-branch不使用标准索引,也不使用您的工作树。当它调用您的每个过滤器时,它会在一个临时目录中运行,该目录在文件系统中的位置取决于您是否提供了一个选项。1 由于没有可用的工作树,所有索引过滤器操作都应使用. 2 Filter-branch 还在临时目录中创建一个临时索引。这个临时索引在过滤的每一步都存在:它用于提取索引过滤器的提交,然后在索引过滤器修改它后进行新的提交。-d directorygit rm--cached

完成所有过滤操作后,最后几个步骤filter-branch是:

  1. 更新所有要修改的引用:过滤的分支和任何标签(如果您使用--tag-name-filter. 例如,之前master可能已经确定了提交a123456...b789abc...如果现在应该使用新提交而不是a123456,Git 必须重写refs/heads/master,以便它现在识别提交b789abc...

  2. 如果您在非裸存储库中(filter-branch用于裸存储库),则实际上git checkout是新提交而不是旧提交。这会更新您的索引和工作树,以便您的索引和工作树保持“干净”。3 实际操作是git read-tree

    if [ "$(is_bare_repository)" = false ]; then
            git read-tree -u -m HEAD || exit
    fi
    

正是这最后一步,即有效签出,删除了您的*.ext文件。

当前的索引将它们显示为tracked,它们是;它们在您当前的commit中,即 (say) a123456...。您的新目标提交b789abc...缺少*.ext文件,因为您使用--index-filter. 所以要从当前提交移动到新提交,正确的操作是删除文件。

要解决问题:

在使用该选项运行filter-branch 命令git rm --cached '*.ext' 之前--index-filter运行,然后提交。这样,您当前的提交(将不再是a123456...将没有文件,并且您的索引将没有文件,因此它们将只是当前提交和新提交中的未跟踪文件,并且不会受到影响.


1 Filter-branch$tempdir从您的-d选项或默认设置开始,即.git-rewrite. 然后它创建$tempdir/t$tempdir如果需要也创建;将位置更改为其中;并将 Git 工作树设置为.. 除非您使用该选项或在任何过滤器中尝试以某种方式操作当前目录,否则此$tempdir/t目录将保持为空。--tree-filter

2没有--cached,git rm会尝试从 中删除文件$GIT_WORK_TREE,即$tempdir/t. Filter-branch 自己的状态文件../来自这里,即 in $tempdir,因此它们通常应该是安全的,但是尝试删除无论如何都不应该存在的东西仍然不是一个好主意:如果没有别的,那就是浪费计算时间。使用--cached甚至git rm不会尝试它。

3这里的“干净”是一种定义模糊的状态,无论如何您都可能认识它。如果需要,精确的定义在 中git-sh-setup.sh,特别是在函数中require_clean_work_tree。如果在非裸存储库上运行,Filter-branch 在开始过滤之前调用它。


推荐阅读