如何使git LFS比git更高效地跟踪和存储二进制数据?
总结:
它并不是更高效地跟踪大型二进制文件。实际上,它只是在一个单独的服务器上远程执行此操作,以释放一些本地存储空间,并使初始的git clone过程下载更少的数据。以下是概要:
使用Git LFS时,文件数据不存储在存储库中,因此当您克隆存储库时,不需要下载存储在LFS中的文件的完整历史记录。只有每个LFS文件的“当前”版本从LFS服务器下载。从技术上讲,LFS文件在“checkout”期间下载,而不是在“clone”期间。
[链接1:@John Zwinck]
[链接2:@Schwern]
它可以大幅减少对存储库的初始git克隆的大小。
它可以大幅减少本地存储库的大小。
@Mark Bramnik:
这个想法是,二进制文件是从“远程”存储库中懒加载下载的,即在检出过程中而不是在克隆或获取过程中。
细节
常规Git存储库
想象一下,你有一个庞大的单一存储库,其中包含约100 GB的文本文件(代码,包括所有git blob和更改),以及100 GB的二进制数据。请注意,这是一个我实际上处理了几年的现实、代表性的例子。如果100 GB的二进制数据已经提交过一次,它将占用100 GB,而您的总git存储库将是100 GB的代码blob + 100 GB的二进制数据提交一次 = 200 GB。
如果每个文件的100 GB二进制数据已经更改了10次,那么它占用了大约100 GB x (1 + 10) = 1.1 TB的空间,再加上100 GB的代码,总共是
1.2 TB的仓库大小。现在,克隆这个仓库:
git clone git@github.com:MyUsername/MyRepo.github.io.git
如果你想执行一个 `git checkout`,不过这个操作很快!所有的二进制数据都存储在本地仓库中,因为你拥有二进制数据的11个快照(初始文件 + 10次更改)!
git checkout some_past_commit
git checkout another_past_commit
与之相比,看看
git lfs
:
一个使用Git LFS存储所有二进制文件的Git仓库
你拥有与上述相同的仓库,只是100GB的代码在git仓库中。Git LFS使得git只存储指向LFS服务器的指针文本文件,因此git仓库中只有100GB的代码+少量用于指针文件的存储空间。
另一方面,Git LFS服务器包含了全部1.1TB的二进制文件。所以,你会得到这样的效果:
git clone git@github.com:MyUsername/my_repo.github.io.git
cd my_repo
git lfs pull
git checkout some_past_commit
git checkout another_past_commit
实际上,普通的Git比Git LFS更有效地存储二进制文件
请参考这里Alexander Gogl在回答中的表格。添加一个28.8 MB的Vectorworks (.vwx)文件作为git blob和Git LFS blob都占用26.5 MB的空间。但是,如果将其作为git blob存储,然后运行git gc
执行"垃圾回收"和blob压缩,普通的git会将其缩小到1.8 MB。而Git LFS对其不做任何处理。请在这个表格中查看其他示例。
如果你看一下这个表格,你会发现git整体上比Git LFS存储得更高效:
类型 | 变化 | 文件 | 作为git blob | git gc后 | 作为git-lfs blob
---|---|---|---|---|---
Vectorworks (.vwx) | 添加几何图形 | 28.8 MB | +26.5 MB | +1.8 MB | +26.5 MB
GeoPackage (.gpkg) | 添加几何图形 | 16.9 MB | +3.7 MB | +3.5 MB | +16.9 MB
Affinity Photo (.afphoto) | 切换图层 | 85.8 MB | +85.6 MB | +0.8 MB | +85.6 MB
FormZ (.fmz) | 添加几何图形 | 66.3 MB | +66.3 MB | +66.3 MB | +66.3 MB
Photoshop (.psd) | 切换图层 | 25.8 MB | +15.8 MB | +15.4 MB | +25.8 MB
Movie (mp4) | 裁剪 | 13.1 MB | +13.2 MB | +0 MB | +13.1 MB
删除文件 | | -13.1 MB | +0 MB | +0 MB | +0 MB
Git LFS的优缺点
Git LFS的优点:
- 克隆仓库的初始过程更快,因为它只克隆轻量级指向二进制数据的指针。
- 本地仓库的大小更小。
Git LFS的缺点:
git checkout
现在必须下载二进制数据,可能需要27GB并花费3个小时以上来完成git checkout
。如果你提前停止,就会全部丢失。
- 每次运行
git checkout
并且Git LFS需要下载更多数据时,这可能会连续发生多次。
- 执行
git checkout
需要一个活跃的高速互联网连接。(在正常的git中,git checkout
可以在离线状态下进行,无需互联网连接)。
- 与常规Git相比,二进制文件存储实际上效率较低(请参见上表)。
注意:您可以定期清理未用于当前检出的Git LFS数据,方法是使用
git lfs prune
命令。请参阅我在这里的回答:
如何缩小您的.git文件夹在您的git存储库中。
普通的git
什么时候从互联网下载文件?详细了解git fetch
与git pull
这可能不为人所知,所以我认为我应该添加这一部分来说明普通的git工作原理。当我使用“下载”一词时,我指的是从互联网上下载。
正常的git只有在执行git clone、git fetch或git pull时才会从互联网上下载文件。如果你当前在主分支main上,那么git pull实际上是执行了git fetch origin main(一个在线命令,用于下载)后跟着一个git merge origin/main(一个离线命令,不进行下载)。克隆操作只是为了最初从互联网上下载存储库,所以让我们先来详细讨论一下git fetch。
但首先,让我们谈谈分支。对于每个分支,实际上你有3个分支。例如,对于你的主分支main,你有:
你本地存储的非隐藏的`main`分支,
你本地存储的远程跟踪的隐藏分支,名为`origin/main`,当你运行`git branch -r`时会显示,
你在名为`origin`的远程服务器上的远程分支,名为`main`。
要查看你的远程仓库及其URL,请运行`git remote -v`。
要查看真正的远程分支,你必须打开一个网页浏览器,并在线访问GitHub、Gitlab或Bitbucket等平台。例如,执行`git fetch && git checkout origin/main`只是显示了它的本地存储的远程跟踪副本。
git fetch
下载所有远程分支到它们隐藏的本地存储的 origin/branch_name
分支对应项,包括将你的远程 main
分支更改下载到你本地存储的、远程跟踪的隐藏分支 origin/main
。当下载远程更改时使用 git fetch
。如果你接着运行 git checkout main
,然后是 git merge origin/main
,在这两个命令中都不会下载新数据。相反,在执行 git merge origin/main
时,已经下载的数据会被合并到你本地存储的非隐藏 main
分支中。在常规的 git 中,git checkout
是一个离线命令,只是将你本地存储的已下载 git 数据库 blob 文件夹中的所有文件更新到你的本地文件系统中。
所以,让我们回顾一下并举一些更多的例子:
git fetch
git fetch origin main
git fetch origin main:main
git checkout main
git merge origin/main
git pull main
与Git LFS相比,可以看出以下对比:使用git lfs时,git checkout现在变成了一个在线命令,从远程在线服务器下载存储在git lfs中的任何在线二进制文件,而不是从本地存储在main或origin/main中复制它们。这就是为什么在一个庞大的仓库中,几秒钟的git checkout现在变成了几个小时的git checkout。这也是我讨厌并且不推荐Git LFS的原因。我需要我的git checkout保持离线命令,只需几秒钟,而不是变成需要几个小时的在线命令,这样我就可以在8小时的工作日内完成8小时的工作,而不需要12到16个小时的工作日,其中一半时间被浪费掉。
附录:还要注意,Git LFS只有在本地缓存的数据不存在时才会从互联网上下载。因此,在一个全新的仓库中,当你在主分支上执行git checkout A时,它会去互联网上下载分支A的LFS数据,并将其缓存在.git/lfs目录下。然后,执行git checkout B时,它会再次去互联网上下载B分支的LFS数据。再次执行git checkout A时,它会直接从.git/lfs目录中检索已经缓存的数据,而不需要再次访问互联网,因为数据已经存在于本地。
关于.git/lfs目录的更多信息,请参考我在这里的回答:
如何缩小你的git仓库中的.git文件夹。
为了缓解`git checkout`变成一个"在线"命令的行为,您可以设置一个定时任务(cronjob)来定期运行`git lfs fetch --all`,比如每晚一次,如果您的硬盘有足够的空间,这样它就会在每晚预先获取一次 Git LFS 数据到您本地的`.git/lfs`目录中。详见我在这里的答案:
《git lfs fetch》、《git lfs fetch --all》和《git lfs pull》之间有什么区别?。但是,如果您的硬盘足够大,那就更好了:首先根本不要使用 Git LFS。
其他参考:
- 关于我最初了解到三个Git分支的地方,包括本地存储的远程跟踪隐藏的
origin/*
分支,请参见此处的答案:如何在本地和远程删除Git分支?,以及我在其下方的几条评论,从这里开始。
另请参阅
我的问题:
在写这个问题后进行了额外学习的更新:不要使用git lfs
。我现在建议不要使用git lfs
- 所有的“参见”链接在我的问题底部。
我的问答:git lfs fetch
、git lfs fetch --all
和git lfs pull
之间有什么区别?
git lfs
,这是一个大约200GB的单一仓库,其中有100GB存储在git lfs
中。每一周,甚至每一天,只是执行git fetch
和git checkout main
,或者git checkout my_branch_from_yesterday
等操作,光是进行checkout
就需要长达3个小时,因为git lfs
会在你执行git checkout
时添加钩子来拉取git lfs
数据。这是因为AI感知团队中的某个人会将大量相机数据或其他内容添加到git lfs
中,而我的checkout
操作会下载这些数据。 - Gabriel Staplesgit checkout
操作,而不是只被分配了1TB的SSD,其中包含一个200GB的仓库和700GB的构建数据,每天需要花费3个小时来切换分支查看某些内容(通过通常无害的git checkout
命令)。 - Gabriel Staples