git 大型项目管理

Entropy Tree Lv4

git 作为一个版本控制工具,管理的通常是一些占用磁盘空间不大的源代码文件等。但是实际应用中,git 也会被用于管理大型项目。

以下介绍了三种可能应用于大型项目管理的方案。

Git LFS

Git LFS 主要用于管理大型文件和二进制文件,例如游戏开发中的美术资源、视频文件、大型数据集等。它通过将大文件的指针存储在 Git 仓库中,而将文件存储在远程的 LFS 存储中,从而避免 Git 仓库变得过于庞大。

使用

安装 git-lfs

1
sudo pacman -S git-lfs

其他发行版使用对应的包管理器安装

在一个本地的 Git 仓库中

初始化 git-lfs 的 Git hooks

1
git lfs install

查看文件状态

1
git lfs status

创建一个大文件 2GB

1
dd if=/dev/random of=./large_file bs=1024M count=2

添加文件到 lfs

1
git lfs track "large_file"

支持通配符 (需要使用引号包围名称及通配符,否则会被 shell 解析为匹配的路径)

1
2
3
git lfs track "*.png" # 当前目录下所有 png 文件
git lfs track "**/*.png" # 所有目录下 png 文件
git lfs track *.zip # 当前目录下所有 zip 文件

也可以指定添加文件夹

1
2
git lfs track "large_dir/**" # large_dir 目录下所有文件和子目录
git lfs track "large_dir/*" # large_dir 目录下所有文件,不含子目录

添加完成后会产生一个 .gitattributes 文件,记录了被 git-lfs 跟踪的大型文件

将大型文件添加到缓存区

1
git add large_file

查看 lfs 跟踪的文件

1
git lfs ls-files

移除文件

取消对该文件或文件夹的跟踪

1
2
3
git lfs untrack "large_file"
git lfs untrack "large_dir/**"
git lfs untrack "large_dir/*"

从缓存区移除文件

1
git rm -r --cached large_file

清理不再跟踪或“悬挂”的 LFS 对象

1
git lfs prune

推送本地仓库到远程仓库,注意提交包含 LFS 跟踪信息的 gitattributes 文件

1
git push

当克隆一个包含 Git LFS 跟踪的大型仓库时

git clone

  • 对于 Git LFS 跟踪的文件,git clone 会下载这些文件的指针(即 .git/lfs/objects 中的引用),但不会立即下载文件的实际内容。
  • git clone 可能会很快完成,因为大文件的指针相对较小。

git lfs pull

  • 这个命令专门用于下载 LFS 跟踪文件的实际内容。
  • git lfs pull 来确保这些文件的完整内容被下载到本地。

通常 git clone 之后再执行 git lfs pull 以得到最新的完整仓库。

将已有的项目迁移到 LFS

将所有的 png 文件导入 LFS 中

1
git lfs migrate import --everything --include="*.png"

将 main 分支中所有大于 100Kb 的文件导入 LFS 中

1
git lfs migrate import --include-ref=main --above=100Kb

推送

1
git push --force --all

远程服务端执行的 gc 命令,清理仓库,通常不需要手动执行

1
git --git-dir=git-repo.git gc --prune=now

演示

在本地搭建一个 git 远程仓库

1
2
3
mkdir large-repo
cd large-repo
git init --bare large-repo.git

克隆到本地仓库

1
git clone /home/entropy/Public/large-repo/large-repo.git

开启 Git LFS

1
git lfs install

开启后会在用户目录下的 .gitconfig 中增加一个 filter 配置

1
cat ~/.gitconfig
1
2
3
4
5
[filter "lfs"]
smudge = git-lfs smudge -- %f
process = git-lfs filter-process
required = true
clean = git-lfs clean -- %f

同时在 .git/hooks 中增加了几个 hook 脚本,增加了 .git/lfs 目录

增加对一个大文件的跟踪

1
2
3
4
5
6
git lfs track '*.bin'
dd if=/dev/random of=a.bin bs=1M count=10
git add .
git lfs ls-files
git commit -am "lfs 1"
git push

将文件修改为 1K 大小并提交推送

1
2
3
4
dd if=/dev/random of=a.bin bs=1024  count=1
git add .
git commit -am "lfs 2"
git push

重新克隆仓库,查看仓库大小

1
2
3
4
5
cd ..
rm -rf large-repo
git clone /home/entropy/Public/large-repo/large-repo.git
cd large-repo
du -sh .

可以发现仓库并没有下载原来 10MB 大小的文件

切换到上一个版本,这个版本中包含 10MB 大小的文件

1
git checkout HEAD~1

LFS 会在 checkout 之后从远程仓库拉取文件到本地,再拷贝到工作目录中,因此整个目录大小变成了 20MB

如果只想保留最新版本,可以执行 git lfs prune 删掉历史数据

1
2
git checkout master
git lfs prune

使用不同的方式配置跟踪文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
git lfs track '**/*.png'
dd if=/dev/urandom of=a.png bs=1024 count=1
git add .
git lfs ls-files

git lfs track 'res/**'
mkdir res && cd res
dd if=/dev/urandom of=a.jpg bs=1024 count=1
mkdir subdir && cd subdir
dd if=/dev/urandom of=a.xz bs=1024 count=1
cd ../..
git add .
git lfs ls-files

git lfs track 'assets/*'
mkdir assets && cd assets
dd if=/dev/urandom of=a.xz bs=1024 count=1
mkdir subdir && cd subdir
dd if=/dev/urandom of=a.jpg bs=1024 count=1
cd ../..
git add .
git lfs ls-files

dd if=/dev/urandom of=a.zip bs=1024 count=1
dd if=/dev/urandom of=b.zip bs=1024 count=1
git lfs track *.zip # 被 shell 解析为匹配的具体路径
dd if=/dev/urandom of=c.zip bs=1024 count=1
git add .
git lfs ls-files

迁移已有项目到 LFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
dd if=/dev/urandom of=c.zip bs=1M count=10
git add .
git commit -am "c.zip"
git push

dd if=/dev/urandom of=c.zip bs=1024 count=1
git add .
git commit -am "c.zip 2"
git push

cd ..
rm -rf large-repo
git clone large-repo.git
cd large-repo

# 执行 migrate 命令将所有 zip 文件迁移到 LFS 中
git lfs migrate import --everything --include="*.zip"
git lfs ls-files
git config lfs.allowincompletepush true
git push --force --all

cd ../large-repo.git/lfs
du -sh .
cd ../..
rm -rf large-repo

# 重新克隆仓库,由于没有在远程仓库执行垃圾清理,克隆下来的仓库还是很大
git clone large-repo.git
cd large-repo
du -sh .

cd ..
rm -rf large-repo
git --git-dir=large-repo.git gc --prune=now

# 远程仓库清理垃圾后,克隆下来的仓库体积变小
git clone large-repo.git
cd large-repo
du -sh .

稀疏克隆

稀疏克隆 (sparse-clone) 允许用户克隆时只下载需要的部分内容,可以是某个文件夹或某个文件,适用于只关注大型仓库中的部分内容的情况。

使用

初始化稀疏检出,在克隆仓库时使用 --sparse 选项,表示稀疏克隆,不会下载所有内容,但是会下载位于项目根目录下的文件。

1
git clone --filter=blob:none --depth 1 --sparse <git-repo-url>

查看 git 仓库中记录的文件和目录,用于后续设置稀疏检出路径

1
git ls-files

设置稀疏检出路径,设置 src 和 docs 目录,会自动下载这两个目录的内容

1
git sparse-checkout set src docs

添加稀疏检出路径,在已有目录的基础上添加额外的目录,不会覆盖前面设置的稀疏检出路径,使用 set 则会重新设置稀疏检出路径。

1
git sparse-checkout add tests

稀疏检出路径会记录在 .git/info/sparse-checkout 文件中。

恢复完整检出,将会下载所有内容

1
git sparse-checkout disable

Git Submodules

子模块允许在一个 Git 仓库中嵌套另一个 Git 仓库,适用于项目需要依赖外部库或多个项目需要共享代码的情况。

使用

添加子模块

1
git submodule add <git-repo-url> module

添加完成后会在项目根目录下生成一个 .gitmodules 文件,记录了子模块的本地和远程地址信息

查看子模块信息

1
git submodule

更新子模块

1
2
git submodule update
git submodule update --remote

修复头指针分离问题

1
2
git branch -f main HEAD # 将 main 分支强制指向当前头指针的位置
git checkout main # 重新检出 main 分支

克隆包含子模块的项目

在子模块的目录内执行以下命令递归初始化并下载子模块的内容

1
git submodule update --init --recursive

或者分为初始化子模块和更新子模块两步

1
2
git submodule init		# 初始化子模块
git submodule update # 更新子模块

也可以在克隆时一步到位地下载所有内容

1
git clone --recursive <git-repo-url>

删除子模块的步骤比较繁琐

1.删除子模块文件夹

1
2
git rm -r --cached module
rm -rf module

2.删除 .gitmodules 文件中相关子模块的信息 (如果存在),类似于

1
2
3
[submodule "module"]
path = module
url = https://github.com/s-chance/module.git

3.删除 .git/config 文件中相关子模块的信息 (如果存在)

4.删除 .git 文件夹中的相关子模块文件 (如果存在)

1
rm -rf .git/modules/module

参考资料

git lfs 使用详解 | sinlov’s blog

高效Git-如何快速下载代码 | Git技术盘点

Git: submodule 子模块简明教程

Git使用-多仓库管理之submodule | Git技术盘点

  • 标题: git 大型项目管理
  • 作者: Entropy Tree
  • 创建于 : 2024-06-28 00:42:25
  • 更新于 : 2024-06-28 01:00:02
  • 链接: https://www.entropy-tree.top/2024/06/28/git-work-with-large-repo/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论