仅在两次提交之间从Git历史记录中删除文件

时间:2013-02-12 16:33:12

标签: git

我正在尝试使用filter-branch从我的历史记录中删除大量大文件。我之前已成功使用此命令,但我目前遇到特殊边缘情况的问题。

问题是这些大型文件从未真正删除,但 被具有相同路径的较小版本取代

据我所知,我相信我有一个独特的问题。

Git Log

详细说明,这是我的回购的基本代表:

----- A ------ B ----------- HEAD

其中:

A is the commit where the large files were introduced
B is the commit (about 30 later) where the large files were replaced with smaller ones
HEAD is thousands of commits forward of B (~2 years of active development)

Git Filter-Branch

理论上,我应该能够做到这样的事情:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filenames' <parent of A>..B 

我认为我应该使用<parent of A>因为filter-branch不具有包容性。 (我不确定我是否也需要使用B的父母,但这是我现在最不担心的事情。)

运行此操作会给我一个错误:

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch filenames' <parent of A>..B 
Which ref do you want to rewrite?

所以我在命令的末尾包含了--glob="refs/heads/master*",这似乎可以解决问题(source)。

执行完成后,文件已被完全删除 - 似乎git忽略了我指定的上限。

所以我想知道这种方法是否可行?

替代方法

我认为我应该列出一些其他的想法,以便将可能的答案集中在解决问题上。

  1. 实用的方法是在HEAD提交文件名更改,然后运行git filter-branch ... HEAD。但是,我的存储库在活动开发中有许多分支,我相信这种方法会非常混乱。
  2. 另一种方法可能是做类似here所描述的事情。引用:create a temporary branch to point at HEAD^, filter-branch it, then add a graft to stitch the remaining commit on top of it, then filter-branch HEAD and then remove the graft.
  3. 希望有人之前遇到过这个问题并且可以提供他们的专业知识。

    更新

    我要删除的文件总数 ~500MB 所以我很想知道要删除它们!它们在我加入公司之前就已经提交了,并且是我们从内部Mercurial服务器迁移到GitHub的剩余部分(我想将500MB推送到内部服务器将比GitHub更不明显......)。

    更新2

    我一直在关注twalberg的第二个答案(我想我正在以正确的方式使用它):

    git filter-branch --index-filter '(( $(git rev-list <SHA-of-child-of-B> --not $GIT_COMMIT | wc -l) > 0 )) && git rm --cached --ignore-unmatch <filenames>' 
    

    这会产生我期望的那种输出:

    ...
    Rewrite dc8a4b29463bfa43c2f3efe0c6e5a29a5cc6e0ef (1071/5680)rm 'file1'
    rm 'file2'
    rm 'file3'
    rm 'file4'
    ...
    

    在结束(预期?)错误之前:

    Rewrite e6b712b57257e2edd0bb9fbbac59e4c9d7b5aa79 (1072/5680)index filter failed: (( $(git rev-list e6b712b --not $GIT_COMMIT | wc -l) > 0 )) && git rm -rf --ignore-unmatch <filename>
    

    其中e6b712bB的孩子。

    此时我假设一切正常,所以我做了我的存储库的本地文件系统克隆来测试它:

    git clone file://<repo> <new repo>
    

    对象数量和packfile大小减少了很少 - 我不知道为什么。通过针对原始存储库运行git count-objects -v而不是针对它运行filter-branch的存储库:

    原始存储库:

    count: 0
    size: 0
    in-pack: 106640
    packs: 1
    size-pack: 815512
    prune-packable: 0
    garbage: 0
    

    filter-branch ed和文件系统克隆存储库:

    count: 0
    size: 0
    in-pack: 96165
    packs: 1
    size-pack: 793656
    prune-packable: 0
    garbage: 0
    

    我真的不确定为什么这仍然不起作用 - 也许我没有正确地遵循建议的答案?

2 个答案:

答案 0 :(得分:1)

不幸的是,如果你真的想要从你的存储库中删除这些对象(而不是简单地从当前版本和将来的版本中删除它们),filter-branch就是这样做的,如果你要重写commit { {1}},每次提交到其历史记录中包含A的每个分支头部也必须重写,因为提交的提交哈希值取决于该提交的每个父级的提交哈希值。如果您不重写包含A的所有分支,那么这些对象仍然是您可访问历史记录中某些提交的合法部分,并且它们不会被修剪。

对于其历史记录中包含A的每个分支BR,这应该有效:

A

将从git filter-branch --index-filter 'git rm --cached --ignore-unmatch filenames' BR --not A~1 (通过修剪A父级的分支)重写到分支A的当前提示。它会删除所有这些提交中的文件,即使它们被较新的较小版本替换。要将它们删除只提交BR,您可以像这样展开过滤器脚本:

B

这使用rev-list列出当前正在重写的提交之后的所有修订,直到... --index-filter '(( $(git rev-list <SHA-of-child-of-B> --not $GIT_COMMIT | wc -l) > 0 )) && git rm ...' ... 的子项,计算这些行,并且只有B如果一个或多个修订属于该范围(当git rm时,将打印一行 - 因此需要使用$GIT_COMMIT == B的孩子。

即使对于单个分支来说,这是一个相当大的变化,如果你有许多在B之后或之后产生的分支,那么很多工作,所以你必须决定它是否最终值得,或者如果你只需要一个更大的磁盘(你没有提到这些文件的大小)。

答案 1 :(得分:0)

A     is the commit where the large files were introduced
B     is the commit (about 30 later) where the large files were replaced 
      with smaller ones
HEAD  is thousands of commits forward of B (~2 years of active development)

你说过这个我会强烈反对filter-branch,因为我相信它会重写2年的承诺SHA。也许另一个解决方案是git revert

git revert SHA_A..SHA_B
    Revert the changes done by commits from commit SHA_A (included) to
    SHA_B (included)