Skip to content

git bundle 格式及应用

· 5 min
TL;DR

git-bundle 文件将 git objects(packfile)与仓库引用 refs 打包在一起,通过 “objects + refs” 即可恢复完整仓库,适合用于备份或离线传输。

本文介绍了 git bundle 的文件格式、常用命令与实践场景,包括全量/增量备份,以及跨机器迁移的示例。

格式#

git bundle v2 格式:

Terminal window
bundle = signature *prerequisite *reference LF pack
signature = "# v2 git bundle " LF
prerequisite = "-" obj-id SP comment LF
comment = *CHAR
reference = obj-id SP refname LF
pack = ... ; packfile

git bundle v3 格式:

Terminal window
bundle = signature *capability *prerequisite *reference LF pack
signature = "# v3 git bundle " LF
capability = "@" key ["=" value] LF
prerequisite = "-" obj-id SP comment LF
comment = *CHAR
reference = obj-id SP refname LF
key = 1*(ALPHA / DIGIT / "-")
value = *(%01-09 / %0b-FF)
pack = ... ; packfile

注:以上均采用 ABNF 标记法

可以看出:

git bundle v2 包含四个部分:

  1. 签名。标识 git bundle 版本
  2. 必要依赖。不包含在当前 bundle 包 中,但是被 bundle 包 中的数据引用到的数据。
  3. 引用。当前 bundle 包 中包含的引用。
  4. pack 文件 git packfile

git bundle v3 仅比 v2 多了一个 capability 部分。

前面提到过 prerequisite 的概念,其格式为 prerequisite = "-" obj-id SP comment LF,所以来看下这个实际是什么意思。

Terminal window
先打包 master 分支上最近三次提交
$ git bundle create recent.bundle master~3..master
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Compressing objects: 100% (8/8), done.
Total 9 (delta 3), reused 0 (delta 0), pack-reused 0
# 再用 git bundle verify 验证一下这个 bundle 包有没有外部依赖
$ git bundle verify recent.bundle
The bundle contains this ref:
0a831168aa94dffaa92f5d73f3e873ef5fd89603 refs/heads/master
The bundle requires this ref:
d5d9b1c95f0012cb7da18deaff6a806f89e48867
recen.bundle is okay

上面的结果很明显:

Terminal window
The bundle requires this ref:
d5d9b1c95f0012cb7da18deaff6a806f89e48867

再打开这个 bundle 包文件看下:

# v2 git bundle
-d5d9b1c95f0012cb7da18deaff6a806f89e48867 demo.tar.gz
0a831168aa94dffaa92f5d73f3e873ef5fd89603 refs/heads/master

第二行的内容 -d5d9b1c95f0012cb7da18deaff6a806f89e48867 demo.tar.gz 即是 prerequisite

并且符合格式 prerequisite = "-" obj-id SP comment LF

我们看一个实际的 git pack 文件:

Terminal window
PACK^@^@^@^B^@^@^@^C<91>^Kx^A^A±^@Nÿtree 212ccb4755ab7c489bee69200388139d7f081e7c
author Li Linchao <lilinchao@oschina.cn> 1646124003 +0800
committer Li Linchao <lilinchao@oschina.cn> 1646124003 +0800
add aa.txt
©&5A5x^A^A^E^@úÿaaaa
^E]^A<8f>¢^Bx^A^A"^@Ýÿ100644 aa.txt^@]0<8e>^]^F^K^L8}E,ôt^?<89>ì¹<93>XQ£÷^KlK<9a><93>pÃ^@ò&bL37©<85>(ÐâWa^X
~

而此时的 git bundle v2 文件如下:

git bundle create master.bundle master

Terminal window
v2 git bundle
e0caba68a7281d4ff86693745a1617ffc72c3e7d refs/heads/master
PACK^@^@^@^B^@^@^@^C<91>^Kx^A^A±^@Nÿtree 212ccb4755ab7c489bee69200388139d7f081e7c
author Li Linchao <lilinchao@oschina.cn> 1646124003 +0800
committer Li Linchao <lilinchao@oschina.cn> 1646124003 +0800
add aa.txt
©&5A¢^Bx^A^A"^@Ýÿ100644 aa.txt^@]0<8e>^]^F^K^L8}E,ôt^?<89>ì¹<93>XQ£÷^Kl5x^A^A^E^@úÿaaaa
^E]^A<8f>,^B<9d>ï<89>DIr^\<87>*<9b>Æû^X¹É}PÖ

git bundle v3 文件如下:

git bundle create —version = 3 master-v3.bundle master

Terminal window
v3 git bundle
@object-format=sha1
e0caba68a7281d4ff86693745a1617ffc72c3e7d refs/heads/master
PACK^@^@^@^B^@^@^@^C<91>^Kx^A^A±^@Nÿtree 212ccb4755ab7c489bee69200388139d7f081e7c
author Li Linchao <lilinchao@oschina.cn> 1646124003 +0800
committer Li Linchao <lilinchao@oschina.cn> 1646124003 +0800
add aa.txt
©&5A¢^Bx^A^A"^@Ýÿ100644 aa.txt^@]0<8e>^]^F^K^L8}E,ôt^?<89>ì¹<93>XQ£÷^Kl5x^A^A^E^@úÿaaaa
^E]^A<8f>,^B<9d>ï<89>DIr^\<87>*<9b>Æû^X¹É}PÖ

操作实践#

在机器 A 中的仓库 R1 中:

Terminal window
$ git bundle create file.bundle master
# 做个标记
$ git tag -f lastR2bundle master

file.bundle 转移到机器 B 上,克隆仓库 R2

Terminal window
$ git clone -b master /path/to/file.bundle R2
Cloning into 'R2'...
Receiving objects: 100% (38/38), 7.97 KiB | 7.97 MiB/s, done.
Resolving deltas: 100% (6/6), done.

回到仓库 R1,仓库 R1 有了新的提交。然后继续打包:

Terminal window
$ git bundle create file.bundle lastR2bundle..master
# 做个标记,下次打包时使用
$ git tag -f lastR2bundle master

file.bundle 转移到机器 B 上,再到 R2 仓库中进行更新:

Terminal window
$ git fetch
Receiving objects: 100% (41/41), 8.28 KiB | 8.28 MiB/s, done.
Resolving deltas: 100% (7/7), done.
From /home/git/test-git/example/repo.bundle
d4f54d9..6a33e12 master -> origin/master
# 还是在 cloned-repo 中
$ git ls-remote
From /path/to/file.bundle
6a33e12f3d7116863a19bf48471c74c597421f8a HEAD
45eab37d3cb8b5be02a59d3690cb63d3f692f6b9 refs/remotes/origin/master
45eab37d3cb8b5be02a59d3690cb63d3f692f6b9 refs/remotes/origin/HEAD
6a33e12f3d7116863a19bf48471c74c597421f8a refs/heads/master

以上操作,看起来都跟读取 Git 远程仓库的一样。 但是不支持写仓库操作,即 git push 操作。

以上操作就完成了仓库的全量备份和增量备份。

还可以用其它形式进行打包:

Terminal window
$ git bundle create mybundle v1.0.0..master
$ git bundle create mybundle --since=10.days master
$ git bundle create mybundle -10 master

只要是 git rev-list 能接受的参数,就可以放在 git bundle create mybundle 后面。 比如可以使用 --max-age 选项,实现根据时间戳来进行备份,大致过程如下:

Terminal window
``` Bash
# 当前 HEAD 的时间戳,作为下次备份的起点时间
$ git cat-file commit HEAD | sed -n " s/^committer .*> \([0-9]*\) .*/\1/p "
1660809497
# --branches 获取 refs/heads/下所有相关 commits
# --tags 获取 refs/tags/下所有相关 commits
# --remotes 获取 refs/remotes/下所有相关 commits
# --all 获取 refs/下所有相关 commits
# --objects 表示 commit 所关联的所有对象
$ git bundle create latest.bundle --max-age = 1660807920 --all --objects
# 获取 bundle 包中的引用信息,可以通过其它选项筛选出部分引用。
$ git bundle list-heads latest.bundle > latest.list
# 通过 git-ls-remote 获取 bundle 包中的所有引用信息。
$ git ls-remote latest.bundle > latest.lsremote

应用#

目前 Gitlab 的 仓库导出、导入功能 以及阿里云效 CodeUp 的 仓库备份 功能主要就是利用 Git-bundle 特性对 Git 仓库进行打包。

参考链接#

  1. https://git-scm.com/docs/git-bundle
  2. https://git-scm.com/docs/bundle-format