删除特定的提交

我正在和一个朋友一起在一个项目上工作,他编辑了一些本不应该被编辑的文件。 不知何故,我把他的工作合并到了我的工作中,无论是当我拉我的工作,还是当我试图挑选出我想要的特定文件时。 我一直在寻找和玩了很长时间,试图找出如何删除包含对这些文件进行编辑的提交,似乎是回退和rebase之间的折腾,并没有直截了当的例子,文档假设我知道比我更多。

所以这是一个简化的问题:

鉴于以下情况,我如何删除提交2?

$ mkdir git_revert_test && cd git_revert_test $ git init Initialized empty Git repository in /Users/josh/deleteme/git_revert_test/.git/ $ echo "line 1" > myfile $ git add -A $ git commit -m "commit 1" [master (root-commit) 8230fa3] commit 1 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 myfile $ echo "line 2" >> myfile $ git commit -am "commit 2" [master 342f9bb] commit 2 1 files changed, 1 insertions(+), 0 deletions(-) $ echo "line 3" >> myfile $ git commit -am "commit 3" [master 1bcb872] commit 3 1 files changed, 1 insertions(+), 0 deletions(-) 

预期的结果是

 $ cat myfile line 1 line 3 

这里是我一直试图恢复的一个例子

 $ git revert 342f9bb Automatic revert failed. After resolving the conflicts, mark the corrected paths with 'git add <paths>' or 'git rm <paths>' and commit the result. 

Git在计算diff时要使用的algorithm需要这样做

  1. 正在恢复的行不会被任何稍后的提交所修改
  2. 在历史的后面没有任何其他“相邻”的承诺。

“相邻”的定义是基于上下文diff的默认行数,即3。因此,如果'myfile'是这样构造的:

 $ cat >myfile <<EOF line 1 junk junk junk junk line 2 junk junk junk junk line 3 EOF $ git add myfile $ git commit -m "initial check-in" 1 files changed, 11 insertions(+), 0 deletions(-) create mode 100644 myfile $ perl -p -i -e 's/line 2/this is the second line/;' myfile $ git commit -am "changed line 2 to second line" [master d6cbb19] changed line 2 1 files changed, 1 insertions(+), 1 deletions(-) $ perl -p -i -e 's/line 3/this is the third line/;' myfile $ git commit -am "changed line 3 to third line" [master dd054fe] changed line 3 1 files changed, 1 insertions(+), 1 deletions(-) $ git revert d6cbb19 Finished one revert. [master 2db5c47] Revert "changed line 2" 1 files changed, 1 insertions(+), 1 deletions(-) 

然后这一切按预期工作。

第二个答案非常有趣。 还有一个function还没有正式发布(尽pipe可以在Git v1.7.2-rc2中find)称为Revert Strategy。 你可以像这样调用git:

git revert –strategy resolve <commit>

而且应该更好地搞清楚你的​​意思。 我不知道可用策略的清单是什么,也不知道任何策略的定义。

有四种方法可以这样做:

  • 干净的方式,恢复但logging恢复:

     git revert --strategy resolve <commit> 
  • 苛刻的方式,只删除最后一次提交:

     git reset --soft "HEAD^" 

注意:避免git reset --hard因为它也将放弃自上次提交以来文件中的所有更改。 如果 – --soft不起作用,则尝试 – --mixed或 – --keep

  • Rebase(显示最近5次提交的日志并删除你不想要的行,或重新sorting,或压缩多个提交,或者做任何你想要的东西,这是一个非常灵活的工具):

     git rebase -i HEAD~5 

如果发生错误:

 git rebase --abort 
  • 快速重新分配:只使用其ID删除特定的提交:

     git rebase --onto commit-id^ commit-id 
  • 替代品:你也可以尝试:

     git cherry-pick commit-id 
  • 还有另一种select:

     git revert --no-commit 
  • 作为最后的手段,如果你需要充分的历史编辑自由(例如,因为git不允许你编辑你想要的),你可以使用这个非常快的开源应用程序: reposurgeon 。

注意:当然,所有这些更改都是在本地完成的,您应该随后将这些更改应用到远程。 如果您的repo不想删除提交(“不允许快进”,当您想删除已经推送的提交时发生),可以使用git push -f强制推送更改。

注2:如果在分支上工作,你需要强制推送,你应该完全避免git push --force因为这可能会覆盖其他分支(如果你已经做了更改,即使你当前的结账在另一个分支上)。 当你强制推送时总是要指定远程分支git push --force origin your_branch

你的select是在之间

  1. 保持错误并引入修复和
  2. 消除错误并改变历史。

你应该select(1)如果错误的改变被其他人拿走,(2)如果错误被限制在一个私人的未被推送的分支。

Git还原是一个自动化的工具(1),它创build一个新的提交撤消一些以前的提交。 您会在项目历史logging中看到错误和删除,但是从存储库中提取的人员在更新时不会遇到问题。 这不是在你的例子中以自动方式工作,所以你需要编辑'myfile'(删除第2行),做git add myfilegit commit来处理冲突。 然后,您将在历史logging中提交四次提交,并提交4还原提交2。

如果没有人关心你的历史变化,你可以重写它,并删除提交2(select2)。 简单的方法是使用git rebase -i 8230fa3 。 这会让你进入一个编辑器,你可以select不包括错误的提交,通过删除提交(并保持“挑选”旁边的其他提交消息。请仔细阅读这样做的后果 。

你可以使用git rebase删除不需要的提交。 假设你从一个同事的主题分支提交了一些提交到你的主题分支,但后来决定你不想要这些提交。

 git checkout -b tmp-branch my-topic-branch # Use a temporary branch to be safe. git rebase -i master # Interactively rebase against master branch. 

此时您的文本编辑器将打开交互式底视图。 例如

混帐底垫,待办事项

  1. 通过删除他们的行来删除你不想要的提交
  2. 保存并退出

如果重build失败,请删除临时分支并尝试其他策略。 否则继续按照以下说明操作。

 git checkout my-topic-branch git reset --hard tmp-branch # Overwrite your topic branch with the temp branch. git branch -d tmp-branch # Delete the temporary branch. 

如果您将主题分支推送到远程,则可能需要强制推送,因为提交历史logging已更改。 如果其他人在同一个分支上工作,就给他们一个领导。

非常简单的方法

git rebase -i HEAD〜x

(x =提交数量)

一旦执行记事本文件将打开“放”除了你的提交 在这里输入图像描述

这就是你所做的…只是同步git仪表板,变化将被推到远程。 如果您提交的提交已经在远程,您将不得不强制更新。 由于–force被认为是有害的 ,请使用git push --force-with-lease

从这里的其他答案来看,我对于如何使用git rebase -i来移除提交感到困惑,所以我希望可以在这里记下我的testing用例(非常类似于OP)。

这里是一个bash脚本,您可以将其粘贴到/tmp文件夹中创build一个testing存储库:

 set -x rm -rf /tmp/myrepo* cd /tmp mkdir myrepo_git cd myrepo_git git init git config user.name me git config user.email me@myself.com mkdir folder echo aaaa >> folder/file.txt git add folder/file.txt git commit -m "1st git commit" echo bbbb >> folder/file.txt git add folder/file.txt git commit -m "2nd git commit" echo cccc >> folder/file.txt git add folder/file.txt git commit -m "3rd git commit" echo dddd >> folder/file.txt git add folder/file.txt git commit -m "4th git commit" echo eeee >> folder/file.txt git add folder/file.txt git commit -m "5th git commit" 

在这一点上,我们有一个file.txt与这些内容:

 aaaa bbbb cccc dddd eeee 

此时,HEAD在第5次提交,HEAD〜1将是第4次,而HEAD〜4将是第1次提交(所以HEAD〜5将不存在)。 比方说,我们要删除第三个提交 – 我们可以在myrepo_git目录中发出这个命令:

 git rebase -i HEAD~4 

请注意, git rebase -i HEAD~5结果是“致命的:需要一个版本;无效的上游 HEAD〜5 ”。 )文本编辑器(参见@Dennis的答案中的截图)将打开这些内容:

 pick 5978582 2nd git commit pick 448c212 3rd git commit pick b50213c 4th git commit pick a9c8fa1 5th git commit # Rebase b916e7f..a9c8fa1 onto b916e7f # ... 

所以我们 (但不包括 )我们要求的HEAD〜4开始提交所有的提交。 删除行pick 448c212 3rd git commit并保存该文件; 你会从git rebase得到这个回应:

 error: could not apply b50213c... 4th git commit When you have resolved this problem run "git rebase --continue". If you would prefer to skip this patch, instead run "git rebase --skip". To check out the original branch and stop rebasing run "git rebase --abort". Could not apply b50213c... 4th git commit 

此时在文本编辑器中打开myrepo_git / folder/file.txt ; 你会看到它已被修改:

 aaaa bbbb <<<<<<< HEAD ======= cccc dddd >>>>>>> b50213c... 4th git commit 

基本上, git看到HEAD第二次提交时,有aaaa + bbbb内容; 然后它有一个补丁cccc + dddd补丁,它不知道如何追加到现有的内容。

所以这里git不能为你决定 – 必须作出决定:通过删除第三个提交,你要么保持它所引入的变化(这里,行cccc ) – 否则你不这样做。 如果不这样做,只需使用文本编辑器删除folder/file.txt中的多余行(包括cccc ,如下所示:

 aaaa bbbb dddd 

…然后保存folder/file.txt 。 现在,您可以在myrepo_git目录中发出以下命令:

 $ nano folder/file.txt # text editor - edit, save $ git rebase --continue folder/file.txt: needs merge You must edit all merge conflicts and then mark them as resolved using git add 

啊 – 所以为了标记我们已经解决了冲突,我们必须在 git add folder/file.txt之前做git rebase --continue

 $ git add folder/file.txt $ git rebase --continue 

这里再次打开一个文本编辑器,显示第4th git commit4th git commit – 在这里我们有机会改变提交消息(在这种情况下,可以有意义的更改为4th (and removed 3rd) commit或类似)。 假设你不想 – 所以只需退出文本编辑器而不保存; 一旦你这样做,你会得到:

 $ git rebase --continue [detached HEAD b8275fc] 4th git commit 1 file changed, 1 insertion(+) Successfully rebased and updated refs/heads/master. 

在这一点上,现在你有一个像这样的历史(你也可以检查与gitk .或其他工具)的folder/file.txt的内容(显然,原始提交的时间戳不变):

 1st git commit | +aaaa ---------------------------------------------- 2nd git commit | aaaa | +bbbb ---------------------------------------------- 4th git commit | aaaa | bbbb | +dddd ---------------------------------------------- 5th git commit | aaaa | bbbb | dddd | +eeee 

如果以前,我们决定保留cccc这一行(我们删除了第三个git commit的内容),那么我们可能会有:

 1st git commit | +aaaa ---------------------------------------------- 2nd git commit | aaaa | +bbbb ---------------------------------------------- 4th git commit | aaaa | bbbb | +cccc | +dddd ---------------------------------------------- 5th git commit | aaaa | bbbb | cccc | dddd | +eeee 

那么,这是我希望我已经find的那种阅读方式,开始讨论git rebase如何在删除提交/修订方面的工作; 所以希望它也可以帮助别人…

所以这听起来好像是在一个合并提交中合并了错误的提交。 你的合并提交被拉了吗? 如果是的话,那么你会想要使用git revert ; 你必须咬紧牙关,努力解决冲突。 如果不是,那么你可以想像要么rebase或还原,但你可以在合并提交之前这样做,然后重新合并。

我们没有太多的帮助,我们可以给你的第一个案件,真的。 在尝试恢复之后,发现自动失败,您必须检查冲突并适当地修复它们。 这与解决合并冲突的过程完全相同; 您可以使用git status来查看冲突的位置,编辑未合并的文件,find冲突的区块,找出如何解决它们,添加冲突的文件,最后提交。 如果你自己使用git commit (no -m <message> ),编辑器中popup的消息应该是由git revert创build的模板消息; 您可以添加关于如何解决冲突的注释,然后保存并退出提交。

对于第二种情况, 合并之前解决问题,根据合并后是否做了更多的工作,有两个子类。 如果你还没有,你可以简单的git reset --hard HEAD^取消合并,回复,然后重新合并。 但是我猜你已经 所以,你最终会做这样的事情:

  • 在合并之前创build一个临时分支,并检查出来
  • 做回复(或使用git rebase -i <something before the bad commit> <temporary branch>去除坏提交)
  • 重新合并
  • 重新git rebase --onto <temporary branch> <old merge commit> <real branch>你的后续工作: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • 删除临时分支

所以你做了一些工作,并推动它,让我们叫他们提交A和B.你的同事也做了一些工作,提交C和D.你合并你的同事工作(合并提交E),然后继续工作,承诺, (承诺F),并发现你的同事改变了一些他不应该拥有的东西。

所以你的提交历史看起来像这样:

 A -- B -- C -- D -- D' -- E -- F 

你真的想摆脱C,D和D'。 既然你说你把你的同事合并到你的工作中,这些提交已经“在那里”了,所以使用git rebase去除提交是一个不好的做法。 相信我,我已经试过了。

现在我看到了两条出路:

  • 如果您还没有将E和F推送给您的同事或任何其他人(通常是您的“起源”服务器),那么您仍然可以暂时将其从历史logging中删除。 这是你想要保存的工作。 这可以用一个

     git reset D' 

    (用你可以从git log获得的实际提交哈希代替D')

    在这一点上,提交E和F都不见了,这个改变也是在你的本地工作空间中的未经改变的改变。 在这一点上,我会把他们移动到一个分支或把他们变成一个补丁,并保存以备后用。 现在,恢复你的同事的工作,或自动git revert或手动。 当你这样做的时候,重播你的作品。 你可能会合并冲突,但至less他们会在写的代码中,而不是你的同事的代码中。

  • 如果你已经推动了你同事提交后所做的工作,那么你仍然可以尝试手动或者使用git revert来获得“反向修补”,但是由于你的工作是“在途中”,所以可以这么说可能会得到更多的合并冲突和更混乱的冲突。 看起来这就是你最终在…

请按照下面的行动,因为我们正在使用 – 你需要有pipe理权限的git回购做到这一点。

第1步:find提交之前提交你想删除git log

第2步:签出提交git checkout <commit hash>

第3步:使用您当前的签出提交git checkout -b <new branch>

第4步:现在你需要在删除提交git cherry-pick <commit hash>后添加提交

第5步:现在重复第4步所有其他提交你想保留。

第6步:一旦所有提交已添加到您的新分支,并已提交。 检查一切是否处于正确的状态并按预期工作。 仔细检查一切已经提交: git status

第7步:切换到你的破碎的分支git checkout <broken branch>

第8步:现在执行一个硬复位的分支到提交之前,你想要删除git reset --hard <commit hash>

第9步:合并你的固定分支到这个分支git merge <branch name>

第10步:将合并的更改推回原点。 警告:这将覆盖远程回购! git push --force origin <branch name>

您可以通过用步骤8replace步骤2和3来创build新分支,然后不执行步骤7和9。