关于git:你如何修复错误的合并,并将你的好提交重放到固定的合并上?

How do you fix a bad merge, and replay your good commits onto a fixed merge?

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

是否有可能重写更改历史,使filename.orig从未首先添加到存储库中?


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

虽然filter-branch会做你想做的,但这是一个相当复杂的命令,我可能会选择用git rebase来做这个。这可能是个人喜好。filter-branch可以在一个稍微复杂一点的命令中完成,而rebase解决方案则是一步一步地执行等效的逻辑操作。

尝试以下配方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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

(请注意,您实际上不需要临时分支,您可以使用"分离的头"执行此操作,但您需要记录由git commit --amend步骤生成的提交ID,以提供给git rebase命令,而不是使用临时分支名称。)


Intro:you have five solutions available

The original poster states:

okay.

I accidentally committed an unwanted file...to my repository several commits
ago...I want to completely delete the file from the repository history.

Ok.

Is it
possible to rewrite the change history such that filename.orig was never
added to the repository in the first place?

Ok.

有很多不同的方法可以从一个完整的文件Git:

okay.

  • Amending commits.
  • 硬复位(可能加上一个反弹)。
  • 不交互反弹
  • 互动反抗
  • 过滤树枝
  • 在原始邮寄的情况下,修改邮寄并不是一个真正的选择。自从他在事后作出了许多额外的承诺,但为了喝酒。从完整的角度看,我也会解释如何做,为了任何人想改变他们以前的习惯。

    okay.

    Note that all of these solutions involve altering/re-writing history/commits在另一个方面,任何人都必须用旧的信件副本去做。为了使他们的故事与新的故事重新同步,做了一些额外的工作。

    okay.解决办法1:修正承诺

    如果你意外改变(如添加文件)但是,你不需要任何改变的故事你可以简单地修改前面的承诺,从下面删除文件:

    okay.

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

    解决方案2:硬复位

    就象解决方案,如果你只是想像以前一样爬上去,那么你此外,还有一个简单的选择,对其父母来说是一个艰难的复苏:

    okay.ZZU1

    指挥官将很难把你的分支重新分配给第一位亲戚Commit.

    okay.

    但是,如果,像原始的海报一样,你在那之后作出了许多承诺。你想要的改变,你仍然可以用硬的复制改变它,但也要用反弹动作。这是那一步你可以用这个来改变历史的另一个方面:

    okay.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 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  master

    # Verify your changes
    git diff master@{1}

    解决方案3:非互动性再生

    如果你只是想从历史上清除一个错误

    okay.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 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:交互式反叛

    这个解决方案会让你完成同样的事情作为解决方案 353,3,I.E.Modify or remove commits further back in history than your immediately如前所述,所以你选择使用的解决方案是属于你的。交互式反叛并不是一个很好的方法来重建委员会性能原因,所以我会使用非交互反弹或过滤器分支Solution(see below)in those order of situations.

    okay.

    To begin the interactive rebase,use the following:

    okay.

    1
    2
    3
    4
    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默认值):好的。

    1
    2
    3
    4
    5
    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"替换为第一行的"编辑",如下所示:好的。

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

    接下来,输入git rebase --continue。如果您选择完全取消提交,然后您需要做的所有事情(除了验证,请参见这个解决方案)。另一方面,如果您想要修改提交,那么git将重新应用提交,然后暂停钢筋网。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    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

    此时,您可以删除该文件并修改提交,然后继续重新平衡:好的。

    1
    2
    3
    git rm <file>
    git commit --amend --no-edit
    git rebase --continue

    就这样。作为最后一步,无论您是修改了提交还是删除了它总的来说,最好是确认没有其他意外的变化是通过将其与钢筋前的状态进行比较而生成的:好的。

    1
    git diff master@{1}

    。解决方案5:过滤分支

    最后,如果您想彻底清除一个文件从历史上就存在了,其他的解决方案都没有达到任务。好的。

    1
    2
    git filter-branch --index-filter \
    'git rm --cached --ignore-unmatch <file>'

    这将从根提交开始,从所有提交中删除。如果相反,您只需要重写提交范围HEAD~5..HEAD,然后就可以把这作为对filter-branch的补充论点,如这个答案:好的。

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

    同样,在filter-branch完成后,通常最好验证一下通过将分支与其筛选操作之前的状态:好的。

    1
    git diff master@{1}

    。过滤支路备选方案:BFG repo cleaner

    我听说bfg repo cleaner工具比git filter-branch运行得更快,所以您也可以选择查看它。甚至在过滤器分支文档中正式提到它是一个可行的替代方案:好的。

    Git过滤器分支允许您进行复杂的shell脚本重写你的Git历史,但如果您只需删除不需要的数据,如大文件或密码。对于这些操作,您可能需要考虑BFG基于JVM的repo cleaner替代Git过滤器分支,通常至少快10-50倍这些用例,具有非常不同的特性:好的。

    • 文件的任何特定版本都将被完全清除一次。与git filter分支不同,bfg没有给您机会处理根据在您的历史。此约束使bfg,非常适合于清除坏数据的任务-您不需要不管坏数据在哪里,你只希望它消失。好的。

    • 默认情况下,bfg充分利用多核机器,并行清理提交文件树。Git过滤器分支清洗按顺序提交(即以单线程方式),尽管可以在针对每个提交执行的脚本。好的。

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

    其他资源

  • Pro Git§;6.4 Git工具-重写历史记录。
  • Git过滤器分支(1)手册页。
  • Git提交(1)手册页。
  • Git重置(1)手册页。
  • Git Rebase(1)手册页。
  • bfg repo cleaner(另见创建者本人的回答)。
  • 好啊。


    如果你从那以后什么都没有做过,那就把文件交给git rmgit commit --amend

    如果你有

    1
    2
    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手册页示例部分中推荐的方法。

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


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

    只需确保先备份文件的副本。

    编辑

    Neon的编辑在审查过程中不幸遭到拒绝。请参阅下面的Neons文章,它可能包含有用的信息!

    例如,要删除所有意外提交到Git存储库的*.gz文件:

    1
    2
    3
    4
    5
    6
    7
    $ 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版)

    1
    $ du -sh .git ==> e.g. 100M

    不知道为什么,因为我只有一个主分支。不管怎样,我最终通过推到一个新的空的、空的Git存储库(例如,Git repo)中,彻底清理了我的Git repo。

    1
    2
    3
    $ git init --bare /path/to/newcleanrepo.git
    $ git push /path/to/newcleanrepo.git master
    $ du -sh /path/to/newcleanrepo.git ==> e.g. 5M

    (是的!)

    然后我克隆到一个新的目录,并将它的.git文件夹移到这个目录中。例如

    1
    2
    3
    4
    $ 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副本,并在清理历史记录后执行新的克隆。IT不便的人越多,你就越需要一个好的理由来做这件事——你多余的文件并不是真正造成问题的原因,但是如果你只是在做这个项目,如果你愿意的话,你最好清理一下Git的历史记录!

    为了尽可能简单,我建议使用bfg repo cleaner,这是一种比git-filter-branch更简单、更快的替代方案,专门设计用于从git历史中删除文件。其中一个让您的生活更容易的方法是,它实际上在默认情况下处理所有引用(所有标记、分支等),但它也快10-50倍。

    您应该仔细执行以下步骤:http://rtyLy.GITHUB.COM/BFG RePoCuff/Suffe用法-但核心位仅此:下载BFG jar(需要Java 6或以上)并运行此命令:

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

    您的整个存储库历史记录将被扫描,任何名为filename.orig的文件(您最近提交的文件中没有)将被删除。这比使用git-filter-branch做同样的事情要容易得多!

    完全公开:我是bfg repo cleaner的作者。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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脚本)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #!/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."

    所有的信用证都交给Annopjohnleontalbot指出。

    注释

    请注意,该脚本不包含验证,因此请确保您不会出错,并且在出现问题时有备份。它对我有用,但在你的情况下可能行不通。小心使用(如果您想知道发生了什么,请按照链接操作)。


    为了添加到CharlesBailey的解决方案中,我只使用了一个git-rebase-i从以前的提交中删除不需要的文件,它工作起来很有魅力。步骤:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 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

    当然,江户十一〔三〕是一条必经之路。

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

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

    埃多克斯1〔6〕


    如果这是您要清理的最新提交,我尝试了Git版本2.14.3(Apple Git-98):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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的设计目的。


    您还可以使用:

    埃多克斯1〔12〕