问题 如何删除Git存储库中不在工作目录中的所有文件?


我正在拆分最初位于单个Subversion存储库中的旧应用程序套件。

我已将其转换为Git存储库并删除了我不想要的内容,但我想通过删除与已删除文件关联的历史数据来缩小存储库(原始存储库将保留以供参考目的所以新的不需要它。

理想情况下,我想要做的是浏览整个存储库并删除工作目录中不存在的任何文件或文件夹,以及与之关联的任何历史记录。这将留给我HEAD的内容和影响这些文件的提交历史。但是,我没有遇到过这样做的方法(孤儿HEAD没有帮助,因为它没有保留历史记录)。

这可能吗?我知道如何通过git-filter-branch从整个历史记录中删除单个文件或文件夹,但是有太多的文件和文件夹,这是一个实用的方法...除非有一种方法可以过滤所有不在HEAD中的文件?


2290
2017-09-07 15:13


起源

那些过去重命名的文件怎么样?重命名时删除历史记录或保留重命名(并在重命名之前跟踪不同的文件名) - knittl
好点子。我更喜欢在重命名之前保留历史记录,因此需要在那里添加一些额外的文件,这很好。 - Luke Bennett


答案:


以下是如何使用git filter-branch来删除所有不需要的文件:

  1. 在重命名的情况下,获取您不希望在历史记录中显示的旧名称和新名称的文件名列表。例如,将它们放在名为toberemoved.txt的文件中

  2. 像这样运行git filter-branch:

    $ git filter-branch --tree-filter "rm -f `cat toberemoved.txt`" branch1 branch2 ...
    

这是git filter-branch的相关手册页:

   --tree-filter <command>
       This is the filter for rewriting the tree and its contents. The
       argument is evaluated in shell with the working directory set to
       the root of the checked out tree. The new tree is then used as-is
       (new files are auto-added, disappeared files are auto-removed -
       neither .gitignore files nor any other ignore rules HAVE ANY
       EFFECT!).

因此,只需确保要删除的文件列表都与已签出树的根相对应。

更新:

要获取过去但不在当前工作目录中的文件列表,可以运行以下命令。请注意,您必须进一步努力保留重命名文件的“重命名前的历史记录”:

$ git log --raw |awk '/^:/ { if (! printed[$6]) { print $6; printed[$6] = 1 }}'|while read f;do if [ ! -f $f ]; then echo Deleted: $f;fi;done

$ 6是在日志的-raw模式中显示的提交中受影响的文件的名称。

如果你想知道发生了什么([D] eleted,[R] enamed,[M] odified,等等)每个提交的每个文件,请参阅git log的--diff-filter选项。

也许其他人可以在重命名的情况下查看如何查找跟踪文件的先前名称。


6
2017-09-13 09:38



谢谢你的新答案。我想,我没有考虑过使用 cat 使用filter-branch。然而,我仍然没有得到的是如何生成文件列表,因为我只对删除文件感兴趣 不 在工作目录中(因此不容易列出)。还有什么想法? - Luke Bennett
我已经更新了答案,包括命令以获取已删除文件的列表。 - holygeek
很好的答案。我删除了 Deleted:  从oneliner获取列表。但是当使用该列表时,由于某种原因,git filter-branch命令中的bash语法不起作用。所以,我使用了 git filter-branch --tree-filter "cat $HOME/toberemoved.txt | xargs -I{} rm -f {}" (注意 toberemoved.txt 在版本控制下需要在dir之外。这可能导致了问题 "cat toberemoved.txt" 语法也是,但我没有检查过。) - jaimedash


我这样做了几次 - 提取单个文件的提交并从中创建新的存储库。它有点像这样:

$ c=10; for commit in $(git log --format=%h -- path/to/file|tac); do
      c=$((c+1))
      git format-patch -1 --stdout $commit > $c.patch
  done

这将创建补丁文件11.patch,12.patch等。然后我编辑这些补丁(使用vim或perl看起来最适合这项工作),删除我不感兴趣的文件的整个数据库,也可以修改名称,以便在diff hunk标头中重命名。

我会在新git存储库的补丁上使用git am。如果某些事情没有出现,那么我会核对新的git存储库并再次编辑补丁并重复git am。

我从10开始计数的原因是因为我懒得在补丁序列前面加一个前导0而且对于超过99的提交我只是从99开始。


3
2017-09-07 17:12



你可以用 $(printf "%02d" $c).patch 预先领先零。 - jfs
谢谢你提到这一点。从现在开始,我必须更频繁地开始使用printf。 - holygeek
谢谢......但这不是逐个文件的工作吗?正如我在我的问题中所说,我知道如何在每个文件的基础上做到这一点,但是有太多的文件可以实现。或许我误解了这里发生的事情? - Luke Bennett
啊,在这种情况下你可以使用git filter-branch。我会把它作为另一个答案。 - holygeek


帮助第二个答案: “也许其他人可以在重命名时找到如何查找跟踪文件的先前名称。”

这将返回项目中的文件以及重命名它们的文件。

for file in `git ls-files`; do git log --follow --name-only --pretty=format: $file | sort -n -b | uniq | sed '/^\s*$/d'; done

您可以使用它们从列表中排除。

整个解决方案是:

for file in `git ls-files`; do git log --follow --name-only --pretty=format: $file | sort -n -b | uniq | sed '/^\s*$/d'; done > current.txt

git log --raw |awk '/^:/ { if (! printed[$6]) { print $6; printed[$6] = 1 }}'|while read f;do if [ ! -f $f ]; then echo $f;fi;done | sort > hist.txt

diff --new-line-format="" --unchanged-line-format="" hist.txt current.txt > for_remove.txt


1
2017-08-19 15:12