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

时间:2010-01-20 11:18:48

标签: git version-control git-rebase git-rewrite-history

偶尔我会把一个DVD-rip放到一个网站项目中,然后不小心git commit -a -m ...,而且,zap,回购邮件被2.2演出臃肿了。下次我做了一些编辑,删除了视频文件,并提交了所有内容,但是历史记录中的压缩文件仍然存储在存储库中。

我知道我可以从这些提交中启动分支并将一个分支重新绑定到另一个分支。但是我该如何将两个提交合并在一起,以便大文件在历史记录中没有显示并在垃圾收集过程中被清除?

20 个答案:

答案 0 :(得分:509)

如果您向其他开发者发布了历史记录,那么您想要做的事情具有很强的破坏性。修复历史记录后,请参阅“Recovering From Upstream Rebase” in the git rebase documentation了解必要的步骤。

您至少有两个选项:git filter-branch和交互式rebase,两者都在下面解释。

使用git filter-branch

我从Subversion导入的庞大二进制测试数据中遇到了类似问题,并写了removing data from a git repository

说你的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开关,我们可以看到与每次提交相关的树修改。

在“粗心”提交(其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发行版上运行,请指定tree in /dev/shm will result in faster execution
  • --index-filter是主要事件,并在历史记录的每一步都针对索引运行。您想要在找到它的地方删除oops.iso,但它并不存在于所有提交中。命令git rm --cached -f --ignore-unmatch oops.iso会在DVD-rip存在时删除,否则不会失败。
  • --tag-name-filter介绍了如何重写标记名称。 cat的过滤器是标识操作。您的存储库(如上面的示例)可能没有任何标记,但我包含此选项以实现完全的通用性。
  • --指定git filter-branch
  • 的选项结束 {li> --all关注--是所有裁判的简写。您的存储库(如上面的示例)可能只有一个ref(master),但我将此选项包含在内以便完全通用。

经过一番翻腾,现在的历史是:

$ 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,并且“删除DVD-rip”提交不再在主分支上。标记为refs/original/refs/heads/master的分支包含您的原始提交,以防您犯了错误。要将其删除,请按照“Checklist for Shrinking a Repository.”

中的步骤操作
$ 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:///...克隆网址复制对象,而不是仅创建硬链接。

现在你的历史是:

$ 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对象名称(“索引”和“管理页面”)保持不变,因为筛选操作未修改这些提交。 “粗心”丢失oops.iso和“登录页面”获得了一个新的父级,因此他们的SHA1 发生了变化。

交互式rebase

有以下历史:

$ 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
# ...

也就是说,我们删除了“删除DVD-rip”这一行,并将“粗心”的操作更改为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

正如消息告诉我们的那样,我们正处于要编辑的“粗心”提交中,因此我们运行了两个命令。

$ 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

这就是你想要的。

答案 1 :(得分:503)

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

仔细遵循usage instructions,核心部分就是这样:

$ 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-50x快,并且通常更容易使用。

完全披露:我是BFG Repo-Cleaner的作者。

答案 2 :(得分:146)

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

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

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

请参阅this link

答案 3 :(得分:50)

(我在这个问题上看到的最好的答案是:https://stackoverflow.com/a/42544963/714112,这里复制了,因为这个帖子在谷歌搜索排名中看起来很高但是其他的没有)

一个极快的贝壳单线

此shell脚本显示存储库中的所有blob对象,从最小到最大排序。

对于我的样本仓库,它比快100倍比这里找到的其他仓库更快。 在我信赖的Athlon II X4系统上,它只需一分钟就可以处理 Linux内核存储库及其中的5,622,155个对象

基本脚本

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

当您运行上面的代码时,您将获得良好的人类可读输出,如下所示:

a

快速删除文件

假设您想要从b可以访问的每个提交中删除文件HEADgit filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD ,您可以使用此命令:

{{1}}

答案 4 :(得分:33)

这些命令适用于我的情况:

git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --prune-empty --tag-name-filter cat -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

与上述版本略有不同。

对于那些需要将其推送到github / bitbucket的人(我只用bitbucket测试过这个):

# WARNING!!!
# this will rewrite completely your bitbucket refs
# will delete all branches that you didn't have in your local

git push --all --prune --force

# Once you pushed, all your teammates need to clone repository again
# git pull will not work

答案 5 :(得分:28)

在SO中尝试了几乎每个答案之后,我终于找到了这个宝石,它快速删除并删除了我的存储库中的大文件并允许我再次同步:http://www.zyxware.com/articles/4027/how-to-delete-files-permanently-from-your-local-and-remote-git-repositories

CD到您当地的工作文件夹并运行以下命令:

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch FOLDERNAME" -- --all

将FOLDERNAME替换为您要从给定git存储库中删除的文件或文件夹。

完成此操作后,请运行以下命令以清理本地存储库:

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

现在将所有更改推送到远程存储库:

git push --all --force

这将清理远程存储库。

答案 6 :(得分:15)

此线程中有很好的答案,但与此同时,许多答案已过时。不再建议使用git-filter-branch,因为它在大型存储库上难以使用且速度非常慢。

git-filter-repo更快,更易于使用。

git-filter-repo是一个Python脚本,可从github:https://github.com/newren/git-filter-repo获得。安装后,它看起来像一个常规的git命令,可以由git filter-repo调用。

您只需要一个文件:Python3脚本git-filter-repo。将其复制到PATH变量中包含的路径。在Windows上,您可能必须更改脚本的第一行(请参阅INSTALL.md)。您需要在系统上安装Python3,但这并不重要。

首先您可以运行

git filter-repo --analyze

这可以帮助您确定下一步要做什么。

您可以在任何地方删除DVD-rip文件:

git filter-repo --invert-paths --path-match DVD-rip
 

Filter-repo非常快。 filter-repo在4分钟内完成了一项任务,该任务在我的计算机上通过filter-branch完成了约9小时。您可以使用filter-repo做更多的事情。请参阅该文档。

警告:在存储库副本上执行此操作。 filter-repo的许多操作无法撤消。 filter-repo将更改所有修改的提交(当然)及其所有后代的提交哈希值,直到最后一次提交!

答案 7 :(得分:9)

请注意,此命令可能非常具有破坏性。如果更多的人正在处理回购,他们都必须拉新树。如果您的目标不是减小尺寸,则不需要三个中间命令。因为过滤器分支创建了已删除文件的备份,并且它可以在那里停留很长时间。

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --prune
$ git push origin master --force

答案 8 :(得分:9)

git filter-branch --tree-filter 'rm -f path/to/file' HEAD 虽然我遇到了here描述的同样的问题,但是我按照this suggestion解决了这个问题。

pro-git书中有rewriting history的完整章节 - 请查看filter-branch/Removing a File from Every Commit部分。

答案 9 :(得分:8)

如果您知道您的提交是最近的,而不是通过整个树执行以下操作: git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD

答案 10 :(得分:6)

这会将其从您的历史记录中删除

git filter-branch --force --index-filter 'git rm -r --cached --ignore-unmatch bigfile.txt' --prune-empty --tag-name-filter cat -- --all

答案 11 :(得分:5)

我遇到了一个bitbucket帐户,我不小心存储了我网站的巨大* .jpa备份。

git filter-branch --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch MY-BIG-DIRECTORY-OR-FILE' --tag-name-filter cat -- --all

使用相关文件夹重新标记MY-BIG-DIRECTORY以完全重写您的历史记录(包括标记)。

来源:http://naleid.com/blog/2012/01/17/finding-and-purging-big-files-from-git-history

答案 12 :(得分:3)

根据GitHub Documentation,只需执行以下步骤:

  1. 摆脱大文件

选项1:您不想保留大文件:

rm path/to/your/large/file        # delete the large file

选项2:您要将大文件保留在未跟踪的目录中

mkdir large_files                       # create directory large_files
touch .gitignore                        # create .gitignore file if needed
'/large_files/' >> .gitignore           # untrack directory large_files
mv path/to/your/large/file large_files/ # move the large file into the untracked directory
  1. 保存更改
git add path/to/your/large/file   # add the deletion to the index
git commit -m 'delete large file' # commit the deletion
  1. 从所有提交中删除大文件
git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch path/to/your/large/file" \
  --prune-empty --tag-name-filter cat -- --all
git push <remote> <branch>

答案 13 :(得分:3)

这对我来说非常合适:在git扩展中:

右键单击选定的提交:

将当前分支重置到此处:

硬重置;

令人惊讶的是,没有其他人能够给出这个简单的答案。

reset current branch to here

hard reset

答案 14 :(得分:3)

我基本上做了这个答案: https://stackoverflow.com/a/11032521/1286423

(对于历史,我会在这里复制粘贴)

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --prune
$ git push origin master --force

它不起作用,因为我喜欢重命名和移动很多东西。所以一些大文件在已重命名的文件夹中,我认为gc无法删除对这些文件的引用,因为在tree对象中引用了这些文件。 我真正杀死它的最终解决方案是:

# First, apply what's in the answer linked in the front
# and before doing the gc --prune --aggressive, do:

# Go back at the origin of the repository
git checkout -b newinit <sha1 of first commit>
# Create a parallel initial commit
git commit --amend
# go back on the master branch that has big file
# still referenced in history, even though 
# we thought we removed them.
git checkout master
# rebase on the newinit created earlier. By reapply patches,
# it will really forget about the references to hidden big files.
git rebase newinit

# Do the previous part (checkout + rebase) for each branch
# still connected to the original initial commit, 
# so we remove all the references.

# Remove the .git/logs folder, also containing references
# to commits that could make git gc not remove them.
rm -rf .git/logs/

# Then you can do a garbage collection,
# and the hidden files really will get gc'ed
git gc --prune --aggressive

我的回购(.git)从32MB变为388KB,即使过滤分支也无法清理。

答案 15 :(得分:3)

您可以使用branch filter命令执行此操作:

git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD

答案 16 :(得分:1)

'agree': new FormControl(true, Validators.pattern('true'))是一个功能强大的命令,您可以使用它从提交历史记录中删除一个大文件。该文件将保留一段时间,Git将在下一个垃圾回收中将其删除。 以下是deleteing files from commit history中的完整过程。为了安全起见,它首先在新分支上运行命令:

git filter-branch

答案 17 :(得分:1)

当您遇到此问题时,git rm是不够的,因为git会记住该文件在我们的历史记录中存在过一次,因此会保留对它的引用。

更糟糕的是,变基也不容易,因为对blob的任何引用都会阻止git垃圾收集器清理空间。这包括远程引用和reflog引用。

我将git forget-blob放在一起,这是一个尝试删除所有这些引用的小脚本,然后使用git filter-branch重写分支中的每个提交。

一旦你的blob被完全取消引用,git gc将摆脱它

使用非常简单git forget-blob file-to-forget。你可以在这里获得更多信息

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

由于Stack Overflow和一些博客条目的答案,我把它放在一起。给他们的信用!

答案 18 :(得分:1)

使用Git Extensions,这是一个UI工具。它有一个名为“查找大文件”的插件,它可以在存储库中找到大量文件并允许它们被隐藏起来。

在使用此工具之前不要使用'git filter-branch',因为它无法找到'filter-branch'删除的文件(Altough'filter-branch'不会从存储库中完全删除文件打包文件)。

答案 19 :(得分:0)

除了git filter-branch(缓慢而纯净的git解决方案)和BFG(更容易且非常高效)之外,还有另一种性能良好的过滤工具:

https://github.com/xoofx/git-rocket-filter

根据其描述:

git-rocket-filter的用途类似于命令git-filter-branch,同时提供以下独特功能:

  • 快速重写提交和树(从x10到x100的顺序)。
  • 内置支持同时使用--keep(保留文件或目录)进行白名单和使用--remove选项进行黑名单。
  • 使用类似.gitignore的模式进行树过滤
  • 用于提交过滤和树过滤的快捷C#脚本
  • 支持按文件/目录模式进行树过滤的脚本编制
  • 自动修剪空/未更改的提交,包括合并提交