如何在Git仓库中删除所有不在工作目录中的文件?

13

我正在拆分一个老的应用套件,它最初位于单个Subversion存储库中。

我已将其转换为Git存储库并删除了不需要的内容,但我想通过摆脱与已删除文件相关联的历史数据来缩小存储库。原始存储库将被维护以供参考,因此在新存储库中不需要这些内容。

理想情况下,我希望能够浏览整个存储库并删除工作目录中不存在的任何文件或文件夹以及与之相关的所有历史记录。这将使我保留HEAD的内容和影响这些文件的提交历史记录。但是,我还没有找到一种方法来实现这一点(孤立HEAD不能帮助,因为它无法保留历史记录)。

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


1
过去重命名的文件怎么办?在重命名发生时剥离历史记录还是保留重命名(并跟踪重命名之前的不同文件名)? - knittl
1
好的观点。我更倾向于保留重命名之前的历史记录,因此需要一些额外的文件,这是可以接受的。 - Luke Bennett
3个回答

7
以下是您能使用 git filter-branch 命令来删除所有不需要的文件的方法:

下面是利用 git filter-branch 命令来移除不需要的所有文件的步骤:

  1. Get a list of the filenames that you don't want to appear in the history both the old names and the new names in case of renames. For example put them in a file called toberemoved.txt

  2. Run git filter-branch like this:

    $ 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

在git log的--raw模式下,$6是提交中受影响的文件名。

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

也许其他人可以帮忙找出已跟踪文件的先前名称,以防重命名。


谢谢您的新回答。我认为我们已经接近了,我之前没有想到使用catfilter-branch。然而,我仍然不明白如何生成文件列表,因为我只对删除不在工作目录中的文件感兴趣(因此无法轻松列出)。还有其他想法吗? - Luke Bennett
我已更新答案,包括获取已删除文件列表的命令。 - holygeek
不错的答案。我从一行代码中删除了“Deleted:”,以获取列表。但是,在使用该列表时,由于某种原因,git filter-branch命令中的bash语法无法正常工作。因此,我改用了git filter-branch --tree-filter“cat $HOME/toberemoved.txt | xargs -I{} rm -f {}”(请注意,toberemoved.txt需要在版本控制之外的目录下。这可能也导致了“cat toberemoved.txt”语法的问题,但我还没有检查过)。 - jaimedash

3

我做过几次这样的事情——从单个文件中提取提交并创建新的存储库。 大致步骤如下:

$ 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,看哪个更适合工作),删除不感兴趣的文件的整个块,并在差异块标题中修复名称(如果重命名)。
接下来,我会在一个新的git存储库上使用git am应用这些补丁。如果出现问题,我会删除新的git存储库并再次编辑补丁,重复git am。
我从10开始计数的原因是因为我懒得在补丁序列前面添加前导0,并且对于超过99的提交,我只从99开始。

你可以使用 $(printf "%02d" $c).patch 来添加前导零。 - jfs
谢谢提醒。从现在开始我必须更经常地使用printf。 - holygeek
谢谢...但是这不是基于文件的工作吗?正如我在问题中所说的,我知道如何在每个文件上执行此操作,但是对于太多的文件来说,这并不实际。或者我可能误解了这里发生的事情? - Luke Bennett
那么在这种情况下,您可以使用git filter-branch。我稍后会把它作为另一个答案放出来。 - holygeek

3

帮助第二个回答:

"也许其他人可以介绍一下如何在重命名的情况下找出已跟踪文件的先前名称。"

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接