GITLens 切换到提交使我的新提交消失

MYZ*_*MYZ 6 git visual-studio-code gitlens

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

现在运行git log显示我的提交日志,仅显示到我选择的提交点。这太可怕了。我的新提交在哪里?

现在我无法找到我的新提交并返回它们。我在切换到旧的提交之前做了一个新的提交,所以我 100% 确定应该有更新的提交。这是一个新项目,我尚未提交到任何远程位置,因此git pull无法保释我。

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

tor*_*rek 11

对于 Git 新手来说,这很可怕。但不用担心:所有提交仍然存在。

\n

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

\n
    \n
  • 始终有1 个当前提交。Git 对此提交有一个特殊的名称:HEAD,全部大写,就像这样。2

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

    \n
  • \n
\n

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

\n

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

\n
commit eb27b338a3e71c7c4079fbac8aeae3f8fbb5c687 (...)\nAuthor: ...\n   ...\n\ncommit fe3fec53a63a1c186452f61b0e55ac2837bf18a1\n...\n
Run Code Online (Sandbox Code Playgroud)\n

也就是说,我们看到所有这些奇怪的哈希 ID 一次一个地溢出。哈希 ID 是每个提交的真实姓名。每个提交都会获得一个全局唯一的哈希 ID:不允许两个不同提交拥有相同的哈希 ID。这就是哈希 ID 如此大且难看的原因。它们看起来很随意。它们实际上不是随机的,但它们不可预测的。3

\n

分支名称(例如main)可转换为提交哈希 ID。原始哈希 ID 已经哈希 ID。无论哪种方式,只要给出正确的哈希 ID,Git 就可以找到该提交。

\n

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

\n

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

\n

这对我们来说意味着提交会记住它们的父母,这形成了一种向后看的链条。我们所要做的就是记住最新提交的原始哈希 ID。当我们这样做时,我们最终会得到一条链,我们可以这样画:

\n
... <-F <-G <-H   <--main\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,名称保存了 最新提交main的真实哈希 ID ,出于绘图目的,我们将其称为。Commit依次保存较早提交的哈希 ID ,后者保存更早提交的哈希 ID ,依此类推。HHGF

\n

我们现在可以看到如何git log工作:它从当前分支所选择的当前提交,开始。为了成为当前分支,我们将特殊名称附加到名称上:Hmainmain HEADmain

\n
...--F--G--H   <-- main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

Git 使用HEAD查找main,使用main查找H,并向我们展示H。然后 Git 使用H查找G并向我们展示G;然后它使用Gfind F,等等。

\n

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

\n
...--F   <-- HEAD\n      \\\n       G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

当我们git log现在运行时,Git 会翻译HEAD成这次直接找到的哈希 ID\xe2\x80\x94;没有附加的分支名称\xe2\x80\x94 并向我们显示 commit F。然后从那里向后git log移动。提交和在哪里?他们无处可见!GH

\n

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

\n
git log --branches\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
git log --all\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n
git switch main\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
\n

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

\n

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

\n

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

\n

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

\n
\n

Git 分支如何生长

\n

假设我们有以下情况:

\n
...--G--H   <-- main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

我们现在想要进行新的提交,但我们想将其放在新的分支上。因此,我们首先为 Git 创建一个新的分支名称,并将该名称H也指向提交:

\n
git branch develop\n
Run Code Online (Sandbox Code Playgroud)\n

结果是:

\n
...--G--H   <-- develop, main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们选择带有or 的develop名称作为HEAD附加名称:git checkoutgit switch

\n
...--G--H   <-- develop (HEAD), main\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,我们仍在使用commit H。我们现在只是通过其他名称使用它。直到并包含的提交H都在两个分支上

\n

现在,我们按照 Git 中通常的方式进行新的提交。准备就绪后,我们运行git commit并向 Git 提供一条日志消息,以放入新提交的元数据。现在 Git:

\n
    \n
  • 保存每个文件的快照(像往常一样进行重复数据删除);
  • \n
  • 使用当前提交作为新提交的提交,这样我们的新提交\xe2\x80\x94(我们称之为I\xe2\x80\x94)将向后指向现有提交H
  • \n
  • 添加我们配置的user.nameanduser.email作为此新提交的作者和提交者,使用“now”作为日期和时间;
  • \n
  • 使用我们的日志消息;和
  • \n
  • 实际上将所有这些作为提交写入,并为其分配唯一的哈希 ID。(唯一性部分来自日期和时间戳,部分来自输入哈希 ID H,部分来自我们保存的快照:新提交中的所有内容都会构成新的随机数-看起来哈希 ID,这就是我们无法预测它的原因。)
  • \n
\n

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

\n
...--G--H\n         \\\n          I\n
Run Code Online (Sandbox Code Playgroud)\n

现在,Git 执行了另一个让一切正常工作的魔法:git commitI\ 的哈希 ID 写入当前分支名称。也就是说,Git 用于HEAD查找当前分支的名称,并更新存储在该分支名称中的哈希 ID。所以我们的图片现在是:

\n
...--G--H   <-- main\n         \\\n          I   <-- develop (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

正是 commitI导致返回 commit H。该名称只是让我们找到提交。提交才是真正重要的:分支名称只是让我们找到最后一次提交。无论该分支名称中的哈希 ID 是什么,Git 都会表示提交该分支上的最后一次提交。所以既然现在main说,是最后一次提交;因为现在说,是最后一次提交。向上提交仍然在两个分支上,但仅在.HHmaindevelopIIdevelopHIdevelop

\n

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

\n
...--G--H--I   <-- develop, main\n
Run Code Online (Sandbox Code Playgroud)\n

然后所有提交都会再次出现在两个分支上。HEAD(我这次省略了,因为如果都选择 ,我们可能不关心我们处于“哪个分支” I。事实上,我们可以删除其中一个名称\xe2\x80\x94,但不能删除两个\xe2\x80\x94,因为两个名称都选择相同的提交,这就是我们找到正确的哈希 ID所需的全部内容。如果我们要将这个哈希 ID 写在某个地方,我们可能不需要任何名称。但这充其量只是......令人讨厌。我们有一台计算机;让它以漂亮整洁的名称为我们保存又大又难看的哈希 ID。)

\n