我和一个朋友一起做一个项目,他编辑了一堆不应该编辑的文件。不知何故,我将他的作品合并到我的作品中,无论是在我拉出它时,还是在我试图挑选出我想要的特定文件时。我一直在寻找和玩很长时间,试图弄清楚如何删除包含对这些文件的编辑的提交,这似乎是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.
有四种方法可以这样做:
干净的方式,还原但保留日志还原: git revert --strategy resolve
苛刻的方式,只删除最后一次提交: 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
。
这是一个简单的解决方案:
git rebase -i HEAD~x
(注:x
是提交次数)
执行记事本文件将打开。在您的提交旁边输入 drop
。
如果您不了解 Vim,只需单击要编辑的每个单词,然后按“I”键(用于插入模式)。完成输入后,按“esc”键退出插入模式。
https://i.stack.imgur.com/nvGO3.png
就是这样,你完成了......只需同步 git 仪表板,更改将被推送到远程。
如果您删除的提交已经在远程,您将不得不强制推送。由于 --force 被视为 harmful,请使用 git push --force-with-lease
。
Git 在计算要还原的差异时使用的算法要求
被还原的行不会被任何以后的提交修改。历史后期没有任何其他“相邻”提交。
“相邻”的定义基于上下文差异的默认行数,即 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 <提交>
它应该更好地弄清楚你的意思。我不知道可用策略列表是什么,也不知道任何策略的定义。
perl -p
可用于编写非常短的(一行)程序,该程序循环并将其输入传递 到输出,类似于 sed。 perl -i
用于就地编辑文件。 perl -e
是如何提交要评估的代码。
方法一
首先获取您需要还原的提交哈希(例如: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 步来执行此过程而无需创建新分支。
您的选择介于
保留错误并引入修复程序并删除错误并更改历史记录。
您应该选择 (1) 如果错误的更改已被其他人拾取,并且 (2) 如果错误仅限于私有未推送的分支。
Git revert 是一个自动化工具来做 (1),它创建一个新的提交来撤销一些以前的提交。您将在项目历史记录中看到错误和删除,但从您的存储库中提取的人在更新时不会遇到问题。在您的示例中它不是以自动方式工作,因此您需要编辑“myfile”(删除第 2 行),执行 git add myfile
和 git commit
来处理冲突。然后,您将在历史记录中得到四个提交,其中提交 4 恢复提交 2。
如果没有人关心你的历史改变,你可以重写它并删除提交 2(选择 2)。执行此操作的简单方法是使用 git rebase -i 8230fa3
。这将使您进入编辑器,您可以通过删除提交来选择不包含错误提交(并在其他提交消息旁边保留“选择”。请阅读 consequences of doing this。
您可以使用 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.
此时,您的文本编辑器将打开交互式变基视图。例如
https://i.stack.imgur.com/Ig79s.png
通过删除它们的行来删除您不想要的提交保存并退出
如果变基不成功,请删除临时分支并尝试其他策略。否则继续以下说明。
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.
如果您将主题分支推送到远程,您可能需要强制推送,因为提交历史已更改。如果其他人在同一分支上工作,请提醒他们。
从这里的其他答案来看,我对如何使用 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 将不存在)。假设我们要删除第 3 次提交 - 我们可以在 myrepo_git
目录中发出以下命令:
git rebase -i HEAD~4
(请注意,git rebase -i HEAD~5
的结果为“致命:需要单个修订;无效的上游 HEAD~5”。)文本编辑器(参见 @Dennis' answer 中的屏幕截图)将打开并显示以下内容:
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
无法为您做出决定 - 您 必须做出决定:通过删除第 3 次提交,您要么保留它引入的更改(此处为第 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 rebase --continue
之前必须 git add
folder/file.txt
:
$ 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.
在这一点上,现在您有了这样的历史记录(您也可以使用 gitk .
或其他工具检查)的内容(显然,原始提交的时间戳未更改):
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
在删除提交/修订方面的工作原理;所以希望它也可以帮助其他人......
所以听起来错误的提交在某个时候被合并到了合并提交中。您的合并提交是否已被拉出?如果是,那么您将要使用 git revert
;你必须咬紧牙关,解决冲突。如果不是,那么您可以想象变基或还原,但您可以在合并提交之前 这样做,然后重做合并。
对于第一种情况,我们无法为您提供太多帮助,真的。在尝试恢复后,发现自动恢复失败,您必须检查冲突并适当地修复它们。这与修复合并冲突的过程完全相同;您可以使用 git status
查看冲突在哪里,编辑未合并的文件,找到冲突的块,弄清楚如何解决它们,添加冲突的文件,最后提交。如果单独使用git commit
(没有-m <message>
),编辑器中弹出的消息应该是git revert
创建的模板消息;您可以添加有关如何修复冲突的注释,然后保存并退出以提交。
对于第二种情况,在合并之前解决问题,有两种子情况,具体取决于您是否在合并后完成了更多工作。如果还没有,您可以简单地 git reset --hard HEAD^
取消合并,进行还原,然后重新进行合并。但我猜你有。所以,你最终会做这样的事情:
在合并之前创建一个临时分支,并检查它
执行还原(或使用 git rebase -i
重做合并
重新定位您的后续工作: git rebase --onto
删除临时分支
所以你做了一些工作并推动了它,我们称之为提交 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 已经消失,并且更改再次成为本地工作区中未提交的更改。在此时我会将它们移动到一个分支或将它们变成一个补丁并保存以供以后使用。现在,使用 git revert 自动或手动恢复您同事的工作。完成后,在上面重播您的工作你可能有合并冲突,但至少它们会出现在你编写的代码中,而不是你同事的代码中。
如果您在同事提交后已经推送了您所做的工作,您仍然可以尝试手动或使用 git revert 获得“反向补丁”,但由于您的工作“阻碍”,可以这么说你会可能会得到更多的合并冲突和更混乱的冲突。看来你最终是这样的...
git revert --strategy resolve 如果提交是合并:使用 git revert --strategy resolve -m 1
我有一个简单的解决方案,使用补丁来恢复您的所有更改。
结帐当前的头分支(例如开发)
git checkout develop
在历史日志中查找您的提交 ID,并仅将您的更改签出到新分支中:
git log
git checkout -b your-branch <your-commit-id>
在您的分支中查找,并找到您要恢复到的先前状态:
git checkout -b prev-status <previous-commit-id>
创建一个可以还原所有更改的补丁:
git diff your-branch..prev-status > reverts.patch
# the comparing order of branches is important
签出当前头分支并应用恢复补丁
git checkout origin develop
git apply reverts.patch
git add *
git commit -m "revert all my changes"
最近遇到了一个类似的问题,最后做了这个,感觉更简单,可以在少数情况下工作。
我的场景:
% git log --oneline
4ad59d6 commit 3
f244533 commit 2
c5b4688 commit 1
我们想要做的是创建提交 4,并还原“提交 2”中的更改。这就是我们所做的:
获取提交 2 中的更改: % git show > ~/patches/commit.2.patch 还原更改: % git apply -R ~/patches/commit.2.patch 创建新提交: % git commit -Am "commit 4 : 还原提交 2 中的更改"
PS:这适用于我们可以轻松应用还原的情况 - 如果随着时间的推移修改了相同的代码行,这将不起作用。
以下是如何使用 egit 执行此操作的快速示例:
我想删除我添加文件的提交 3 右键单击要删除的提交之前的提交,然后选择“交互式变基” 在变基视图中选择提交 3,然后单击上面的图标跳过。将鼠标悬停在此图标上时,它会告诉您提交将被删除。单击开始变基。进入 git staging 并点击 push。在出现的弹出窗口中单击强制并继续前进之后,您可以看到提交已被删除。
最简单的方法,只需查找日志并计算提交的数量并检查您要删除的提交消息。
将 x
替换为计数,我将显示所有的 commit-msg,只需删除您要删除的 commit-msg。
# git rebase -i HEAD~x
我会看到一个非常简单的方法
git reset --hard HEAD <YOUR COMMIT ID>
然后重置远程分支
git push origin -f
fatal: Cannot do hard reset with paths.
git rebase -i HEAD~5
为我工作。然后我只是删除了我不需要的提交,我能够在不到 30 秒的时间内解决问题。谢谢你。git revert ...
帮助我阻止了生产分支(例如master
)的提交,然后结合git cherry-pick
能够将那些恢复的提交包含到我的develop
分支中,以便在仅部署需要部署的内容时不会丢失任何开发工作。