如何修复错误的合并,并将您的好提交重放到固定合并?

时间:2008-11-21 04:11:21

标签: git git-filter-branch git-rewrite-history git-rm

我在几次提交之前意外地将一个不需要的文件(filename.orig在解析合并时)提交到我的存储库,直到现在我才注意到它。我想从存储库历史记录中完全删除该文件。

是否可以重写更改历史记录,以便filename.orig从未首先添加到存储库?

12 个答案:

答案 0 :(得分:292)

如果您的情况不是问题中描述的情况,请不要使用此食谱。此配方用于修复错误合并,并将您的好提交重播到固定合并。

虽然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命令,而不是使用临时分支名称。)

答案 1 :(得分:199)

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

原始海报说明:

  

我意外地将不需要的文件提交给我的存储库几次提交    之前...我想从存储库历史记录中完全删除该文件。

     是的吗?    可以重写更改历史记录,以便filename.orig永远不会    首先添加到存储库?

有许多不同的方法可以完全删除文件的历史记录 GIT中:

  1. 修改提交。
  2. 硬重置(可能加上一个rebase)。
  3. 非交互式rebase。
  4. 交互式rebase。
  5. 过滤分支。
  6. 在原始海报的情况下,修改提交并不是一个真正的选择 因为他之后做了几次额外的提交,但是为了这个缘故 为了完整性,我还将解释如何做到这一点,对于其他任何正义的人 想要修改他们之前的提交。

    请注意,所有这些解决方案都涉及更改/重写历史记录/提交 在某种程度上,所以任何拥有旧版本的提交都必须这样做 将历史记录与新历史重新同步的额外工作。


    解决方案1:修改提交

    如果您不小心在之前进行了更改(例如添加文件) 提交,然后您不希望该更改的历史存在 你可以简单地修改先前的提交以从中删除文件:

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

    解决方案2:硬重置(可能加上重新基础)

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

    git reset --hard HEAD^
    

    该命令会将您的分支硬重置为前一个 st 父级 提交。

    然而 ,如果像原始海报一样,您之后做了几次提交 要撤消更改的提交,您仍然可以使用硬重置 修改它,但这样做也涉及使用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,即修改或删除历史记录中的提交,而不是立即提交 之前的提交,您选择使用哪种解决方案取决于您。 交互式rebase并不适合用于改变数百次提交 性能原因,所以我会使用非交互式rebase或filter分支 在这种情况下的解决方案(见下文)。

    要开始交互式rebase,请使用以下命令:

    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)
    

    您要修改或删除的提交将位于此列表的顶部。 要删除它,只需删除列表中的行。否则,请更换&#34; pick&#34;同 &#34;编辑&#34;在1 st 行上,如下:

    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
    

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

    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的附加参数传递,如中所述 this answer

    git filter-branch --index-filter \
    'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
    

    同样,在filter-branch完成后,验证通常是一个好主意 通过使用它来分支你的分支没有其他意想不到的变化 过滤操作前的先前状态:

    git diff master@{1}
    

    过滤器分支替代方案:BFG Repo Cleaner

    我听说BFG Repo Cleaner工具的运行速度比git filter-branch快,所以您可能也希望将其作为一个选项进行检查。 它甚至在filter-branch documentation中正式提到作为一种可行的替代方案:

      

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

         
        
    • 文件的任何特定版本都会完全一次清除。与git-filter-branch不同,BFG不会给你机会处理   文件的不同之处取决于文件在何处或何时提交   历史。这种约束赋予了The的核心性能优势   BFG,非常适合清理坏数据的任务 - 你没有   关注哪里坏数据,你只是想要消失

    •   
    • 默认情况下,BFG充分利用多核机器,并行清理提交文件树。 git-filter-branch清理   顺序提交(即以单线程方式),但   可以编写包含自己的并行性的过滤器   针对每次提交执行的脚本。

    •   
    • command options很多   比git-filter分支更严格,专用于   删除不需要的数据的任务 - 例如:--strip-blobs-bigger-than 1M

    •   

    其他资源

    1. Pro Git § 6.4 Git Tools - Rewriting History
    2. git-filter-branch(1) Manual Page
    3. git-commit(1) Manual Page
    4. git-reset(1) Manual Page
    5. git-rebase(1) Manual Page
    6. The BFG Repo Cleaner(另见this answer from the creator himself)。

答案 2 :(得分:118)

如果您之后没有提交任何内容,只需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的每次更改,删除filename.orig并重写更改。使用--ignore-unmatch表示如果由于某种原因,更改中缺少filename.orig,命令将不会失败。这是git-filter-branch man page中示例部分的推荐方式。

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

答案 3 :(得分:47)

这是最好的方法:
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目录(可能在几周或几个月后,以防万一...)

答案 4 :(得分:26)

重写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的文件(不在您的latest commit中)。这比使用git-filter-branch做同样的事情要容易得多!

完全披露:我是BFG Repo-Cleaner的作者。

答案 5 :(得分:13)

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

答案 6 :(得分:4)

只是将其添加到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

答案 7 :(得分:4)

我找到的最简单的方法是由leontalbot(作为评论)建议的,这是一个post published by 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以指出。

注意

请注意,该脚本不包含验证,因此请确保您不会犯错,并且如果出现问题,您还有备份。它对我有用,但它可能不适用于你的情况。请谨慎使用(如果您想知道发生了什么,请点击链接。)

答案 8 :(得分:3)

当然,x64\vc14\staticlib是可行的方法。

可悲的是,这不足以从您的仓库中完全删除git filter-branch,因为它仍然可以被标签,reflog条目,遥控器等引用。

我建议同时删除所有这些引用,然后调用垃圾收集器。您可以使用this网站上的filename.orig脚本一步完成所有这些操作。

git forget-blob

答案 9 :(得分:1)

如果它是您想要清理的最新提交,我尝试使用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

答案 10 :(得分:0)

这是git filter-branch的目的。

答案 11 :(得分:-1)

您也可以使用:

git reset HEAD file/path