章节 ▾ 第二版

7.12 Git 工具 - 打包

打包

尽管我们已经介绍了通过网络传输 Git 数据的常用方式(HTTP、SSH 等),但实际上还有一种不常用但却非常实用的方法。

Git 能够将其数据“打包”成单个文件。这在各种场景下都很有用。也许你的网络中断了,你想把改动发送给同事;也许你在某个偏远地方工作,出于安全原因无法访问本地网络;也许你的无线/以太网卡坏了;又或许你暂时无法访问共享服务器,想通过电子邮件发送更新,但又不想通过 format-patch 传输 40 个提交。

这时 git bundle 命令就派上用场了。bundle 命令会将原本通过 git push 命令在网络上传输的所有内容打包成一个二进制文件。你可以通过电子邮件将其发送给他人,或者存入闪存驱动器,然后将其解包到另一个仓库中。

让我们看一个简单的例子。假设你有一个包含两个提交的仓库:

$ git log
commit 9a466c572fe88b195efd356c3f2bbeccdb504102
Author: Scott Chacon <schacon@gmail.com>
Date:   Wed Mar 10 07:34:10 2010 -0800

    Second commit

commit b1ec3248f39900d2a406049d762aa68e9641be25
Author: Scott Chacon <schacon@gmail.com>
Date:   Wed Mar 10 07:34:01 2010 -0800

    First commit

如果你想把这个仓库发给别人,但又无法访问可供推送的仓库,或者干脆不想设置一个,你可以使用 git bundle create 对其进行打包。

$ git bundle create repo.bundle HEAD master
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 441 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)

现在你就有了一个名为 repo.bundle 的文件,其中包含了重建该仓库 master 分支所需的所有数据。使用 bundle 命令时,你需要列出你想要包含的每一个引用或特定的提交范围。如果你打算将其克隆到其他地方,你应该像我们这里一样,把 HEAD 也作为一个引用添加进去。

你可以通过电子邮件将这个 repo.bundle 文件发送给他人,或者存入 U 盘并亲自带过去。

在另一端,假设你收到了这个 repo.bundle 文件并想继续进行该项目的工作。你可以像从 URL 克隆一样,从这个二进制文件克隆到一个目录中。

$ git clone repo.bundle repo
Cloning into 'repo'...
...
$ cd repo
$ git log --oneline
9a466c5 Second commit
b1ec324 First commit

如果你在引用中没有包含 HEAD,则必须指定 -b master 或其他包含的分支,否则它将不知道该检出哪个分支。

现在假设你在上面进行了三次提交,并想通过 U 盘或电子邮件以打包文件的形式发回这些新提交。

$ git log --oneline
71b84da Last commit - second repo
c99cf5b Fourth commit - second repo
7011d3d Third commit - second repo
9a466c5 Second commit
b1ec324 First commit

首先,我们需要确定要包含在包中的提交范围。与网络协议会自动为我们计算要传输的最小数据集不同,我们必须手动计算。当然,你可以像之前一样打包整个仓库,这也能行得通,但最好只打包差异——即我们刚才在本地所做的三次提交。

为了做到这一点,你必须计算差异。正如我们在 提交范围 中所述,你可以用多种方式指定提交范围。要获取我们在 master 分支中拥有但最初克隆的分支中没有的那三个提交,我们可以使用类似 origin/master..mastermaster ^origin/master 的语法。你可以用 log 命令进行验证。

$ git log --oneline master ^origin/master
71b84da Last commit - second repo
c99cf5b Fourth commit - second repo
7011d3d Third commit - second repo

现在我们有了要包含在包中的提交列表,让我们开始打包。我们使用 git bundle create 命令来完成,并指定我们要生成的包文件名以及要包含的提交范围。

$ git bundle create commits.bundle master ^9a466c5
Counting objects: 11, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (9/9), 775 bytes, done.
Total 9 (delta 0), reused 0 (delta 0)

现在我们的目录下有了一个 commits.bundle 文件。如果我们把它发给同事,她就可以将其导入到原始仓库中,即使在此期间该仓库已经有了更多的工作进展。

当她收到这个包时,可以在导入到仓库之前先检查一下它的内容。第一个命令是 bundle verify,它会确保该文件确实是一个有效的 Git 包,并且你拥有正确还原它所需的所有先决祖先提交。

$ git bundle verify ../commits.bundle
The bundle contains 1 ref
71b84daaf49abed142a373b6e5c59a22dc6560dc refs/heads/master
The bundle requires these 1 ref
9a466c572fe88b195efd356c3f2bbeccdb504102 second commit
../commits.bundle is okay

如果打包者只打包了最后两个提交,而不是全部三个,原始仓库将无法导入它,因为它缺少必需的历史记录。此时 verify 命令看起来会像这样:

$ git bundle verify ../commits-bad.bundle
error: Repository lacks these prerequisite commits:
error: 7011d3d8fc200abe0ad561c011c3852a4b7bbe95 Third commit - second repo

不过,我们的第一个包是有效的,所以我们可以从中拉取提交。如果你想查看包中有哪些可以导入的分支,还有一个命令可以列出所有分支头 (heads):

$ git bundle list-heads ../commits.bundle
71b84daaf49abed142a373b6e5c59a22dc6560dc refs/heads/master

verify 子命令也会告诉你分支头。关键在于查看哪些内容可以被拉取,这样你就可以使用 fetchpull 命令从该包中导入提交。这里我们将包中的 master 分支拉取到我们仓库中一个名为 other-master 的分支里:

$ git fetch ../commits.bundle master:other-master
From ../commits.bundle
 * [new branch]      master     -> other-master

现在我们可以看到,导入的提交位于 other-master 分支上,同时我们自己的 master 分支中也保留了我们在此期间所做的提交。

$ git log --oneline --decorate --graph --all
* 8255d41 (HEAD, master) Third commit - first repo
| * 71b84da (other-master) Last commit - second repo
| * c99cf5b Fourth commit - second repo
| * 7011d3d Third commit - second repo
|/
* 9a466c5 Second commit
* b1ec324 First commit

总之,当你没有合适的网络或共享仓库时,git bundle 对于共享数据或执行网络类操作非常有用。