首页 > 解决方案 > git checkout 真的复制提交吗?

问题描述

我有一个开发分支,这个分支有这些提交 X、Y、Z。当我从开发分支签出一个新功能(调用 FeatureX)时,FeatureX 中的提交仍然是 X、Y、Z 或被复制到 X'、Y ',Z'?请帮我!

标签: git

解决方案


git checkout命令将文件从提交中复制出来。它必须这样做,因为提交中的文件不是可用的形式。但是在这种情况下,您提出了一个问题,其答案对您没有多大帮助。有一组更好的问题要问。

什么是 Git 存储库?

在大多数情况下,存储库是提交的集合。不过,存储库还有更多内容。例如,除了提交之外,每个存储库都有一个名称集合:分支名称、标签名称和其他此类名称。

数量更多取决于存储库的类型

  • 在服务器上发现的所谓的存储库大多停在这里。
  • 您将使用的存储库类型添加了一个工作树,您将在其中进行实际工作,以及完成工作、从错误中恢复等所需的许多其他功能。

虽然这些很重要,但提交是这里的关键。分支名称也很重要,但这仅仅是因为它们可以帮助我们(和 Git)找到我们想要查找的提交。因此,作为 Git 新手,您需要了解什么是提交,以及为您做什么。

什么是提交?

提交是 Git 存储库中的主要存储单元。它具有以下特点:

  • 每个提交都有编号。但是,这些数字并不是简单的计数:我们没有提交 #1,然后是提交 #2,然后是 #3 和 #4,依此类推。相反,每个数字都是唯一的,但看起来完全是随机的,而且拼写很有趣,就像一个大的十六进制数字。Git 调用这些哈希 ID

  • 每一次提交,一旦做出,就会永远完全冻结。

  • 每个提交存储两件事:

    • 它具有您所有文件的完整快照,包括您提交时文件中的任何内容。这是每个文件的一种存档,例如 tar 或 rar 存档。

      显然这会占用大量的磁盘空间,因此 Git以一种特殊的、只读的、仅限 Git 的、压缩和去重的形式存储这些文件。这使得文件对除了 Git 本身之外的所有东西都无法使用(这就是为什么必须将文件复制出来,以将它们扩展为可用版本),但这意味着如果您进行一百万次提交,但继续重复使用您的文件,这几乎不占用任何空间——每个文件仍然只有一个副本,在所有百万次提交中重复使用。git checkout

    • 除了存储的文件之外,每个提交还存储一些元数据:关于提交本身的一些信息。例如,这包括提交人的姓名和电子邮件地址。它包括一个日期和时间戳,说明他们制作的时间(实际上,每个提交都有两个,一个用于“作者”,一个用于“提交者”——它们通常是相同的,因此两者都没有意义,但它不会占用太多空间,所以没有人关心很多。)

还有更多可选功能,但我们现在并不真正需要关心这些。不过,我们在这里确实需要关心的一件事是元数据的一个特定功能,这对 Git 本身至关重要:每个提交都存储一些先前提交的哈希 ID。 大多数提交只存储一个这样的哈希 ID;Git 调用这些普通提交。

提交形式链,就像一串珍珠

这意味着什么——这是你直接问题的关键——是每个提交“记住”它的父提交。我们可以直观地画出这一点,通过注意一些带有一些哈希 ID 的提交——我们就这么称呼它吧——将在那个提交中存储一些早期提交的H哈希IDhash

我们说后提交或提交指向较早提交或提交,我们可以将该指针绘制为从提交中出来的箭头:

            <-H

如果我们调用较早的提交——使用前面G的字母H——并把它画进去,我们得到:

        <-G <-H

当然,G有自己的父级,F,也有自己的父级,以此类推:

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

这一直追溯到某人所做的第一次提交。 提交不能向后指向,所以它不会:

A--B--C--...--G--H

CommitA是第一个提交,是根提交,非空存储库必须至少具有其中一个。(Git 允许你制作多个,但是,好吧,我们不要去那里。)

这里我要说明两点:

  1. 我变得懒惰并停止在提交之间绘制箭头,只使用线条。但请记住,它们单向箭头:Git 只能真正倒退

  2. 虽然我在这里使用了简单的字母,很明显这H最后一个,但在真实的存储库中,实际的哈希 ID 是这些可怕的、大而丑陋的随机十六进制数字,没有人能保持正确。

这第二点是一个大问题:我们如何H轻松找到commit? 为了让Git做到这一点,我们必须告诉 Git commit 的实际哈希 ID H。我们可能不得不把它写在某个地方,在纸上或白板上,然后输入。

但是,等一下,我们有一台电脑。计算机擅长这种记忆!让我们取散列 ID ,不管它是什么,然后在计算机的H某个地方“写下来” 。让我们将它存储在文件、数据库或其他东西中。让我们使用一个易于记忆(对我们而言)的名称来保存哈希 ID。

这是分支名称进入图片的地方

如果我们像这样绘制我们的提交,您可以看到分支名称是如何工作的:

...--G--H   <-- dev

分支名称——在我们这里的例子中是<code>dev——保存了序列中最后一次提交的真实哈希 ID 。那是提交。因此,通过并包括在内的提交是“在”分支上的。HH

当您添加一个的分支名称时,例如featureX,我们只需让 Git 创建一个新文件,或在分支名称数据库中创建一个新条目,或其他任何内容,并在该名称中保存相同的哈希 ID

...--G--H   <-- dev, featureX

这意味着通过和包括的提交H现在都在两个分支上。

如果您习惯了其他版本控制系统,您现在可能会反对:等等,等等,为什么它们都在两个分支上? 其他版本控制系统说提交只在一个分支上。Git 不是那些其他版本控制系统:在 Git 中,任何一次提交都可以在任意数量的分支上。

Git 提交的工作方式,我们使用分支名称来查找最后一个,然后从那里向后工作。所以 commitH现在是两个分支上的最后一次提交。commit也H指向更早的 commit G——事实上,因为 commitH永远不会改变,commitH指向G 永远——所以ifH位于两个分支上,G也位于两个分支上。

特别的名字HEAD

这里还有一些事情要知道。尝试运行git status。它的第一行是:

on branch dev

或者:

on branch featureX

管他呢。 Git 如何知道我们“在”哪个分支,这是什么意思?

我已经提到过——事实上有两次——git checkout从提交中复制文件,在这个过程中解冻和取消 Git 化它们。这为我们提供了一组我们可以实际使用的文件,即与 / 一起工作。

git checkout命令选择我们将“打开”的分支,正如git status将要说的。

它这样做的方式是使用一个非常特殊的名称,HEAD,它根本不是一个分支名称。(这个名字非常特别,如果它被损坏,Git 不再相信 Git 存储库Git 存储库。它也是一种活动文件,所以如果你的计算机在存储库中工作时崩溃或断电,HEAD文件有可能被损坏。幸运的是,通常很容易从这种情况中恢复。)Git 对这个名称所做的就是将它“附加”到一个分支名称,如下所示:

...--G--H   <-- dev (HEAD), featureX

on branch dev正如git status将要说的,这意味着我们是。该git checkout命令从 中提取所有文件H,并附HEAD加到名称dev中。所以我们当前的分支是 nowdev并且我们当前的提交是由 name 选择的dev,即 commit H

如果我们运行git checkout featureX,Git:

  • 删除来自的文件H
  • 移动HEAD以将其附加到featureX; 和
  • 放入来自H.

Git 实际上注意到第一步和最后一步是多余的,并且不会费心去做。所以唯一真正的变化是我们的绘图:

...--G--H   <-- dev, featureX (HEAD)

但是,让我们看看当我们进行新的提交时会发生什么。

进行新的提交

我们将跳过有关提交的所有细节,包括为什么必须继续运行git add。我们只需要注意,当你运行时git commit,Git 实际上并没有使用你工作树中的文件——你可以看到和处理的文件——而是使用一些预先准备好的、预先去重的副本。这些是进入提交的文件。

新的提交将获得一些看起来随机的(尽管是唯一的)哈希 ID,但我们称之为 commit I。Git 将:

  • 保存快照;
  • 为元数据添加我们的姓名和电子邮件地址,以“现在”作为日期和时间;
  • 将新提交的父级设置为当前哈希 IDH;和
  • 写出提交,这是为新提交生成真实哈希 ID 的原因I

如果我们只绘制提交本身,新提交I将指向现有提交H

...--G--H
         \
          I

但是——分支名称呢? 这是真正偷偷摸摸的把戏。Git 现在将新提交的哈希 ID 写入附加的名称中HEAD。由于我们的HEAD曾经——现在仍然是——附加到 name featureX,所以 namefeatureX是被更新的:

...--G--H   <-- dev
         \
          I   <-- featureX (HEAD)

现在,通过H的提交仍然在两个分支上,但新的提交I分支上featureX

如果我们现在运行git checkout dev,这就是我们的绘图中发生的情况:

...--G--H   <-- dev (HEAD)
         \
          I   <-- featureX

这一次,Git 删除了 commit 附带I的文件并提取了commit 附带的文件H

git log从这里向后工作

如果我们git log现在运行,重新启动dev,我们将根本看不到提交I。原因很简单:git log用来HEAD查找分支名,然后使用分支名查找提交。这是将显示的第一个提交git log:在这种情况下, commit H

然后,git log将使用存储commit中的级移回较早的 commit 。Git 将向我们显示 的日志消息,然后使用其中的元数据移回提交。HGGGF

Git无法前进, from Hto I:commitH 并不指向 commitI,因为H它是在之前I存在的。Git 不知道I将来会有什么哈希 ID(因为哈希 ID 是由提交的每个部分组成的,包括源快照和日期和时间戳)。

但是,如果我们运行git log featureX, 或git checkout featureXthen git log,Git将从提交开始I并向后工作。这将向我们展示HG也向我们展示F。毕竟,这些提交都在两个分支上。

要记住什么

关于 Git,有很多东西要记住。这是一个很大的程序套件,有很多命令,还有很多奇特的能力等等。但是现在,你已经学会了:

  • 存储库是关于提交的。提交保存快照和元数据,Git 通过看起来随机的哈希 ID 找到它们。
  • Git可以通过名称查找提交:主要是分支名称,但也可以使用其他类型的名称,例如标签名称。这些名称存储一个哈希 ID。对于分支名称,根据定义,该哈希 ID 是分支中的最后一次提交。
  • 一个提交向后链接到之前的提交——不是向前的,因为它不能;每次提交一旦完成,就会一直冻结。
  • 分支名称本身会随着时间的推移而移动。分支名称移动的正常方式是向前一步,一次提交,在您进行新提交时。(还有其他 Git 命令可让您“快速”或“远”移动分支名称,并且以违反此处正常简单规则的方式。)
  • 这种git checkout选择一个分支名称,切换该分支,并提交中获取文件——无论如何,该提交是该分支上的最后一个文件。
  • 特殊名称HEAD,全部大写,是 Git 如何知道哪个分支名称当前分支的方式。这反过来又确定了哪个提交当前提交

还有很多问题要问(例如:如果提交是快照,为什么git show将其显示为更改?)但我会在这里停下来。


推荐阅读