协慌网

登录 贡献 社区

如何从 Git 存储库中的提交历史记录中删除 / 删除大文件?

我不小心将 DVD-rip 放到了一个网站项目中,然后不小心git commit -a -m ... ,然后,zap 的回购膨胀了 2.2 个演出。下次我进行一些编辑,删除视频文件并提交所有内容,但压缩文件仍在历史记录中。

我知道我可以从这些提交开始分支,并将一个分支重新建立到另一个分支。但是,我应该怎么做才能将 2 个提交合并在一起,以便大文件不显示在历史记录中,并在垃圾回收过程中清除?

答案

使用BFG Repo-Cleaner git-filter-branch一种更简单,更快的替代方法,专门用于从 Git 历史记录中删除不需要的文件。

认真遵循使用说明,核心部分就是这样:

$ java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

任何大小超过 100MB 的文件(不在您的最新提交中)都将从 Git 存储库的历史记录中删除。然后,您可以使用git gc清除失效的数据:

$ git gc --prune=now --aggressive

BFG 通常比运行git-filter-branch 至少快 10-50倍,并且通常更易于使用。

完全公开:我是 BFG Repo-Cleaner 的作者。

如果您已将历史记录发布给其他开发人员,那么您想要做的就是极具破坏性的。有关修复历史记录后的必要步骤,请参见git rebase文档中的 “从上游 Rebase 中恢复”。

您至少有两个选择: git filter-branch和交互式 rebase,这两个都在下面说明。

使用git filter-branch

我从 Subversion 导入中获取大量的二进制测试数据时遇到了类似的问题,并写了有关从 git 存储库中删除数据的信息

假设您的 git 历史记录是:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

请注意, git lola是非标准的但非常有用的别名。使用--name-status开关,我们可以看到与每次提交相关联的树修改。

在 “Careless” 提交(SHA1 对象名称为 ce36c98)中,文件oops.iso是偶然添加的 DVD-rip,并在下一个提交 cb14efd 中删除。使用上述博客文章中描述的技术,要执行的命令是:

git filter-branch --prune-empty -d /dev/shm/scratch \
  --index-filter "git rm --cached -f --ignore-unmatch oops.iso" \
  --tag-name-filter cat -- --all

选项:

  • --prune-empty删除由于过滤操作而变为空的提交(,不更改树)。在典型情况下,此选项会产生更清晰的历史记录。
  • -d命名一个临时目录,该目录尚不用于构建过滤的历史记录。如果您在现代 Linux 发行版上运行,则/dev/shm指定树将加快执行速度
  • --index-filter是主要事件,在历史记录的每一步都针对索引运行。您希望删除oops.iso无论它在哪里找到,但并非在所有提交中都存在。 git rm --cached -f --ignore-unmatch oops.iso删除存在的 DVD-rip,否则不会失败。
  • --tag-name-filter描述了如何重写标签名称。 cat的过滤器是标识操作。您的存储库与上面的示例一样,可能没有任何标签,但是出于全面考虑,我包括了此选项。
  • -- git filter-branch选项的结尾
  • --all以下--是所有裁判的简写。像上面的示例一样,您的存储库可能只有一个 ref(主文件),但是出于全面考虑,我包括了此选项。

经过一番搅动,现在的历史是:

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
|
| * f772d66 (refs/original/refs/heads/master) Login page
| | A   login.html
| * cb14efd Remove DVD-rip
| | D   oops.iso
| * ce36c98 Careless
|/  A   oops.iso
|   A   other.html
|
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

请注意,新的 “Careless” 提交仅添加other.html ,并且 “Remove DVD-rip” 提交不再位于 master 分支上。标为refs/original/refs/heads/master的分支包含您的原始提交,以防万一您出错。要删除它,请按照“收缩存储库的清单” 中的步骤进行操作。

$ git update-ref -d refs/original/refs/heads/master
$ git reflog expire --expire=now --all
$ git gc --prune=now

作为一种更简单的选择,请克隆存储库以丢弃不需要的位。

$ cd ~/src
$ mv repo repo.old
$ git clone file:///home/user/src/repo.old repo

使用file:///...克隆 URL 复制对象而不是仅创建硬链接。

现在您的历史记录是:

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

前两个提交(“索引” 和 “管理页面”)的 SHA1 对象名称保持不变,因为过滤操作未修改这些提交。 “Careless” 丢失了oops.iso ,“Login page” 有了新的父代,因此其 SHA1确实发生了变化。

互动基础

具有以下历史:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

您想oops.iso ,就好像您从未添加过它一样,然后 “删除 DVD-rip” 对您来说毫无用处。因此,我们计划进行交互式基础调整的是保留 “管理页面”,编辑 “无忧无虑” 并丢弃 “删除 DVD-rip”。

运行$ git rebase -i 5af4522启动具有以下内容的编辑器。

pick ce36c98 Careless
pick cb14efd Remove DVD-rip
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

执行我们的计划,我们将其修改为

edit ce36c98 Careless
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
# ...

也就是说,我们用 “Remove DVD-rip” 删除该行,并将对 “Careless” 的操作更改为edit而不是pick

保存退出编辑器后,在命令提示符处显示以下消息。

Stopped at ce36c98... Careless
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

消息告诉我们,我们处于我们要编辑的 “Careless” 提交中,因此我们运行两个命令。

$ git rm --cached oops.iso
$ git commit --amend -C HEAD
$ git rebase --continue

第一个从索引中删除有问题的文件。第二个将 “Careless” 修改或修改为更新的索引,并且-C HEAD指示 git 重用旧的提交消息。最后, git rebase --continue继续进行其余的 rebase 操作。

这提供了以下历史记录:

$ git lola --name-status
* 93174be (HEAD, master) Login page
| A     login.html
* a570198 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

这就是你想要的。

为什么不使用这个简单但功能强大的命令?

git filter-branch --tree-filter 'rm -f DVD-rip' HEAD

--tree-filter选项在每次检出项目后运行指定的命令,然后重新提交结果。在这种情况下,请从每个快照中删除一个名为 DVD-rip 的文件,无论该文件是否存在。

如果您知道哪个提交引入了巨大的文件(例如 35dsa2),则可以将 HEAD 替换为 35dsa2..HEAD 以避免重写过多的历史记录,从而避免在尚未推送的情况下分散提交。 @ alpha_989 提供的此评论似乎太重要了,不能在此处忽略。

请参阅此链接