使用Git管理大型二进制文件

我正在寻找如何处理我的源代码(web应用程序)依赖的大型二进制文件的意见。我们目前正在讨论几种替代方案:

  1. 手动拷贝二进制文件。
    • 教授:不确定。
    • Contra:我强烈反对这样做,因为它增加了建立新站点/迁移旧站点时出错的可能性。又增加了一个障碍。
    • 李< / ul > < / >
    • 使用Git管理它们。
      • 利:消除了“忘记”复制重要文件的可能性
      • 反:膨胀存储库,降低管理代码库和检出的灵活性,克隆等将花费相当长的时间。
      • 李< / ul > < / > <李>单独的存储库。
        • 利:检出/克隆源代码比以往任何时候都快,并且图像被正确地存档在自己的存储库中。
        • 反:消除了在项目中使用唯一的 Git存储库的简单性。它肯定介绍了一些我没有想到的其他东西。
        • 李< / ul > < / >

你对此有什么经验/想法?

还有:有人有在一个项目中使用多个Git存储库并管理它们的经验吗?

这些文件是用于生成包含这些文件的pdf文件的程序的图像。这些文件不会经常更改(例如几年),但它们与程序非常相关。没有这些文件,程序将无法工作。

201715 次浏览

在我看来,如果您可能经常修改这些大文件,或者您打算制作大量的git clonegit checkout文件,那么您应该认真考虑使用另一个Git存储库(或者另一种访问这些文件的方法)。

但是如果您像我们一样工作,并且您的二进制文件不经常修改,那么第一次克隆/签出将会很长,但是在那之后它应该和您想要的一样快(考虑到您的用户一直使用他们拥有的第一个克隆存储库)。

如果没有这些文件程序就不能工作,那么将它们分割成一个单独的repo似乎是一个坏主意。我们有大型的测试套件,我们将它们分解到一个单独的repo中,但这些都是真正的“辅助”文件。

但是,您可以在单独的repo中管理这些文件,然后使用git-submodule以合理的方式将它们拉入您的项目。你仍然有所有源代码的完整历史但是,据我所知,你只有图像子模块的一个相关修订。git-submodule工具应该帮助您保持正确的代码版本与正确的图像版本保持一致。

这里有一个来自Git Book的子模块介绍

我将使用子模块(如Pat Notz)或两个不同的存储库。如果你太频繁地修改二进制文件,那么我会尽量减少巨大的存储库清理历史记录的影响:

几个月前,我遇到了一个非常类似的问题:大约21 GB的MP3文件,未分类(糟糕的名称,糟糕的id3,不知道我是否喜欢这个MP3文件……),并在三台计算机上复制。

我使用带有主Git存储库的外部硬盘驱动器,并将其克隆到每台计算机中。然后,我开始用习惯的方式对它们进行分类(推、拉、合并……)多次删除和重命名)。

最后,我的MP3文件只有~6 GB, .git目录下只有~83 GB。我使用git-write-treegit-commit-tree来创建一个新的提交,没有提交祖先,并开始一个指向该提交的新分支。该分支的“git日志”只显示了一次提交。

然后,我删除了旧的分支,只保留了新的分支,删除了ref-logs,并运行“git prune”:在那之后,我的.git文件夹的重量只有~6 GB…

你可以不时地用同样的方法“清除”这个巨大的存储库:你的“git克隆”会更快。

SVN似乎比Git更有效地处理二进制增量。

我必须决定文档的版本控制系统(JPEG文件、PDF文件和.odt文件)。我刚刚测试了添加一个JPEG文件并将其旋转90度4次(以检查二进制增量的有效性)。Git的存储库增长了400%。SVN的存储库仅增长了11%。

因此,看起来SVN使用二进制文件更有效率。

所以我选择Git作为源代码,SVN作为文档之类的二进制文件。

看看git bup,它是一个Git扩展,可以在Git存储库中巧妙地存储大型二进制文件。

您希望将它作为子模块使用,但不必担心存储库变得难以处理。他们的一个示例用例是在Git中存储VM映像。

实际上我还没有看到更好的压缩率,但我的存储库中并没有真正大的二进制文件。

你的里程可能会有所不同。

我最近发现了git-annex,我觉得很棒。它是为有效地管理大文件而设计的。我用它来收集我的照片/音乐(等)。git-annex的开发非常活跃。文件的内容可以从Git存储库中删除,Git只跟踪树的层次结构(通过符号链接)。然而,要获得文件的内容,在拉/推之后需要第二步,例如:

$ git annex add mybigfile
$ git commit -m'add mybigfile'
$ git push myremote
$ git annex copy --to myremote mybigfile ## This command copies the actual content to myremote
$ git annex drop mybigfile ## Remove content from local repo
...
$ git annex get mybigfile ## Retrieve the content
## or to specify the remote from which to get:
$ git annex copy --from myremote mybigfile

有很多可用的命令,网站上有很好的文档。在Debian上有一个可用的包。

你也可以使用git-fat。我喜欢它只依赖于Python和rsync。它还支持通常的Git工作流,使用以下自解释命令:

git fat init
git fat push
git fat pull

此外,您需要将.gitfat文件签入存储库,并修改.gitattributes以指定要git fat管理的文件扩展名。

您可以使用普通的git add添加一个二进制文件,然后根据gitattributes规则调用git fat

最后,它的优点是二进制文件实际存储的位置可以跨存储库和用户共享,并支持rsync所做的任何事情。

更新:如果你正在使用Git-SVN网桥,不要使用git-fat。它最终将从Subversion存储库中删除二进制文件。但是,如果您使用的是纯Git存储库,那么它的工作效果非常好。

看看camlistore。它不是真正基于git的,但我发现它更适合您必须做的事情。

另一个解决方案,自2015年4月以来是Git大文件存储(LFS)(由GitHub)。

它使用< >强git-lfs < / >强(参见git-lfs.github.com),并在支持它的服务器上测试:< >强lfs-test-server < / >强:
你只能在git repo中存储元数据,而在其他地方存储大文件

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

我想提出的解决方案是基于孤儿分支和对标记机制的轻微滥用,今后称为*孤儿标记二进制存储(otab)

TL;博士12-01-2017如果你可以使用github的LFS或其他第三方,无论如何你应该。如果你不能,那么继续读下去。请注意,这个解决方案是一个黑客,应该被这样对待。

OTABS的理想属性

  • 这是一个纯gitgit只的解决方案——它可以在没有任何第三方软件(如git-annex)或第三方基础设施(如github的LFS)的情况下完成工作。
  • 它存储二进制文件有效地,也就是说,它不会膨胀你的存储库的历史。
  • git pullgit fetch,包括git fetch --all仍然是带宽效率,也就是说,默认情况下不是所有大型二进制文件都从远程提取。
  • 它在窗户上工作。
  • 它将所有内容存储在单个git存储库中。
  • 它允许过时的二进制文件使用删除(不像bup)。

OTABS的不良属性

  • 它使git clone可能效率低下(但不一定,这取决于您的使用情况)。如果您部署了这个解决方案,您可能不得不建议您的同事使用git clone -b master --single-branch <url>而不是git clone。这是因为git克隆默认复制整个存储库,包括你通常不想浪费带宽的东西,比如未引用的提交。摘自所以4811434
  • 它使git fetch <remote> --tags带宽效率低下,但不一定是存储效率低下。你可以建议你的同事不要使用它。
  • 你必须定期使用git gc技巧来清除你的存储库中任何你不想要的文件。
  • 它不如向左git-bigfiles有效。但它分别更适合你想做的事情和更多现成的东西。您可能在处理数十万个小文件或gb大小的文件时遇到麻烦,但是请继续阅读以找到解决方法。

添加二进制文件

在开始之前,请确保您已经提交了所有的更改,您的工作树是最新的,并且您的索引不包含任何未提交的更改。这可能是一个好主意,把你所有的本地分支推到你的远程(github等)以防任何灾难发生。

  1. 创建一个新的孤儿分支。git checkout --orphan binaryStuff就可以了。这将产生一个与任何其他分支完全断开连接的分支,并且在该分支中进行的第一次提交将没有父级,这将使其成为根提交。
  2. 使用git rm --cached * .gitignore清理索引。
  3. 深呼吸,使用rm -fr * .gitignore删除整个工作树。内部的.git目录将保持不变,因为*通配符与它不匹配。
  4. 复制到你的VeryBigBinary.exe,或你的VeryHeavyDirectory/。
  5. 加起来&&提交它。
  6. 现在它变得很棘手——如果你把它作为一个分支推到远程,那么所有开发人员在下次调用git fetch阻塞连接时都会下载它。您可以通过推送标记而不是分支来避免这种情况。如果您的同事有输入git fetch <remote> --tags的习惯,这仍然会影响他们的带宽和文件系统存储,但是请阅读下面的文章,找到解决方法。点击git tag 1.0.0bin
  7. 推送孤儿标签git push <remote> 1.0.0bin
  8. 为了避免意外推入二进制分支,可以删除它git branch -D binaryStuff。您的提交将不会被标记为垃圾收集,因为指向它的孤立标记1.0.0bin足以使它存活。

签出二进制文件

  1. 我(或我的同事)如何得到VeryBigBinary.exe检出到当前工作树?如果你当前工作的分支是master,你可以简单地git checkout 1.0.0bin -- VeryBigBinary.exe
  2. 如果您没有下载孤儿标签1.0.0bin,这将失败,在这种情况下,您必须事先下载git fetch <remote> 1.0.0bin
  3. 您可以将VeryBigBinary.exe添加到您的主文件.gitignore中,这样您的团队中就不会有人意外地用二进制文件污染项目的主历史记录。

完全删除二进制文件

如果你决定完全清除VeryBigBinary.exe从你的本地存储库,你的远程存储库和你的同事的存储库,你可以:

  1. 删除远程git push <remote> :refs/tags/1.0.0bin上的孤立标记
  2. 在本地删除孤儿标记(删除所有其他未引用的标记)git tag -l | xargs git tag -d && git fetch --tags。取自所以1841341,稍作修改。
  3. 使用git gc技巧在本地删除现在未引用的提交。# EYZ0。它还将删除所有其他未引用的提交。摘自所以1904860
  4. 如果可能,在远程上重复git gc技巧。如果你是自托管你的存储库,这是可能的,但对于一些git提供者,比如github或在一些公司环境中,这可能是不可能的。如果你使用的提供商不给你远程的ssh访问权限,那就让它去吧。您的提供者的基础设施可能会在自己的最佳时间清理未引用的提交。如果你在一个公司环境中,你可以建议你的IT运行一个cron作业,每周一次左右对你的遥控器进行垃圾收集。不管他们做还是不做,在带宽和存储方面都不会对你的团队产生任何影响,只要你建议你的同事总是使用git clone -b master --single-branch <url>而不是git clone
  5. 所有想要摆脱过时的孤立标签的同事只需要应用步骤2-3。
  6. 然后,您可以重复添加二进制文件的步骤1-8来创建一个新的孤立标记2.0.0bin。如果你担心你的同事输入git fetch <remote> --tags,你可以重新命名为1.0.0bin。这将确保下次他们获取所有标签时,旧的1.0.0bin将不被引用,并标记为后续的垃圾收集(使用步骤3)。当你试图覆盖远程上的标签时,你必须像这样使用-f: git push -f <remote> <tagname>

后记

  • OTABS不会触及您的主代码或任何其他源代码/开发分支。这些分支的提交哈希值、所有历史和较小的大小都不受影响。如果您已经用二进制文件膨胀了源代码历史,那么您必须将其作为一个单独的工作来清理。这个脚本可能有用。

  • 确认工作在Windows与git-bash。

  • 应用标准trics是一个好主意,可以使二进制文件的存储更有效。频繁运行git gc(没有任何附加参数)使git通过使用二进制增量优化文件的底层存储。然而,如果你的文件在每次提交时都不太可能保持相似,你可以完全关闭二进制增量。此外,由于压缩已经压缩或加密的文件(如.zip、.jpg或.crypt)没有意义,git允许您关闭底层存储的压缩。不幸的是,这种全有或全无的设置也会影响您的源代码。

  • 您可能需要编写OTABS的部分脚本,以便更快地使用。特别是,将步骤2-3从完全删除二进制文件脚本编写到update git钩子中,可以为git获取提供一个引人注目但可能危险的语义(“获取并删除所有过期的内容”)。

  • 您可能希望跳过完全删除二进制文件的第4步,以中央存储库膨胀为代价,在远程上保留所有二进制更改的完整历史。本地存储库将随着时间的推移保持精简。

  • 在Java世界中,可以将此解决方案与maven --offline结合起来,以创建完全存储在版本控制中的可重复的离线构建(使用maven比使用gradle更容易)。在Golang世界中,构建这个解决方案来管理你的GOPATH而不是go get是可行的。在python世界中,可以将其与virtualenv结合起来,以产生一个自包含的开发环境,而不必依赖于PyPi服务器从头开始进行每次构建。

  • 如果您的二进制文件经常更改,就像构建工件一样,编写一个解决方案,将5个最新版本的工件存储在孤立标签monday_bintuesday_bin,…friday_bin,以及每个版本的孤儿标签1.7.8bin 2.0.0bin等等。您可以每天旋转weekday_bin并删除旧的二进制文件。通过这种方式,您可以获得两个世界的最佳结果:您保留源代码的完整历史,但只保留二进制依赖项的相关历史。获得给定标签的二进制文件也非常容易,而不需要获得包含所有历史的完整源代码:git init && git remote add <name> <url> && git fetch <name> <tag>应该为您做这件事。

我正在寻找如何处理我的源代码(web应用程序)依赖的大型二进制文件的意见。你对此有什么经验/想法?

我个人在我的一些云主机上遇到过与Git同步失败,一旦我的web应用程序二进制数据达到高于3 GB标志。我当时考虑过BFT回购清洁,但感觉像是一个黑客。从那以后,我开始把文件放在Git的权限之外,而是利用专用工具(比如Amazon S3)来管理文件、版本控制和备份。

有人有在一个项目中使用多个Git存储库并管理它们的经验吗?

是的。雨果的主题主要是这样管理的。这有点滑稽,但它能完成任务。


我的建议是为工作选择合适的工具。如果它是为一个公司,你在GitHub上管理你的代码线,付钱并使用Git-LFS。否则,你可以探索更有创意的选项,比如去中心化、加密的使用区块链存储文件

需要考虑的其他选项包括Minio生成

git clone --filter从Git 2.19 +浅克隆

这个新选项可能最终会成为二进制文件问题的最终解决方案,如果Git和GitHub开发并使其足够友好(例如他们可以争辩的子模块仍未实现)。

它实际上只允许为服务器获取您想要的文件和目录,并与远程协议扩展一起引入。

有了这个,我们可以先做一个浅克隆,然后自动使用构建系统为每种类型的构建获取哪些blobs。

甚至已经有了--filter=blob:limit<size>,它允许限制获取的最大blob大小。

我已经在如何克隆Git存储库的子目录?提供了一个关于该功能外观的最小详细示例

Git LFS就是答案

# Init LFS
git lfs install
git lfs track "large_file_pattern"


# Then follow regular git workflow
git add large_file
git commit -m "Init a very large file"
git push origin HEAD

在后台,git lfs会创建一个对你的大文件的引用,而不是直接存储在git repo中

更多信息:https://git-lfs.github.com/