如何修复错误的合并,并将良好的提交重放到固定的合并上?
-
08-07-2019 - |
题
我不小心提交了不需要的文件(filename.orig
在解决合并时)到我的存储库几次提交之前,直到现在我才注意到。我想从存储库历史记录中完全删除该文件。
是否可以重写更改历史记录 filename.orig
最初从未添加到存储库中?
解决方案
如果您的情况不是问题中描述的情况,请不要使用此食谱。此配方用于修复错误合并,并将您的好提交重播到固定合并。
虽然filter-branch
会做你想做的事,但这是一个非常复杂的命令,我可能会选择用git rebase
来做。这可能是个人偏好。 rebase
可以在一个稍微复杂的命令中完成,而git commit --amend
解决方案一次只执行一个等效的逻辑操作。
尝试以下方法:
# 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'来执行此操作,但是您需要记下<=>步骤生成的提交ID以提供给< =>命令而不是使用临时分支名称。)
其他提示
介绍:您有 5 种可用解决方案
原海报写道:
我不小心将不需要的文件提交给我的存储库,几个前都提交了我的存储库...我想从存储库历史记录中完全删除该文件。
是否可以重写变更历史,以便
filename.orig
从来没有首先添加到存储库中吗?
有许多不同的方法可以完全从git中删除文件的历史记录:
- 修改提交。
- 硬重置(可能加上变基)。
- 非交互式变基。
- 交互式变基。
- 过滤分支。
就原始海报而言,修改该提交本身并不是真正的选择,因为他之后他进行了几次提交,但是出于完整的目的,我还将解释如何做到这一点修改他们以前的提交。
请注意,所有这些解决方案都涉及 改变/重写 历史/投入以彼此的方式,因此任何拥有旧副本的人都必须做额外的工作,以将其历史与新历史重新同步。
解决方案一:修改提交
如果您在上一个提交中意外进行了更改(例如添加文件),并且您不希望更改的历史记录,那么您可以简单地修改以前的提交以从中删除该文件:
git rm <file>
git commit --amend --no-edit
解决方案2:硬重置(可能加上变基)
就像解决方案#1一样,如果您只想摆脱以前的提交,那么您也可以选择简单地向其父母进行硬重置:
git reset --hard HEAD^
该命令会将您的分支硬重置为之前的 1英石 父母提交。
然而, ,如果像原始海报一样,您要在要撤消更改的提交之后做出了几项提交,则您仍然可以使用硬重置来修改它,但也可以使用rebase。这是您可以使用的步骤来修改历史上的提交:
# 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}
解决方案3:非交互式变基
如果您只想从历史记录中完全删除提交,这将起作用:
# 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}
解决方案4:交互式变基
该解决方案将使您能够完成与解决方案#2和#3相同的事情,即与您以前的提交相比,修改或删除历史上的提交更多,因此您选择使用哪种解决方案取决于您。出于性能原因,交互式篮板不适合重新审议数百个提交,因此我将在这种情况下使用非相互作用的篮板或过滤器分支解决方案(见下文)。
要开始交互式变基,请使用以下命令:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
这将导致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上替换为“编辑”英石 线,像这样:
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}
解决方案5:过滤分支
最后,如果您想从历史记录中彻底清除文件存在的所有痕迹,并且其他解决方案都不取决于任务,则该解决方案是最好的。
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 回购清理器
我听说 BFG 回购清理器 工具运行速度快于 git filter-branch
, ,所以您可能也想将其作为一个选项进行检查。 甚至在官方中也提到过 过滤器分支文档 作为一个可行的替代方案:
Git-Filter-Branch允许您对GIT历史记录进行复杂的壳标记重写,但是如果您只是简单地,您可能不需要这种灵活性 删除不需要的数据 例如大文件或密码。对于那些您可能需要考虑的操作 bfg回购器, 这是一种基于JVM的Git-Filter-Branch替代品,对于这些用例而言,通常至少更快10-50倍,并且具有完全不同的特征:
文件的任何特定版本都会被准确清理 一次. 。与Git-Filter-Branch不同,BFG并不能根据您的历史记录中的何时何时或何时以不同的方式处理文件。该约束为BFG带来了核心性能优势,并且非常适合清洁不良数据的任务 - 您不在乎 在哪里 坏数据是,你只是想要它 消失了.
默认情况下,BFG 充分利用多核机器,并行清理提交文件树。git-filter-branch依次清洁提交(即以单线读取方式),尽管 是可以在对每个提交执行的脚本中编写包括自己的相似之处的过滤器。
这 命令选项 比Git-Filter分支更具限制性,并且仅专门用于删除不需要的数据的任务:
--strip-blobs-bigger-than 1M
.
其他资源
如果您之后没有提交任何内容,只需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用户请注意:文件路径必须使用正斜杠
这是最好的方法:
http://github.com/guides/completely-remove-a -file-from-all-revisions
请务必先备份文件的副本。
修改强>
Neon 的编辑在审核期间遗憾地被拒绝。
请参阅下面的Neons帖子,它可能包含有用的信息!
E.g。删除意外提交到git存储库的所有*.gz
文件:
$ 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
不知道为什么,因为我只有一个主分支。无论如何,我终于通过推入一个新的空的裸git存储库来清理我的git repo,例如。
$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M
(是!)
然后我将其克隆到一个新目录并将其.git文件夹移到这个目录中。 e.g。
$ 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
目录(可能在几周或几个月后,以防万一......)
重写Git历史记录需要更改所有受影响的提交ID,因此每个正在处理该项目的人都需要删除他们的旧版本repo,并在清理完历史记录后进行全新的克隆。不方便的人越多,你就越需要一个充分的理由去做 - 你多余的文件并没有真正导致问题,但如果只有你正在处理这个项目,那么你也可以清理一下如果你想要Git历史记录!
为了尽可能简单,我建议使用 BFG Repo-Cleaner ,一个更简单,更快速的替代git-filter-branch
专门设计用于从Git历史记录中删除文件。它让你的生活更轻松的一种方式是,它实际上默认处理所有引用(所有标签,分支等),但它也是 10 - 50x 更快。
您应该仔细按照此处的步骤操作: 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
的文件(不在您的 最新提交)将被删除。这比使用<=>做同样的事情要容易得多!
完全披露:我是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
只是将其添加到Charles Bailey的解决方案中,我只是使用git rebase -i从先前的提交中删除不需要的文件,它就像一个魅力。 步骤:
# Pick your commit with 'e'
$ git rebase -i
# Perform as many removes as necessary
$ git rm project/code/file.txt
# amend the commit
$ git commit --amend
# continue with rebase
$ git rebase --continue
我找到的最简单的方法是由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
,然后<=>指出。
注意强>
请注意,该脚本不包含验证,因此请确保您不会出错,并且在出现问题时请备份。它对我有用,但它可能不适用于你的情况。请谨慎使用(如果您想知道发生了什么,请点击链接。)
当然,git filter-branch
是要走的路。
可悲的是,这还不足以从您的仓库中完全删除filename.orig
,因为它仍然可以被标签,reflog条目,遥控器等引用。
我建议同时删除所有这些引用,然后调用垃圾收集器。您可以使用git forget-blob脚本blob /“rel =”nofollow noreferrer“>这个网站只需一步即可完成所有这些工作。
git forget-blob filename.orig
如果这是你要清理的最新提交,我尝试使用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 filter-branch
就是为此设计的。
您也可以使用:
git reset HEAD file/path