Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 103 additions & 46 deletions book/07-git-tools/sections/advanced-merging.asc

Large diffs are not rendered by default.

45 changes: 33 additions & 12 deletions book/07-git-tools/sections/bundling.asc
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@

虽然我们已经了解了网络传输 Git 数据的常用方法(如 HTTP,SSH 等),但还有另外一种不太常见却又十分有用的方式。

Git 可以将它的数据 ``打包'' 到一个文件中。这在许多场景中都很有用。有可能你的网络中断了,但你又希望将你的提交传给你的合作者们。可能你不在办公网中并且出于安全考虑没有给你接入内网的权限。可能你的无线、有线网卡坏掉了。可能你现在没有共享服务器的权限,你又希望通过邮件将更新发送给别人,却不希望通过 `format-patch` 的方式传输 40 个提交。
Git 可以将它的数据 ``打包'' 到一个文件中。
这在许多场景中都很有用。
有可能你的网络中断了,但你又希望将你的提交传给你的合作者们。
可能你不在办公网中并且出于安全考虑没有给你接入内网的权限。
可能你的无线、有线网卡坏掉了。
可能你现在没有共享服务器的权限,你又希望通过邮件将更新发送给别人,却不希望通过 `format-patch` 的方式传输 40 个提交。

这些情况下 `git bundle` 就会很有用。`bundle` 命令会将 `git push` 命令所传输的所有内容打包成一个二进制文件,你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。
这些情况下 `git bundle` 就会很有用。
`bundle` 命令会将 `git push` 命令所传输的所有内容打包成一个二进制文件,你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。

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

[source,console]
----
Expand Down Expand Up @@ -37,11 +44,14 @@ Writing objects: 100% (6/6), 441 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)
----

然后你就会有一个名为 `repo.bundle` 的文件,该文件包含了所有重建该仓库 `master` 分支所需的数据。在使用 `bundle` 命令时,你需要列出所有你希望打包的引用或者提交的区间。如果你希望这个仓库可以在别处被克隆,你应该像例子中那样增加一个 HEAD 引用。
然后你就会有一个名为 `repo.bundle` 的文件,该文件包含了所有重建该仓库 `master` 分支所需的数据。
在使用 `bundle` 命令时,你需要列出所有你希望打包的引用或者提交的区间。
如果你希望这个仓库可以在别处被克隆,你应该像例子中那样增加一个 HEAD 引用。

你可以将这个 `repo.bundle` 文件通过邮件或者U盘传给别人。

另一方面,假设别人传给你一个 `repo.bundle` 文件并希望你在这个项目上工作。你可以从这个二进制文件中克隆出一个目录,就像从一个 URL 克隆一样。
另一方面,假设别人传给你一个 `repo.bundle` 文件并希望你在这个项目上工作。
你可以从这个二进制文件中克隆出一个目录,就像从一个 URL 克隆一样。

[source,console]
----
Expand All @@ -67,9 +77,14 @@ c99cf5b fourth commit - second repo
b1ec324 first commit
----

首先我们需要确认我们希望被打包的提交区间。和网络协议不太一样,网络协议会自动计算出所需传输的最小数据集,而我们需要手动计算。当然你可以像上面那样将整个仓库打包,但最好仅仅打包变更的部分 —— 就是我们刚刚在本地做的 3 个提交。
首先我们需要确认我们希望被打包的提交区间。
和网络协议不太一样,网络协议会自动计算出所需传输的最小数据集,而我们需要手动计算。
当然你可以像上面那样将整个仓库打包,但最好仅仅打包变更的部分 —— 就是我们刚刚在本地做的 3 个提交。

为了实现这个目标,你需要计算出差别。就像我们在 <<_commit_ranges>> 介绍的,你有很多种方式去指明一个提交区间。我们可以使用 `origin/master..master` 或者 `master ^origin/master` 之类的方法来获取那 3 个在我们的 master 分支而不在原始仓库中的提交。你可以用 `log` 命令来测试。
为了实现这个目标,你需要计算出差别。
就像我们在 <<_commit_ranges>> 介绍的,你有很多种方式去指明一个提交区间。
我们可以使用 `origin/master..master` 或者 `master ^origin/master` 之类的方法来获取那 3 个在我们的 master 分支而不在原始仓库中的提交。
你可以用 `log` 命令来测试。

[source,console]
----
Expand All @@ -79,7 +94,8 @@ c99cf5b fourth commit - second repo
7011d3d third commit - second repo
----

这样就获取到我们希望被打包的提交列表,让我们将这些提交打包。我们可以用 `git bundle create` 命令,加上我们想用的文件名,以及要打包的提交区间。
这样就获取到我们希望被打包的提交列表,让我们将这些提交打包。
我们可以用 `git bundle create` 命令,加上我们想用的文件名,以及要打包的提交区间。

[source,console]
----
Expand All @@ -91,9 +107,11 @@ Writing objects: 100% (9/9), 775 bytes, done.
Total 9 (delta 0), reused 0 (delta 0)
----

现在在我们的目录下会有一个 `commits.bundle` 文件。如果我们把这个文件发送给我们的合作者,她可以将这个文件导入到原始的仓库中,即使在这期间已经有其他的工作提交到这个仓库中。
现在在我们的目录下会有一个 `commits.bundle` 文件。
如果我们把这个文件发送给我们的合作者,她可以将这个文件导入到原始的仓库中,即使在这期间已经有其他的工作提交到这个仓库中。

当她拿到这个包时,她可以在导入到仓库之前查看这个包里包含了什么内容。`bundle verify` 命令可以检查这个文件是否是一个合法的 Git 包,是否拥有共同的祖先来导入。
当她拿到这个包时,她可以在导入到仓库之前查看这个包里包含了什么内容。
`bundle verify` 命令可以检查这个文件是否是一个合法的 Git 包,是否拥有共同的祖先来导入。

[source,console]
----
Expand All @@ -114,15 +132,18 @@ error: Repository lacks these prerequisite commits:
error: 7011d3d8fc200abe0ad561c011c3852a4b7bbe95 third commit - second repo
----

而我们的第一个包是合法的,所以我们可以从这个包里提取出提交。如果你想查看这边包里可以导入哪些分支,同样有一个命令可以列出这些顶端:
而我们的第一个包是合法的,所以我们可以从这个包里提取出提交。
如果你想查看这边包里可以导入哪些分支,同样有一个命令可以列出这些顶端:

[source,console]
----
$ git bundle list-heads ../commits.bundle
71b84daaf49abed142a373b6e5c59a22dc6560dc refs/heads/master
----

`verify` 子命令同样可以告诉你有哪些顶端。该功能的目的是查看哪些是可以被拉入的,所以你可以使用 `fetch` 或者 `pull` 命令从包中导入提交。这里我们要从包中取出 `master` 分支到我们仓库中的 'other-master' 分支:
`verify` 子命令同样可以告诉你有哪些顶端。
该功能的目的是查看哪些是可以被拉入的,所以你可以使用 `fetch` 或者 `pull` 命令从包中导入提交。
这里我们要从包中取出 `master` 分支到我们仓库中的 'other-master' 分支:

[source,console]
----
Expand Down
2 changes: 1 addition & 1 deletion book/07-git-tools/sections/debugging.asc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ $ git blame -L 12,22 simplegit.rb
接下来就是行号和文件内容。
注意一下 `^4832fe2` 这个提交的那些行,这些指的是这个文件第一次提交的那些行。
这个提交是这个文件第一次加入到这个项目时的提交,并且这些行从未被修改过。
这会带来小小的困惑,因为你已经至少看到三种 Git 使用 `^` 来修饰一个提交的 SHA 值的不同含义,但这里确实就是这个意思。
这会带来小小的困惑,因为你已经至少看到三种 Git 使用 `^` 来修饰一个提交的 SHA-1 值的不同含义,但这里确实就是这个意思。

另一件比较酷的事情是 Git 不会显式地记录文件的重命名。
它会记录快照,然后在事后尝试计算出重命名的动作。
Expand Down
3 changes: 2 additions & 1 deletion book/07-git-tools/sections/interactive-staging.asc
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,5 @@ simplegit.rb 文件的状态很有趣。

也可以不必在交互式添加模式中做部分文件暂存 - 可以在命令行中使用 `git add -p` 或 `git add --patch` 来启动同样的脚本。

更进一步地,可以使用 `reset --patch` 命令的补丁模式来部分重置文件。通过 `checkout --patch` 命令来部分检出文件与 `stash save --patch` 命令来部分暂存文件。我们将会在接触这些命令的高级使用方法时了解更多详细信息。
更进一步地,可以使用 `reset --patch` 命令的补丁模式来部分重置文件,通过 `checkout --patch` 命令来部分检出文件与 `stash save --patch` 命令来部分暂存文件。
我们将会在接触这些命令的高级使用方法时了解更多详细信息。
35 changes: 24 additions & 11 deletions book/07-git-tools/sections/replace.asc
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

Git 对象是不可改变的,但它提供一种有趣的方式来用其他对象假装替换数据库中的 Git 对象。

`replace` 命令可以让你在 Git 中指定一个对象并可以声称“每次你遇到这个 Git 对象时,假装它是其他的东西”。在你用一个不同的提交替换历史中的一个提交时,这会非常有用。
`replace` 命令可以让你在 Git 中指定一个对象并可以声称“每次你遇到这个 Git 对象时,假装它是其他的东西”。
在你用一个不同的提交替换历史中的一个提交时,这会非常有用。

例如,你有一个大型的代码历史并想把自己的仓库分成一个短的历史和一个更大更长久的历史,短历史供新的开发者使用,后者给喜欢数据挖掘的人使用。 你可以通过用新仓库中最早的提交 `replace` 老仓库中最新的提交来连接历史,这种方式可以把一条历史移植到其他历史上。这意味着你不用在新历史中真正替换每一个提交(因为历史来源会影响 SHA 的值),你可以加入他们。
例如,你有一个大型的代码历史并想把自己的仓库分成一个短的历史和一个更大更长久的历史,短历史供新的开发者使用,后者给喜欢数据挖掘的人使用。
你可以通过用新仓库中最早的提交 `replace` 老仓库中最新的提交来连接历史,这种方式可以把一条历史移植到其他历史上。
这意味着你不用在新历史中真正替换每一个提交(因为历史来源会影响 SHA 的值),你可以加入他们。

让我们来试试吧!首先获取一个已经存在的仓库,并将其分成两个仓库,一个是最近的仓库,一个是历史版本的仓库。然后我们将看到如何在不更改仓库 SHA 值的情况下通过 `replace` 命令来合并他们。
让我们来试试吧。
首先获取一个已经存在的仓库,并将其分成两个仓库,一个是最近的仓库,一个是历史版本的仓库,然后我们将看到如何在不更改仓库 SHA 值的情况下通过 `replace` 命令来合并他们。

我们将使用一个拥有 5 个提交的简单仓库:

Expand All @@ -21,7 +25,9 @@ c6e1e95 fourth commit
c1822cf first commit
----

我们想将其分成拆分成两条历史。第一个到第四个提交的作为第一个历史版本,第四、第五个提交的作为最近的第二个历史版本。
我们想将其分成拆分成两条历史。
第一个到第四个提交的作为第一个历史版本。
第四、第五个提交的作为最近的第二个历史版本。

image::images/replace1.png[]

Expand Down Expand Up @@ -56,7 +62,9 @@ To git@github.com:schacon/project-history.git
* [new branch] history -> master
----

这样一来,我们的历史版本就发布了。稍难的部分则是删减我们最近的历史来让它变得更小。我们需要一个重叠以便于用一个相等的提交来替换另一个提交,这样一来,我们将截断到第四、五个提交。
这样一来,我们的历史版本就发布了。
稍难的部分则是删减我们最近的历史来让它变得更小。
我们需要一个重叠以便于用一个相等的提交来替换另一个提交,这样一来,我们将截断到第四、五个提交。

[source,console]
----
Expand All @@ -68,7 +76,8 @@ c6e1e95 (history) fourth commit
c1822cf first commit
----

在这种情况下,创建一个能够指导扩展历史的基础提交是很有用的。这样一来,如果其他的开发者想要修改第一次提交或者其他操作时就知道要做些什么。 因此,接下来我们要做的是用命令创建一个最初的提交对象,然后将剩下的提交(第四、第五个提交)变基到它的上面。
在这种情况下,创建一个能够指导扩展历史的基础提交是很有用的。
这样一来,如果其他的开发者想要修改第一次提交或者其他操作时就知道要做些什么,因此,接下来我们要做的是用命令创建一个最初的提交对象,然后将剩下的提交(第四、第五个提交)变基到它的上面。

为了这么做,我们需要选择一个点去拆分,对于我们而言是第三个提交(SHA 是 `9c68fdc`)。因此我们的提交将基于此提交树。我们可以使用 `commit-tree` 命令来创建基础提交,这样我们就有了一个树,并返回一个全新的、无父节点的 SHA 提交对象。

Expand Down Expand Up @@ -97,7 +106,8 @@ Applying: fifth commit

image::images/replace4.png[]

我们已经用基础提交重写了最近的历史,基础提交包括如何重新组成整个历史的说明。我们可以将新历史推送到新项目中,当其他人克隆这个仓库时,他们仅能看到最近两次提交以及一个包含上述说明的基础提交。
我们已经用基础提交重写了最近的历史,基础提交包括如何重新组成整个历史的说明。
我们可以将新历史推送到新项目中,当其他人克隆这个仓库时,他们仅能看到最近两次提交以及一个包含上述说明的基础提交。

现在我们将以想获得整个历史的人的身份来初次克隆这个项目。
在克隆这个截断后的仓库后为了得到历史数据,需要添加第二个远程的历史版本库并对其做获取操作:
Expand Down Expand Up @@ -134,7 +144,8 @@ c6e1e95 fourth commit
c1822cf first commit
----

为了合并它们,你可以使用 `git replace` 命令加上你想替换的提交信息来进行替换。这样一来,我们就可以将 master 分支中的第四个提交替换为 `project-history/master` 分支中的“第四个”提交。
为了合并它们,你可以使用 `git replace` 命令加上你想替换的提交信息来进行替换。
这样一来,我们就可以将 master 分支中的第四个提交替换为 `project-history/master` 分支中的“第四个”提交。

[source,console]
----
Expand All @@ -153,11 +164,12 @@ e146b5f fifth commit
c1822cf first commit
----

很酷,是不是?不用改变上游的 SHA 我们就能用一个提交来替换历史中的所有不同的提交,并且所有的工具(`bisect`,`blame` 等)也都奏效。
很酷,是不是?不用改变上游的 SHA-1 我们就能用一个提交来替换历史中的所有不同的提交,并且所有的工具(`bisect`,`blame` 等)也都奏效。

image::images/replace5.png[]

有趣的是,即使是使用了 `c6e1e95` 提交数据来进行替换,它的 SHA 仍显示为 `81a708d`。即使你运行了 `cat-file` 命令,它仍会显示你替换的数据:
有趣的是,即使是使用了 `c6e1e95` 提交数据来进行替换,它的 SHA-1 仍显示为 `81a708d`。
即使你运行了 `cat-file` 命令,它仍会显示你替换的数据:

[source,console]
----
Expand All @@ -184,4 +196,5 @@ e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040
----

这意味着我们可以轻而易举的和其他人分享替换,因为我们可以将替换推送到服务器中并且其他人可以轻松地下载。也许在历史移植情况下不是很有用(既然每个人都乐意下载最新版本和历史版本,为何还要拆分他们呢?),但在其他情况下仍然很有用。
这意味着我们可以轻而易举的和其他人分享替换,因为我们可以将替换推送到服务器中并且其他人可以轻松地下载。
也许在历史移植情况下不是很有用(既然每个人都乐意下载最新版本和历史版本,为何还要拆分他们呢?),但在其他情况下仍然很有用。
Loading