372

我和一个朋友一起做一个项目,他编辑了一堆不应该编辑的文件。不知何故,我将他的作品合并到我的作品中,无论是在我拉出它时,还是在我试图挑选出我想要的特定文件时。我一直在寻找和玩很长时间,试图弄清楚如何删除包含对这些文件的编辑的提交,这似乎是revert和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.
4

14 回答 14

367

有四种方法可以这样做:

  • 干净的方式,还原但保留日志还原:

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

    git reset --soft "HEAD^"
    

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

  • Rebase(显示最后 5 次提交的日志并删除您不想要的行,或重新排序,或将多个提交压缩为一个,或做任何您想做的事情,这是一个非常通用的工具):

    git rebase -i HEAD~5
    

如果出现错误:

git rebase --abort
  • 快速变基:使用其 id 仅删除特定提交:

    git rebase --onto commit-id^ commit-id
    
  • 替代方案:您也可以尝试:

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

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

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

注意2:如果在一个分支上工作并且你需要强制推送,你应该绝对避免git push --force,因为这可能会覆盖其他分支(如果你对它们进行了更改,即使你当前的结帐在另一个分支上)。强制推送时,最好始终指定远程分支git push --force origin your_branch

于 2012-08-16T17:11:07.613 回答
193

这是一个简单的解决方案:

git rebase -i HEAD~x

(注:x是提交次数)

执行记事本文件将打开。drop在您的提交之外输入。
如果您不了解 Vim,只需单击要编辑的每个单词,然后按“I”键(用于插入模式)。完成输入后,按“esc”键退出插入模式。



在此处输入图像描述

就是这样,你完成了......只需同步 git 仪表板,更改将被推送到远程。

如果您删除的提交已经在远程,您将不得不强制推送。由于 --force 被认为是 有害的,因此使用git push --force-with-lease.

于 2016-12-02T07:31:39.070 回答
82

Git 在计算要还原的差异时使用的算法要求

  1. 被还原的行不会被任何以后的提交修改。
  2. 历史后期没有任何其他“相邻”提交。

“相邻”的定义基于上下文差异的默认行数,即 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(-)

然后一切都按预期工作。

第二个答案很有趣。有一个尚未正式发布的功能(尽管它在 Git v1.7.2-rc2 中可用)称为 Revert Strategy。你可以像这样调用 git:

git revert --strategy resolve <提交>

它应该更好地弄清楚你的意思。我不知道可用策略列表是什么,也不知道任何策略的定义。

于 2010-07-08T19:26:04.780 回答
41

方法一

首先获取您需要还原的提交哈希(例如:1406cd61)。简单的修复将在命令下方,

$ git revert 1406cd61

如果您在 1406cd61 提交后提交了更多与 1406cd61 文件相关的更改,上述简单命令将不起作用。然后您必须执行以下步骤,即樱桃采摘。

方法二

请遵循以下操作顺序,由于我们使用 --force,您需要对git repo拥有管理员权限才能执行此操作。

第 1 步:找到要删除的提交之前的提交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>

您可以通过将第 2 步和第 3 步替换为第 8 步然后不执行第 7 步和第 9 步来执行此过程而无需创建新分支。

于 2017-09-05T07:14:51.930 回答
40

您的选择介于

  1. 保持错误并引入修复和
  2. 删除错误并更改历史记录。

您应该选择 (1) 如果错误的更改已被其他人拾取,并且 (2) 如果错误仅限于私有未推送的分支。

Git revert 是一个自动化工具来做 (1),它创建一个新的提交来撤销一些以前的提交。您将在项目历史记录中看到错误和删除,但从您的存储库中提取的人在更新时不会遇到问题。在您的示例中它不是以自动方式工作,因此您需要编辑“myfile”(删除第 2 行),执行git add myfilegit commit处理冲突。然后,您将在历史记录中得到四个提交,其中提交 4 恢复提交 2。

如果没有人关心你的历史改变,你可以重写它并删除提交 2(选择 2)。最简单的方法是使用git rebase -i 8230fa3. 这将使您进入编辑器,您可以通过删除提交来选择不包含错误提交(并在其他提交消息旁边保留“选择”。请阅读这样做的后果

于 2010-05-30T11:26:50.253 回答
20

您可以使用 删除不需要的提交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.

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

git-rebase-todo

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

如果变基不成功,请删除临时分支并尝试其他策略。否则继续以下说明。

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.

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

于 2014-01-24T17:04:46.193 回答
9

从这里的其他答案来看,我对如何用来删除提交有点困惑git rebase -i,所以我希望在这里记下我的测试用例是可以的(与 OP 非常相似)。

这是一个bash脚本,您可以粘贴它以在文件夹中创建测试存储库/tmp

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 将是第一次提交(因此 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

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

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

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

所以这里git不能为你做决定——必须做出决定:通过删除第三次提交,你要么保留它引入的更改(这里是 line cccc) - 或者你不保留。如果你不这样做,只需使用文本编辑器删除额外的行 - 包括cccc- folder/file.txt,它看起来像这样:

aaaa
bbbb
dddd

...然后保存folder/file.txtmyrepo_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 addfolder/file.txt做之前 , git rebase --continue

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

这里文本编辑器再次打开,显示该行4th 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.

在这一点上,现在您有这样的历史记录(您也可以使用 saygitk .或其他工具检查)的内容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 提交的内容),我们将拥有:

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

嗯,这是我希望我能找到的那种阅读,开始探索git rebase在删除提交/修订方面是如何工作的;所以希望它也可以帮助其他人......

于 2015-03-15T10:36:15.247 回答
3

所以听起来错误的提交在某个时候被合并到了合并提交中。您的合并提交是否已被拉出?如果是,那么您将要使用git revert; 你必须咬紧牙关,解决冲突。如果不是,那么您可以想象变基或还原,但您可以在合并提交之前这样做,然后重做合并。

对于第一种情况,我们无法为您提供太多帮助,真的。在尝试恢复后,发现自动恢复失败,您必须检查冲突并适当地修复它们。这与修复合并冲突的过程完全相同;您可以使用git status查看冲突在哪里,编辑未合并的文件,找到冲突的块,弄清楚如何解决它们,添加冲突的文件,最后提交。如果单独使用git commit(否-m <message>),编辑器中弹出的消息应该是由git revert;创建的模板消息。您可以添加有关如何修复冲突的注释,然后保存并退出以提交。

对于第二种情况,在合并之前解决问题,有两个子情况,具体取决于您是否在合并后完成了更多工作。如果还没有,您可以简单git reset --hard HEAD^地取消合并,进行还原,然后重做合并。但我猜你有。所以,你最终会做这样的事情:

  • 在合并之前创建一个临时分支,并检查它
  • 执行还原(或用于git rebase -i <something before the bad commit> <temporary branch>删除错误提交)
  • 重做合并
  • 重新定位您的后续工作:git rebase --onto <temporary branch> <old merge commit> <real branch>
  • 删除临时分支
于 2010-05-30T13:22:06.250 回答
1

所以你做了一些工作并推动了它,我们称之为提交 A 和 B。你的同事也做了一些工作,提交 C 和 D。你将同事的工作合并到你的工作中(合并提交 E),然后继续工作,提交,也(提交 F),并发现您的同事更改了一些他不应该更改的内容。

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

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

你真的想摆脱 C、D 和 D'。由于您说您将同事的工作合并到您的工作中,因此这些提交已经“存在”,因此使用例如 git rebase 删除提交是一个禁忌。相信我,我已经试过了。

现在,我看到了两条出路:

  • 如果您尚未将 E 和 F 推送给您的同事或其他任何人(通常是您的“原始”服务器),您仍然可以暂时将它们从历史记录中删除。这是您要保存的工作。这可以通过一个

    git reset D'
    

    (将 D' 替换为您可以从git log

    此时,提交 E 和 F 消失了,更改再次成为本地工作区中未提交的更改。在这一点上,我会将它们移动到一个分支或将它们变成一个补丁并保存以备后用。现在,使用 a 自动git revert或手动恢复您同事的工作。完成后,在此基础上重播您的工作。您可能有合并冲突,但至少它们会出现在编写的代码中,而不是您同事的代码中。

  • 如果您在同事提交后已经推送了您所做的工作,您仍然可以尝试手动或使用“反向补丁” git revert,但由于您的工作“妨碍”,可以这么说,您可能会得到更多的合并冲突和更混乱的冲突。看来你最终是这样的...

于 2014-12-09T20:01:43.287 回答
1

git revert --strategy resolve 如果提交是合并: 使用 git revert --strategy resolve -m 1

于 2019-10-02T13:17:28.187 回答
1

我有一个简单的解决方案,使用补丁来恢复您的所有更改。

  1. 结帐当前的头分支(例如开发)
git checkout develop
  1. 在历史日志中查找您的提交 ID,并仅将您的更改签出到新分支中:
git log
git checkout -b your-branch <your-commit-id>
  1. 在您的分支中查找,并找到您要恢复到的先前状态:
git checkout -b prev-status <previous-commit-id>
  1. 创建一个可以还原所有更改的补丁:
git diff your-branch..prev-status > reverts.patch
# the comparing order of branches is important
  1. 签出当前头分支并应用恢复补丁
git checkout origin develop
git apply reverts.patch
git add *
git commit -m "revert all my changes"
于 2021-04-06T03:22:21.310 回答
0

以下是如何使用 egit 执行此操作的快速示例:

  1. 我想删除我添加文件 3 的提交 3 在此处输入图像描述
  2. 右键单击要删除的提交之前的提交,然后选择“交互式变基” 在此处输入图像描述
  3. 在变基视图中选择提交三并单击上面的图标跳过。将鼠标悬停在此图标上时,它会告诉您提交将被删除。 在此处输入图像描述
  4. 单击开始变基。 在此处输入图像描述
  5. 进入 git staging 并点击 push。 在此处输入图像描述
  6. 在出现的弹出窗口中单击强制并继续 在此处输入图像描述
  7. 在此之后,您可以看到提交已被删除。 在此处输入图像描述
于 2022-01-18T10:52:19.337 回答
0

最近遇到了一个类似的问题,最后做了这个,感觉更简单,可以在少数情况下工作。

我的场景:

% git log --oneline

4ad59d6 commit 3
f244533 commit 2
c5b4688 commit 1

我们想要做的是创建提交 4,并还原“提交 2”中的更改。这就是我们所做的:

  1. 获取提交 2 中的更改:

    % git show > ~/patches/commit.2.patch

  2. 还原更改:

    % git apply -R ~/patches/commit.2.patch

  3. 创建新提交:

    % git commit -Am "commit 4 : reverts changes in commit 2"

PS:这适用于我们可以轻松应用还原的情况 - 如果随着时间的推移修改了相同的代码行,这将不起作用。

于 2022-01-21T07:25:06.140 回答
-1

我会看到一个非常简单的方法

git reset --hard HEAD <YOUR COMMIT ID>

然后重置远程分支

git push origin -f

于 2020-08-24T13:53:41.360 回答