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

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

在那个实例中,git回购的总大小约为6MB,而实际代码(除了那个文件夹之外的所有代码)仅为300 KB左右。

现在我在寻找的是一种方法,从git的历史记录中去除该包文件夹的细节,这样如果有人克隆它,他们就不必下载6mb的历史记录,因为他们最后一次提交时得到的唯一实际文件将是300KB。

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

从git存储库中删除文件(历史) http://help.github.com/remove-sensitive-data/ https://gist.github.com/1588371

Gist似乎在运行脚本后工作,它显示它摆脱了那个文件夹,之后它显示50个不同的提交被修改。但它不让我输入那个代码。当我试图推动它时,它说分支是最新的,但显示50个提交被修改了git状态。另外两种方法也不起作用。

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

我想要澄清的是,如果有一种方法不仅摆脱提交历史(这是我认为发生的唯一一件事),而且那些文件git保持假设一个想要回滚。

让我们说一个解决方案是提出了这个,并应用于我的本地主机,但不能复制到GitHub回购,是否有可能克隆该回购,回滚到第一次提交执行技巧,并推动它(或者这意味着git仍然会有所有这些提交的历史?——又名。6 mb)。

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

我该怎么做呢?


警告:git filter-branch不再被正式推荐使用。官方推荐使用git-filter-repo;详情见André Anjos的回答。


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

这是一个从历史记录中删除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,没有-r, rm不会删除文件夹),不给用户提示(-f)。添加的——prune-empty删除无用(不改变任何东西)递归提交。

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

其余的命令相对简单。


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

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


我发现在其他答案中使用的——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

除了上面流行的答案之外,我还想补充一些关于windows系统的注意事项。命令

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

工作完美,无需任何修改!因此,您不能使用Remove-Item, del或其他任何东西来代替rm -rf。 如果您需要指定文件或目录的路径,请使用。/path/to/node_modules这样的斜杠


我发现最好和最准确的方法是下载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

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

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

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


对于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

对于这个问题,最新的答案似乎是不要直接使用filter-branch(至少git本身不再推荐它了),并将这项工作交给外部工具。目前特别推荐使用git-filter-repo。该工具的作者提供了关于为什么直接使用filter-branch会导致问题的论据。

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

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

显然,这个工具比这更强大。您可以通过作者,电子邮件,参考名称和更多应用过滤器(完整的manpage在这里)。此外,它是快速的。安装很容易——它以多种格式分发。


对于复印机(从这里):

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