关于版本控制:如何撤消git reset –hard HEAD~1?

How can I undo git reset --hard HEAD~1?

是否可以撤消由以下命令引起的更改?如果是,如何?

1
git reset --hard HEAD~1

帕特·诺茨是对的。只要几天之内,你就可以取回承诺。Git只在大约一个月后收集垃圾,除非您明确告诉它删除新的blob。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ git init
Initialized empty Git repository in .git/

$ echo"testing reset"> file1
$ git add file1
$ git commit -m 'added file1'
Created initial commit 1a75c1d: added file1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 file1

$ echo"added new file"> file2
$ git add file2
$ git commit -m 'added file2'
Created commit f6e5064: added file2
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 file2

$ git reset --hard HEAD^
HEAD is now at 1a75c1d... added file1

$ cat file2
cat: file2: No such file or directory

$ git reflog
1a75c1d... HEAD@{0}: reset --hard HEAD^: updating HEAD
f6e5064... HEAD@{1}: commit: added file2

$ git reset --hard f6e5064
HEAD is now at f6e5064... added file2

$ cat file2
added new file

您可以在示例中看到,由于硬重置,文件2被删除,但当我通过reflog重置时,它被放回原位。


您要做的是指定要还原到的提交的sha1。您可以通过检查reflog(git reflog来获取sha1,然后执行

江户十一〔一〕号

但不要等太久…几周后,Git最终会将提交视为未引用,并删除所有blob。


答案隐藏在上面的详细响应中,您可以简单地执行以下操作:

1
$> git reset --hard HEAD@{1}

(见git reflog show输出)


如果Git还没有收集到垃圾,则可以恢复它。

了解与fsck悬而未决的承诺:

1
2
$ git fsck --lost-found
dangling commit b72e67a9bb3f1fc1b64528bcce031af4f0d6fcbf

使用REBASE恢复挂起的提交:

1
$ git rebase b72e67a9bb3f1fc1b64528bcce031af4f0d6fcbf

如果你真的很幸运,像我一样,你可以回到你的文本编辑器,点击"撤销"。

我知道这不是一个正确的答案,但它节省了我半天的工作,所以希望它能为别人做同样的事情!


据我所知,--hard将放弃未提交的更改。因为Git不跟踪这些。但是你可以撤销discarded commit

1
$ git reflog

遗嘱清单:

1
2
3
b0d059c HEAD@{0}: reset: moving to HEAD~1
4bac331 HEAD@{1}: commit: added level introduction....
....

其中4bac331discarded commit

现在,只要把头移到那个任务上:

1
$ git reset --hard 4bac331


IRL案例示例:埃多克斯1〔13〕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Checking object directories: 100% (256/256), done.
Checking objects: 100% (3/3), done.
dangling blob 025cab9725ccc00fbd7202da543f556c146cb119
dangling blob 84e9af799c2f5f08fb50874e5be7fb5cb7aa7c1b
dangling blob 85f4d1a289e094012819d9732f017c7805ee85b4
dangling blob 8f654d1cd425da7389d12c17dd2d88d318496d98
dangling blob 9183b84bbd292dcc238ca546dab896e073432933
dangling blob 1448ee51d0ea16f259371b32a557b60f908d15ee
dangling blob 95372cef6148d980ab1d7539ee6fbb44f5e87e22
dangling blob 9b3bf9fb1ee82c6d6d5ec9149e38fe53d4151fbd
dangling blob 2b21002ca449a9e30dbb87e535fbd4e65bac18f7
dangling blob 2fff2f8e4ea6408ac84a8560477aa00583002e66
dangling blob 333e76340b59a944456b4befd0e007c2e23ab37b
dangling blob b87163c8def315d40721e592f15c2192a33816bb
dangling blob c22aafb90358f6bf22577d1ae077ad89d9eea0a7
dangling blob c6ef78dd64c886e9c9895e2fc4556e69e4fbb133
dangling blob 4a71f9ff8262701171d42559a283c751fea6a201
dangling blob 6b762d368f44ddd441e5b8eae6a7b611335b49a2
dangling blob 724d23914b48443b19eada79c3eb1813c3c67fed
dangling blob 749ffc9a412e7584245af5106e78167b9480a27b
dangling commit f6ce1a403399772d4146d306d5763f3f5715cb5a    <- it's this one

。埃多克斯1〔14〕

1
2
3
4
5
6
7
8
9
10
11
12
commit f6ce1a403399772d4146d306d5763f3f5715cb5a
Author: Stian Gudmundsen H?iland <stian@Stians-Mac-mini.local>
Date:   Wed Aug 15 08:41:30 2012 +0200

    *MY COMMIT MESSAGE IS DISPLAYED HERE*

diff --git a/Some.file b/Some.file
new file mode 100644
index 0000000..15baeba
--- /dev/null
+++ b/Some.file
*THE WHOLE COMMIT IS DISPLAYED HERE*

。埃多克斯1〔15〕

1
2
First, rewinding head to replay your work on top of it...
Fast-forwarded master to f6ce1a403399772d4146d306d5763f3f5715cb5a.

在大多数情况下,是的。

根据运行该命令时存储库所处的状态,git reset --hard的效果可以从微不足道的到撤销的,甚至基本上不可能。

下面我列出了一系列不同的可能场景,以及您如何从中恢复。

我的所有更改都已提交,但现在提交已不复存在!

这种情况通常发生在使用参数运行git reset时,如git reset --hard HEAD~中所示。别担心,这很容易恢复!

如果您刚刚运行git reset,之后没有做任何其他操作,您可以回到使用这一行程序的位置:

1
git reset --hard @{1}

这将重置当前分支,无论它在上次修改之前处于何种状态(在您的情况下,对分支的最新修改将是您尝试撤消的硬重置)。

但是,如果您在重置之后对分支进行了其他修改,则上面的一行程序将无法工作。相反,您应该运行git reflog来查看对您的分支所做的所有最近更改(包括重置)的列表。该列表如下所示:

1
2
3
4
7c169bd master@{0}: reset: moving to HEAD~
3ae5027 master@{1}: commit: Changed file2
7c169bd master@{2}: commit: Some change
5eb37ca master@{3}: commit (initial): Initial commit

在此列表中查找要"撤消"的操作。在上面的例子中,它是第一行,上面写着"重置:移动到头部"。然后在该操作之前(下面)复制提交的表示。在我们的情况下,这将是master@{1}3ae5027,它们都表示相同的提交,并运行git reset --hard 将当前分支重置为该提交。

我和git add分阶段进行了修改,但从未承诺过。现在我的零钱不见了!

这是一个比较棘手的恢复。Git确实有您添加的文件的副本,但是由于这些副本从未绑定到任何特定的提交,所以您不能一次恢复所有更改。相反,您必须在Git的数据库中找到各个文件,并手动恢复它们。您可以使用git fsck来完成此操作。

有关此操作的详细信息,请参阅登台区域中的undo git reset--hard with uncommitted files。

我对工作目录中的文件进行了更改,我从未与git add一起进行过,也从未承诺过。现在我的零钱不见了!

哦,哦。我不想告诉你这个,但你可能走运了。Git不存储您没有添加或提交的更改,并且根据git reset的文档:

--hard

Resets the index and working tree. Any changes to tracked files in the working tree since are discarded.

有可能您可以使用某种磁盘恢复实用程序或专业的数据恢复服务来恢复更改,但此时这可能比它的价值更麻烦。


如果您还没有垃圾收集您的存储库(例如,使用git repack -dgit gc,但请注意,垃圾收集也可以自动进行),那么您的提交仍然存在——它不再可以通过头部访问。

您可以通过查看git fsck --lost-found的输出来尝试找到您的提交。

较新版本的Git有一个称为"reflog"的东西,它是对refs所做的所有更改的日志(而不是对存储库内容所做的更改)。因此,例如,每次您切换您的头(即每次您执行git checkout切换分支)时,都会记录下来。当然,你的git reset也操纵了头部,所以它也被记录下来了。通过使用@符号而不是~符号(如git reset HEAD@{1}符号),可以以与访问存储库的旧状态类似的方式访问refs的旧状态。

我花了一段时间才理解head@1和head~1之间的区别,所以这里有一个小小的解释:

1
2
3
4
5
6
7
8
9
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@{1}的意思是"在当前头指向的位置之前执行该头指向的提交"。

这很容易让您找到丢失的提交并恢复它。


在回答之前,让我们添加一些背景,解释一下这个HEAD是什么。

江户十一〔一〕号

HEAD只是对当前分支上当前提交(最新)的引用。在任何给定时间只能有一个HEAD。(不包括git worktree)

HEAD的内容存储在.git/HEAD中,包含当前提交的40字节sha-1。

江户十一〔七〕号

如果您没有进行最新的提交,这意味着HEAD指向的是历史上以前的提交,它称为detached HEAD

enter image description here

在命令行上,它看起来像是-sha-1而不是分支名称,因为HEAD没有指向当前分支的尖端。

氧化镁

关于如何从分离的头部恢复的几个选项:江户十一〔11〕。

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

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

1
2
3
4
5
6
7
8
# 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>

号埃多克斯1〔12〕

您也可以使用refloggit reflog将显示更新HEAD的任何更改,并签出所需的reflog条目将HEAD设置回此提交。

每次修改磁头时,reflog中都会有一个新条目。

1
2
git reflog
git checkout HEAD@{...}

这将使你回到你想要的承诺

氧化镁

埃多克斯1〔18〕

"移动"你的头回到想要的承诺。

1
2
3
4
5
6
7
8
9
10
11
# 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

埃多克斯1〔20〕

"撤消"给定的提交或提交范围。重置命令将"撤消"在给定提交中所做的任何更改。将提交带有撤销补丁的新提交,而原始提交也将保留在历史记录中。

1
2
3
# 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

氧化镁


我知道这是一条旧线…但是,随着很多人在寻找在Git中撤销内容的方法,我仍然认为继续在这里提供提示可能是个好主意。

当您在Git GUI中执行"Git添加"或从左上角到左下角移动任何内容时,文件的内容存储在一个blob中,并且可以从该blob中恢复文件内容。

因此,即使没有提交文件,也可以恢复该文件,但必须添加该文件。

1
2
3
git init  
echo hello >> test.txt  
git add test.txt

现在已经创建了blob,但它被索引引用,因此在重置之前,它不会与git fsck一起列出。所以我们重置…

1
2
git reset --hard  
git fsck

您将得到一个悬空的斑点CE013625030BA8DBA906F756967F9E9CA394464A

1
git show ce01362

会把文件内容"你好"还给你

为了找到未被引用的承诺,我在某个地方找到了一个建议。

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

我在GitGUI中把它作为一个工具使用,它非常方便。


我刚做了一个错误的项目的硬重置。挽救我生命的是Eclipse的本地历史。Intellij的想法据说也有一个,所以你的编辑,这是值得检查的:

  • 关于本地历史的Eclipse帮助主题
  • http://wiki.eclipse.org/faq_在哪里_工作区_本地_历史记录_存储了%3f

  • 制作了一个很小的脚本,使其更容易找到所需的提交:

    江户十一〔16〕号

    是的,它可以用锥子或类似的东西做得相当漂亮,但它很简单,我只是需要它。可能会节省别人30秒。


    推荐阅读