用大文件Git

Jak*_*edl 41 git large-files gitlab

情况

我有两台服务器,生产和开发.在生产服务器上,有两个应用程序和多个(6)数据库(MySQL),我需要将它们分发给开发人员进行测试.所有源代码都存储在开发服务器上的GitLab中,开发人员只能使用此服务器,并且无权访问生产服务器.当我们发布一个应用程序时,master会登录到生产环境并从Git中提取新版本.数据库很大(每个超过500M并且数不胜数),我需要尽可能简单地将它们分发给开发人员进行测试.

可能的解决方案

  • 在将数据库(每个数据库)转储到单个文件的备份脚本之后,执行将每个数据库推送到其自己的分支的脚本.如果开发人员想要更新他的本地副本,则会拉出其中一个分支.

    发现这个不起作用.

  • 生产服务器上的Cron每天都会保存二进制日志,并将它们推送到该数据库的分支中.因此,在分支中,有每日更改的文件,开发人员会提取他没有的文件.当前的SQL转储将以另一种方式发送给开发人员.当存储库的大小变得太大时,我们将向开发人员发送完全转储并刷新存储库中的所有数据并从头开始.

问题

  • 解决方案可行吗?
  • 如果git正在推送/拉出存储库,它是上传/下载整个文件,还是只更改它们(即添加新行或编辑当前的行)?
  • Git可以管理如此大的文件吗?没有.
  • 如何设置存储库中保留的修订数量?与新解决方案无关.
  • 有没有更好的解决方案?我不想强迫开发人员通过FTP或类似的东西下载这么大的文件.

Von*_*onC 60

2017年更新:

微软正在协助微软/ GVFS:一个Git虚拟文件系统允许的Git来处理" 这个星球上最大的回购 "
(即:Windows的代码库,这是大约3.5M的文件,当到一个Git回购检查,结果约300GB回购,并产生1,760日报"实验室建立"跨除了数以千计的拉请求440枝验证版本)

GVFS虚拟化文件系统中的混帐回购协议下,这样Git和所有工具看这似乎是一个正常的回购,但只GVFS下载对象在需要的时候.

GVFS的某些部分可能是上游贡献的(对Git本身而言).
但与此同时,所有新的Windows开发现在(2017年8月)都在Git上.


2015年4月更新:GitHub提议:宣布Git大文件存储(LFS)

使用git-lfs(请参阅git-lfs.github.com)和支持它的服务器:lfs-test-server,您只能在git仓库中存储元数据,在其他地方存储大型文件.

https://cloud.githubusercontent.com/assets/1319791/7051226/c4570828-ddf4-11e4-87eb-8fc165e5ece4.gif

请参阅git-lfs/wiki/Tutorial:

git lfs track '*.bin'
git add .gitattributes "*.bin"
git commit -m "Track .bin files"
Run Code Online (Sandbox Code Playgroud)

原始答案:

关于大文件的git限制,可以考虑bup(在GitMinutes#24中详细介绍)

bup设计突出了限制git repo的三个问题:

  • 大文件(文件xdelta仅在内存中,对大文件不好)
  • 大量的文件,这意味着每个blob一个文件,并且git gc一次生成一个packfile的速度很慢.
  • large packfiles,packfile索引无法从(巨大的)packfile中检索数据.

处理大文件和 xdelta

git无法处理大文件的主要原因是它运行它们xdelta,这通常意味着它尝试一次将文件的全部内容加载到内存中.
如果它没有这样做,它将必须存储每个文件的每个修订版的全部内容,即使您只更改了该文件的几个字节.
这将是磁盘空间非常低效的使用
,而git因其惊人的高效存储库格式而闻名.

不幸的是,它xdelta适用于小文件,并且对于大文件来说非常慢并且内存很大.
对于git的主要目的,即.管理您的源代码,这不是问题.

什么bup而不是xdelta就是我们所说的" hashsplitting."
我们想要一种通用的方法来有效地备份可能以小方式改变的任何大文件,而不是每次都存储整个文件.我们一次读取一个字节的文件,计算最后128个字节的滚动校验和.

rollsum似乎做得很好.你可以找到它bupsplit.c.
基本上,它将读取的最后128个字节转换为32位整数.然后我们做的是获取rollum的最低13位,如果它们都是1,我们认为这是一个块的结尾.
这平均每次发生一次2^13 = 8192 bytes,因此平均块大小为8192字节.
我们根据滚动校验和将这些文件分成块.
然后我们将每个块分别存储(由其sha1sum索引)作为git blob.

使用hashsplitting,无论您在文件中间添加,修改或删除多少数据,受影响块之前之后的所有块都是完全相同的.
对hashsplitting算法重要的是32字节"分隔符"序列,并且单个更改最多只能影响一个分隔符序列或两个分隔符序列之间的字节.
像魔术一样,hashsplit分块算法每次都会以相同的方式对文件进行分块,即使不知道它以前是如何分块的.

下一个问题不太明显:在你将一系列块存储为git blobs后,你如何存储它们的序列?每个blob都有一个20字节的sha1标识符,这意味着简单的blob列表将20/8192 = 0.25%具有文件长度.
对于200GB的文件,这是488兆的序列数据.

我们使用我们称之为"扇出"的东西进一步扩展了hashsplit算法.我们不使用校验和的最后13位来检查,而是使用额外的校验和位来产生额外的分裂.
你最终得到的是一个真正的blob树 - git'树'对象是理想的代表.

处理大量文件和 git gc

git旨在处理相对较小的存储库,这些存储库相对不频繁地更改.您可能认为您"经常"更改了源代码,并且git处理的频率更高,比如说svn可以处理.
但这与我们谈论的"经常"不同.

第一个杀手就是它将新对象添加到存储库的方式:它为每个blob创建一个文件.然后你运行'git gc'并将这些文件合并到一个文件中(使用高效的xdelta压缩,并忽略任何不再相关的文件).

' git gc'很慢,但是对于源代码存储库,由此产生的超高效存储(以及对存储文件的快速访问相关联)是值得的.

bup不这样做.它只是直接写packfiles.
幸运的是,这些packfiles仍然是git格式的,因此git可以在编写后愉快地访问它们.

处理庞大的存储库(意味着大量的庞大的包文件)

Git实际上并不是为处理超大型存储库而设计的.
大多数git存储库都足够小,将它们全部合并到一个包文件中是合理的,这git gc通常最终会完成.

大型文件包的问题部分不是包文件本身 - git旨在期望所有包的总大小大于可用内存,并且一旦它能够处理它,它几乎可以有效地处理任何数量的数据.
问题是packfile indices(.idx)文件.

*.packgit中的每个packfile()都有一个关联的idx(*.idx),它是git对象哈希值和文件偏移量的排序列表.
如果您正在根据其sha1查找特定对象,则打开idx,二进制搜索它以找到正确的哈希值,然后获取相关文件偏移量,在packfile中查找该偏移量,并读取对象内容.

二进制搜索的性能大约是O(log n)在群雄,具有优化的第一步散列的数量(你可以在其他地方了解吧)多少有些改善它O(log(n)-7).
不幸的是,当你有很多包时,这会分解一下.

为了提高这种操作的性能,bup引入了midx(发音为"midix"和"multi-idx"的缩写)文件.
顾名思义,它们一次索引多个包.

  • 您使用的模糊概念"频繁","巨大"和"批次"的定义是什么?"一天两次","1 GB"和"1E6"? (3认同)

Amb*_*ber 32

你真的,真的,真的不希望大型二进制文件被检入你的Git存储库.

您添加的每个更新都会累积性地添加到存储库的总体大小,这意味着您的Git存储库需要更长时间才能克隆并消耗越来越多的磁盘空间,因为Git会在本地存储分支的整个历史记录,这意味着当有人签出分支时,他们不仅需要下载最新版本的数据库; 他们还必须下载每个以前的版本.

如果需要提供大型二进制文件,请将它们单独上载到某个服务器,然后检查带有URL的文本文件,开发人员可以在其中下载大型二进制文件.FTP实际上是更好的选择之一,因为它专门用于传输二进制文件,尽管HTTP可能更简单.

  • 我同意.git中的历史无论如何都不会有任何意义,因此添加二进制文件没有多大意义.而是提出一个好的命名约定,将它们存储在某个地方,并使用脚本来处理提取. (2认同)

Pet*_*rSW 27

rsync可以是有效更新数据库开发人员副本的好选择.

它使用增量算法逐步更新文件.这样它只传输已更改或新的文件块.他们当然仍然需要先下载完整的文件,但以后的更新会更快.

基本上,你得到一个类似的增量更新作为git fetch,而没有git clone提供的不断扩展的初始副本.损失没有历史,但听起来你不需要.

rsync是大多数Linux发行版的标准部分,如果你需要它在Windows上有一个打包的端口可用:http://itefix.no/cwrsync/

要将数据库推送给开发人员,您可以使用类似于以下的命令:

rsync -avz path/to/database(s) HOST:/folder
Run Code Online (Sandbox Code Playgroud)

或者开发人员可以通过以下方式提取他们需要的数据库:

rsync -avz DATABASE_HOST:/path/to/database(s) path/where/developer/wants/it
Run Code Online (Sandbox Code Playgroud)


Von*_*onC 26

您可以查看git-annex这样的解决方案,它是关于使用git管理(大)文件,而不是将文件内容检入git(!)
(2015年2月:像GitLab这样的服务托管本机集成它:
请参阅" GitLab支持大吗?通过git-annex或其他文件? ")

git不会管理大文件,正如Amber她的回答中所解释的那样.

这并不意味着有一天git将无法做得更好.
GitMinutes第9集(2013年5月,另见下文),来自Peff(Jeff King),在36'10'':

(成绩单)

存在大型存储库的所有其他领域,人们有兴趣存储20或30或40 GB,有时甚至是TB大小的存储库,是的,它来自拥有大量文件,但其中很多都来了从拥有非常大的文件和非常大的二进制文件,彼此不能很好地处理.

那是一个开放的问题.有几个解决方案:git-annex可能是最成熟的解决方案,他们基本上不会将资产放入git,他们将大资产放在资产服务器上,并将指针放入git.

我想做类似的事情,资产在概念上是在git中,即该对象的SHA1是进入树的SHA1的一部分,进入提交ID和所有这些事情.
所以从git的角度来看,它是存储库的一部分,但在下面的层次,在对象存储级别,在概念历史图下面的一个级别,我们已经有多种方式存储对象:我们有松散的对象,我们有打包的对象,我想有一种新的方式来存储一个对象,也就是说"我们这里没有它,但它可以由资产服务器提供",或类似的东西.

(Thomas Ferris Nicolaisen)很酷......

这样的问题git-annex是:一旦你使用它们,你就会......锁定你当时做出的决定.你知道,如果你决定哦200 MB是大的,我们将存储在一个资产服务器上,然后,你决定,aah它应该是300 MB,运气不错:这在你的历史中永远编码.
所以在概念上说,在git级别,这个对象 git存储库中,而不是指向它的一些指针,而不是指向资产服务器的指针,实际的对象就在那里,然后在低处处理这些细节.等级,在存储级别,然后释放你做出很多不同的决定,甚至改变你以后关于你如何将这些东西存储在磁盘上的决定.

现在不是一个高优先级的项目......


3年后,2016年4月,Git Minutes 40在31'左右接受了来自GitHubMichael Haggerty的采访(感谢Christian Couder的采访).

专门研究参考后端很长一段时间.
他引用David Turner关于后端的工作是目前最有趣的.(参见David当前pluggable-backends的git/git fork分支)

(成绩单)

Christian Couder(CD):目标是将git ref存储在数据库中,例如?Michael Haggerty(MH):是的,我把它看作两个有趣的方面:​​第一个是简单地能够插入不同的源条目引用.条目引用存储在文件系统中,作为松散引用和压缩引用的组合.
松散引用是每个引用一个文件,压缩引用是一个包含许多引用列表的大文件.

这是一个很好的系统,特别是对于本地使用; 因为它对普通人来说没有任何真正的性能问题,但它确实有一些问题,比如在删除引用后你不能存储引用reflogs,因为可能与使用类似的创建的较新引用存在冲突名.还存在一个问题,即引用名称存储在文件系统上,因此您可以使用名称相似但大写不同的引用.
因此,通常可以通过使用不同的参考后端系统来修复这些问题.
David Turner的补丁系列的另一个方面是在名为lmdb的数据库中存储引用的更改,这是一个非常快速的基于内存的数据库,与文件后端相比具有一些性能优势.

[关于更快包装和参考补丁广告的其他考虑因素]