3

我正在尝试使用filter-branch. 我之前成功地使用过这个命令,但我目前在特定的边缘情况下遇到了麻烦。

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

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

Git 日志

详细地说,这是我的回购的基本表示:

----- 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 过滤器分支

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

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. 另一种方法可能是做类似这里描述的事情。去引用: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.

希望有人以前遇到过这个问题并且可以提供他们的专业知识。

更新

我要删除的文件总共约为 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>

e6b712b的孩子在哪里B

在这一点上,我假设一切正常,所以我对我的存储库进行了本地文件系统克隆来测试它:

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

对象的数量和包文件的大小减少了非常少 - 我不知道为什么。通过git count-objects -v针对原始存储库filter-branch运行与针对它运行的存储库:

原始存储库:

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

filter-branched 和文件系统克隆的存储库:

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

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

4

2 回答 2

1

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

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

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filenames' BR --not A~1

这将从A(通过修剪A父级的分支)重写到分支的当前尖端BR。但是,即使它们被更新的较小版本替换,它也会从所有这些提交中删除文件。为了只删除它们直到 commit B,您可以像这样展开过滤器脚本:

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

这使用 rev-list 列出当前正在重写的提交之后的所有修订,直到 的子级B,计算这些行,并且仅git rm当一个或多个修订落在该范围内时(当 时$GIT_COMMIT == B,将打印一行 - 因此需要使用 ) 的子节点B

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

于 2013-02-12T17:22:42.387 回答
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)
于 2013-02-12T16:41:00.047 回答