当使用--depth 1完成克隆时,使用GIT获取另一个分支



我最近被介绍了gitclone的--depth 1。显然,这并不是所有的历史,而且速度要快得多。我用过:

git克隆--深度1-b开发https://github.MyCompany.com/CoolProduct/CoolProduct.git

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

然而,现在我想看看另一个分支"BillsFeature"我尝试过:git checkout BillsFeature,得到错误:路径规范"BillsFeature"与git 已知的任何文件都不匹配

这对我来说有一定的道理。大概是因为我使用了--depth 1,所以我没有删除分支名称如何获得另一个分支我也不需要BillsFeature的历史记录。我应该说我试过了:git fetch—深度1原点BillsFeature似乎发生了什么事。然而,当我做git状态时,我得到了:

论分支机构的发展您的分支机构是最新的"起源/发展"。

无需提交,工作树清理

谢谢,Dave

这里问题的根源是git clone--depth选项也打开了--single-branch。要在克隆时击败它,请使用--no-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将调用一些其他Git,并列出其所有分支、标记和其他名称,而不使用--single-branch--depth标志。然后,它将使用这些名称在原始存储库中查找所有提交和其他Git对象,并让其他Git发送所有这些对象。结果是对象数据库的完整副本4您现在拥有了来自其他Git存储库的所有提交。

然而,与此同时,你自己的Git会取下他们所有的名称,并选择要取的名称支的名称,它们的完整拼写是refs/heads/masterrefs/heads/topic等,然后重命名,使其成为你自己的远程跟踪名称:refs/remotes/origin/masterrefs/remotes/origin/topic等。然后,你的Git会创建自己的独立名称来哈希ID数据库,其中没有的分支名称。5

最终的结果是,在git clone的这一步骤之后,您立即拥有所有提交,而没有分支不过,git clone的最后一步很快纠正了这种情况。假设您没有说--no-checkoutgit 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远程的名称,不会出现在此处。这些通常位于.git目录中的config文件中。

这个数据库目前实现得相当糟糕。有时,名称存储为文件系统中的目录和文件名,这意味着在不区分大小写的文件系统(如Windows和macOS系统上的默认文件系统)上,分支名称变得不区分大小字母。有时,这些名称存储在一个名为packed-refs的纯文本文件中,这使得它们都像Git一直想要的那样区分大小写。一些特殊的名称,如HEAD,根本不会进入压缩的refs文件,而是始终作为单独的文件存储在.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中,每个分支名称只标识一个提交。但是Git中的一个分支——如果我们忽略了我们所说的";分支"?(我们不应该忽略它,但我们会在这里忽略它)--通常有一堆提交。这是怎么回事?

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

让我们画一个简单的链,其中我们使用一个大写字母来代替每个提交的真实哈希ID。散列H将是链中最后一个,我们可以说这是分支br1:

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

名称br1保存上次提交H哈希ID。这就是我们可以让Git从对象数据库中提取它的方法(记住,这是一个简单的键值存储:hash ID就是密钥)。但是在提交H的主体中,Git存储了提交G之前的的哈希ID。因此,从H中,我们可以获得G的ID,并让Git在键值存储中查找提交G。同时提交G具有F的ID,因此我们可以从G向后走到F

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有更多唯一的提交,我们就不会有从br2返回到shared的连接。

这里的小写提交字母表示Git知道有一个父级,但这些父级被标记为";故意失踪";。更准确地说,浅移植提交的哈希ID保存在.git目录中名为shallow的文件中。Git知道不要试图从对象存储库加载这些提交,它们并不是一个缺失的bug。通常情况下,这将是一个错误。

由于它们是故意丢失的,git log不能也不会显示这些提交,而且浅移植的提交就好像根本没有父级一样。这在某种程度上是误导,但也是你应该期待的。在大多数情况下,它是无害的。


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


git fetch操作获得提交

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

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

--depth选项在步骤5中传递给git fetch。因此,如果我们必须调整origin远程配置,以取消克隆的单个分支,因为步骤3只添加了一个特定分支的远程(请参阅git remote文档),我们必须运行一个新的git fetch。这个新的git fetch需要相同的--depth选项。

结论

git clone--depth选项同时启用--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

并且您的单个分支克隆是main上的深度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

--这并不是错误的,从技术上讲,这只是一种误导。

最新更新