繁体   English   中英

从“分离的 HEAD”结帐后丢失了 git 数据

[英]Lost git data after checkout from "detached HEAD"

我是新手,无法使用 git。
我以某种方式将头从我的 git 项目存储库中分离出来,编写了许多代码,并尝试提交更改。
我用了:

git add *
git commit -m "comment"

我尝试使用git push ,但我得到了

致命:您当前不在分支上。
将历史推向当前(分离的 HEAD)

我使用git checkout master并且更改返回到 master 的分支提交,分离的 HEAD 分支被删除,我不知道如何返回丢失的代码。

对于命令git checkout x ,其中x是提交、标签或任何不是短分支名称的东西,它会导致分离的 HEAD。 分离的 HEAD 就像一个无名的分支。 一旦你切换到另一个分支或另一个分离的 HEAD,前一个就消失了,它让人感到困惑并认为它丢失了。

git checkout c8919ac48bc24080fa9d5c477bae8bcfb47991d4  # detached HEAD
git checkout master # switch to master, not detached
git checkout refs/heads/master # detached HEAD, though "master" and "refs/heads/master" are equivalent in most cases
git tag v1.0 c8919ac48bc24080fa9d5c477bae8bcfb47991d4
git checkout v1.0 # detached HEAD
git checkout origin/master # detached HEAD

运行git reflog ,您可以找到分离的 HEAD 所在的提交。

然后使用git checkout <commit>返回分离的 HEAD。 更好的做法是运行git checkout -b foo <commit>代替,从提交创建一个本地分支foo以便可以跟踪提交。 这个命令等于git branch foo <commit> && git checkout foo

如果您确实打算在master上进行新提交,则在找到提交后,您可以直接将其挑选到mastergit checkout master && git cherry-pick <commit>

如果你真的想将一个分离的 HEAD 推送到一个分支,你需要指定远程和 refspec 而不是一个裸的git push

git pull origin -r foo   # before push, pull first to avoid non-fast-forward error
                         # -r is recommended to do a rebase pull
git push origin HEAD:foo # push the detached HEAD in the local repository to "foo" in "origin" 

(总结其他答案中的信息)

你的提交没有丢失,你可以使用git reflog找到它:

# Here is a sample output :
# your commit should appear as 'HEAD@{xx}: commit: comment'
#    ('comment' is the commit message you used)
$ git reflog
f9a32813c (HEAD -> master) HEAD@{0}: checkout: moving from 92e0cd0c6... to master
92e0cd0c6 HEAD@{1}: commit: comment
046a74ef8 (tag: foo) HEAD@{2}: checkout: moving from master to 046a74ef87ea...

复制您在该行开头看到的哈希(在上面的示例中: 92e0cd0c6 ),然后从您当前的master分支运行:

git merge --ff-only 92e0cd0c6

好消息是您的提交仍然可用。 您将需要使用git reflog查找它们。

首先要知道的是,Git 与文件无关,也与分支无关。 这是关于commits 每个提交都保存文件,提交的集合形成一个分支或一组分支,但 Git 中的核心是提交本身。

每个提交都有一个唯一的编号。 这个数字采用一个大而难看的随机哈希 ID 的形式,例如51ebf55b9309824346a6589c9f3b130c6f371b8f 您所做的每次提交都以只读、压缩、永久冻结、仅限 Git 的格式存储所有文件的完整快照——好吧,无论如何,所有跟踪的文件,只有 Git 可以用。 因此,当您进行提交时,Git 会生成一个新的看似随机的哈希 ID:一个新数字,与以往的所有其他提交都不同。 这就是为什么这个数字必须如此之大。

如果我们没有一台计算机来为我们记住它们,我们将不得不写下这些看似随机的哈希 ID 中的每一个。 但是我们有一台电脑,所以我们让它为我们记住 ID。

我们所做的每次提交都会记住一些较早提交的哈希 ID。 也就是说,我们通过git checkout选择一个提交,例如,通过一些哈希 ID。 当您进入分离的 HEAD状态时,这就是您所做的。 那个提交——我们已经检出的那个——是当前的提交。 然后,当您进行新的提交时,Git 进行我们的新提交并将一些元数据与快照一起放入其中:您的姓名和电子邮件地址、您进行提交的日期和时间、您的日志消息、和非常GIT中本身,该散列ID重要的承诺,你已经签出,这目前的承诺。

这个创建新提交的过程会产生一个新的唯一哈希 ID,现在提交成为当前提交。 也就是说,Git 将提交的哈希 ID 写入HEAD 由于当前提交包含前一个提交的 ID,Git 可以从这里向后找到前一个提交的路径:

... <--previous  <--current   <--HEAD

但是,分离的 HEAD 不是正常的工作方式。 通常,我们git checkout branch-name ,例如git checkout master 在这种情况下,名称HEAD附加到分支名称。 如果我们使用单个大写字母代表实际的哈希 ID,并且master分支的当前末尾是 commit H ,我们可以这样绘制:

... <-F <-G <-H   <--master(HEAD)

也就是说,Git的节省了最后的散列ID在我们的命名分公司提交master下我们的名字master 然后,它将特殊名称HEAD附加到名称master HEAD ,Git 可以找到名称master ,从那里,Git 可以找到提交H的哈希 ID。

同时,提交H包含先前提交G的哈希 ID,因此从H Git 可以向后工作到G 当然, G包含较早提交F的散列 ID,因此 Git 也可以在那里向后工作,到F ...而F包含另一个较早提交的散列 ID,依此类推。

如果您git checkout master ,做一些工作,然后创建一个提交,Git 会使用一些看起来随机的哈希 ID 创建该新提交,但我们将其称为I 提交I将指向提交H

...--F--G--H   <-- ???
            \
             I   <-- ???

刚才名字master指向了H ,但是因为HEAD附加到名字master ,Git 现在将I的提交哈希 ID 写入名字master ,使master指向I

...--F--G--H
            \
             I   <-- master (HEAD)

我们将如何找到提交H 嗯,名字master写了I的哈希 ID,所以我们可以很容易地找到I 并且提交I已经提交了H的哈希 ID 写在里面——所以这就是我们找到H ,从HEAD开始让masterI找到H

我们不再需要绘图中的扭结,因为我们不需要直接指向H的指针。 但事实上,我们确实有几个。 一个在HEADreflog中,一个在master的 reflog 中。 Git 保留了master命名的每个提交的历史记录,以及HEAD命名的每个提交的历史记录,包括通过像master这样的分支名称,所以此时我们有:

...--F--G--H   <-- master@{1}, HEAD@{1}
            \
             I   <-- master (HEAD)

如果名称master指向G ,那么您也将拥有master@{2}

当你分离HEAD ——例如,通过git checkout <hash-of-F> ——你得到HEAD直接指向提交,因为不是包含分支名称, HEAD现在只包含原始提交哈希 ID。 所以你有这个 - 我会在HEAD@{1}画画,但不会画任何其他的:

...--F   <-- HEAD
      \
       G--H--I   <-- master, HEAD@{1}

如果您现在创建一个新提交,它将获得一个新的唯一哈希 ID,我们将其称为J

       J   <-- HEAD
      /
...--F   <-- HEAD@{1}
      \
       G--H--I   <-- master, HEAD@{2}

注意怎么样HEAD@{1}现在是HEAD@{2} ; 以前的HEAD现在是HEAD@{1} HEAD指的是新提交J

每次移动HEAD或将其附加到不同的分支时,所有数字都会增加一个,以便为前一个值腾出空间。 Git会把从以前的哈希ID HEAD到引用日志的HEAD ,和适当的分支名称或哈希ID写入名HEAD

所以:运行git reflog HEAD 请注意,它同时溢出了缩写的哈希 ID 和HEAD@{ number } 哈希 ID 是提交的真实名称。 提交就是 Git 的全部内容; 这就是它所保留的。 带有@{1}@{2}等的名称都会频繁地重新编号。 请注意,Git 只会将这些旧值保留一段时间——默认情况下为 30 到 90 天。 1之后,Git 认为您可能不再关心,并开始擦除旧的 reflog 条目。

一旦您看到哪些引用日志条目是您想要返回的提交,请给它们命名。 例如,您可以创建一个新的分支名称来引用链中的最后一次提交。 如果你已经这样做了:

       J--K--L   <-- HEAD
      /
...--F
      \
       G--H--I   <-- master

通过进行三个独立的 HEAD 提交,然后执行以下操作:

       J--K--L   <-- HEAD@{1}
      /
...--F
      \
       G--H--I   <-- master (HEAD)

通过将HEAD重新附加到名称master ,您可以这样做:

git branch lazarus HEAD@{1}

然后你会有:

       J--K--L   <-- lazarus, HEAD@{1}
      /
...--F
      \
       G--H--I   <-- master (HEAD)

新名称lazarus现在指向复活的提交。 (也许我应该制作四个?)

GIT restore last detached HEAD 中查看更多信息。


1 30 天限制适用于无法访问的提交,90 天限制适用于可访问的提交。 这里的“可达”一词来自图论,可达性意味着一个起点。 在这种情况下,起点是引用中的当前提交哈希 ID。 如果可以从 ref-name 值访问 reflog 保存的哈希 ID,则该条目的生命周期更长。

一旦 reflog 条目消失,提交本身就少了一种到达它的方法。 提交必须可以从某个名称(某个分支名称、标签名称或任何其他类型的名称)访问,否则 Git 的垃圾收集器git gc最终将删除它们。 因此,这些 reflog 条目用于使提交本身保持活动状态。 虽然所有提交都被永久冻结,但如果无法访问,提交本身将被回收(丢弃),因此一旦 reflog 条目消失,提交需要从其他可访问的提交中访问,或者直接以其他名称命名。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM