章节 ▾ 第二版

7.12 Git 工具 - 打包

打包

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

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 在共享或执行此类操作时会非常有用。