从“ git checkout <SHA1>”中恢复

时间:2019-09-10 21:18:20

标签: git

我进行了 git checkout 5c24d6d 并丢失了我提交的内容。然后,我试图回到原来的状态。为此,我执行了以下步骤。 我知道checkout通常用于在分支之间进行切换。有人可以解释一下我执行的步骤后发生了什么。 使用我的第一个checkout命令,git是否创建了一个临时分支(称为 HEAD在5c24d6d分离)? 我检查原产地时发生了什么?它是否再次创建了一个新的临时分支并删除了旧的临时分支? 最后,当我切换到master时,git是否删除了临时分支?

要进行进一步的实验:我做了 git checkout 5c24d6d ,然后使用 git checkout master 切换到master,结果相同。

我在master分支上,并进行了 git checkout 5c24d6d 。这将我的工作副本更改为5c24d6d。还说,我处于独立的HEAD状态。然后,当我执行 git log 时,我可以看到直到5c24d6d的日志,但看不到之后的日志。此时,我查看了分支,可以看到:

* (HEAD detached at 5c24d6d)
  master

为了恢复,我执行了 git checkout origin 。这使我回到具有SHA1的最后一次提交,即8c584c3。此时分支看起来像这样:

* (HEAD detached at origin/master)
  master

然后我使用 git checkout master 切换到了分支主服务器,并使用 git branch 检查了我的分支。现在我只能看到:

* master

带有头部详细信息的分支消失了。

以下是我的详细步骤:

$ git log --oneline  -10
8c584c3 (HEAD -> master, origin/master, origin/HEAD) deleting test
c4fcf55 adding line 3
fdd4670 adding line 2
8504aa4 adding test
be91837 Deleting .gitignore files from projects
5c24d6d Merge branch 'master' of https://github.com/siddswork/gradle
c80ace7 Creating lib an bin directories inside run with empty .gitignore
e91516d Updating README.md
a54a193 Create README.md
89b60ec Adding makefiles

$ git branch
* master

$ git checkout 5c24d6d
Note: checking out '5c24d6d'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 5c24d6d Merge branch 'master' of https://github.com/siddswork/gradle

$ git log --oneline  -3 
5c24d6d (HEAD) Merge branch 'master' of https://github.com/siddswork/gradle
c80ace7 Creating lib an bin directories inside run with empty .gitignore
e91516d Updating README.md

$ git branch
* (HEAD detached at 5c24d6d)
  master

$ git checkout origin
Previous HEAD position was 5c24d6d Merge branch 'master' of https://github.com/siddswork/gradle
HEAD is now at 8c584c3 deleting test

$ git branch
* (HEAD detached at origin/master)
  master

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

$ git branch
* master

$ git log --oneline -10
8c584c3 (HEAD -> master, origin/master, origin/HEAD) deleting test
c4fcf55 adding line 3
fdd4670 adding line 2
8504aa4 adding test
be91837 Deleting .gitignore files from projects
5c24d6d Merge branch 'master' of https://github.com/siddswork/gradle
c80ace7 Creating lib an bin directories inside run with empty .gitignore
e91516d Updating README.md
a54a193 Create README.md
89b60ec Adding makefiles

1 个答案:

答案 0 :(得分:0)

  

我了解到checkout通常用于在分支之间进行切换...

嗯,嗯,通常是一个已加载的单词。根据您的计算方式,git checkout命令可以执行的操作从大约3或4个正常事物到大约7或8个正常事物。这就是为什么Git 2.23引入了两个 new 命令,这些命令主要执行git checkout的功能,但是shooting yourself in the foot却没有太多机会。

  

使用我的第一个签出命令,git是否创建了一个临时分支(称为HEAD在5c24d6d分离)?我检查原产地时发生了什么?它是否再次创建了一个新的临时分支并删除了旧的临时分支?最后,当我切换到master时,git是否删除了临时分支?

不,可以,和/或mu。这在很大程度上取决于您所说的“分支”。另请参阅What exactly do we mean by "branch"?关于Git的怪事之一是分支并不是非常重要。

  

...然后当我执行git log时,我可以看到直到5c24d6d的日志,但之后看不到。

这也是正常的。

我认为,刚开始接触Git的人通常会认为Git与文件有关。不是。在意识到这一点之后,他们认为:哈哈,Git必须完全是关于分支机构 A,那也是错误的。分支名称​​确实很重要,而分支—无论如何,我们最终都定义了它们。有效的定义有多个相互矛盾的地方-很重要,但是它们并不是有效使用Git的真正关键。关键是 commits

提交 hold 文件,然后在 中通过分支找到提交,这再次导致定义单词的问题分支-但最后,提交是Git中最重要的事情。正是提交方式导致了有关Git的所有其他事情。

每个提交都有自己唯一的哈希ID,这似乎是随机的。如果只有我们事先知道将要提交的每个字节,包括日期和时间戳,那么哈希ID完全可以预测。但是我们只是不知道这些事,因此哈希ID look 是随机的。它们充当大型key-value database中的密钥,这使得Git只需给出唯一的哈希ID就能获得完整的提交。

提交中的内容是:

  • 哈希ID定位快照( tree 对象);
  • 一些哈希ID可以找到一定数量的 parent 提交(通常是一个父对象);
  • 提交的作者(姓名,电子邮件地址和日期和时间戳);
  • 提交者(和作者一样,通常是相同的);和
  • 日志消息。

快照哈希ID表示提交实际上包含所有处于提交时冻结状态的文件,尽管多个不同的提交可以共享这些版本。我们必须更深入地了解这种结构的工作原理,但是通常的想法是:将文件的快照冻结到提交中,并且每次提交都故意与其他所有 except 无关。共享文件以及这些父链接,所有这些链接均通过哈希ID起作用。因此, Git中的所有内容都在该大型键值数据库中,该数据库由哈希ID索引

但这是问题所在:您从哪里获得哈希ID?在完成任何工作之前,我们是否必须遍历整个数据库,找到每个 哈希ID?那太慢了,所以,不,我们不必这样做。这是HEAD和分支名称出现的地方。

查找提交哈希

Git可以在特殊文件.git/HEAD中存储两件事:

  • 分支名称,例如ref: refs/heads/master:在on的描述中,您就是git status的分支,或者
  • 一些现有提交的哈希ID:然后您有一个detached HEAD

同时,分支名称(例如refs/heads/master)始终包含一些现有提交的哈希ID。因此,如果未分离HEAD ,则Git可以从HEAD读取分支名称,然后从分支名称读取哈希ID。如果HEAD 被分离,Git可以从HEAD读取哈希ID。无论哪种方式,我们都有一个哈希ID。

git log命令将解析为一个或多个哈希ID的所有内容作为参数:

git log master develop a123456 ...

分支名称通过查看其存储的哈希ID解析为哈希ID。哈希ID已经是哈希ID。无论哪种方式,git log都会找到那些对象并确保它们是提交对象(与其他三种内部对象类型之一相对)。这些是git log可能显示的 first 提交。

如果您没有给git log提供任何哈希ID和/或分支名称,则Git将使用HEAD。该HEAD始终 1 解析为有效的提交哈希ID,这是git log将显示的第一个提交。

已经显示了一些提交,git log接下来要做的是查看该提交中的父代。这些为Git提供了更多的哈希ID。因此,如果在HEAD处找到一个当前提交,并且有一个父提交,则该父提交将是git log将显示的 next 提交。对父母的父母重复此操作,依此类推。

如果您git checkout进行一些提交,然后运行git log,则会看到该提交,然后是其提交,然后是其提交的父。那就是你所看到的。那是分支吗?我将其留给链接的问题What exactly do we mean by "branch"?


1 在一个全新的完全空的存储库中有一个例外。这里的HEAD将包含分支名称master,但由于没有提交,因此master不存在。随着时间的推移,各种Git命令已经适应了这种情况,以更好地处理这种情况:例如git status现在表示“尚无提交”。您也可以使用git checkout --orphan触发这种情况:这将尚不存在的分支的名称写入HEAD。在这两种情况下,创建新的提交都会创建分支,从而解决了这种不舒服的情况。