从 git/GitHub 的历史记录中删除文件夹及其内容

Posted

技术标签:

【中文标题】从 git/GitHub 的历史记录中删除文件夹及其内容【英文标题】:Remove folder and its contents from git/GitHub's history 【发布时间】:2012-04-21 12:13:53 【问题描述】:

我在我的 GitHub 帐户上处理一个存储库,这是我偶然发现的一个问题。

Node.js 项目的文件夹中安装了一些 npm 包 包在node_modules文件夹中 将该文件夹添加到 git 存储库并将代码推送到 github(当时没有考虑 npm 部分) 意识到您实际上并不需要该文件夹成为代码的一部分 删除那个文件夹,推送它

当时,整个 git 存储库的大小约为 6MB,而实际代码(除该文件夹外的所有代码)仅约为 300 KB

现在我最终要寻找的是一种从 git 历史记录中删除该包文件夹详细信息的方法,因此如果有人克隆它,他们不必下载 6mb 的历史记录,其中只有他们唯一的实际文件将在最后一次提交时获得 300KB。

我为此查找了可能的解决方案并尝试了这两种方法

Remove file from git repository (history) http://help.github.com/remove-sensitive-data/ https://gist.github.com/1588371

Gist 似乎在运行脚本后有效,它显示它摆脱了该文件夹,然后它显示 50 个不同的提交被修改。但它并没有让我推送那个代码。当我尝试推送它时,它显示Branch up to date,但显示有50 个提交在git status 上被修改。其他两种方法也没有帮助。

现在,尽管它显示它已经摆脱了该文件夹的历史记录,但当我在本地主机上检查该 repo 的大小时,它仍然在 6MB 左右。 (我还删除了refs/original文件夹,但没有看到 repo 大小的变化)。

我要澄清的是,如果有办法摆脱提交历史记录(这是我认为唯一发生的事情),而且 git 一直假设想要回滚的那些文件。

假设为此提出了一个解决方案并应用于我的本地主机但无法复制到该 GitHub 存储库,是否可以克隆该存储库,回滚到第一次提交执行技巧并推送它(或者这是否意味着那个 git 仍然会有所有这些提交的历史吗? - 又名 6MB)。

我的最终目标基本上是找到从 git 中删除文件夹内容的最佳方法,这样用户就不必下载 6MB 的东西,并且仍然可能有其他从未触及模块文件夹的提交(这几乎是所有这些)在 git 的历史中。

我该怎么做?

【问题讨论】:

如果以下任何一个答案解决了您的问题,也许您应该考虑接受一个作为您问题的答案。 meta.stackexchange.com/questions/5234/… 最佳答案是:***.com/a/32886427/5973334 【参考方案1】:

警告:git filter-branch 是no longer officially recommended

如果你在这里复制粘贴代码:

这是一个从历史记录中删除 node_modules 的示例

git filter-branch --tree-filter "rm -rf node_modules" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

git 的实际作用:

第一行遍历与 HEAD(您的当前分支)相同的树 (--tree-filter) 上的所有引用,运行命令 rm -rf node_modules。此命令删除 node_modules 文件夹(-r,没有-rrm 不会删除文件夹),没有给用户提示(-f)。添加的--prune-empty 递归删除无用(不更改任何内容)提交。

第二行删除了对旧分支的引用。

其余的命令比较简单。

【讨论】:

附注:我使用git count-objects -v 检查文件是否实际被删除,但存储库的大小保持不变,直到我再次克隆存储库。 Git 保留了我认为的所有原始文件的副本。 对于非古代 git,这应该是 --force-with-lease,而不是 --force 这些命令都不适用于 Windows。或者至少不是 Windows 10,请发布“剪切和粘贴”工作的操作系统 对于 Windows 10 用户,这在 Bash for Windows 下运行良好(我使用的是 Ubuntu) 我用 windows shell 和 git bash 试过,但没有用。第一个命令通过,第二个命令失败!【参考方案2】:

我发现其他答案中使用的--tree-filter 选项可能非常慢,尤其是在具有大量提交的大型存储库中。

这是我使用 --index-filter 选项从 git 历史记录中完全删除目录的方法,它运行得更快:

# Make a fresh clone of YOUR_REPO
git clone YOUR_REPO
cd YOUR_REPO

# Create tracking branches of all branches
for remote in `git branch -r | grep -v /HEAD`; do git checkout --track $remote ; done

# Remove DIRECTORY_NAME from all commits, then remove the refs to the old commits
# (repeat these two commands for as many directories that you want to remove)
git filter-branch --index-filter 'git rm -rf --cached --ignore-unmatch DIRECTORY_NAME/' --prune-empty --tag-name-filter cat -- --all
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d

# Ensure all old refs are fully removed
rm -Rf .git/logs .git/refs/original

# Perform a garbage collection to remove commits with no refs
git gc --prune=all --aggressive

# Force push all branches to overwrite their history
# (use with caution!)
git push origin --all --force
git push origin --tags --force

您可以在gc 之前和之后检查存储库的大小:

git count-objects -vH

【讨论】:

你能解释一下为什么这要快得多吗? @knocte:来自文档 (git-scm.com/docs/git-filter-branch)。 "--index-filter: ... 类似于树过滤器,但不检查树,这使它更快" 为什么这不是公认的答案?太彻底了。 如果在 Windows 中这样做,你需要双引号而不是单引号。 --quiet 传递给上面的git rm 至少加快了我的重写速度4 倍。【参考方案3】:

似乎对此的最新答案是直接使用filter-branch(至少git本身不再推荐它),并将这项工作推迟到外部工具.目前特别推荐git-filter-repo。该工具的作者provides arguments 解释了为什么直接使用filter-branch 会导致问题。

上面用于从历史记录中删除dir 的大部分多行脚本可以重写为:

git filter-repo --path dir --invert-paths

显然,该工具的功能远不止于此。您可以按作者、电子邮件、参考名称等应用过滤器 (full manpage here)。此外,它快速。安装很容易 - 它是distributed in a variety of formats。

【讨论】:

不错的工具!在 Ubuntu 20.04 上运行良好,您只需 pip3 install git-filter-repo,因为它是 stdlib-only 并且不安装任何依赖项。在 Ubuntu 18 上,它与发行版的 git 版本 Error: need a version of git whose diff-tree command has the --combined-all-paths option 不兼容,但在 docker run -ti ubuntu:20.04 上运行它很容易 它只是工作,简单而优雅!谢谢推荐! 你是对的!但是,如果你能把答案和filter-repo 的信息分开,我的意思是,也许写下关于filter-repo 的所有信息替换filter-branch,然后写一个------- 运算符,然后给我们更多关于命令本身 - 例如--invert-paths 是什么。谢谢! 重要:如果您的目录不在顶层,您必须提供它的完整路径。目录/子目录 git: 'filter-repo' is not a git command. See 'git --help'.【参考方案4】:

除了流行的答案above,我想为 Windows 系统添加一些注释。命令

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD

无需任何修改即可完美运行!因此,您不得使用Remove-Itemdel 或其他任何东西来代替rm -rf

如果您需要指定文件或目录的路径,请使用 斜杠,例如 ./path/to/node_modules

【讨论】:

如果目录包含 . (点)在名称中。 我找到了解决方案。对 rm 命令使用双引号,如下所示:“rm -rf node.modules”。【参考方案5】:

我发现最好和最准确的方法是下载 bfg.jar 文件: https://rtyley.github.io/bfg-repo-cleaner/

然后运行命令:

git clone --bare https://project/repository project-repository
cd project-repository
java -jar bfg.jar --delete-folders DIRECTORY_NAME
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --mirror https://project/new-repository

如果您想删除文件,请改用 delete-files 选项:

java -jar bfg.jar --delete-files *.pyc

【讨论】:

非常简单 :) 如果您想让舒尔只删除特定文件夹,这将有所帮助:***.com/questions/21142986/… 但是当有多个文件夹与您要删除的特定文件夹同名时,使用BFG可能会遇到麻烦,即BFG不能接受--delete-folders的路径名。【参考方案6】:

完整的复制粘贴配方,只需在 cmets 中添加命令(用于复制粘贴解决方案),经过测试:

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

在此之后,您可以从 .gitignore 中删除“node_modules/”行

【讨论】:

你为什么要从.gitignore 中删除node_modules?这样他们就可以不小心再次犯下?? 它不会从 gitignore 中删除,而是添加到 gitignore 中。提交消息说“git history”,而不是“gitignore”:) 但是评论说你可以从.gitignore中删除node_modules 第二个问题...“在此之后,您可以从 .gitignore 中删除“node_modules/”行”答案中的这一行(答案...不是 git commit 消息)说您可以删除node_modules/... 但你为什么要这样做?【参考方案7】:

Windows 用户请注意使用" 而不是' 如果另一个备份已经存在,还添加了-f 以强制执行该命令。

git filter-branch -f --tree-filter "rm -rf FOLDERNAME" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo FOLDERNAME/ >> .gitignore
git add .gitignore
git commit -m "Removing FOLDERNAME from git history"
git gc
git push origin master --force

【讨论】:

【参考方案8】:

我在 Windows 上使用 git 从旧 C# 项目中删除了 bin 和 obj 文件夹。小心

git filter-branch --tree-filter "rm -rf bin" --prune-empty HEAD

它通过删除git install文件夹中的usr/bin文件夹破坏了git安装的完整性。

【讨论】:

【参考方案9】:

对于复制粘贴者(来自here):

git filter-repo --invert-paths --path PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA
echo "YOUR-FILE-WITH-SENSITIVE-DATA" >> .gitignore
git add .gitignore
git commit -m "Add YOUR-FILE-WITH-SENSITIVE-DATA to .gitignore"
git push origin --force --all

【讨论】:

以上是关于从 git/GitHub 的历史记录中删除文件夹及其内容的主要内容,如果未能解决你的问题,请参考以下文章

如何从我没有强制推送权限的主分支中删除包含其历史记录的文件(删除历史记录很重要)?

如何从 Git 历史记录中永久删除提交?

从历史记录中删除文件(磁盘空间不足)

从 Git 历史记录中删除大文件

从存储库历史记录中删除大文件后,Git 存储库仍然很大

markdown 从repo历史记录中删除大尺寸文件