章节 ▾ 第二版

7.12 Git 工具 - 打包

打包

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

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

这时 git bundle 命令就能派上用场。bundle 命令会将通过 git push 命令通常会在网络上传输的所有内容打包成一个二进制文件,你可以将其通过电子邮件发送给他人,或将其放在 U 盘上,然后在另一个仓库中解包。

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

$ 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

但是,我们的第一个包是有效的,所以我们可以从中获取提交。如果你想查看包中可以导入的分支,还有一个命令可以只列出头节点。

$ 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 对于共享或进行网络类操作来说真的非常有用。