你如何修复一个坏的合并,并在一个固定的合并上重放你好的提交?

我不小心提交了一个不想要的文件(filename.orig,同时解决合并)到我的存储库几个提交前,没有我注意到它,直到现在。我想从存储库历史记录中完全删除该文件。

是否有可能重写更改历史,这样filename.orig就永远不会被添加到存储库中?

336117 次浏览

这就是设计git filter-branch的目的。

如果你的情况不是问题中描述的情况,请不要使用这个方法。这个配方是用来修复一个坏的合并,并在一个固定的合并中重新播放你好的提交。

虽然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

(请注意,你实际上并不需要一个临时分支,你可以用一个'detached HEAD'来做到这一点,但你需要注意由git commit --amend步骤生成的提交id,以提供给git rebase命令,而不是使用临时分支名称。)

如果你还没有提交任何东西,只要git rm文件和git commit --amend

如果你有

git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD

将遍历从merge-pointHEAD的每次更改,删除文件名。创建并重写更改。使用--ignore-unmatch意味着如果由于某种原因filename. exe命令不会失败。奥里格在变化中消失了。这是Git-filter-branch手册页中的Examples部分推荐的方法。

Windows用户注意:文件路径必须使用正斜杠

你还可以使用:

git reset HEAD file/path

这是最好的方法:
http://github.com/guides/completely-remove-a-file-from-all-revisions < / p >

一定要先备份文件副本。

编辑

霓虹灯的编辑在审阅时不幸被拒绝 请看下面的霓虹灯帖子,它可能包含有用的信息!< / p >


例如,删除所有意外提交到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仓库,例如。

$ 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目录(可能在几周或一个月后,以防万一……)

重写Git历史记录需要更改所有受影响的提交id,因此每个参与项目的人都需要删除他们的旧回购副本,并在清理历史记录后进行新的克隆。它给人们带来的不便越多,你就越需要一个好的理由来这样做——你多余的文件并没有真正造成问题,但如果只有在项目中工作,如果你想的话,你也可以清理Git历史记录!

为了尽可能简单,我建议使用高炉煤气Repo-Cleaner,这是一个更简单、更快的git-filter-branch替代方案,专门用于从Git历史记录中删除文件。它让你的生活更轻松的一种方式是,它实际上默认处理所有引用(所有标签,分支等),但它也更快10 - 50x

你应该仔细遵循这里的步骤:http://rtyley.github.com/bfg-repo-cleaner/#usage -但核心是:下载高炉煤气瓶(需要Java 6或以上)并运行以下命令:

$ java -jar bfg.jar --delete-files filename.orig my-repo.git

你的整个存储库历史将被扫描,任何名为filename.orig的文件(不在你的最新< em > < / em >提交中)将被删除。这比使用git-filter-branch来做同样的事情要容易得多!

完全披露:我是好心眼巨人回收清理器的作者。

只是为了把它添加到Charles Bailey的解决方案中,我只是使用了git rebase -i来从之前的提交中删除不需要的文件,它就像一个魅力。 步骤:< / p >
# 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

简介:你有5个可用的解决方案

最初的海报写道:

我不小心提交了一个不想要的文件…多次提交到我的存储库 前……我想从存储库历史记录中完全删除该文件。< / p > < p > 可以重写更改历史,这样filename.orig就永远不会 首先添加到存储库中?< / p >

有许多不同的方法可以完全删除一个文件的历史记录 git: < / p >
  1. 修改提交。
  2. 硬重置(可能加上一个rebase)。
  3. 非交互式变基。
  4. 交互式重置。
  5. 过滤分支。
在原始海报的情况下,修改提交实际上不是一个选项 它本身,因为他后来又作了几次补充,但为了看在份上 完整的,我也将解释如何做,为任何人谁只是

.

. 注意,所有这些解决方案都涉及改变/重写历史/提交 以某种方式,所以任何拥有旧提交副本的人都必须这样做 额外的工作,重新同步他们的历史与新的历史

解决方案1:修改提交

如果你不小心在你的前 提交,那么您就不希望该更改的历史再存在了 你可以简单地修改之前的提交,从它删除文件:

git rm <file>
git commit --amend --no-edit

解决方案2:硬重置(可能加上一个基数调整)

就像解决方案#1,如果你只是想摆脱你之前的提交,那么你 还可以选择简单地对其父对象进行硬重置:

git reset --hard HEAD^
该命令将硬重置你的分支到之前的1父节点 提交。< / p >

然而< em > < / em >,如果,像最初的海报,你已经做了几次提交 要撤消更改的提交,仍然可以使用硬重置 修改它,但这样做也涉及到使用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:非交互式Rebase

如果你只是想从历史记录中完全删除一个提交,这将是有效的:

# 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,即修改或删除历史上更早的提交,而不是立即提交 以前的提交,所以你选择使用哪种解决方案是由你决定的。 交互式数据库重构并不适合对数百个提交进行数据库重构 由于性能原因,所以我将使用非交互式的rebases或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)
你想要修改或删除的提交将在这个列表的顶部。 要删除它,只需在列表中删除它的行。否则,将“pick”替换为 "edit"在1行,像这样:

edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`

接下来,输入git rebase --continue。如果你选择完全删除提交, 然后,这就是你需要做的所有事情(除了验证,请参阅最后一步 这个解决方案)。另一方面,如果您想修改提交,则使用git 将重新应用提交,然后暂停rebase

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

此时,您可以删除文件并修改提交,然后继续 变基:< / p >

git rm <file>
git commit --amend --no-edit
git rebase --continue
< p >就是这样。最后一步,是修改提交还是删除提交 完全正确,验证没有其他意外更改总是一个好主意 是通过改变你的分支在重基之前的状态来实现的:

git diff master@{1}

方案5:过滤分支

最后,如果你想彻底清除所有的痕迹,这个解决方案是最好的 一个文件的存在历史,没有其他的解决方案是相当 这个任务。< / p >
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
将从所有提交中删除<file>,从根提交开始。如果 相反,你只需要重写提交范围HEAD~5..HEAD,然后你就可以 将其作为附加参数传递给filter-branch,如 这个答案: < / p >
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
同样,在filter-branch完成后,进行验证通常是一个好主意 通过将您的分支与它的分支进行区分,没有其他意想不到的更改 过滤操作前的状态:

git diff master@{1}

过滤器分支替代方案:BFG回购清洁

我听说BFG Repo Cleaner工具比git filter-branch运行得更快,所以你可能也想把它作为一个选项来检查。甚至在filter-branch文档中正式提到了它作为一个可行的替代方案:

git-filter-branch允许您进行复杂的shell脚本重写 你的Git历史,但你可能不需要这种灵活性,如果 你只是删除不需要的数据像大文件或密码。 对于这些操作,您可能需要考虑 BFG . io/ BFG -repo-cleaner/" rel="noreferrer Repo-Cleaner,基于jvm的 替代git-filter-branch,通常至少快10-50倍 这些用例,具有完全不同的特征:

  • 文件的任何特定版本都被精确地一次清除。与git-filter分支不同,BFG不会给你处理的机会 一个不同的文件,这取决于它是在何时何地提交的 历史。该约束提供了the的核心性能优势 BFG,非常适合清理坏数据的任务-你不 在哪里坏数据是,你只需要它走了.

  • 默认情况下,BFG充分利用了多核机器的优势,并行清理提交文件树。git-filter-branch清洗 顺序提交(即单线程方式),尽管它 可以编写包含自己的并行度的过滤器

    . 命令选项是很多的 比git-filter分支更具限制性,并且仅用于 删除不需要的数据的任务-例如:--strip-blobs-bigger-than 1M.

额外的资源

  1. Pro Git §6.4 Git工具-重写历史
  2. git-filter-branch(1) Manual Page
  3. git-commit(1) Manual Page
  4. git-reset(1)手动页面
  5. git-rebase(1)手册页
  6. 好心眼巨人回收清洁器(另见这是造物主自己的回答)。

我发现的最简单的方法是由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指出它。

请注意

请注意,脚本不包括验证,因此请确保不会出错,并有备份以防出现错误。这招对我很管用,但对你可能就不管用了。小心使用它(如果你想知道发生了什么,请点击链接)。

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

当然,git filter-branch是正确的方法。

遗憾的是,这还不足以从你的repo中完全删除filename.orig,因为它仍然可以被标签、reflog条目、遥控器等引用。

我建议删除所有这些引用,然后调用垃圾回收器。你可以使用网站上的git forget-blob脚本一步完成所有这些。

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