我最近在工作中检查了我的一个git存储库,它有超过10,000个分支和超过30000个标记.新鲜克隆后,回收的总大小为12Gigs.我相信没有理由拥有10000个分支机构.所以我相信它们会在磁盘中占用相当大的空间.所以,我的问题如下
分支和标签如何存储在磁盘中,例如使用什么数据结构,为每个分支存储什么信息?
如何获取有关分支的元数据?就像创建分支时一样,分支的大小是多少.
poke.. 17
所以,我将稍微扩展一下这个主题并解释Git 如何存储什么.这样做将解释存储哪些信息,以及存储库大小的确切重要性.作为一个公平的警告:这个答案相当长:)
Git本质上是一个对象数据库.这些对象有四种不同的类型,并且都由其内容的SHA1哈希标识.这四种类型是blob,树,提交和标签.
甲斑点是对象的最简单的类型.它存储文件的内容.因此,对于存储在Git存储库中的每个文件内容,对象数据库中都存在一个blob对象.由于它仅存储文件内容,而不存储文件名等元数据,因此这也是防止具有相同内容的文件被多次存储的机制.
向上一级,树是将blob放入目录结构的对象.单个树对应于单个目录.它本质上是一个文件和子目录列表,每个条目包含一个文件模式,一个文件或目录名,以及对属于该条目的Git对象的引用.对于子目录,此引用指向描述子目录的树对象; 对于文件,此引用指向存储文件内容的blob对象.
Blob和树已足以代表完整的文件系统.要在其上添加版本控制,我们有提交对象.无论何时在Git中提交内容,都会创建提交对象.每次提交都代表修订历史记录中的快照.
它包含对描述存储库根目录的树对象的引用.这也意味着每次实际引入一些更改的提交至少需要一个新的树对象(可能更多).
提交还包含对其父提交的引用.虽然通常只有一个父级(对于线性历史记录),但提交可以包含任意数量的父级,在这种情况下,它通常称为合并提交.大多数工作流程只会让你与两个父母合并,但你也可以拥有任何其他数字.
最后,提交还包含您希望提交的元数据:作者和提交者(名称和时间),当然还有提交消息.
这就是拥有完整版本控制系统所需的一切; 但当然还有一种对象类型:
标记对象是存储标记的一种方法.确切地说,标记对象存储带注释的标记,这些标记具有类似于提交的标记 - 一些元信息.它们由git tag -a
(或创建签名标记时)创建并需要标记消息.它们还包含对它们指向的提交对象的引用,以及标记器(名称和时间).
到目前为止,我们有一个完整的版本控制系统,带有注释标签,但我们所有的对象都由它们的SHA1哈希标识.这当然有点烦人,所以我们还有一些其他的东西可以让它变得更容易:引用.
引用有不同的风格,但最重要的是它们:它们是包含40个字符的简单文本文件 - 它们指向的对象的SHA1哈希.因为它们很简单,所以非常便宜,所以使用很多参考文件都没有问题.它不会产生任何开销,也没有理由不使用它们.
通常有三种"类型"的引用:分支,标签和远程分支.他们真的工作相同,都指向提交对象; 除了指向标记对象的带注释标记之外(普通标记也只是提交引用).它们之间的区别在于您如何创建它们以及/refs/
它们存储在哪个子路径中.我现在不会介绍这个,因为几乎每个Git教程都会对此进行解释; 请记住:引用,即分支,非常便宜,所以不要犹豫为几乎所有东西创建它们.
现在因为torek在他的回答中提到了关于Git压缩的一些内容,我想稍微澄清一下.不幸的是,他混合了一些东西.
因此,通常对于新的存储库,所有Git对象都存储在.git/objects
由SHA1哈希标识的文件中.前两个字符从文件名中删除,用于将文件分区为多个文件夹,这样可以更容易导航.
在某些时候,当历史变得更大或由其他东西触发时,Git将开始压缩对象.它通过将多个对象打包到单个包文件中来实现.这究竟如何起作用并不是那么重要; 它将减少单个Git对象的数量并有效地将它们存储在单个索引存档中(此时,Git将使用delta压缩btw.).然后将包文件存储在其中,.git/objects/pack
并且可以轻松地获得几百MiB的大小.
作为参考,情况有点类似,虽然简单得多.所有当前引用都存储在.git/refs
,例如分支.git/refs/heads
,标签.git/refs/tags
和远程分支中.git/refs/remotes/
.如上所述,它们是简单的文本文件,仅包含它们所指向的对象的40个字符标识符.
在某些时候,Git会将任何类型的旧引用移动到单个查找文件中:.git/packed-refs
.该文件只是一个很长的哈希和引用名称列表,每行一个条目.保留在那里的引用将从refs
目录中删除.
Torek也提到了这些,reflogs本质上只是引用的日志.他们跟踪引用的内容.如果您执行任何影响引用(提交,检出,重置等)的操作,则会添加一个新的日志条目,以便记录发生的事情.它还提供了一种在出错之后返回的方法.例如,一个常见的用例是在意外地将分支重置到不应该去的地方之后访问reflog.然后,您可以使用git reflog
查看日志并查看引用之前指向的位置.由于松散的Git对象不会立即删除(永远不会删除属于历史记录的对象),因此通常可以轻松恢复以前的情况.
然而,Reflog是本地的:它们只跟踪本地存储库发生的情况.它们不与遥控器共享,也不会被转移.新克隆的存储库将具有带有单个条目的reflog,它是克隆操作.它们也被限制在一定的长度之后,旧的动作被修剪,因此它们不会成为存储问题.
所以,回到你的实际问题.克隆存储库时,Git通常已经以压缩格式接收存储库.这已经完成以节省传输时间.引用非常便宜,因此它们永远不是大型存储库的原因.但是,由于Git的本质,单个当前提交对象中有一个完整的非循环图,最终将到达第一个提交,第一个树和第一个blob.因此,存储库将始终包含所有修订的所有信息.这就是使历史悠久的存储库变大的原因.不幸的是,你无法做到这一点.好吧,你可以在某些部分切断旧的历史记录,但这会让你有一个破损的存储库(你通过克隆--depth
参数来做到这一点).
至于你的第二个问题,正如我在上面解释的那样,分支只是对提交的引用,而引用只是指向Git对象的指针.所以不,没有关于可以从中获取分支的任何元数据.唯一可以给你一个想法的是你在历史中分支时所做的第一次提交.但是拥有分支并不会自动意味着历史中确实存在一个分支(快速合并和重新定位对其起作用),并且仅仅因为历史中存在一些分支并不意味着分支(引用,指针)仍然存在.
所有git引用(分支,标签,注释,存储等)都使用相同的系统.这些是:
引用本身,和
"reflogs"
Reflogs .git/logs/refs/
基于引用名称存储,但有一个例外:reflogs for HEAD
存储在.git/logs/HEAD
而不是.git/logs/refs/HEAD
.
引用来自"松散"或"打包".打包引用.git/packed-refs
,这是简单引用的(SHA-1,refname)对的平面文件,以及带注释标记的额外信息."宽松"的裁判在.这些文件包含原始SHA-1(可能是最常见的),或文字字符串后跟符号引用的另一个引用的名称(通常仅用于但您可以创建其他引用).符号引用不包装(或者至少,我似乎无法实现这一点:-))..git/refs/name
ref:
HEAD
包装标签和"空闲"分支头(没有主动更新的那些)节省了空间和时间.你可以git pack-refs
用来做这件事.但是,请为您git gc
调用git pack-refs
,因此通常您不需要自己执行此操作.
所以,我将稍微扩展一下这个主题并解释Git 如何存储什么.这样做将解释存储哪些信息,以及存储库大小的确切重要性.作为一个公平的警告:这个答案相当长:)
Git本质上是一个对象数据库.这些对象有四种不同的类型,并且都由其内容的SHA1哈希标识.这四种类型是blob,树,提交和标签.
甲斑点是对象的最简单的类型.它存储文件的内容.因此,对于存储在Git存储库中的每个文件内容,对象数据库中都存在一个blob对象.由于它仅存储文件内容,而不存储文件名等元数据,因此这也是防止具有相同内容的文件被多次存储的机制.
向上一级,树是将blob放入目录结构的对象.单个树对应于单个目录.它本质上是一个文件和子目录列表,每个条目包含一个文件模式,一个文件或目录名,以及对属于该条目的Git对象的引用.对于子目录,此引用指向描述子目录的树对象; 对于文件,此引用指向存储文件内容的blob对象.
Blob和树已足以代表完整的文件系统.要在其上添加版本控制,我们有提交对象.无论何时在Git中提交内容,都会创建提交对象.每次提交都代表修订历史记录中的快照.
它包含对描述存储库根目录的树对象的引用.这也意味着每次实际引入一些更改的提交至少需要一个新的树对象(可能更多).
提交还包含对其父提交的引用.虽然通常只有一个父级(对于线性历史记录),但提交可以包含任意数量的父级,在这种情况下,它通常称为合并提交.大多数工作流程只会让你与两个父母合并,但你也可以拥有任何其他数字.
最后,提交还包含您希望提交的元数据:作者和提交者(名称和时间),当然还有提交消息.
这就是拥有完整版本控制系统所需的一切; 但当然还有一种对象类型:
标记对象是存储标记的一种方法.确切地说,标记对象存储带注释的标记,这些标记具有类似于提交的标记 - 一些元信息.它们由git tag -a
(或创建签名标记时)创建并需要标记消息.它们还包含对它们指向的提交对象的引用,以及标记器(名称和时间).
到目前为止,我们有一个完整的版本控制系统,带有注释标签,但我们所有的对象都由它们的SHA1哈希标识.这当然有点烦人,所以我们还有一些其他的东西可以让它变得更容易:引用.
引用有不同的风格,但最重要的是它们:它们是包含40个字符的简单文本文件 - 它们指向的对象的SHA1哈希.因为它们很简单,所以非常便宜,所以使用很多参考文件都没有问题.它不会产生任何开销,也没有理由不使用它们.
通常有三种"类型"的引用:分支,标签和远程分支.他们真的工作相同,都指向提交对象; 除了指向标记对象的带注释标记之外(普通标记也只是提交引用).它们之间的区别在于您如何创建它们以及/refs/
它们存储在哪个子路径中.我现在不会介绍这个,因为几乎每个Git教程都会对此进行解释; 请记住:引用,即分支,非常便宜,所以不要犹豫为几乎所有东西创建它们.
现在因为torek在他的回答中提到了关于Git压缩的一些内容,我想稍微澄清一下.不幸的是,他混合了一些东西.
因此,通常对于新的存储库,所有Git对象都存储在.git/objects
由SHA1哈希标识的文件中.前两个字符从文件名中删除,用于将文件分区为多个文件夹,这样可以更容易导航.
在某些时候,当历史变得更大或由其他东西触发时,Git将开始压缩对象.它通过将多个对象打包到单个包文件中来实现.这究竟如何起作用并不是那么重要; 它将减少单个Git对象的数量并有效地将它们存储在单个索引存档中(此时,Git将使用delta压缩btw.).然后将包文件存储在其中,.git/objects/pack
并且可以轻松地获得几百MiB的大小.
作为参考,情况有点类似,虽然简单得多.所有当前引用都存储在.git/refs
,例如分支.git/refs/heads
,标签.git/refs/tags
和远程分支中.git/refs/remotes/<remote>
.如上所述,它们是简单的文本文件,仅包含它们所指向的对象的40个字符标识符.
在某些时候,Git会将任何类型的旧引用移动到单个查找文件中:.git/packed-refs
.该文件只是一个很长的哈希和引用名称列表,每行一个条目.保留在那里的引用将从refs
目录中删除.
Torek也提到了这些,reflogs本质上只是引用的日志.他们跟踪引用的内容.如果您执行任何影响引用(提交,检出,重置等)的操作,则会添加一个新的日志条目,以便记录发生的事情.它还提供了一种在出错之后返回的方法.例如,一个常见的用例是在意外地将分支重置到不应该去的地方之后访问reflog.然后,您可以使用git reflog
查看日志并查看引用之前指向的位置.由于松散的Git对象不会立即删除(永远不会删除属于历史记录的对象),因此通常可以轻松恢复以前的情况.
然而,Reflog是本地的:它们只跟踪本地存储库发生的情况.它们不与遥控器共享,也不会被转移.新克隆的存储库将具有带有单个条目的reflog,它是克隆操作.它们也被限制在一定的长度之后,旧的动作被修剪,因此它们不会成为存储问题.
所以,回到你的实际问题.克隆存储库时,Git通常已经以压缩格式接收存储库.这已经完成以节省传输时间.引用非常便宜,因此它们永远不是大型存储库的原因.但是,由于Git的本质,单个当前提交对象中有一个完整的非循环图,最终将到达第一个提交,第一个树和第一个blob.因此,存储库将始终包含所有修订的所有信息.这就是使历史悠久的存储库变大的原因.不幸的是,你无法做到这一点.好吧,你可以在某些部分切断旧的历史记录,但这会让你有一个破损的存储库(你通过克隆--depth
参数来做到这一点).
至于你的第二个问题,正如我在上面解释的那样,分支只是对提交的引用,而引用只是指向Git对象的指针.所以不,没有关于可以从中获取分支的任何元数据.唯一可以给你一个想法的是你在历史中分支时所做的第一次提交.但是拥有分支并不会自动意味着历史中确实存在一个分支(快速合并和重新定位对其起作用),并且仅仅因为历史中存在一些分支并不意味着分支(引用,指针)仍然存在.