我最近被介绍了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/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
等远程的名称,不会出现在此处。这些通常位于.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命令:
mkdir
,创建一个新的空目录/文件夹git init
在步骤1中制作的目录中创建新的空存储库git remote add
,添加名称origin
,或我们选择的其他名称,以及一个URL和一个fetch
配置——这是我们为克服单一分支而更改的配置git config
,如果需要,添加在git clone
命令中指定的配置选项git fetch
,以获得提交并为步骤3中选择的一个或多个分支创建远程跟踪名称;以及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
--这并不是错误的,从技术上讲,这只是一种误导。