如何撤销 git reset --hard HEAD~1?

1610

以下命令引起的更改是否可以被撤销?如果可以,该如何操作?

git reset --hard HEAD~1

14
我已经写了一份完整的指南,可以帮助你使用git找回丢失的提交记录,甚至还配有插图 :-) [点击这里查看][fixLink]。[fixLink]: http://www.programblings.com/2008/06/07/the-illustrated-guide-to-recovering-lost-commits-with-git/ - webmat
38
--hard 会丢弃未提交的更改。由于这些更改未被 Git 跟踪,因此无法通过 Git 恢复它们。 - Zaz
1
这是一篇很棒的文章,可以帮助您恢复文件。 - Jan Swart
2
这是一份来自Github的绝佳资源:如何使用Git撤销(几乎)所有操作 - jasonleonhard
我也遇到过这种情况,吸取了教训:不要使用--hard。相反,只需使用git reset开始 - 如果出现错误,它会提醒你--hard的实际含义... - undefined
显示剩余7条评论
20个回答

23
如果您尚未垃圾收集您的仓库(例如使用 git repack -d 或 git gc ,但请注意垃圾收集也可能会自动发生),那么您的提交仍然存在-只是不再通过HEAD可达。
您可以尝试通过查看 git fsck --lost-found 的输出来查找您的提交。
Git的新版本有一个称为“reflog”的东西,它是对引用所做的所有更改的日志(而不是对存储库内容所做的更改)。因此,例如,每次切换HEAD(即每次执行 git checkout 以切换分支)都将被记录。当然,您的 git reset 也操作了HEAD,因此也被记录下来。您可以像访问存储库旧状态一样,通过使用@符号而不是~来访问您的引用的旧状态,例如 git reset HEAD@{1} 。
我花了一段时间才理解HEAD@{1}和HEAD~1之间的区别,因此在这里进行一些解释。
git init
git commit --allow-empty -mOne
git commit --allow-empty -mTwo
git checkout -b anotherbranch
git commit --allow-empty -mThree
git checkout master # This changes the HEAD, but not the repository contents
git show HEAD~1 # => One
git show HEAD@{1} # => Three
git reflog

因此,HEAD~1表示“转到HEAD当前指向的提交之前的提交”,而HEAD@{1}表示“转到HEAD指向它当前指向的位置之前所指向的提交”。

这将轻松帮助您找到丢失的提交并恢复它。


2
我认为更清晰的另一个解释是:HEAD~1 表示“到 HEAD 的父级”,而 HEAD@{1} 表示“在 HEAD 的历史记录中向后退一步”。 - kizzx2
1
问题在于,“历史”这个术语在版本控制系统中有很多含义。另一种表达方式是,波浪符(~)向后移动到“提交历史”中,而at符号(@)则向后移动到“时间历史”或“时间轴”中。但是这三个版本都不是特别好的选择。 - Jörg W Mittag
@kizzx2(和Jorg),实际上这三个解释结合起来非常有帮助 - 谢谢。 - Richard Le Mesurier

19
在回答问题之前,让我们先解释一下什么是{{HEAD}}。
首先,{{HEAD}}是指当前分支上最新提交的引用。在任何给定的时间,只能有一个{{HEAD}}(不包括{{git worktree}})。
{{HEAD}}的内容存储在{{.git/HEAD}}中,其中包含当前提交的40个字节的SHA-1值。

分离头指针

如果您不在最新的提交上 - 意味着HEAD指向历史记录中的先前提交,则称为分离头指针

enter image description here

在命令行中,它会显示为这样 - SHA-1而不是分支名称,因为 HEAD 没有指向当前分支的末尾。

enter image description here


以下是解决分离 HEAD 的几种方法:


git checkout

git checkout <commit_id>
git checkout -b <new branch> <commit_id>
git checkout HEAD~X // x is the number of commits t go back

这将检出指向所需提交的新分支。
该命令将检出到给定的提交。
此时,您可以创建一个分支并从这个点开始工作。

# Checkout a given commit. 
# Doing so will result in a `detached HEAD` which mean that the `HEAD`
# is not pointing to the latest so you will need to checkout branch
# in order to be able to update the code.
git checkout <commit-id>

# create a new branch forked to the given commit
git checkout -b <branch name>

git reflog

您可以随时使用 reflog
git reflog 将显示更新了HEAD的任何更改,并检出所需的reflog条目将把HEAD设置回此提交。

每次修改HEAD都会在reflog中创建一个新条目

git reflog
git checkout HEAD@{...}

这将使你回到所需的提交

enter image description here


git reset HEAD --hard <commit_id>

"将"您的头指向所需的提交。

# This will destroy any local modifications.
# Don't do it if you have uncommitted work you want to keep.
git reset --hard 0d1d7fc32

# Alternatively, if there's work to keep:
git stash
git reset --hard 0d1d7fc32
git stash pop
# This saves the modifications, then reapplies that patch after resetting.
# You could get merge conflicts, if you've modified things which were
# changed since the commit you reset to.

注意:(自Git 2.7起
你也可以使用git rebase --no-autostash

git revert <sha-1>

撤销指定的提交或提交范围。
reset命令将“撤销”给定提交中所做的任何更改。
一个新的提交将包含撤消补丁,而原始提交也将保留在历史记录中。

# add new commit with the undo of the original one.
# the <sha-1> can be any commit(s) or commit range
git revert <sha-1>

这个模式图说明了每个命令的作用。
你可以看到 reset && checkout 修改了 HEAD

enter image description here


1
看起来,你的 git reset HEAD --hard <commit_id> 的示例来源于 https://dev59.com/im855IYBdhLWcg3w1oLa#4114122 - 如果是这样的话,请编辑并加上归属。 - Rob

12

我知道这是一个旧的主题...但是由于很多人正在寻找在Git中撤消操作的方法,我仍然认为在这里继续提供提示可能是个好主意。

当你在git gui中执行“git add”或将任何东西从左上角移动到左下角时,文件的内容会被存储在一个blob中,文件的内容可以从该blob中恢复。

因此,即使文件还没有提交,也可以从blob中恢复文件,但是必须已经添加到版本控制中。

git init  
echo hello >> test.txt  
git add test.txt  

现在已经创建了 blob,但它是通过索引引用的,因此在重置之前不会在 git fsck 中列出。所以我们进行重置...

git reset --hard  
git fsck  

你将获得一个悬挂的Blob ce013625030ba8dba906f756967f9e9ca394464a

git show ce01362  

会将文件内容“hello”返回给您。

为了找到未被引用的提交,我在某个地方发现了一个提示。

gitk --all $(git log -g --pretty=format:%h)  

我在 Git GUI 中使用它作为工具,非常方便。


1
+1. 如https://dev59.com/P3vaa4cB1Zd3GeqPG7u5#21350689所述,`git fsck --lost-found`可以帮助解决问题。 - VonC

6

1
Jetbrains CLion 的本地历史记录功能非常出色,为我节省了2小时的工作时间 :) - Fabian Knapp

5

我写了一个小脚本,使查找所需的提交更加容易:

git fsck --lost-found | grep commit | cut -d ' ' -f 3 | xargs -i git show \{\} | egrep '^commit |Date:'

是的,可以使用awk或类似工具使其更美观,但它很简单,而且我只需要它。也许可以为其他人节省30秒。


4
这拯救了我的生命: https://medium.com/@CarrieGuss/how-to-recover-from-a-git-hard-reset-b830b5e3f60c 基本上,您需要运行:
for blob in $(git fsck --lost-found | awk ‘$2 == “blob” { print $3 }’); do git cat-file -p $blob > $blob.txt; done

然后手动痛苦地重新组织你的文件到正确的结构中。

要点:如果你不完全明白git reset --hard的工作原理,请不要使用它,最好避免使用。


有人将此放入存储库(需要Python):https://github.com/pendashteh/git-recover-index 非常有用,我刚刚运行了它并搜索了它创建的文件。 - Rafael Leite

4

git reflog 命令和回到上一个 HEAD 6a56624 (HEAD -> master) HEAD@{0}: reset: 移动到 HEAD~3 1a9bf73 HEAD@{1}: 提交:在模型生成二进制文件中添加更改


4

我的问题与这个几乎相同。在输入 git reset --hard 命令之前,我有未提交的文件。

幸运的是,我成功地跳过了所有这些资源。在我注意到我可以撤消 (ctrl-z 在Windows / Linux上,cmd-shift-z 在Mac上) 之后。我只想把这加到上面所有答案中。

注意,无法撤消未打开的文件。


1
没错。谢谢你发布这个,我也可以用这种方式恢复我的未提交更改。 - Franck Mercado
你在哪个应用程序和什么系统上按 ctrl-z - BPS
感谢 @flyingpluto7,阅读上面的答案让我看起来似乎没有希望,直到我往下滚动,你的答案就像它可以是那样简单。我已经失去了恢复最近两天工作的未提交更改的希望,而我完全没有记住 ctrl - z。我使用 vscode,这个方法非常完美。 - jking
撤销操作需要注意的一点是,您必须将其应用于每个文件,并且在 vscode 中打开文件。 - jking

4
注意:如果您使用像IntelliJ这样的IDE,则此答案仅有效。
最近我遇到了一个类似的问题,即我既没有暂存更改也没有提交更改。有一个本地历史记录选项。我能够从IntelliJ的本地历史记录中还原更改(参考:ref)。
希望能对某些人有所帮助。

这个救了我。在使用Android Studio时有效。通过右键单击最近剩余的文件夹并从IDE的历史记录中恢复,成功地恢复了Git意外删除的文件。谢谢! - Erik Uggeldahl

1

git reset --hard - 你可以使用这个命令来撤销一个提交,并且之后可以从远程仓库重新获取所有内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接