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


318

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

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

在那种情况下,总git repo的大小约为6MB,而实际代码(除该文件夹以外的所有代码)仅为300 KB左右。

现在,我最后要寻找的是一种从git的历史记录中删除该软件包文件夹的详细信息的方法,因此,如果有人对其进行了克隆,则无需下载价值6mb的历史记录,而只有这些文件才可以获取截至上一次提交将为300KB。

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

Gist似乎在运行脚本后在哪里工作,它表明它删除了该文件夹,并表明修改了50个不同的提交。但这并没有让我推送该代码。当我尝试推动它时,它说Branch up to date但显示对50个提交进行了修改git status。其他2种方法也无济于事。

现在,即使显示它摆脱了该文件夹的历史记录,当我在本地主机上检查该存储库的大小时,它仍约为6MB。(我还删除了该refs/original文件夹,但没有看到存储库大小的变化)。

我要澄清的是,是否有一种方法不仅可以消除提交历史记录(这是我认为唯一发生的事情),而且可以摆脱那些git一直假设要回滚的文件。

可以说为此提供了一个解决方案,该解决方案已在我的本地主机上应用,但无法复制到该GitHub存储库,是否可以克隆该存储库,回滚到第一个提交执行该技巧并将其推送(或者这意味着git将还有所有这些提交的历史吗?-又名6MB)。

我的最终目标是从根本上找到从git摆脱文件夹内容的最佳方法,以便用户不必下载6MB的内容,而仍然可以拥有从未触及modules文件夹的其他提交(这很漂亮很多))在git的历史中。

我怎样才能做到这一点?


3
如果以下任何一个答案解决了您的问题,则也许您应该考虑接受一个作为您问题的答案。meta.stackexchange.com/questions/5234/…–
starbeamrainbowlabs

Answers:


556

如果您要在此处复制粘贴代码:

这是一个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实际做什么:

第一行在--tree-filterHEAD(当前分支)所在的同一树()上遍历所有引用,并运行命令rm -rf node_modules。该命令删除node_modules文件夹(-r,不带-rrm不会删除文件夹),而没有提示用户(-f)。添加的--prune-empty删除无用(不更改任何内容)以递归方式提交。

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

其余命令相对简单。


3
附带说明:我曾经git count-objects -v检查过是否确实删除了文件,但是在再次克隆存储库之前,存储库的大小保持不变。Git保留了我认为的所有原始文件的副本。
Davide Icardi,2015年

4
如果使用的是非古旧的git,则应该阅读--force-with-lease而不是--force
Griwes '16

4
这些命令在Windows上均不起作用。或至少不是Windows 10,请发布“剪切并粘贴”的操作系统
David David

3
对于Windows 10用户,这在Windows的Bash(我使用Ubuntu)下运行良好
Andrej Kyselica '17

3
我用Windows Shell和git bash尝试了一下,但是没有用。第一条命令通过,第二条命令失败!
Mohy Eldeen '17

240

我发现--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

3
您能解释一下为什么这么快吗?
knocte

7
@knocte:来自docs(git-scm.com/docs/git-filter-branch)。“ --index-filter:...类似于树过滤器,但不检出树,这使它变得更快”
Lee Netherton

23
为什么这不是公认的答案?太彻底了。
疯狂物理学家,2015年

2
如果在Windows中执行此操作,则需要双引号而不是单引号。
克里斯·莫尼斯

12
传递--quietgit rm上面加快我至少改写了4倍
ctusch

46

除了上面流行的答案外我还要为Windows系统添加一些注意事项。命令

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
  • 无需任何修改即可完美运行!因此,你不能使用Remove-Itemdel或其他任何东西来代替rm -rf

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


如果目录包含,则在Windows上将无法使用。(点)的名称。
Corneliu Serediuc

4
我找到了解决方案。对rm命令使用双引号,例如:“ rm -rf node.modules”。
Corneliu Serediuc

23

我发现最好,最准确的方法是下载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  # i.e. 'node_modules' in other examples
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

1
非常简单:)如果您想确保仅删除特定文件夹,这将有所帮助:stackoverflow.com/questions/21142986/…–
emjay

8

似乎最新的解决方案是filter-branch直接使用(至少git本身不再推荐使用),并将其工作推迟到外部工具上进行。特别是,目前推荐使用git-filter-repo。该工具的作者提供了有关为何filter-branch直接使用会导致问题的争论。

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

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

显然,该工具不仅功能强大。您可以按作者,电子邮件,refname等进行过滤(完整的手册页在此处)。此外,它速度很快。安装很容易-它以多种格式分发


不错的工具!在Ubuntu 20.04上运行良好,您可以这样做,pip3 install git-filter-repo因为它仅是stdlib并且不安装任何依赖项。在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
kubanczyk

7

测试完命令后,只需在注释中添加命令(用于复制粘贴解决方案)即可完成复制和粘贴食谱:

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 /”行


为什么你会然后取出node_modules.gitignore?这样,他们可能会再次被意外犯下??
Adamski

1
它不会从gitignore中删除,而是添加到了gitignore中。提交消息说“ git history”,而不是“ gitignore” :)
Danny Tuppeny

但评论说,随后可以删除node_modules.gitignore
zavr19年

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

3

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.