首页 > 解决方案 > 克隆完成后使用 GIT 获取另一个分支 --depth 1

问题描述

我最近被介绍给 git clone 的 --depth 1。显然,这并没有得到所有的历史记录,而且速度要快得多。我用了:

git clone --depth 1 -b 开发https://github.MyCompany.com/CoolProduct/CoolProduct.git

这让我可以玩、修改和分支开发分支。

但是,现在我想查看我尝试过的另一个分支“BillsFeature”:git checkout BillsFeature 并出现错误:pathspec 'BillsFeature' 与 git 已知的任何文件都不匹配

这对我来说有点道理。大概是因为我使用了--depth 1,所以我没有拉下分支名称。我如何获得另一个分支? 我也不需要 BillsFeature 的历史。我应该说我尝试过: git fetch --depth 1 origin BillsFeature 并且似乎发生了一些事情。但是,当我执行 git status 时,我得到了:

在分支开发您的分支是最新的'origin/develop'。

没什么可提交的,工作树干净

谢谢,戴夫

标签: gitgithub

解决方案


这里问题的根源在于git clone's--depth选项也打开了--single-branch。要在克隆时打败它,请使用--no-single-branch. 之后要打败它,请参阅如何“撤消” --single -branch 克隆?

请注意,在对您拥有的克隆进行去单分支后,您将不得不git fetch --depth 1再次运行。这将从您克隆的存储库中检索其余的分支名称——所有这些都成为远程跟踪名称;请参阅下面的详细信息 - 并允许您git checkout在每个此类名称上运行以创建具有相同名称的本地分支。您还可以使用git remote set-branches --add将个人名称添加到现有遥控器;再次,您将需要另一个git fetch --depth.

可选阅读:详细信息,或者,为什么上述工作

Git 存储库——从技术上讲,是一个非裸存储库——实际上由以下三个部分组成:

  • 一对数据库,如下所述;
  • 一个索引,Git 通过它知道要提交哪些文件,即要跟踪哪些文件,尽管索引不仅仅是一个文件列表;和
  • 可以在其中使用和修改文件的工作树工作树。这些文件实际上是你的,实际上根本不在 Git 中。Git 内部的文件,在主数据库中,都是只读的,并且是一种特殊的压缩和重复数据删除形式,只有 Git 本身可以使用。

当你运行时git clone,你让你的 Git 复制主数据库——一个保存所有提交和文件等的数据库——或多或少批发,但让它读取另一个数据库,解析它并理解它并写入你的克隆,不同的数据库。

--depth标志会影响主数据库,因此您不会大量复制它。--single-branch正如我们所指出的,该标志--depth会自动打开 - 会影响辅助数据库。在我们继续之前,让我们给这两个数据库起个名字,这样我们就不会一直提到一些尴尬的短语,比如“第一部分的派对”

  • 我一直称之为“主数据库”的东西是 Git 的对象存储。这是一个简单的键值对数据库,其中键是哈希 ID,值是 Git 的提交和其他内部对象。1 通常这是 Git 存储库的最大部分。2

  • 第二个数据库也是一个简单的键值存储,键是名称——包括分支和标签名称,但也包括几乎所有 Git 的其他名称3——值是哈希 ID。每个名称只存储一个哈希 ID,因为这就是所有必需的。

因此,回顾一下,无论如何git clone都会调用其他--single-branchGit并让它列出其所有分支和标签以及其他名称。然后它将使用这些名称来查找原始存储库中的所有提交和其他 Git 对象,并让其他 Git 发送所有这些对象。结果是对象数据库的完整副本。4 您现在拥有来自其他 Git 存储库的所有提交。--depth

但是,与此同时,您自己的 Git 会获取他们所有的名字,然后选择要使用的名字以及如何处理它们。一般来说,你的 Git 会使用它们所有的分支名称——它们的完整拼写是refs/heads/master,refs/heads/topic等等——然后将它们重命名为你自己的远程跟踪名称:refs/remotes/origin/master、、refs/remotes/origin/topic等等。然后,您的 Git 会创建自己的独立名称到哈希 ID 数据库,其中没有分支名称。5

最终结果是,在这一步之后git clone,您立即拥有所有提交但没有任何分支! 不过,这种情况很快就被 的最后一步纠正了git clone。如果你没有说--no-checkout,最后一步git clone是运行git checkout,这一步实际上创建了一个分支。您的 Git 创建的分支名称是您提供的-b选项。如果您没有提供-b选项,您的 Git 会询问另一个 Git推荐哪个分支,如果其他所有方法都失败,您的 Git 会采用您自己的默认初始分支名称。6


1每个提交对象引用一个(单个)对象,该对象保存该提交的快照,并具有元数据。每个树对象都包含一个部分文件名(名称组件,将根据需要串在一起)和另一个哈希 ID 的数组。该哈希 ID 标识另一棵树或存储某些文件内容的blob对象。Git 通过根据需要读取所有子树来构建文件的全名,并将完整的文件名存储在其索引中,然后使用在索引中看到的名称和 blob 哈希 ID 提取文件。这不是一个完整的描述,但这就是 Git 不能存储空目录的原因:没有办法将一个目录放入 Git 的索引中。

对象数据库还可以包含带注释的标记对象,每个对象都包含一个哈希 ID,通常是提交的 ID。这就是 Git 提供其注释标签的方式。

2也有例外:由于某种原因,旧存储库不断积累新名称,例如新的分支和标签名称,但几乎没有任何新的提交。但总的来说,对象数据库是使用大部分空间的地方,并且大部分时间用于初始克隆。

3其他名称包括诸如注释、进行中的二等分、某些交互式变基期间需要的名称等。基本上,任何将存储单个哈希 ID 的名称都会进入该数据库。不这样做的名称,例如遥控器的名称origin,不要进入这里。那些通常放在目录中的config文件中.git

该数据库目前实施得相当差。有时,名称在文件系统中存储目录和文件名,这意味着在不区分大小写的文件系统上,例如 Windows 和 macOS 系统上的默认文件系统,分支名称变得不区分大小写。有时名称存储在一个名为 的纯文本文件中,这使得它们都像 Git 一直想要的那样区分大小写。一些特殊名称,例如,根本不会进入打包的引用文件,而是始终作为单独的文件存储在目录中。现在正在进行工作以提供适当的数据库,以解决这里的一系列问题。packed-refsHEAD.git

4从技术上讲,结果可以而且通常会省略使用名称无法找到的任何对象。不过,我们将在这里忽略这个细微的区别。

5你的 Git 通常也会忽略它们所有的非分支非标签名称。它如何处理它们的标签名称很复杂,但是在普通(不是单分支,没有深度限制)克隆中,您通常会复制所有它们的标签名称。

6这过去只是硬编码为master,但现在变得可配置。


对此有何--single-branch影响

使用该--single-branch选项,您的 Git不会使用它们的所有名称。相反,您的 Git 仅使用您选项中的一个分支名称-b,具有相同的默认值:如果您不提供-b,您的 Git 会询问他们的 Git 他们推荐什么,或者使用另一个默认值。然后,您的 Git 将该分支名称转换为一个远程跟踪名称。它确保只向他们的 Git 询问该分支上的提交,在另一个 Git 存储库中。

最终结果是您获得了一个远程跟踪名称,以及所有提交的一部分。最后git checkout一步然后创建一个本地分支名称:您的 Git 在选择要获取的提交子集时使用的名称相同。

对此有何--depth影响

除了自动打开(--single-branch但请注意,您可以使用它关闭--no-single-branch它)之外,还可以--depth创建一个克隆。要完全理解浅克隆,我们必须进入图论。(不过,我们不会在这里走得太远。)

在 Git 中,每个分支名称都准确地标识一个 commit。但是 Git 中的一个分支——如果我们忽略“分支”到底是什么意思的问题?(我们不应该忽略它,但我们会在这里)——通常有一堆提交。这是如何运作的?

答案是 Git 中的每个提交都包含一些较早提交的哈希 ID。在通常的简单情况下,我们最终会得到一长串提交,每个提交都向后指向一个较早的提交。此链中的最后一个提交是分支的尖端,或尖端提交

让我们画一个简单的链,我们用一个大写字母代表每个提交的真实哈希 ID。哈希H将是链中的最后一个,我们会说这是分支br1

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

名称 br1包含最后一次提交的哈希 IDH。这就是我们如何让 Git 从对象数据库中取出它(记住,它是一个简单的键值存储:哈希 ID 是键)。但是在 commit 的主体中,Git 已经存储了之前commitH的 hash ID 。因此,我们可以从中获取的 ID,并让 Git在键值存储中查找提交。同时 commit有的 ID,所以我们可以从to倒退。GHGGGFGF

这就是 Git 的工作方式:向后。名称,如分支或标签或远程跟踪名称,存储一个哈希 ID。这就是我们想要的提交,然后,如果我们想要所有的提交,Git 会从那个提交倒退到前一个提交,然后继续前进。这个名字让我们开始;提交本身提供了路径的其余部分。

我们遍历的路径,以及我们在走这条路径时收集的所有提交,都是该分支上的可达提交。7 当两个分支分叉时,它们有一些共同的顺序:

             I--J   <-- br1
            /
...--F--G--H   <-- shared
            \
             K--L   <-- br2

在这里,所有三个分支上的提交H都在所有三个分支上,每个分支上的最后两个提交对其br*分支都是唯一的。

这种可达性理念是 Git 的核心。这也是如何--depth运作的。如果我们说--depth 1,我们是在告诉我们的 Git:当你从另一个 Git 获得提交时,只执行一步。 如果我们--depth 1在这里使用,我们得到:

             i--J   <-- br1

        g--H   <-- shared

             j--L   <-- br2

如果我们使用--depth 2,我们会告诉我们的 Git:当您从另一个 Git 获取提交时,请执行两个步骤。 这次我们得到:

             I--J   <-- br1
            /
     f--G--H   <-- shared
            \
             K--L   <-- br2

请注意,如果br2有更多独特的提交,我们将不会有从br2back 到shared.

这里的小写提交字母表示 Git 知道有父级,但这些父级被标记为“故意丢失”。更准确地说,浅层移植提交的哈希 ID 保存在目录中调用的文件shallow.git。Git 知道不要尝试从对象存储库中加载这些提交,并且它们丢失并不是一个错误。通常,这将是一个错误。

由于它们是故意丢失的,git log因此不能也不会显示这些提交,就好像浅嫁接的提交根本没有父母。这在某种程度上具有误导性,但也是您应该期望的。在大多数情况下,它是无害的。


7这假设我们使用的名称是一个分支名称。如果我们使用标签名称,这些是可从标签访问的提交;如果我们使用远程跟踪名称,这些是可从远程跟踪名称访问的提交。由于所有名称都使用相同的系统,因此每个名称都提供了某种方式来达到某些提交集。


git fetch是获得提交的操作

当我们使用 时git clone,我们实际上是在运行相当于六个命令序列,其中五个是 Git 命令:

  1. mkdir, 创建一个新的空目录/文件夹;
  2. git init, 在步骤 1 中创建的目录中创建一个新的空存储库;
  3. git remote add,添加 nameorigin或我们选择的其他名称,以及 URL 和fetch配置——这是我们更改以击败单分支的配置;
  4. git config,如果需要,添加在git clone命令中指定的配置选项;
  5. git fetch,获取提交并为步骤 3 中选择的一个或多个分支创建远程跟踪名称;和
  6. git checkout, 创建一个本地分支名称并填写 Git 的索引和我们的工作树。

--depth选项被传递给git fetch第 5 步。因此,如果我们必须调整我们的origin远程配置,因为第 3 步仅添加了具有一个特定分支的远程(请参阅文档git remote,我们必须运行一个新的git fetch。这个新git fetch的需要相同的--depth选项。

结论

打开 both的--depth选项,这限制了从另一个 Git 存储库获得的名称集,从而限制了提交,并将传递到 fetch 步骤,这限制了从另一个 Git 存储库获得的提交图的深度。在克隆时使用会抑制名称限制,同时保持深度限制。如果您需要撤消名称限制,或者如果您使用更新受限分支名称集,则必须再次运行。如果您希望它具有深度限制,则必须再次通过。git clone--single-branch--depth--no-single-branchgit remotegit fetchgit fetch--depth

请注意,git fetch 确实尊重现有的浅移植点,因此在某些情况下,省略--depth有点无害。例如,如果您有一个如下所示的存储库的单分支克隆:

...--V--W--X   <-- main
            \
             Y--Z   <-- topic

并且您的单分支克隆深度为 1 on main,因此提交W被标记为浅移植点:

        w--X   <-- main

然后添加topic没有 a--depth得到你:

        w--X   <-- main
            \
             Y--Z   <-- topic

也就是说,main这次没有深入。但如果图表是:

...--V--W--X   <-- main
      \
       Y--Z   <-- topic

并且您在没有 new 的情况下添加topic和获取--depth,您将得到:

...--V  w--X   <-- main
      \
       Y--Z   <-- topic

在您的克隆中,这意味着您必须V更早地获得提交和所有内容。请注意,提交W仍然标记和丢失:因为它丢失了,你的Git 看不到它w会连接回来V,你自己的 Git 会显示为:

           X   <-- main

..--V--Y--Z   <-- topic

——这并没有,从技术上讲,这只是一种误导。


推荐阅读