删除特定提交

我和一个朋友一起做一个项目,他编辑了一堆不应该编辑的文件。不知怎的,我把他的工作合并到我的工作中,要么是我把它拉出来,要么是我试图挑出我想要的特定文件。我一直在寻找和玩了很长一段时间,试图弄清楚如何删除包含这些文件编辑的提交,这似乎是一个在恢复和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.
589266 次浏览

你的选择是

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

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

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

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

听起来好像坏提交在某个时候被合并提交了。你的合并提交被拉回来了吗?如果是,那么你将使用git revert;你必须咬紧牙关,克服矛盾。如果不是,那么你可以想象要么rebase或还原,但你可以这样做之前合并提交,然后重做合并。

对于第一个案子,我们真的帮不上什么忙。在尝试恢复并发现自动恢复失败后,您必须检查冲突并适当地修复它们。这与修复合并冲突的过程完全相同;你可以使用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>
  • 移除临时分支

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中可用)称为恢复策略。你可以像这样调用git:

git恢复——策略解决 <

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

有四种方法:

  • 干净的方式,恢复,但保留日志恢复:

    git revert --strategy resolve <commit>
    
  • Harsh way, remove altogether only the last commit:

    git reset --soft "HEAD^"
    

Note: Avoid git reset --hard as it will also discard all changes in files since the last commit. If --soft does not work, rather try --mixed or --keep.

  • Rebase (show the log of the last 5 commits and delete the lines you don't want, or reorder, or squash multiple commits in one, or do anything else you want, this is a very versatile tool):

    git rebase -i HEAD~5
    

And if a mistake is made:

git rebase --abort
  • 快速rebase:只删除一个特定的提交使用其id:

    git rebase --onto commit-id^ commit-id
    
  • Alternatives: you could also try:

    git cherry-pick commit-id
    
  • Yet another alternative:

    git revert --no-commit
    
  • As a last resort, if you need full freedom of history editing (eg, because git don't allow you to edit what you want to), you can use this very fast open source application: reposurgeon.

Note: of course, all these changes are done locally, you should git push afterwards to apply the changes to the remote. And in case your repo doesn't want to remove the commit ("no fast-forward allowed", which happens when you want to remove a commit you already pushed), you can use git push -f to force push the changes.

Note2: if working on a branch and you need to force push, you should absolutely avoid git push --force because this may overwrite other branches (if you have made changes in them, even if your current checkout is on another branch). Prefer to always specify the remote branch when you force push: git push --force origin your_branch.

你可以使用git rebase删除不需要的提交。 假设你从同事的topic分支中包含了一些提交到你的topic分支中,但后来决定不需要这些提交
git checkout -b tmp-branch my-topic-branch  # Use a temporary branch to be safe.
git rebase -i master  # Interactively rebase against master branch.

此时,文本编辑器将打开交互式rebase视图。例如

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.

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

所以你做了一些工作并推动了它,我们称之为提交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 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是第1次提交(因此HEAD~5不存在)。假设我们想要删除第三次提交——我们可以在myrepo_git目录下发出这个命令:

git rebase -i HEAD~4

(注意git rebase -i HEAD~5的结果是“fatal:需要一次修订;上游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 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.

在这一点上,现在你有一个类似这样的folder/file.txt内容的历史(你也可以用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 rebase -i HEAD~x

其中x是提交数。

在提交之前输入drop:
< br > enter image description here

就这样,完成了。如果您删除的提交已经在远程上,则必须强制push。因为——force被认为是有害的,所以使用git push --force-with-lease

方法1

首先获取需要恢复的提交散列(例如:1406cd61)。简单的修复将在命令之下,

$ git revert 1406cd61

如果你在1406cd61文件提交后提交了更多与1406cd61文件相关的更改,上述简单命令将无法工作。然后你要做下面的步骤,也就是摘樱桃。

方法2

请遵循下面的动作顺序,因为我们使用——force,你需要在git repo上有管理的权利来做到这一点。

步骤1:在你想要删除git log的提交之前找到提交

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

使用当前的签出提交git checkout -b <new branch>创建一个新的分支

现在你需要在删除的提交git cherry-pick <commit hash>之后添加提交

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

步骤6:一旦所有的提交都被添加到你的新分支并被提交。检查所有东西是否处于正确的状态并按预期工作。仔细检查已提交的所有内容:git status

第七步:切换到你的坏分支git checkout <broken branch>

现在,在你想要删除git reset --hard <commit hash>的分支之前,对损坏的分支执行硬重置

将你的固定分支合并到这个分支git merge <branch name>

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

您可以在不创建新分支的情况下完成该过程,只需替换步骤2 &3与步骤8,然后不执行步骤7 &9.

git恢复策略解析 如果commit是一个merge: 使用git revert——strategy resolve -m 1

我能想到一个很简单的方法

git reset --hard HEAD <YOUR COMMIT ID>

然后重置远程分支

git push origin -f

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

  1. 检查当前的主要分支机构(例如开发)
git checkout develop
  1. 在历史日志中查找你的commit-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"

这里有一个快速的例子,如何用egit做到这一点:

  1. 我想删除提交3,我添加的文件3 李enter image description here < / >
  2. 右键单击要删除的文件之前的提交文件,选择“interactive rebase”; 李enter image description here < / > 在rebase视图中选择commit three并单击上面的跳过图标。在胡佛这个图标,它告诉你提交将被删除。 李enter image description here < / >
  3. 单击start rebase。 李enter image description here < / >
  4. 进入git staging并单击push。 李enter image description here < / >
  5. 在弹出的窗口中,点击force并继续前进 李enter image description here < / >
  6. 在这之后你可以看到提交被删除了。 李enter image description here < / >

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

我的情况:

% git log --oneline


4ad59d6 commit 3
f244533 commit 2
c5b4688 commit 1

我们想做的是创建commit 4,并对“commit 2”进行修改。恢复。这就是我们所做的:

  1. 获取commit 2中的更改:

    % git show >~ / / commit.2.patch补丁

  2. % git应用-R ~/patches/commit.2.patch

  3. 创建新的提交:

    % git commit -Am“commit 4:还原commit 2中的更改”;

附注:这适用于我们可以轻松应用revert的情况-如果相同的代码行在一定时间内被修改了,这将不起作用。

< p >最简单的方式, 只需查找日志并计算提交的次数,并检查您想要删除的commit-msg

x替换为count,我将显示所有的commit-msg,只需删除你想删除的commit-msg。

# git rebase -i HEAD~x