52

情况

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

可能的解决方案

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

    这个被发现不工作。

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

问题

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

7 回答 7

67

2017 年更新:

微软正在为Microsoft/GVFS做出贡献:一个 Git 虚拟文件系统,它允许 Git 处理“地球上最大的存储库”
(即:Windows 代码库,大约有 350 万个文件,当签入到 Git 存储库时,生成大约 300GB 的存储库,除了数千个拉取请求验证构建之外,还每天在 440 个分支中生成 1,760 个“实验室构建”)

GVFS 将您的 git 存储库下的文件系统虚拟化,以便 git 和所有工具看到看似正常的存储库,但 GVFS 仅在需要时下载对象。

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


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

使用git-lfs(参见git-lfs.github.com)和支持它的服务器:lfs-test-server,您只能将元数据存储在 git repo 中,而将大文件存储在其他地方。每次提交最多 2 Gb。

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"

原答案:

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

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

  • 大文件packfile 的 xdelta只在内存中,这对大文件不好)
  • 大量文件,这意味着每个 blob 一个文件,并且git gc一次生成一个包文件的速度很慢。
  • 巨大的包文件,包文件索引从(巨大的)包文件中检索数据效率低下。

处理大文件和xdelta

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

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

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

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

使用散列拆分,无论您在文件中间添加、修改或删除多少数据,受影响块之前之后的所有块都绝对相同。
对哈希分割算法而言,重要的是 32 字节的“分隔符”序列,一次更改最多只能影响一个分隔符序列或两个分隔符序列之间的字节。
就像魔术一样,hashsplit 分块算法每次都会以相同的方式分块您的文件,即使不知道它之前是如何分块的。

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

我们使用所谓的“扇出”进一步扩展了 hashsplit 算法。我们不只检查校验和的最后 13 位,而是使用额外的校验和位来产生额外的拆分。
你最终得到的是一个实际的 blob 树 - git 'tree' 对象非常适合表示。

处理大量文件和git gc

git 旨在处理相对不频繁更改的大小合理的存储库。您可能会认为您“经常”更改源代码,并且 git 处理的更改比svn可以处理的更频繁。
但这与我们所说的“经常”不同。

#1 杀手是它向存储库添加新对象的方式:它为每个 blob 创建一个文件。然后您稍后运行“git gc”并将这些文件合并为一个文件(使用高效的 xdelta 压缩,并忽略任何不再相关的文件)。

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

bup不这样做。它只是直接写入包文件。
幸运的是,这些包文件仍然是 git 格式的,所以一旦它们被写入,git 就可以愉快地访问它们。

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

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

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

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

二进制搜索的性能与包中哈希的数量有关O(log n),经过优化的第一步(您可以在其他地方阅读它)在某种程度上将其改进为O(log(n)-7).
不幸的是,当你有很多包时,这会有点崩溃

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

于 2013-10-21T12:14:32.397 回答
32

您真的,真的,真的不希望将大型二进制文件签入您的 Git 存储库。

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

如果您需要提供大型二进制文件,请将它们单独上传到某个服务器,然后签入带有 URL 的文本文件,开发人员可以在该 URL 中下载大型二进制文件。FTP 实际上是更好的选择之一,因为它是专门为传输二进制文件而设计的,尽管 HTTP 可能更直接。

于 2013-07-26T18:38:20.273 回答
30

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

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

本质上,您会获得与 git fetch 类似的增量更新,而无需 git clone 会提供的不断扩展的初始副本。损失是没有历史,但听起来你不需要那个。

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

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

rsync -avz path/to/database(s) HOST:/folder

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

rsync -avz DATABASE_HOST:/path/to/database(s) path/where/developer/wants/it
于 2013-08-07T03:57:29.513 回答
26

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

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

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

(成绩单)

还有一个大型存储库的所有其他领域,人们有兴趣存储,你知道,20、30 或 40 GB,有时甚至是 TB 大小的存储库,是的,它来自于拥有大量文件,但其中很多来自避免拥有非常大的文件和非常大的二进制文件,它们彼此之间处理得不太好。

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

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

托马斯·费里斯·尼古拉森)哦,酷……

诸如此类的问题git-annex是:一旦你使用它们,你就会……永远被锁定在你当时做出的决定中。你知道,如果你决定哦 200 MB 很大,我们将存储在资产服务器上,然后你决定,啊,它应该是300 MB,运气不好:这将永远编码在你的历史中。
所以从概念上说,在 git 级别,这个对象git 存储库中,而不是指向它的指针,不是指向资产服务器的指针,实际的对象在那里,然后在低端处理这些细节级别,在存储级别,然后你可以自由地做出很多不同的决定,甚至改变您稍后会决定如何将这些内容存储在磁盘上。

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


3 年后的 2016 年 4 月,Git Minutes 40包括31' 左右对来自 GitHub的Michael Haggerty的采访(感谢Christian Couder的采访)。

专注于参考后端很长一段时间
他认为David Turner在后端的工作是目前最有趣的。(参见David 当前pluggable-backends的 git/git fork 分支

(成绩单)

Christian Couder (CD):例如,目标是将 git refs 存储在数据库中?Michael Haggerty (MH):是的,我认为这是两个有趣的方面:​​首先是能够插入不同的源条目引用。条目引用作为松散引用和打包引用的组合存储在文件系统中。
松散引用是每个引用一个文件,而打包引用是一个包含许多引用列表的大文件。

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

[遵循有关更快打包的其他考虑,并参考补丁广告]

于 2013-07-27T12:14:57.193 回答
2

大多数人都会去辅助存储从你的 git-stashed 代码中引用的文件。 git-annex看起来确实很全面,但是许多商店只是使用 FTP 或 HTTP(或 S3)存储库来存储大文件,例如 SQL 转储。我的建议是通过将一些元数据(特别是校验和(可能是 SHA))填充到哈希以及日期中,将 git 存储库中的代码与辅助存储中的文件名称联系起来。

  • 因此,每个 aux 文件都有一个基本名称、日期和 SHA(对于某些版本 n)总和。
  • 如果您的文件周转率很高,则仅使用 SHA 会造成微小但真正的哈希冲突威胁,因此包含日期(纪元时间或 ISO 日期)。
  • 将生成的文件名放入代码中,以便非常具体地通过引用包含辅助块。
  • 以这样一种方式构建名称,以便可以轻松编写一个小脚本来 git grep 所有 aux 文件名,这样任何提交的列表都很容易获得。这也允许旧的在某个时候退役,并且可以与部署系统集成以在从 git repo 激活代码之前将新的 aux 文件拉出到生产环境中,而不会破坏旧的(还)。

将大文件塞进 git(或大多数 repos)中会在一段时间后对 git 的性能产生严重影响 - 例如git clone,实际上不应该花费 20 分钟。而通过引用使用文件意味着一些开发人员根本不需要下载大块(与git clone. 当然,您的里程可能会有所不同。

于 2013-08-04T05:41:29.460 回答
0

有时上传大文件会产生问题和错误。这通常发生。主要是git支持小于50MB的文件上传。在 git 仓库中上传超过 50MB 的文件,用户需要安装另一个助手来配合上传大文件(.mp4,.mp3,.psd)等。

在 git 中上传大文件之前,您需要了解一些基本的 git 命令。这是在 github 上传的配置。它需要 从lfsinstall.exe安装gitlfs.exe



那么你应该使用 git 的基本命令以及一些不同的命令

git lfs install
git init
git lfs track ".mp4"
git lfs track ".mp3"
git lfs track ".psd"
git add .
git add .gitattributes
git config lfs.https://github.com/something/repo.git/info/lfs.locksverify false 
git commit -m "Add design file"
git push origin master` ones

如果在不使用它的情况下推送,您可能会发现它lfs.https://github.com/something/repo.git/info/lfs.locksverify false 类似于推送命令期间的说明

于 2018-07-04T17:11:57.560 回答
0

正如许多其他答案所述,强烈不推荐在 git 中存储大文件。对此我不再赘述。

您的问题似乎更像是关于数据库持久性而不是 git 的问题。如果数据库信息不是那么多,那么

  1. 对于 Java,您可以使用 flywaydb(java) 来存储每个版本之间的数据库差异。
  2. 对于 Django,它可以将 db 信息存储到 json 转储 ( python manage.py dumpdata your_app > datadump.json) 并在其他地方重新加载 ( python manage.py loaddata datadump.json)

但是,由于您的数据库很大,因此您应该考虑流行的二进制存储,例如可以存储二进制文件用作 gitlfs 的存储的nexusartifactory。然后为了减轻开发人员的负担,因为您不希望他们显式下载文件,您需要构建自己的 CI/CD 管道,使开发人员能够单击发布它。

于 2021-10-19T09:35:15.860 回答