git-bundle 文件将 git objects(packfile)与仓库引用 refs 打包在一起,通过 “objects + refs” 即可恢复完整仓库,适合用于备份或离线传输。
本文介绍了 git bundle 的文件格式、常用命令与实践场景,包括全量/增量备份,以及跨机器迁移的示例。
格式#
git bundle v2 格式:
bundle = signature *prerequisite *reference LF packsignature = "# v2 git bundle " LF
prerequisite = "-" obj-id SP comment LFcomment = *CHARreference = obj-id SP refname LF
pack = ... ; packfilegit bundle v3 格式:
bundle = signature *capability *prerequisite *reference LF packsignature = "# v3 git bundle " LF
capability = "@" key ["=" value] LFprerequisite = "-" obj-id SP comment LFcomment = *CHARreference = obj-id SP refname LFkey = 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,所以来看下这个实际是什么意思。
先打包 master 分支上最近三次提交$ git bundle create recent.bundle master~3..masterEnumerating 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.bundleThe bundle contains this ref:0a831168aa94dffaa92f5d73f3e873ef5fd89603 refs/heads/masterThe bundle requires this ref:d5d9b1c95f0012cb7da18deaff6a806f89e48867recen.bundle is okay上面的结果很明显:
The bundle requires this ref:d5d9b1c95f0012cb7da18deaff6a806f89e48867再打开这个 bundle 包文件看下:
# v2 git bundle-d5d9b1c95f0012cb7da18deaff6a806f89e48867 demo.tar.gz0a831168aa94dffaa92f5d73f3e873ef5fd89603 refs/heads/master第二行的内容 -d5d9b1c95f0012cb7da18deaff6a806f89e48867 demo.tar.gz 即是 prerequisite
并且符合格式 prerequisite = "-" obj-id SP comment LF
我们看一个实际的 git pack 文件:
PACK^@^@^@^B^@^@^@^C<91>^Kx^A^A±^@Nÿtree 212ccb4755ab7c489bee69200388139d7f081e7cauthor Li Linchao <lilinchao@oschina.cn> 1646124003 +0800committer 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
v2 git bundlee0caba68a7281d4ff86693745a1617ffc72c3e7d refs/heads/master
PACK^@^@^@^B^@^@^@^C<91>^Kx^A^A±^@Nÿtree 212ccb4755ab7c489bee69200388139d7f081e7cauthor Li Linchao <lilinchao@oschina.cn> 1646124003 +0800committer 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
v3 git bundle@object-format=sha1e0caba68a7281d4ff86693745a1617ffc72c3e7d refs/heads/master
PACK^@^@^@^B^@^@^@^C<91>^Kx^A^A±^@Nÿtree 212ccb4755ab7c489bee69200388139d7f081e7cauthor Li Linchao <lilinchao@oschina.cn> 1646124003 +0800committer 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 中:
$ git bundle create file.bundle master# 做个标记$ git tag -f lastR2bundle master将 file.bundle 转移到机器 B 上,克隆仓库 R2:
$ git clone -b master /path/to/file.bundle R2Cloning into 'R2'...Receiving objects: 100% (38/38), 7.97 KiB | 7.97 MiB/s, done.Resolving deltas: 100% (6/6), done.回到仓库 R1,仓库 R1 有了新的提交。然后继续打包:
$ git bundle create file.bundle lastR2bundle..master# 做个标记,下次打包时使用$ git tag -f lastR2bundle master将 file.bundle 转移到机器 B 上,再到 R2 仓库中进行更新:
$ git fetchReceiving 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-remoteFrom /path/to/file.bundle6a33e12f3d7116863a19bf48471c74c597421f8a HEAD45eab37d3cb8b5be02a59d3690cb63d3f692f6b9 refs/remotes/origin/master45eab37d3cb8b5be02a59d3690cb63d3f692f6b9 refs/remotes/origin/HEAD6a33e12f3d7116863a19bf48471c74c597421f8a refs/heads/master以上操作,看起来都跟读取 Git 远程仓库的一样。
但是不支持写仓库操作,即 git push 操作。
以上操作就完成了仓库的全量备份和增量备份。
还可以用其它形式进行打包:
$ 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 选项,实现根据时间戳来进行备份,大致过程如下:
``` 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 仓库进行打包。