【问题标题】:GITLens switch to commit made my newer commits disappearGITLens 切换到提交使我的新提交消失了
【发布时间】:2021-08-05 10:49:33
【问题描述】:

现在非常可怕的情况:我使用了VSCodeGitLens 扩展来跳回旧的提交。我想checkout 提交,将其定位在COMMITS 侧边栏中,右键单击并选择Switch to Commit...。我确实希望签出该提交,然后能够签出回到我当前的状态。

现在运行git log向我显示我的提交日志,直到我选择的提交点为止。这很可怕。我的新提交在哪里?

现在我无法找到我的新提交并返回它们。我在切换到旧提交之前做了一个新的提交,所以我 100% 肯定应该有更新的提交。 这是一个新项目,我还没有承诺到任何偏远地区,所以git pull不能保释我。

我真的希望有人可以帮助我,我不想失去两天的工作......

【问题讨论】:

    标签: git visual-studio-code gitlens


    【解决方案1】:

    对于刚接触 Git 的人来说,这可怕的。但别担心:所有的提交都还在。

    包括 Visual Studio 在内的各种 GUI 会阻止对 Git 的访问(这可能是好是坏,取决于您的观点),因此您无法看到真正发生的事情,我也不使用 这些 GUI,因为它们使您无法看到正在发生的事情,所以我不能准确地说,您的 GUI 中的每个单击按钮都做了什么。然而,Git 是这样工作的:

    • 在任何时候,1当前提交。 Git 对此提交有一个特殊的名称:HEAD,全部大写,就像这样。2

    • 大多数次,还有一个当前分支。 Git 有一个特殊的名称,您可以通过它访问当前分支:HEAD

    你可能——事实上,你应该——在这一点上提出异议:我们如何知道 HEAD 是指 commit 还是 分支名称? Git 的答案是:我会根据我目前想要的任何一个来选择一个或另一个。有些东西需要一个分支名称,在这种情况下,HEAD 变成分店名称。有些事情需要一个提交,在这种情况下HEAD变成了提交。基本上,Git 有两种内部方式来询问HEAD 现在是什么。一个给出分支名称的答案,例如 mastermain 或其他任何东西,另一个给出原始提交哈希 ID。

    好的,所以,考虑到这一点,我们现在记得git log 打印出这样的日志:

    commit eb27b338a3e71c7c4079fbac8aeae3f8fbb5c687 (...)
    Author: ...
       ...
    
    commit fe3fec53a63a1c186452f61b0e55ac2837bf18a1
    ...
    

    也就是说,我们看到所有这些奇怪的哈希 ID 一次一个地溢出。哈希 ID 是每个提交的真实姓名。每个提交都有一个全局唯一的哈希 ID:没有两个 不同 提交 永远 允许拥有相同的一个。这就是为什么哈希 ID 如此之大和丑陋的原因。它们看起来是随机的。它们实际上并不是随机的,但它们不可预测的。3

    main 这样的分支名称会转换为提交哈希ID。原始哈希 ID 已经一个哈希 ID。无论哪种方式,只要给定正确的哈希 ID,Git 都可以找到提交。

    每个提交都包含每个文件的完整快照,4 加上一些关于提交本身的元数据: 信息,例如提交者、提交时间和日志他们当时可以写的信息。对于 Git 本身来说至关重要的是,此元数据中的一项是上一次提交的原始哈希 ID

    这里还有一个关于提交的随机事实需要记住:一旦提交,任何提交的任何部分都不能更改。这就是哈希 ID 的实际工作方式,这对于 Git 作为一个 分布式 版本控制系统至关重要。但这也意味着任何 Git 提交都不能包含其未来 children 提交的原始哈希 ID,因为当我们创建提交时,我们不知道那些会是什么。提交可以存储他们父母的“名字”(哈希 ID),因为我们在创建孩子时确实知道他们的祖先。

    这对我们来说意味着 commits 记住他们的 parents,这形成了一种向后看的链。我们所要做的就是记住 latest 提交的原始哈希 ID。当我们这样做时,我们最终会得到一个可以像这样绘制的链:

    ... <-F <-G <-H   <--main
    

    这里,name main 保存了 最新提交 的真实哈希 ID,出于绘图目的,我们将其称为 H。提交H 又持有较早提交G 的哈希ID,后者持有仍较早提交F 的哈希ID,依此类推。

    我们现在可以看到git log 是如何工作的:它从当前提交 H 开始,由当前分支 main 选择。为了使main成为当前分支,我们附加特殊名称HEAD到名称main

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

    Git 使用HEAD 查找main,使用main 查找H,并显示H。然后 Git 使用H 找到G 并向我们显示G;然后它使用G 找到F,以此类推。

    当我们想要查看任何历史提交时,我们通过哈希 ID 将其挑选出来,然后告诉 Git:HEAD 直接附加到该提交。我们可以这样画:

    ...--F   <-- HEAD
          \
           G--H   <-- main
    

    当我们现在运行git log 时,Git 将HEAD 转换为一个哈希ID——这一次它直接找到了;没有附加分支名称——向我们展示了提交F。然后git log 从那里继续前进,向后。提交GH 在哪里?他们无处可寻!

    但是没关系:如果我们运行 git log maingit log 以名称 main 开头,而不是名称 HEAD。找到提交Hgit log 显示;然后git log 移动到G,依此类推。或者,我们甚至可以运行:

    git log --branches
    

    或:

    git log --all
    

    git log找到所有分支所有引用(“引用”包括分支和标签,但也包括其他种类的名称)。

    (这带来了另一个单独的蠕虫罐,这完全是关于git log 如何处理“想要”“同时”显示多个提交的情况。我不会去那里在这个答案中。)

    这种“查看历史提交”模式在 Git 中称为分离 HEAD 模式。这是因为特殊名称 HEAD 不再附加到分支名称。要重新附加您的HEAD,您只需选择一个分支名称,使用git checkout 或(Git 2.23 或更高版本)git switch

    git switch main
    

    例如。您现在已经检查了分支名称 main 选择的提交,并且 HEAD 现在重新附加到名称 main

    在我们停下来之前,还有一件非常重要的事情要学习,那就是:树枝是如何生长的。但让我先把脚注弄明白。


    1这个规则有一个例外,在一个完全没有提交的新的、完全空的存储库中是必需的。以后可以在非空存储库中以一种奇怪的方式使用该异常。不过你不会使用它。

    2小写变体 head 通常在 Windows 和 macOS 上“有效”(但在 Linux 和其他系统上无效)。但是,这是具有欺骗性的,因为如果您开始使用 git worktree 功能,head(小写)不能正常工作——它有时会让你错误提交!——而 HEAD(大写)确实。如果您不喜欢全大写,请考虑使用简写字符@,您可以使用它来代替HEAD

    3Git 在这里使用加密哈希:与加密货币中发现的相同类型的东西,虽然没有那么严格(Git 目前仍在使用 SHA-1,它在加密术语中已经过时了)。

    4快照以一种特殊的、只读的、仅限 Git 的、压缩的和去重复的格式存储。 Git 显示提交为“自上次提交以来的更改”,但存储提交为快照。


    Git 分支如何成长

    假设我们有以下情况:

    ...--G--H   <-- main (HEAD)
    

    我们现在想进行一个新的提交,但我们想把它放在一个新的分支上。所以我们首先以 Git 的身份创建一个新的分支名称,并将该名称也指向提交 H

    git branch develop
    

    导致:

    ...--G--H   <-- develop, main (HEAD)
    

    现在我们选择develop 作为名称以附加HEAD,并使用git checkoutgit switch

    ...--G--H   <-- develop (HEAD), main
    

    请注意,我们仍在使用 commit H。我们现在只是通过另一个 name 来使用它。通过H 并包括H 的提交都在两个分支上

    我们现在进行一个新的提交,这是我们在 Git 中的常用方式。准备就绪后,我们运行 git commit 并给 Git 一条日志消息以放入新提交的 元数据。立即使用:

    • 保存每个文件的快照(照常去重);
    • 使用当前提交作为新提交的,这样我们的新提交——我们称之为I——将向后指向现有提交H;
    • 将我们配置的user.nameuser.email 添加为这个新提交的作者和提交者,使用“现在”作为日期和时间;
    • 使用我们的日志信息;和
    • 实际上将所有这些都写为提交,并为其分配唯一的哈希 ID。 (唯一性部分来自日期和时间戳,部分来自输入哈希 ID H,部分来自我们保存的快照:everything新提交用于组成新的随机哈希 ID,这就是我们无法预测它的原因。)

    所以现在我们有了这个新的提交I,指向现有的提交H

    ...--G--H
             \
              I
    

    现在,Git 做了另一件神奇的事,让这一切都正常工作:git commitI 的哈希 ID 写入当前分支名称。也就是说,Git 使用HEAD 查找当前分支的名称,并更新存储在该分支名称中的哈希ID。所以我们现在的照片是:

    ...--G--H   <-- main
             \
              I   <-- develop (HEAD)
    

    名称HEAD 仍然附加到分支名称develop,但分支名称develop 现在选择提交I,而不是提交H

    是提交 I 导致返回提交 Hname 只是让我们找到 commitcommits 才是真正重要的:分支名称只是为了让我们找到 last 提交。无论该分支名称中的哈希 ID 是什么,Git 都会说 that commit is last 该分支上的提交。所以既然main 现在说HHmain 上的last 提交;因为develop 现在说IIdevelop 上的最后 次提交。通过H 向上提交仍然在两个分支上,但I 仅在develop 上。

    稍后,如果我们愿意,我们可以让 Git 移动名称 main。一旦我们将main 移动到I

    ...--G--H--I   <-- develop, main
    

    然后所有提交再次在两个分支上。 (这次我省略了HEAD,因为如果两者都选择I,我们可能不在乎我们“在”哪个分支。事实上,我们可以删除任何一个名称——但不能同时删除——因为两个名称都选择了相同的提交,这就是我们需要找到正确的哈希ID。如果我们将这个哈希ID写在某个地方,我们可能不需要任何名称。但这将是......糟糕,充其量。我们有一台 计算机; 让我们 为我们保存大而丑陋的哈希 ID,以漂亮整洁的名称。)

    【讨论】:

    • 哇,非常感谢您的深入解释!现在我看到了,因为我已经将我的 HEAD 移到了提交树下,所以我没有看到任何新的提交。不知道它是这样工作的。 git log --all 有很大帮助。
    【解决方案2】:

    好的,克服最初的恐慌帮助我找到了这个解决方案:

    git 的reflog 存储有关在 git 中所做更改的所有信息。

    Runnin git reflog 给了我这句话:

    e7aaac3 HEAD@{3}: commit: Some Commit...

    有了这个我可以 git checkout HEAD@{3} 这将我带到了我的最新提交(但我在尝试检索提交之前已经移动了 HEAD)。 现在为了在不使用 reset 命令的情况下干净地将 HEAD 恢复到此提交,我确实创建了一个新分支,然后将分支合并回 main。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-30
      • 1970-01-01
      • 1970-01-01
      • 2011-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      相关资源
      最近更新 更多