我不小心将一个不需要的文件(filename.orig
在解决合并时)提交到了我的存储库中,但之前没有提交,直到现在我还没有注意到。我想从存储库历史记录中完全删除该文件。
是否有可能重写filename.orig
从未被添加到存储库的更改历史记录?
我不小心将一个不需要的文件(filename.orig
在解决合并时)提交到了我的存储库中,但之前没有提交,直到现在我还没有注意到。我想从存储库历史记录中完全删除该文件。
是否有可能重写filename.orig
从未被添加到存储库的更改历史记录?
Answers:
如果您的情况不是问题中描述的情况,请不要使用此食谱。此食谱用于修复错误的合并,并将您的良好提交重播到固定的合并中。
尽管filter-branch
可以完成您想要的操作,但这是一个非常复杂的命令,我可能会选择使用来完成git rebase
。这可能是个人喜好。filter-branch
可以用一个稍微复杂一点的命令来完成,而rebase
解决方案是一次执行等效的逻辑操作。
尝试以下食谱:
# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>
# remove the incorrectly added file
git rm somefile.orig
# commit the amended merge
git commit --amend
# go back to the master branch
git checkout master
# replant the master branch onto the corrected merge
git rebase tmpfix
# delete the temporary branch
git branch -d tmpfix
(请注意,您实际上并不需要临时分支,可以使用“分离的HEAD”来执行此操作,但是您需要记下该git commit --amend
步骤生成的提交ID,以提供给git rebase
命令,而不是使用临时分支名称。)
git rebase -i
更快且仍然如此简单?$ git rebase -i <sh1-of-merge>将正确的标记为“编辑” $ git rm somefile.orig $ git commit --amend $ git rebase --continue但是由于某些原因,我仍然在最后一个文件中保存该文件时间我做到了。可能丢失了一些东西。
git rebase -i
这非常有用,尤其是当您需要执行多个rebase-y操作时,但是当您实际上并没有真正指向某人的肩膀并且可以看到他们在用编辑器做什么时,准确地描述它是正确的选择。我使用vim,但并非所有人都会满意:“ ggjcesquash <Esc> jddjp:wq”和类似的指示“将第一行移动到当前第二行之后,并将第四行的第一个单词更改为'edit',现在保存并退出”似乎比实际步骤要复杂得多。通常你结束了一些--amend
和--continue
行动,以及。
原始海报指出:
我不小心将不需要的文件提交给存储库,几次提交之前,我想从存储库历史记录中完全删除该文件。
是否有可能重写
filename.orig
从未被添加到存储库的更改历史记录?
有很多不同的方法可以从git中完全删除文件的历史记录:
在原始海报的情况下,修改提交本身并不是一个选择,因为他随后又进行了几次提交提交,但是为了完整起见,我还将为任何想要的人解释如何做修改他们以前的提交。
请注意,所有这些解决方案都以另一种方式涉及更改/重写历史记录/提交,因此拥有旧提交副本的任何人都必须做额外的工作才能将其历史记录与新历史记录重新同步。
如果您在上一次提交中无意中进行了更改(例如添加文件),并且您不想再存在该更改的历史记录,那么您可以简单地修改上一次提交以从中删除文件:
git rm <file>
git commit --amend --no-edit
像解决方案#1一样,如果您只是想摆脱以前的提交,那么您还可以选择简单地对其父级进行硬重置:
git reset --hard HEAD^
该命令会将您的分支硬重置为先前的第一个父提交。
但是,如果像原始发布者一样,在要撤消更改的提交之后进行了几次提交,则仍可以使用硬重置来对其进行修改,但是这样做还涉及使用重新设置基准。您可以使用以下步骤修改历史记录中的提交:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
如果您只想从历史记录中完全删除提交,这将起作用:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
该解决方案将使您能够完成与解决方案2和#3相同的任务,即,修改或删除历史记录中的提交要比直接提交更早,因此您选择使用哪种解决方案取决于您自己。出于性能方面的考虑,交互式rebase不太适合对数百个提交进行基础调整,因此在这种情况下,我将使用非交互式rebase或filter分支解决方案(请参见下文)。
要开始交互式变基,请使用以下命令:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
这将导致git将提交历史回滚到您要修改或删除的提交的父级。然后,它将以设置为git的任何编辑器(以默认方式为Vim)的反向顺序显示重新提交的列表:
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
您要修改或删除的提交将在此列表的顶部。要删除它,只需在列表中删除它的行即可。否则,将“选择”对1条“编辑” ST线,像这样:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
接下来,输入git rebase --continue
。如果您选择完全删除提交,那么这就是您需要做的所有事情(除了验证,请参阅此解决方案的最后一步)。另一方面,如果您想修改提交,则git将重新应用提交,然后暂停重新设置基准。
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
此时,您可以删除文件并修改提交,然后继续进行变基:
git rm <file>
git commit --amend --no-edit
git rebase --continue
而已。作为最后一步,无论您是修改提交还是完全删除提交,通过将其与重新设置基准前的状态进行比较来验证对分支没有其他意外更改始终是一个好主意:
git diff master@{1}
最后,如果您想从历史记录中完全清除文件存在的所有痕迹,并且没有其他解决方案能够胜任该任务,那么此解决方案是最佳选择。
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
<file>
从根提交开始,这将从所有提交中删除。相反,如果您只想重写提交范围HEAD~5..HEAD
,则可以将其作为附加参数传递给filter-branch
,如此答案所指出
:
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
同样,在filter-branch
完成之后,通常最好通过在过滤操作之前通过将分支与分支的先前状态进行比较来验证是否没有其他意外更改:
git diff master@{1}
我听说BFG Repo Cleaner工具的运行速度比快git filter-branch
,因此您可能也希望将其检出。过滤器分支文档中甚至正式提到了可行的替代方法:
git-filter-branch允许您对Git历史记录进行复杂的shell脚本重写,但是如果您只是删除不需要的数据(例如大文件或密码),则可能不需要这种灵活性。对于这些操作,您可能需要考虑使用BFG Repo-Cleaner,它是git-filter-branch的基于JVM的替代品,对于这些用例,通常至少快10-50倍,并且具有完全不同的特性:
文件的任何特定版本仅清除一次。BFG与git-filter-branch不同,它没有给您机会根据文件在历史记录中的提交位置或时间对文件进行不同的处理。该约束为BFG提供了核心性能优势,并且非常适合清理不良数据的任务-您不在乎不良数据在哪里,只希望它消失了。
默认情况下,BFG充分利用了多核计算机的优势,可以并行清除提交文件树。git-filter-branch按顺序清理提交(即以单线程方式),尽管有 可能在针对每个提交执行的脚本中编写包含其自身并行性的过滤器。
该命令选项都远远超过git的过滤分支更严格,并致力于只是为了消除不必要的数据-例如任务:
--strip-blobs-bigger-than 1M
。
filter-branch
会导致重新计算哈希?如果团队使用应该过滤大文件的仓库,他们该如何做,以便每个人最终都处于仓库的相同状态?
如果您此后未提交任何内容,则只需git rm
输入文件和即可git commit --amend
。
如果你有
git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD
将经历从merge-point
到的每个更改HEAD
,删除filename.orig并重写更改。使用--ignore-unmatch
意味着如果由于某种原因filename.orig丢失而导致命令不会失败。这是git-filter-branch手册页中 “示例”部分的推荐方法。
Windows用户注意事项:文件路径必须使用正斜杠
git-filter-branch
似乎给出了第一个。
filter-branch
"
而不是'
Windows时使用,否则会收到无用的措辞“错误修订”错误。
这是最好的方法:http :
//github.com/guides/completely-remove-a-file-from-all-revisions
只要确保首先备份文件的副本即可。
编辑
不幸的是,Neon的编辑在审核期间被拒绝。
请参阅下面的霓虹灯发布,其中可能包含有用的信息!
例如,删除所有*.gz
意外提交到git存储库中的文件:
$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD
$ git push origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now
$ git gc --aggressive --prune=now
那对我还是没有用?(我目前是git版本1.7.6.1)
$ du -sh .git ==> e.g. 100M
不知道为什么,因为我只有一个master分支。无论如何,我终于通过推送到一个新的空的和裸露的git仓库中来真正清理了git仓库。
$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M
(是!)
然后,将其克隆到一个新目录,并将其.git文件夹移至该目录中。例如
$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M
(是的!终于收拾了!)
确认一切正常后,您可以删除../large_dot_git
和../tmpdir
目录(也许从现在起几个星期或一个月,以防万一...)
--prune-empty
到filter-branch命令。
重写Git历史记录要求更改所有受影响的提交ID,因此,从事该项目的每个人都需要删除其回购的旧副本,并在清理历史记录后进行新的克隆。带来不便的人越多,您需要做它的理由就越多-多余的文件并没有真正引起问题,但是如果您只是在从事该项目,那么您也可以清理Git历史记录至!
为了使它尽可能简单,我建议使用BFG Repo-Cleaner,这是一种git-filter-branch
专门为从Git历史记录中删除文件而设计的,更简单,更快速的替代方法。一种使您的生活更轻松的方法是,它实际上默认处理所有引用(所有标记,分支等),但速度也提高了10-50倍。
您应该在此处仔细执行以下步骤:http : //rtyley.github.com/bfg-repo-cleaner/#usage-但是核心部分是这样:下载BFG jar(需要Java 6或更高版本)并运行此命令:
$ java -jar bfg.jar --delete-files filename.orig my-repo.git
您的整个存储库历史记录将被扫描,并且任何名为filename.orig
(不在您的最新 commit中)的文件都将被删除。这比使用git-filter-branch
相同的工具容易得多!
完全公开:我是BFG Repo-Cleaner的作者。
You should probably clone your repository first.
Remove your file from all branches history:
git filter-branch --tree-filter 'rm -f filename.orig' -- --all
Remove your file just from the current branch:
git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD
Lastly you should run to remove empty commits:
git filter-branch -f --prune-empty -- --all
我发现的最简单的方法leontalbot
是由Anoopjohn发表(作为评论)建议的。我认为值得用自己的空间作为答案:
(我将其转换为bash脚本)
#!/bin/bash
if [[ $1 == "" ]]; then
echo "Usage: $0 FILE_OR_DIR [remote]";
echo "FILE_OR_DIR: the file or directory you want to remove from history"
echo "if 'remote' argument is set, it will also push to remote repository."
exit;
fi
FOLDERNAME_OR_FILENAME=$1;
#The important part starts here: ------------------------
git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
if [[ $2 == "remote" ]]; then
git push --all --force
fi
echo "Done."
所有的功劳归于Annopjohn
,并leontalbot
指出。
注意
请注意,该脚本不包含验证,因此请确保您不会出错,并且在发生问题时可以进行备份。它对我有用,但在您的情况下可能不起作用。请谨慎使用(如果您想了解发生了什么,请点击链接)。
如果这是您要清除的最新提交,我尝试使用git版本2.14.3(Apple Git-98):
touch empty
git init
git add empty
git commit -m init
# 92K .git
du -hs .git
dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake
# 5.1M .git
du -hs .git
git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --prune=now
# 92K .git
du -hs .git
git reflog expire --expire=now --all; git gc --prune=now
这是一件非常不好的事情。除非磁盘空间不足,否则请让git垃圾在几周后收集这些提交
这就是git filter-branch
设计的目的。
您还可以使用:
git reset HEAD file/path