git-bundle 文件是一种将 git objects 数据(git packfile)与仓库引用 refs 结合在一起的一种数据包格式,通过 objects + refs 就能恢复出一个完整的 git 仓库,因此可以用 git-bundle 文件来备份 git 仓库。
本文详细介绍了 git bundle 的文件格式、命令用法及实际应用场景,包括如何创建全量与增量备份,以及在不同机器间传输仓库数据的操作示例。
格式
git bundle v2 格式:
1 2 3 4 5 6 7 8
| 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 格式:
1 2 3 4 5 6 7 8 9 10 11
| 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 包含四个部分:
签名。标识 git bundle 版本
必要依赖。不包含在当前 bundle包 中,但是被 bundle包 中的数据引用到的数据。
引用。当前 bundle包 中包含的引用。
pack文件。git packfile
git bundle v3 仅比 v2 多了一个 capability 部分。
前面提到过 prerequisite 的概念,其格式为 prerequisite = "-" obj-id SP comment LF,所以来看下这个实际是什么意思。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 先打包 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 recent.bundle The bundle contains this ref: 0a831168aa94dffaa92f5d73f3e873ef5fd89603 refs/heads/master The bundle requires this ref: d5d9b1c95f0012cb7da18deaff6a806f89e48867 recen.bundle is okay
|
上面的结果很明显:
1 2
| The bundle requires this ref: d5d9b1c95f0012cb7da18deaff6a806f89e48867
|
再打开这个 bundle 包文件看下:
1 2 3
| # v2 git bundle -d5d9b1c95f0012cb7da18deaff6a806f89e48867 demo.tar.gz 0a831168aa94dffaa92f5d73f3e873ef5fd89603 refs/heads/master
|
第二行的内容 -d5d9b1c95f0012cb7da18deaff6a806f89e48867 demo.tar.gz 即是 prerequisite
并且符合格式 prerequisite = "-" obj-id SP comment LF
我们看一个实际的 git pack 文件:
1 2 3 4 5 6 7 8
| 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
1 2 3 4 5 6 7 8 9 10
| 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
1 2 3 4 5 6 7 8 9 10 11
| 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 中:
1 2 3
| $ git bundle create file.bundle master
$ git tag -f lastR2bundle master
|
将 file.bundle 转移到机器 B 上,克隆仓库 R2:
1 2 3 4
| $ 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 有了新的提交。然后继续打包:
1 2 3
| $ git bundle create file.bundle lastR2bundle..master
$ git tag -f lastR2bundle master
|
将 file.bundle 转移到机器 B 上,再到 R2 仓库中进行更新:
1 2 3 4 5 6 7 8 9 10 11 12 13
| $ 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 $ 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 操作。
以上操作就完成了仓库的全量备份和增量备份。
还可以用其它形式进行打包:
1 2 3
| $ 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 选项,实现根据时间戳来进行备份,大致过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ``` Bash
$ git cat-file commit HEAD | sed -n " s/^committer .*> \([0-9]*\) .*/\1/p " 1660809497
$ git bundle create latest.bundle --max-age = 1660807920 --all --objects
$ git bundle list-heads latest.bundle > latest.list
$ git ls-remote latest.bundle > latest.lsremote
|
应用
目前 Gitlab 的仓库导出、导入功能以及阿里云效 CodeUp 的仓库备份功能主要就是利用 Git-bundle 特性对 Git 仓库进行打包。
参考链接
https://git-scm.com/docs/git-bundle
https://git-scm.com/docs/bundle-format