1
1
[[_rebasing]]
2
- === 衍合
2
+ === 变基
3
3
4
4
(((rebasing)))
5
5
在 Git 中整合来自不同分支的修改主要有两种方法:`merge` 以及 `rebase`。
6
- 在本节中我们将学习什么是“衍合 ”,怎样使用“衍合 ”,并将展示该操作的惊艳之处,以及指出在何种情况下你应避免使用它。
6
+ 在本节中我们将学习什么是“变基 ”,怎样使用“变基 ”,并将展示该操作的惊艳之处,以及指出在何种情况下你应避免使用它。
7
7
8
- ==== 衍合的基本操作
8
+ ==== 变基的基本操作
9
9
10
10
请回顾之前在 <<_basic_merging>> 中的一个例子,你会看到开发任务分叉到两个不同分支,又各自提交了更新。
11
11
@@ -19,7 +19,7 @@ image::images/basic-rebase-1.png[分叉的提交历史]
19
19
image::images/basic-rebase-2.png[通过合并操作来整合分叉了的历史]
20
20
21
21
其实,还有一种方法:你可以提取在 `C4` 中引入的补丁和修改,然后在 `C3` 的基础上再应用一次。
22
- 在 Git 中,这种操作就叫做 _衍合_ 。
22
+ 在 Git 中,这种操作就叫做 _变基_ 。
23
23
你可以使用 `rebase` 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。(((git commands, rebase)))
24
24
25
25
在上面这个例子中,运行:
@@ -32,10 +32,10 @@ First, rewinding head to replay your work on top of it...
32
32
Applying: added staged command
33
33
----
34
34
35
- 它的原理是首先找到这两个分支(即当前分支 `experiment`、衍合操作的目标基底分支 `master`)的最近共同祖先 `C2`,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 `C3`, 最后以此将之前另存为临时文件的修改依序应用。(译注:写明了 commit id,以便理解,下同)
35
+ 它的原理是首先找到这两个分支(即当前分支 `experiment`、变基操作的目标基底分支 `master`)的最近共同祖先 `C2`,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 `C3`, 最后以此将之前另存为临时文件的修改依序应用。(译注:写明了 commit id,以便理解,下同)
36
36
37
- .将 `C4` 中的修改衍合到 `C3` 上
38
- image::images/basic-rebase-3.png[将 `C4` 中的修改衍合到 `C3` 上]
37
+ .将 `C4` 中的修改变基到 `C3` 上
38
+ image::images/basic-rebase-3.png[将 `C4` 中的修改变基到 `C3` 上]
39
39
40
40
现在回到 `master` 分支,进行一次快进合并。
41
41
@@ -49,19 +49,19 @@ $ git merge experiment
49
49
image::images/basic-rebase-4.png[master 分支的快进合并]
50
50
51
51
此时,`C4'` 指向的快照就和上面使用 `merge` 命令的例子中 `C5` 指向的快照一模一样了。
52
- 这两种整合方法的最终结果没有任何区别,但是衍合使得提交历史更加整洁 。
53
- 你在查看一个经过衍合的分支的历史记录时会发现 ,尽管实际的开发工作是并行的,但它们看上去就像是先后串行的一样,提交历史是一条直线没有分叉。
52
+ 这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁 。
53
+ 你在查看一个经过变基的分支的历史记录时会发现 ,尽管实际的开发工作是并行的,但它们看上去就像是先后串行的一样,提交历史是一条直线没有分叉。
54
54
55
55
一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个别人维护的项目贡献代码时。
56
- 在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码衍合到 `origin/master` 上,然后再向主项目提交修改。
56
+ 在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到 `origin/master` 上,然后再向主项目提交修改。
57
57
这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。
58
58
59
- 请注意,无论是通过衍合 ,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。
60
- 衍合是将一系列提交按照原有次序依次应用到另一分支上 ,而合并是把最终结果合在一起。
59
+ 请注意,无论是通过变基 ,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。
60
+ 变基是将一系列提交按照原有次序依次应用到另一分支上 ,而合并是把最终结果合在一起。
61
61
62
- ==== 更有趣的衍合例子
62
+ ==== 更有趣的变基例子
63
63
64
- 在对两个分支进行衍合时 ,所生成的“重演”并不一定要在目标分支上应用,你也可以指定另外的一个分支进行应用。
64
+ 在对两个分支进行变基时 ,所生成的“重演”并不一定要在目标分支上应用,你也可以指定另外的一个分支进行应用。
65
65
就像 <<rbdiag_e>> 中的例子这样。
66
66
你创建了一个特性分支 `server`,为服务端添加了一些功能,提交了 `C3` 和 `C4`。
67
67
然后从 `C3` 上创建了特性分支 `client`,为客户端添加了一些功能,提交了 `C8` 和 `C9`。
@@ -82,8 +82,8 @@ $ git rebase --onto master server client
82
82
以上命令的意思是:“取出 `client` 分支,找出处于 `client` 分支和 `server` 分支的共同祖先之后的修改,然后把它们在 `master` 分支上重演一遍”。
83
83
这理解起来有一点复杂,不过效果非常酷。
84
84
85
- .截取特性分支上的另一个特性分支,然后衍合到其他分支
86
- image::images/interesting-rebase-2.png[截取特性分支上的另一个特性分支,然后衍合到其他分支 ]
85
+ .截取特性分支上的另一个特性分支,然后变基到其他分支
86
+ image::images/interesting-rebase-2.png[截取特性分支上的另一个特性分支,然后变基到其他分支 ]
87
87
88
88
现在可以快进合并 `master` 分支了。(如图 <<rbdiag_g>>):
89
89
@@ -98,7 +98,7 @@ $ git merge client
98
98
image::images/interesting-rebase-3.png[快进合并 master 分支,使之包含来自 client 分支的修改]
99
99
100
100
接下来你决定将 `server` 分支中的修改也整合进来。
101
- 使用 `git rebase [basebranch] [topicbranch]` 命令可以直接将特性分支(即本例中的 `server`)衍合到目标分支 (即 `master`)上。这样做能省去你先切换到 `server` 分支,再对其执行衍合命令的多个步骤 。
101
+ 使用 `git rebase [basebranch] [topicbranch]` 命令可以直接将特性分支(即本例中的 `server`)变基到目标分支 (即 `master`)上。这样做能省去你先切换到 `server` 分支,再对其执行变基命令的多个步骤 。
102
102
103
103
[source,console]
104
104
----
@@ -108,8 +108,8 @@ $ git rebase master server
108
108
如图 <<rbdiag_h>> 所示,`server` 中的代码被“续”到了 `master` 后面。
109
109
110
110
[[rbdiag_h]]
111
- .将 server 中的修改衍合到 master 上
112
- image::images/interesting-rebase-4.png[将 server 中的修改衍合到 master 上]
111
+ .将 server 中的修改变基到 master 上
112
+ image::images/interesting-rebase-4.png[将 server 中的修改变基到 master 上]
113
113
114
114
然后就可以快进合并主分支 master 了:
115
115
@@ -132,20 +132,20 @@ $ git branch -d server
132
132
image::images/interesting-rebase-5.png[最终的提交历史]
133
133
134
134
[[_rebase_peril]]
135
- ==== 衍合的风险
135
+ ==== 变基的风险
136
136
137
137
(((rebasing, perils of)))
138
- 呃,奇妙的衍合也并非完美无缺 ,要用它得遵守一条准则:
138
+ 呃,奇妙的变基也并非完美无缺 ,要用它得遵守一条准则:
139
139
140
- **不要对在你的仓库外有副本的分支执行衍合 。**
140
+ **不要对在你的仓库外有副本的分支执行变基 。**
141
141
142
142
如果你遵循这条金科玉律,就不会出差错。
143
143
否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
144
144
145
- 衍合操作的实质是丢弃一些现有的提交 ,然后相应地新建一些内容一样但实际上不同的提交。
145
+ 变基操作的实质是丢弃一些现有的提交 ,然后相应地新建一些内容一样但实际上不同的提交。
146
146
如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 `git rebase` 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
147
147
148
- 让我们来看一个在公开的仓库上执行衍合操作所带来的问题 。
148
+ 让我们来看一个在公开的仓库上执行变基操作所带来的问题 。
149
149
假设你从一个中央服务器克隆然后在它的基础上进行了一些开发。
150
150
你的提交历史如图所示:
151
151
@@ -158,12 +158,12 @@ image::images/perils-of-rebasing-1.png[克隆一个仓库,然后在它的基
158
158
.抓取别人的提交,合并到自己的开发分支
159
159
image::images/perils-of-rebasing-2.png[抓取别人的提交,合并到自己的开发分支]
160
160
161
- 接下来,这个人又决定把合并操作回滚,改用衍合 ;继而又用 `git push --force` 命令覆盖了服务器上的提交历史。
161
+ 接下来,这个人又决定把合并操作回滚,改用变基 ;继而又用 `git push --force` 命令覆盖了服务器上的提交历史。
162
162
之后你从服务器抓取更新,会发现多出来一些新的提交。
163
163
164
164
[[_pre_merge_rebase_work]]
165
- .有人推送了经过衍合的提交 ,并丢弃了你的本地开发所基于的一些提交
166
- image::images/perils-of-rebasing-3.png[有人推送了经过衍合的提交 ,并丢弃了你的本地开发所基于的一些提交]
165
+ .有人推送了经过变基的提交 ,并丢弃了你的本地开发所基于的一些提交
166
+ image::images/perils-of-rebasing-3.png[有人推送了经过变基的提交 ,并丢弃了你的本地开发所基于的一些提交]
167
167
168
168
结果就是你们两人的处境都十分尴尬。
169
169
如果你执行 `git pull` 命令,你将合并来自两条提交历史的内容,生成一个新的合并提交,最终仓库会如图所示:
@@ -173,17 +173,17 @@ image::images/perils-of-rebasing-3.png[有人推送了经过衍合的提交,
173
173
image::images/perils-of-rebasing-4.png[你将相同的内容又合并了一次,生成了一个新的提交]
174
174
175
175
此时如果你执行 `git log` 命令,你会发现有两个提交的作者、日期、日志居然是一样的,这会令人感到混乱。
176
- 此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被衍合抛弃的提交又找了回来 ,这会令人感到更加混乱。
177
- 很明显对方并不想在提交历史中看到 `C4` 和 `C6`,因为之前就是她把这两个提交通过衍合丢弃的 。
176
+ 此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被变基抛弃的提交又找了回来 ,这会令人感到更加混乱。
177
+ 很明显对方并不想在提交历史中看到 `C4` 和 `C6`,因为之前就是她把这两个提交通过变基丢弃的 。
178
178
179
179
[[_rebase_rebase]]
180
- ==== 用衍合解决衍合
180
+ ==== 用变基解决变基
181
181
182
182
如果你*真的*遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。
183
183
184
184
实际上,Git 除了对整个提交计算 SHA 校验和以外,也对本次提交所引入的修改计算了校验和——即 ``patch-id''。
185
185
186
- 如果你拉取被覆盖过的更新并将你手头的工作基于此进行衍合的话 ,一般情况下 Git 都能成功分辨出哪些是你的修改,并把它们应用到新分支上。
186
+ 如果你拉取被覆盖过的更新并将你手头的工作基于此进行变基的话 ,一般情况下 Git 都能成功分辨出哪些是你的修改,并把它们应用到新分支上。
187
187
188
188
举个例子,如果遇到前面提到的 <<_pre_merge_rebase_work>> 那种情境,如果我们不是执行合并,而是执行 `git rebase teamone/master`, Git 将会:
189
189
@@ -195,24 +195,24 @@ image::images/perils-of-rebasing-4.png[你将相同的内容又合并了一次
195
195
从而我们将得到与 <<_merge_rebase_work>> 中不同的结果,如图 <<_rebase_rebase_work>> 所示。
196
196
197
197
[[_rebase_rebase_work]]
198
- .在一个被衍合然后强制推送的分支上再次执行衍合
199
- image::images/perils-of-rebasing-5.png[在一个被衍合然后强制推送的分支上再次执行衍合 ]
198
+ .在一个被变基然后强制推送的分支上再次执行变基
199
+ image::images/perils-of-rebasing-5.png[在一个被变基然后强制推送的分支上再次执行变基 ]
200
200
201
- 要想上述方案有效,还需要对方在衍合时确保 C4' 和 C4 是几乎一样的。否则衍合操作将无法识别 ,并新建另一个类似 C4 的补丁(而这个补丁很可能无法整洁的整合入历史,因为补丁中的修改已经存在于某个地方了)。
201
+ 要想上述方案有效,还需要对方在变基时确保 C4' 和 C4 是几乎一样的。否则变基操作将无法识别 ,并新建另一个类似 C4 的补丁(而这个补丁很可能无法整洁的整合入历史,因为补丁中的修改已经存在于某个地方了)。
202
202
203
203
在本例中另一种简单的方法是使用 `git pull --rebase` 命令而不是直接 `git pull`。又或者你可以自己手动完成这个过程,先 `git fetch`,再 `git rebase teamone/master`。
204
204
205
205
如果你习惯使用 `git pull` ,同时又希望默认使用选项 `--rebase`,你可以执行这条语句 `git config --global pull.rebase true` 来更改 `pull.rebase` 的默认配置。
206
206
207
- 只要你把衍合命令当作是在推送前清理提交使之整洁的工具,并且只在从未推送至共用仓库的提交上执行衍合命令 ,你就不会有事。
208
- 假如你在那些已经被推送至共用仓库的提交上执行衍合命令 ,并因此丢弃了一些别人的开发所基于的提交,那你就有大麻烦了,你的同事也会因此鄙视你。
207
+ 只要你把变基命令当作是在推送前清理提交使之整洁的工具,并且只在从未推送至共用仓库的提交上执行变基命令 ,你就不会有事。
208
+ 假如你在那些已经被推送至共用仓库的提交上执行变基命令 ,并因此丢弃了一些别人的开发所基于的提交,那你就有大麻烦了,你的同事也会因此鄙视你。
209
209
210
210
如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 `git pull --rebase` 命令,这样尽管不能避免伤痛,但能有所缓解。
211
211
212
- ==== 衍合 vs. 合并
212
+ ==== 变基 vs. 合并
213
213
214
214
(((rebasing, vs. merging)))(((merging, vs. rebasing)))
215
- 至此,你已在实战中学习了衍合和合并的用法 ,你一定会想问,到底哪种方式更好。
215
+ 至此,你已在实战中学习了变基和合并的用法 ,你一定会想问,到底哪种方式更好。
216
216
在回答这个问题之前,让我们退后一步,想讨论一下提交历史到底意味着什么。
217
217
218
218
有一种观点认为,仓库的提交历史即是*记录实际发生过什么*。
@@ -225,8 +225,8 @@ image::images/perils-of-rebasing-5.png[在一个被衍合然后强制推送的
225
225
没人会出版一本书的第一批草稿,软件维护手册也是需要反复修订才能方便使用。
226
226
持这一观点的人会使用 rebase 及 filter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。
227
227
228
- 现在,让我们回到之前的问题上来,到底合并还是衍合好 ?希望你能明白,并没有一个简单的答案。
228
+ 现在,让我们回到之前的问题上来,到底合并还是变基好 ?希望你能明白,并没有一个简单的答案。
229
229
Git 是一个非常强大的工具,它允许你对提交历史做许多事情,但每个团队、每个项目对此的需求并不相同。
230
230
既然你已经分别学习了两者的用法,相信你能够根据实际情况作出明智的选择。
231
231
232
- 总的原则是,只对尚未推送或分享给别人的本地修改执行衍合操作清理历史,从不对已推送至别处的提交执行衍合操作 ,这样,你才能享受到两种方式带来的便利。
232
+ 总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作 ,这样,你才能享受到两种方式带来的便利。
0 commit comments