不同的分支如何从 git 本地存储在我的磁盘上?

Sar*_*tel 5 git version-control github

我的本地硬盘上只有一个版本存储库,但 Github 上有多个分支。每个分支不应该有代码副本吗?我的本地磁盘上有什么版本的代码?

tor*_*rek 10

每个分支不应该有代码副本吗?

不。这不是 Git 的分支名称的工作方式。

从某种意义上说,Git 存储的根本不是分支。Git 存储提交。提交几乎是 Git 中的一切。分支机构进入的时间要晚得多,它们的作用相当小。当然,它们对人类很重要,因为任何提交的实际名称都是一个对人类无用的大而丑陋的哈希 ID。分支名称让我们可以使用名称而不是哈希 ID。但是 Git 最关心的是提交,以及它们的哈希 ID。

我的本地硬盘上只有一个版本存储库,但 Github 上有多个分支。

通常,本地驱动器上的存储库是 GitHub 上存储库的克隆。或者,我们同样可以说 GitHub 上的存储库是本地驱动器上存储库的克隆。两个存储库都不比另一个“更好”,它们只是拥有不同的计算机来保存它们,并且可能略有不同的提交集,以及它们中更可能不同的分支名称(但由于 Git 更关心提交,这并不对 Git 来说太重要了)。

要了解这是如何工作的,请从提交开始。每个提交——由一些丑陋的大哈希 ID 命名,例如——8a0ba68f6dab2c8b1f297a0d46b710bb9af3237a存储一些文件集的完整快照。除了那个快照,每个提交都有一些元数据,一些关于提交的数据。如果你做的全心投入,例如,提交存储您的姓名和电子邮件地址,以及时间戳记,当你做出的承诺。而且,每个提交通常存储其前一个或提交的哈希 ID 。(例如,8a0ba68f6dab2c8b1f297a0d46b710bb9af3237a上面的父对象是15cc2da0b5aaf5350f180951450e0a5318f7d34d。)

Git 使用这些链接来查找提交。让我们考虑一下您最近创建的一个小型存储库,您仅在其中进行了 3 次提交。每个提交都有一些丑陋的哈希 ID,但让我们使用单个大写字母代替。因此,您所做的第一次提交是 commit A。因为它第一次提交,所以它没有父级。

然后,通过提交A,您提交了B。它有A作为它的父级。同样,您曾经使用Cmake B,因此C存储了B的哈希 ID。

每当我们拥有某个提交的哈希 ID 时,我们就说我们可以指向该提交。所以C指向BB指向A。如果我们画这个,我们得到:

A <-B <-C
Run Code Online (Sandbox Code Playgroud)

现在,简单的单大写字母和此图中,很明显,我们需要开始C和工作倒退。这就是 Git 所做的,但Git 查找 commit的方式C是使用分支名称。由于我们刚刚启动这个存储库,我们可能仍在使用master,所以让我们将其绘制在:

A <-B <-C   <--master
Run Code Online (Sandbox Code Playgroud)

该名称master包含 commit 的哈希 ID C,以便 Git 可以找到C. 从那里,Git 可以向后工作,到B然后A.

如果我们想添加一个新的提交,我们运行git checkout master,它会为我们提供一份C我们可以处理的副本,并记住我们现在在master哪个 commit 上C。我们完成我们的工作,git add根据需要创建文件,然后运行git commit. Git 进行新的提交D(来自index 中的任何内容,我不会在这里定义),并设置D为指向C

A--B--C   <--master
       \
        D
Run Code Online (Sandbox Code Playgroud)

现在棘手的问题发生了:因为我们刚刚创建的D,而且我们的master,现在的Git更新 master与任何真正的哈希ID是新的提交D。所以现在master指向D,而不是C

A--B--C
       \
        D   <--master
Run Code Online (Sandbox Code Playgroud)

我们可以理顺图中的扭结(我也喜欢稍微松开箭头):

A--B--C--D   <-- master
Run Code Online (Sandbox Code Playgroud)

现在假设我们决定添加一个新分支。我们运行,例如,git checkout -b develop。这只是添加一个新名称,但保留所有相同的提交。新名称 ,develop指向我们选择的提交,默认为我们现在使用的提交,即 commit D

A--B--C--D   <-- master, develop
Run Code Online (Sandbox Code Playgroud)

现在我们需要一种方法来绘制我们正在使用的分支名称。为此,我们将把单词HEAD(全部大写)附加到两个分支之一。由于我们确实git checkout附加HEAD到了 new develop,让我们画出来:

A--B--C--D   <-- master, develop (HEAD)
Run Code Online (Sandbox Code Playgroud)

现在是时候进行另一个新的提交了。没有做任何其他事情,我们修改了一些文件,用于git add将更新的文件复制到索引中,然后运行git commit以进行新的提交,我们将调用它E(但它会像往常一样获得一些难以理解的哈希 ID)。当 Git 更新一个分支名称时,它更新的名称是HEAD附加到它的名称,所以图形现在看起来像这样:

A--B--C--D   <-- master
          \
           E   <-- develop (HEAD)
Run Code Online (Sandbox Code Playgroud)

在这一点上,假设我克隆了您的存储库(直接从您的机器,或从您发送到 GitHub 的精确副本,具有相同的提交和相同的分支名称)。我的 Git 首先会这样做:

A--B--C--D   <-- origin/master
          \
           E   <-- origin/develop
Run Code Online (Sandbox Code Playgroud)

在这里,我没有任何分支。我有所有相同的提交,但记住它们的目的不是分支名称,而是远程跟踪名称。取而代之的是master,我必须origin/master记住 commitD的哈希 ID。取而代之的是develop,我必须origin/develop记住E的哈希 ID。

作为我克隆的最后一步,我自己的 Git 尝试将git checkout master. 相反,失败,因为我不的一个master,这实际上造成我的master,用我的origin/master,我的Git从Git的复制master。所以现在我有:

A--B--C--D   <-- origin/master, master (HEAD)
          \
           E   <-- origin/develop
Run Code Online (Sandbox Code Playgroud)

如果我现在运行git checkout develop,我的 Git 将查看我的存储库develop,但不会找到origin/develop. 然后,我的 Git 将创建一个新名称, develop,指向 commit E,并附HEAD加到该名称而不是 to master

A--B--C--D   <-- origin/master, master
          \
           E   <-- origin/develop, develop (HEAD)
Run Code Online (Sandbox Code Playgroud)

请注意,没有复制任何提交。Git 只是添加了一个新的name,指向一些现有的提交。现有的提交已经存在,在我的本地存储库中。

您使用fetch和连接两个 Git 存储库push

这是您从 GitHub 存储库获取更新的方式。如果他们有一些你没有的新提交,你可以git fetch在你的 Git 存储库中运行。您的 Git 使用origin您的 Git 在原始git clone操作期间创建的名称来查找 GitHub 的 URL。然后你的 Git 调用 GitHub,你的 Git 和他们的 Git 有一些对话。

使用git fetch,您的 Git 会向他们的 Git 询问他们的分支名称以及每个名称的最终提交。他们的 Git 可能会说:master指出一些带有丑陋的大散列的提交——让我们称之为H,而不是试图猜测实际的散列 ID。您的 Git 查看您的存储库并自言自语:我没有H,我最好要求它。 你的 Git 询问他们的 Git H;他们说H的父母是G,谁的父母是F,谁的父母是D。你们都有D,所以这部分对话就完成了。他们的 Git 也可能会说:my developpoints to commit E 你的 Git 已经有了E,所以这部分对话也完成了。有左担心没有其他的名字,所以现在你的Git有自己的Git送过来的提交FGH,你的Git的版本库中保存走:

           F--G--H   <-- origin/master
          /
A--B--C--D   <-- master
          \
           E   <-- origin/develop, develop (HEAD)
Run Code Online (Sandbox Code Playgroud)

请注意,除了向您自己的存储库添加提交之外,您的 Git 所做的唯一另一件事就是更新您的所有origin/*名称,以便它们与其他 Git 的名称匹配。也就是说,您origin/master已经从共享提交D转移到了现在共享的提交H

Fetch 总是安全的,但git push不同

运行总是安全的git fetch:这会将您的 Git 连接到其他某个 Git,从它们获取任何提交,并更新您的远程跟踪名称。因为这些名字只是记住他们的工作,而不是你的工作,所以这是安全的。如果你做了新的提交,你的新提交仍然在你的分支上,而不是你分支,除了你们共同的任何提交。

当您使用 时git push,您的 Git 会调用他们的 Git,并且这两个 Git 会进行非常相似的对话。如果你有新的提交,你的 Git 会向他们的 Git 提供一些新的提交。但是,你的 Git 不是更新你的远程跟踪名称,而是向他们的 Git 提供一个礼貌的请求:请,如果可以,更新你的master以匹配我的master或者,如果可以,更新你develop的匹配我的develop- 或者也许甚至两者。(您可以一次推送任意数量的分支名称。)

假设在之前的 之后git fetch,你有这个:

           F--G--H   <-- origin/master
          /
A--B--C--D   <-- master
          \
           E   <-- origin/develop, develop (HEAD)
Run Code Online (Sandbox Code Playgroud)

你继续你的develop并做了一些新的提交,我们称之为Iand J,所以现在你有这个:

           F--G--H   <-- origin/master
          /
A--B--C--D   <-- master
          \
           E   <-- origin/develop
            \
             I--J   <-- develop (HEAD)
Run Code Online (Sandbox Code Playgroud)

但是,您不知道的是,其他他们的 develop. 该提交具有您在任何地方都没有的哈希 ID。让我们K他们的Git 中调用它,以便他们拥有:

           F--G--H   <-- master
          /
A--B--C--D
          \
           E--K   <-- develop
Run Code Online (Sandbox Code Playgroud)

你发送他们IJ,所以现在他们有这个:

           F--G--H   <-- master
          /
A--B--C--D
          \
           E--K   <-- develop
            \
             I--J   <-- (polite request to make develop go here)
Run Code Online (Sandbox Code Playgroud)

如果他们接受你的请求,并移动他们的develop,他们的提交会发生什么K?他们最终是这样的:

           F--G--H   <-- master
          /
A--B--C--D
          \
           E--K   ???
            \
             I--J   <-- develop
Run Code Online (Sandbox Code Playgroud)

正如我们上面所描述的,Git 处理提交和分支名称的方式是它使用分支名称来查找最后一次提交,然后向后工作。从 向后工作J,他们的 Git 将转到J. 从J他们会回去I,然后E,然后DCB,和A。是,犯下这是什么意思K输了!

其结果是,他们的Git会说没有你有礼貌的请求:不,如果我感动我的develop,我会失去一些承诺(S)。 (您的 Git 将此报告为“被拒绝”和“不是快进”。)

在这种情况下,您需要做的是git fetch获取他们的新提交K,然后在您自己的存储库中执行一些操作以适应它。不过,这是针对另一组问题的(所有这些问题都已经在 StackOverflow 上得到了回答)。