使用GIT在克隆时使用--depth 1参数,如何获取另一个分支?

5

最近我了解到了git clone的--depth 1参数。显然,这不会获取所有历史记录,但速度更快。我使用了以下命令:

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

这使我能够玩耍、修改和分支开发分支。

然而,现在我想查看另一个分支"BillsFeature" 我尝试输入:git checkout BillsFeature,但出现了以下错误: error: pathspec 'BillsFeature' did not match any file(s) known to git

对我来说,这有些合理。可能是因为我使用了--depth 1,没有拉取分支名称。如何获取其他分支? 我也不需要BillsFeature的历史记录。 我应该说,我尝试过: git fetch --depth 1 origin BillsFeature 好像有些事情发生了。但当我执行git status时,我得到了以下结果:

在develop分支上 您的分支已经是'origin/develop'最新版本。

没有要提交的内容,工作树干净

谢谢, Dave


1
这并不是显而易见的重复,但实际上它是重复的,因为 --depth 意味着 --single-branch,除非你还加上 --no-single-branch - torek
我不认为这是重复的。鉴于我使用了--depth(与--single-branch相同),我如何获取其他分支? - Dave
1
因为 --depth 1 意味着 --single-branch,所以你只得到一个分支的一个提交。在克隆时使用 --no-single-branch 选项可以获取每个分支的一个提交(从而获得正确数量的远程跟踪名称),或者使用接受的答案更新现有的克隆,然后运行 git fetch --depth 1 来更新事物,以便你对于每个远程跟踪名称都有一个提交。 - torek
(如果您愿意,我将重新开放此问题以获得直接答案,而不是间接实现--depth意味着--single-branch因此需要--no-single-branch来打败它。) - torek
您始终可以执行 git fetch --unshallow,请参见 https://dev59.com/6mw15IYBdhLWcg3wJ4UR#6802238。 - Marcin Kłopotek
显示剩余4条评论
1个回答

4
问题的根源在于 git clone--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-branch--depth 标志的话——会调用另一个 Git,并让其列出其所有分支、标签和其他名称。然后,它将使用这些名称来查找原始存储库中的所有提交和其他 Git 对象,并让另一个 Git 发送所有这些对象。结果是一个对象数据库的完全副本。4 现在,您已经拥有了另一个 Git 存储库中的所有提交。
同时,你自己的Git会获取他们所有的“名称”,并挑选其中一些“名称”以及处理它们。通常情况下,你的Git会获取他们所有的“分支”名称——其全名可能是像refs/heads/masterrefs/heads/topic等等,然后将它们“重命名”为你自己的“远程跟踪”名称:如refs/remotes/origin/masterrefs/remotes/origin/topic等等。接着,你的Git会创建自己独立的名称到哈希ID的数据库,其中没有任何分支名称。5 最终结果是,在git clone这一步之后,你拥有了所有的提交没有分支!不过,这种情况很快就会被git clone的最后一步纠正。只要你没有使用--no-checkout选项,git clone的最后一步就是运行git checkout,这一步实际上会创建一个分支。你的Git创建的分支名称就是你用-b选项提供的名称。如果你没有提供-b选项,你的Git会询问另一个Git推荐哪个分支,如果其他方法都失败了,你的Git会假定你自己的默认初始分支名称。6
每个提交对象都引用一个(单独的)“树”对象,该对象保存该提交的快照并具有元数据。每个树对象都保存一组部分文件名 - 名称组成部分,按需要将它们串在一起和另一个哈希ID。该哈希ID标识要么是另一个树形结构,要么是存储某个文件内容的“blob”对象。Git通过阅读所有必要的子树来构建文件的完整名称,并在其索引中存储完整文件名称,然后使用索引中所见的名称和blob哈希ID提取文件。这不是完整的说明,但这就是为什么Git无法存储空目录的原因:无法将其放入Git的索引中。
对象数据库还可以包含“注释标记对象”,每个对象都保存哈希ID,通常是提交的哈希ID。这就是Git提供其注释标记的方式。
有例外情况:由于某种原因保留积累新名称(如新分支和标签名称),但几乎从不获得任何新提交的旧存储库。但通常情况下,对象数据库是使用最多的地方,也是最初克隆的大部分时间使用的地方。
其他名称包括诸如注释,正在进行的二分法,某些交互式rebase期间需要的名称等等。基本上,任何存储单个哈希ID的名称都会进入此数据库。不会这样做的名称,例如像origin这样的远程名称,不会出现在这里。它们通常会出现在.git目录中的config文件中。
当前实现这个数据库的方式相当糟糕。有时,名称被存储为文件系统中的目录和文件名,这意味着在大小写不敏感的文件系统上(例如Windows和macOS系统上的默认文件系统),分支名称变成了大小写不敏感的。有时,名称存储在名为packed-refs的纯文本文件中,使它们始终区分大小写,就像Git一直打算的那样。一些特殊的名称,例如HEAD,根本不会进入packed-refs文件,而是始终作为单独的文件存储在.git目录中。目前正在进行工作以提供一个合适的数据库,以解决这里的一堆问题。
技术上说,结果可以并且通常会省略任何无法通过使用名称找到的对象。不过在这里我们忽略这个细微的区别。
您的 Git 通常也会省略所有非分支和非标签名称。它如何处理它们的标签名称是复杂的,但在正常(非单分支、非深度限制)克隆中,您通常会复制所有它们的标签名称。
这以前只是硬编码为 master,但现在变成可配置的。

如何影响 --single-branch

使用 --single-branch 选项,你的 Git 不会使用所有分支名称,而是只使用从你的 -b 选项中指定的一个分支名称,并采用相同的默认设置: 如果你没有提供 -b,你的 Git 会询问对方的 Git 推荐哪个分支或者回退到另一个默认值。然后,你的 Git 将该分支名称转换为一个远程跟踪名称。它确保仅向他们的 Git 请求在该分支上存在于该 Git 存储库中的提交。

最终结果是你将得到一个远程跟踪名称和一些子集的所有提交。最后的 git checkout 步骤会创建一个本地分支名称:与选择提交子集时 Git 使用的名称相同。

如何影响 --depth

除了自动打开--single-branch之外(但请注意,您可以使用--no-single-branch关闭它),--depth的作用是创建一个浅层克隆。要完全理解浅层克隆,我们必须涉及图形理论。(在这里我们不会深入探讨这个问题。)
在Git中,每个分支名称标识恰好一个提交。但在Git中,如果我们忽略“分支”究竟是什么意思?的问题(尽管我们不应该忽略它,但是在这里我们会忽略它),通常会有一堆提交。那这是如何工作的呢?
答案是,在Git中,每个提交都包含某个早期提交的哈希ID。在通常的简单情况下,我们最终得到了一长串提交,每个提交都指向前面的一个提交。这条链中的最后一个提交是分支的末端末尾提交
让我们画一个简单的链,其中使用一个大写字母代表每个提交的真实哈希 ID。哈希 H 将是链中的最后一个提交,并且我们将称其为分支br1:
... <-F <-G <-H   <-- br1

名字br1保存了最新提交H的哈希ID。这就是Git如何从对象数据库中获取它的方式(请记住,这是一个简单的键值存储:哈希ID是键)。但在提交H的主体内部,Git存储了更早提交G的哈希ID。因此,我们可以从H获取G的ID,并让Git在键值存储中查找提交G。同时,提交GF的ID,因此我们可以从G向后遍历到F

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

我们走过的路径以及我们收集到的所有提交,都是该分支上可达的提交。当两个分支分叉时,它们都有一些共同的序列:
             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有更多的提交是独有的,我们就不会从br2回到shared
这里的小写字母表示Git知道有一个父节点,但这些父节点被标记为“故意缺失”。更准确地说,浅嫁接(commit)的哈希ID保存在名为shallow的文件中,该文件位于.git目录中。Git知道不要尝试从对象库加载这些提交,并且它们缺失并不是一个错误。通常情况下,这将是一个错误。
由于它们是故意缺失的,git log不能且不会显示这些提交,因此看起来好像浅嫁接的提交根本没有父节点。这在某种程度上是具有误导性的,但也是你应该期望的。在大多数情况下,这足够无害。
这意味着我们使用的是分支名称。如果我们使用了标签名称,那么这些提交是可从标签达到的;如果我们使用了远程跟踪名称,那么这些提交是可从远程跟踪名称达到的。由于所有名称都使用相同的系统,因此每个名称都提供了一些方法来达到一些提交。

通过 git fetch 操作获取提交记录

当我们使用 git clone 命令时,实际上运行了一个包含六个命令的序列,其中五个是 Git 命令:

  1. mkdir,创建一个新的空目录/文件夹;
  2. git init,在第一步创建的目录中创建一个新的空仓库;
  3. git remote add,添加名称为 origin 或其他我们选择的名称、URL 和 fetch 配置,这是我们更改以避免单分支性的配置;
  4. git config,如果需要,在 git clone 命令中指定配置选项;
  5. git fetch,获取提交记录并为步骤 3 中选择的分支或分支创建远程跟踪名称;
  6. git checkout,创建一个本地分支名称,并填充 Git 的索引和我们的工作树。
第五步将选项--depth传递给git fetch。因此,如果我们必须调整我们的origin远程配置,以取消单一分支克隆,因为第3步仅添加了一个特定分支的远程(请参阅git remote文档),我们必须运行一个新的git fetch。这个新的git fetch需要相同的--depth选项。
结论: --depth选项用于git clone命令,它同时开启了--single-branch选项,该选项限制了从其他Git存储库获取的名称和提交,以及将--depth传递到提取步骤,该步骤限制了从其他Git存储库获得的提交图的深度。在克隆时使用--no-single-branch可以抑制名称限制,同时保持深度限制。如果您需要撤消名称限制,或者如果您使用git remote更新受限分支名称集合,则必须再次运行git fetch。如果您希望git fetch具有深度限制,则必须再次传递--depth
请注意,git fetch确实尊重现有的浅嫁接点,因此在某些情况下,省略--depth是相当无害的。例如,如果您有一个单分支克隆的存储库,它看起来像这样:
...--V--W--X   <-- main
            \
             Y--Z   <-- topic

您的单分支克隆深度为1,位于主分支上,因此提交W被标记为浅植接点:
        w--X   <-- main

然后,如果不使用--depth参数添加topic,就会得到以下结果:
        w--X   <-- main
            \
             Y--Z   <-- topic

那就是说,这一次 main 没有更深入了。但如果图表如下:
...--V--W--X   <-- main
      \
       Y--Z   <-- topic

如果您添加了topic并且没有使用新的--depth选项获取,那么您将会得到:
...--V  w--X   <-- main
      \
       Y--Z   <-- topic

在你的克隆中,这意味着你需要获取提交V和更早的所有内容。请注意,提交W仍然被标记为缺失:由于它丢失了,你的 Git 无法看到w会连接回V,你自己的 Git 将显示如下:
           X   <-- main

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

"这并不是技术上的错误,只是有误导性。"

谢谢你,torek,感谢你的答案和背景信息。我猜这对未来的很多人都会有用。Git被广泛使用,“我想快速启动,但稍后再获取其他分支”的问题是一个普遍的问题,我相信。谢谢! - Dave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接