Revise 5.1 代码合并:Merge、Rebase的选择.

This commit is contained in:
ZhongyiTong
2015-12-07 02:09:30 +08:00
parent b67e128a28
commit 1ec6c87805

View File

@ -4,17 +4,17 @@
> >
> 这是一篇在[原文(BY atlassian)](https://www.atlassian.com/git/tutorials/merging-vs-rebasing)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))协议共享。 > 这是一篇在[原文(BY atlassian)](https://www.atlassian.com/git/tutorials/merging-vs-rebasing)基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名([CC BY 2.5 AU](http://creativecommons.org/licenses/by/2.5/au/deed.zh))协议共享。
`git rebase` 这个命令经常被人认为是一种Git巫术初学者应该避而远之。但如果使用得当的话它能给你的团队开发省去太多烦恼。在这篇文章中我们会比较`git rebase` 和类似的`git merge` 命令找到Git工作流中rebase的所有用法。 `git rebase` 这个命令经常被人认为是一种Git巫术初学者应该避而远之。但如果使用得当的话它能给你的团队开发省去太多烦恼。在这篇文章中我们会比较`git rebase`和类似的`git merge`命令找到Git工作流中rebase的所有用法。
## 概述 ## 概述
你要知道的第一件事是,`git rebase``git merge` 做的事其实是一样的。它们都被设计来将一个分支的更改并入另一个分支,只不过方式有些不同。 你要知道的第一件事是,`git rebase``git merge` 做的事其实是一样的。它们都被设计来将一个分支的更改并入另一个分支,只不过方式有些不同。
想象一下你刚创建了一个专门的分支开发新功能然后团队中另一个成员在master分支上添加了新的commit。这就会造成提交历史被Fork一份用Git来协作的开发者应该都很清楚。 想象一下你刚创建了一个专门的分支开发新功能然后团队中另一个成员在master分支上添加了新的提交。这就会造成提交历史被Fork一份用Git来协作的开发者应该都很清楚。
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/01.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/01.svg)
现在如果master中新的commit和你的工作是相关的。为了将新的commit并入你的分支你有两个选择merge或rebase。 现在如果master中新的提交和你的工作是相关的。为了将新的提交并入你的分支你有两个选择merge或rebase。
### Merge ### Merge
@ -25,21 +25,21 @@ git checkout feature
git merge master git merge master
``` ```
或者你也可以把它们压缩在一行里。 或者你也可以把它们压缩在一行里。
``` ```
git merge master feature git merge master feature
``` ```
feature分支中新的merge commit将两个分支的历史连在了一起。你会得到下面这样的分支结构: feature分支中新的合并提交(merge commit)将两个分支的历史连在了一起。你会得到下面这样的分支结构:
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/02.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/02.svg)
Merge之所以好,因为它是一个安全的操作。现有的分支不会被更改。这就避免rebase潜在的缺点后面会说 Merge好在它是一个安全的操作。现有的分支不会被更改避免rebase潜在的缺点后面会说
另一方面,同样意味着每次合并上游更改的时候feature分支都会引入一个外来的merge commit。如果master非常活跃的话这或多或少会污染你的分支历史。虽然高级的`git log` 选项可以环节这个问题,但对于开发者来说,还是会增加理解项目历史的难度。 另一方面,同样意味着每次合并上游更改feature分支都会引入一个外来的合并提交。如果master非常活跃的话这或多或少会污染你的分支历史。虽然高级的`git log` 选项可以减轻这个问题,但对于开发者来说,还是会增加理解项目历史的难度。
### Rebase ### Rebase
@ -50,17 +50,17 @@ git checkout feature
git rebase master git rebase master
``` ```
它会把整个feature分支移动到master分支的后面有效地把所有master分支上新的commit并入过来。但是rebase为原分支上每一个commit创建全新的commit,重写了项目历史,并且不会带来merge commit 它会把整个feature分支移动到master分支的后面有效地把所有master分支上新的提交并入过来。但是rebase为原分支上每一个提交创建一个新的提交,重写了项目历史,并且不会带来合并提交
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/03.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/03.svg)
rebase最大的好处是你的项目历史会非常整洁。首先它不像`git merge` 那样引入不必要的merge commit。其次如上图所示rebase导致最后的项目历史呈现出完美的线性——你可以从项目终点到起点浏览而不需要任何的Fork。这让你更容易使用`git log` 、`git bisect` 和`gitk` 来查看项目历史。 rebase最大的好处是你的项目历史会非常整洁。首先它不像`git merge` 那样引入不必要的合并提交。其次如上图所示rebase导致最后的项目历史呈现出完美的线性——你可以从项目终点到起点浏览而不需要任何的Fork。这让你更容易使用`git log` 、`git bisect` 和`gitk` 来查看项目历史。
不过,这种简单的commit历史会带来两个后果安全性和可跟踪性。如果你违反了Rebase黄金法则重写项目历史可能会给你的协作工作流带来灾难性的影响。此外rebase不会有merge commit中附带的信息——你看不到feature分支中并入了上游的哪些更改。 不过,这种简单的提交历史会带来两个后果安全性和可跟踪性。如果你违反了Rebase黄金法则重写项目历史可能会给你的协作工作流带来灾难性的影响。此外rebase不会有合并提交中附带的信息——你看不到feature分支中并入了上游的哪些更改。
### 交互式的rebase ### 交互式的rebase
交互式的rebase允许你更改并入新分支的commit。这比自动的rebase更加强大因为它提供了对分支上提交历史完整的控制。一般来说这被用来在把feature分支并入master分支之前清理混乱的历史。 交互式的rebase允许你更改并入新分支的提交。这比自动的rebase更加强大因为它提供了对分支上提交历史完整的控制。一般来说这被用于将feature分支并入master分支之前清理混乱的历史。
把`-i` 传入`git rebase` 选项来开始一个交互式的rebase过程 把`-i` 传入`git rebase` 选项来开始一个交互式的rebase过程
@ -69,7 +69,7 @@ git checkout feature
git rebase -i master git rebase -i master
``` ```
它会打开一个文本编辑器,显示所有将被移动的commit 它会打开一个文本编辑器,显示所有将被移动的提交
``` ```
pick 33d5b7a Message for commit #1 pick 33d5b7a Message for commit #1
@ -77,7 +77,7 @@ pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3 pick 5c67e61 Message for commit #3
``` ```
这个列表定义了rebase将被执行后分支会是什么样的。更改`pick` 命令或者重新排序,这个分支的历史就能如你所愿了。比如说,如果第二个commit修复了第一个commit中的小问题,你可以用`fixup` 命令把它们压缩到一行 这个列表定义了rebase将被执行后分支会是什么样的。更改`pick` 命令或者重新排序,这个分支的历史就能如你所愿了。比如说,如果第二个提交修复了第一个提交中的小问题,你可以用`fixup` 命令把它们合到一个提交中
``` ```
pick 33d5b7a Message for commit #1 pick 33d5b7a Message for commit #1
@ -85,44 +85,44 @@ fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3 pick 5c67e61 Message for commit #3
``` ```
保存后关闭文件Git会根据你的指令来执行rebase这会导致项目历史看上去是这样 保存后关闭文件Git会根据你的指令来执行rebase项目历史看上去是这样:
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/04.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/04.svg)
忽略不重要的commit会让你的feature分支的历史更清晰易读。这是`git merge` 做不到的。 忽略不重要的提交会让你的feature分支的历史更清晰易读。这是`git merge` 做不到的。
## Rebase的黄金法则 ## Rebase的黄金法则
当你理解rebase是什么的时候最重要的事情就是什么时候不能用rebase。`git rebase` 的黄金法则便是,绝不要在公共的分支上使用它。 当你理解rebase是什么的时候最重要的就是什么时候 *不能* 用rebase。`git rebase` 的黄金法则便是,绝不要在公共的分支上使用它。
比如说如果你把master分支rebase到你的feature分支上会发生什么 比如说如果你把master分支rebase到你的feature分支上会发生什么
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/05.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/05.svg)
这次rebase将master分支上的所有commit都移到了feature分支后面。问题是它只发生在你的代码仓库中其他所有的开发者还在原来的master上工作。因为rebase导致了全新的commitGit会认为你的master分支和其他人的master已经分叉了。 这次rebase将master分支上的所有提交都移到了feature分支后面。问题是它只发生在你的代码仓库中其他所有的开发者还在原来的master上工作。因为rebase引起了新的提交Git会认为你的master分支和其他人的master已经分叉了。
同步两个master分支的唯一办法是把它们merge到一起导致一个额外的merge commit和两堆包含同样更改的commit。不用说,这会让人非常困惑。 同步两个master分支的唯一办法是把它们merge到一起导致一个额外的合并提交和两堆包含同样更改的提交。不用说,这会让人非常困惑。
所以,在你运行`git rebase` 之前,一定要问问你自己“有没有别人正在这个分支上工作?”。如果答案是,那么把你的爪子放回去,重新找到一个无害的方式(如`git revert`)来提交你的更改。不,你可以随心所欲地重写历史。 所以,在你运行`git rebase` 之前,一定要问问你自己“有没有别人正在这个分支上工作?”。如果答案是肯定的,那么把你的爪子放回去,重新找到一个无害的方式(如`git revert`)来提交你的更改。不然的话,你可以随心所欲地重写历史。
### 强制push ### 强制推送
如果你想把rebase之后的master分支push到远程仓库中去的话Git会阻止你这么做因为两个分支冲突。但你可以传入`--force` 标记来强行push。就像下面一样: 如果你想把rebase之后的master分支推送到远程仓库Git会阻止你这么做因为两个分支包含冲突。但你可以传入`--force` 标记来强行推送。就像下面一样:
``` ```
# Be very careful with this command! # 小心使用这个命令!
git push --force git push --force
``` ```
它会重写远程的master分支来匹配你仓库中rebase之后的master对于团队中其他成员来说这看上去很诡异。所以务必小心这个命令只有当你知道你在做什么的时候再使用。 它会重写远程的master分支来匹配你仓库中rebase之后的master分支,对于团队中其他成员来说这看上去很诡异。所以,务必小心这个命令,只有当你知道你在做什么的时候再使用。
仅有的几个强制push的使用场景之一是,当你在想向远程仓库push了一个私有分支之后,执行了一个本地的清理(比如说为了回滚)。这就像是在说“哦,其实我并不想push之前那个feature分支的。用我现在的版本替换掉吧。”同样你要注意没有人正在这个feature分支上工作。 仅有的几个强制推送的使用场景之一是,当你在想向远程仓库推送了一个私有分支之后,执行了一个本地的清理(比如说为了回滚)。这就像是在说“哦,其实我并不想推送之前那个feature分支的。用我现在的版本替换掉吧。”同样你要注意没有人正在这个feature分支上工作。
## 工作流 ## 工作流
你的团队或多或少都可以在现在Git工作流中使用rebase。在这一节中我们来看看在feature分支开发的各个阶段中rebase有哪些好处。 rebase可以或多或少应用在你们团队的Git工作流中。在这一节中我们来看看在feature分支开发的各个阶段中rebase有哪些好处。
第一步是在任何和`git rebase` 有关的工作流中为每一个feature专门创建一个分支。它会给你带来安全使用rebase的分支结构 第一步是在任何和`git rebase` 有关的工作流中为每一个feature专门创建一个分支。它会给你带来安全使用rebase的分支结构
@ -130,20 +130,20 @@ git push --force
### 本地清理 ### 本地清理
在你工作流中使用rebase最好的用法之一就是清理本地正在开发的分支。隔一段时间执行一次交互式rebase你可以保证你feature分支中的每一个commit都是专注和有意义的。你在写代码时不用担心造成孤立的commit——因为你后面一定能修复。 在你工作流中使用rebase最好的用法之一就是清理本地正在开发的分支。隔一段时间执行一次交互式rebase你可以保证你feature分支中的每一个提交都是专注和有意义的。你在写代码时不用担心造成孤立的提交——因为你后面一定能修复。
调用`git rebase` 的时候你有两个base可以选择上游分支比如master或者你feature分支中早先的一个commit。我们在“交互式rebase”一节看到了第一种的例子。后一种在当你只需要修改最新几次commit的时候也很有用。比如说下面的命令对最新的3次commit进行了交互式rebase 调用`git rebase` 的时候,你有两个基(base)可以选择上游分支比如master或者你feature分支中早先的一个提交。我们在“交互式rebase”一节看到了第一种的例子。后一种在当你只需要修改最新几次提交时也很有用。比如说下面的命令对最新的3次提交进行了交互式rebase
``` ```
git checkout feature git checkout feature
git rebase -i HEAD~3 git rebase -i HEAD~3
``` ```
通过指定`HEAD~3`作为新的base你实际上没有移动分支——你只是将之后的3次commit重写了。注意它不会把上游分支的更改并入到feature分支中。 通过指定`HEAD~3`作为新的基提交你实际上没有移动分支——你只是将之后的3次提交重写了。注意它不会把上游分支的更改并入到feature分支中。
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/07.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/07.svg)
如果你想用这个方法重写整个feature分支`git merge-base` 命令非常方便地找出feature分支开始的base。下面这段命令返回原base commit的ID你可以接下来将它传给`git rebase` 如果你想用这个方法重写整个feature分支`git merge-base` 命令非常方便地找出feature分支开始分叉的基。下面这段命令返回基提交的ID你可以接下来将它传给`git rebase`
``` ```
git merge-base feature master git merge-base feature master
@ -153,9 +153,7 @@ git merge-base feature master
但同样的这只能用在私有分支上。如果你在同一个feature分支和其他开发者合作的话这个分支是公开的你不能重写这个历史。 但同样的这只能用在私有分支上。如果你在同一个feature分支和其他开发者合作的话这个分支是公开的你不能重写这个历史。
用带有交互式的rebase清理本地commit这个用法无法用`git merge` 命令来完成 用带有交互式的rebase清理本地提交,这是无法用`git merge` 命令代替的
### 将上游分支上的更改并入feature分支 ### 将上游分支上的更改并入feature分支
@ -165,7 +163,7 @@ git merge-base feature master
记住rebase到远程分支而不是master也是完全合法的。当你和另一个开发者在同一个feature分之上协作的时候你会用到这个用法将他们的更改并入你的项目。 记住rebase到远程分支而不是master也是完全合法的。当你和另一个开发者在同一个feature分之上协作的时候你会用到这个用法将他们的更改并入你的项目。
比如说如果你和另一个开发者——John——往feature分支上添加了几个commit在从John的仓库中fetch之后你的仓库可能会像下面这样 比如说如果你和另一个开发者——John——往feature分支上添加了几个提交在从John的仓库中fetch之后你的仓库可能会像下面这样
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/08.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/08.svg)
@ -173,25 +171,23 @@ git merge-base feature master
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/09.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/09.svg)
注意这里的rebase没有违反Rebase黄金法则因为只有你的本地分支上的commit被移动了之前的所有东西都没有变。这就像是在说“把我的改动加到John已经做的后面去”。在大多数情况下,这比通过merge commit来同步远程分支来的更符合直觉。 注意这里的rebase没有违反Rebase黄金法则因为只有你的本地分支上的commit被移动了之前的所有东西都没有变。这就像是在说“把我的改动加到John的后面去”。在大多数情况下这比通过合并提交来同步远程分支更符合直觉。
默认情况下,`git pull` 命令会执行一次merge但你可以传入`--rebase` 来强制它通过rebase来整合远程分支。 默认情况下,`git pull` 命令会执行一次merge但你可以传入`--rebase` 来强制它通过rebase来整合远程分支。
By default, the git pull command performs a merge, but you can force it to integrate the remote branch with a rebase by passing it the --rebase option.
### 用Pull Request进行审查 ### 用Pull Request进行审查
如果你将pull request作为你代码审查过程中的一环你需要避免在创建pull request之后使用`git rebase`。只要你发起了pull request其他开发者能看到你的代码也就是说这个分支变成了公共分支。重写历史会造成Git和你的同事难以找到这个分支接下来的任何commit 如果你将pull request作为你代码审查过程中的一环你需要避免在创建pull request之后使用`git rebase`。只要你发起了pull request其他开发者能看到你的代码也就是说这个分支变成了公共分支。重写历史会造成Git和你的同事难以找到这个分支接下来的任何提交
来自其他开发者的任何更改都应该用`git merge` 而不是`git rebase` 来并入。 来自其他开发者的任何更改都应该用`git merge` 而不是`git rebase` 来并入。
因此在提交pull request用交互式的rebase进行代码清理通常是一个好的做法。 因此在提交pull request用交互式的rebase进行代码清理通常是一个好的做法。
### 并入通过的功能分支 ### 并入通过的功能分支
如果某个功能被你们团队通过了你可以选择将这个分支rebase到master分支之后或是使用`git merge` 来将这个功能并入主代码库中。 如果某个功能被你们团队通过了你可以选择将这个分支rebase到master分支之后或是使用`git merge` 来将这个功能并入主代码库中。
这和将上游改动并入feature分支很相似但是你不可以在master分支重写commit,你最后需要用`git merge` 来并入这个feature。但是在merge之前执行一次rebase你可以确保merge是一直向前的最后生成的是一个完全线性的提交历史。这样你还可以加入pull request之后的commit 这和将上游改动并入feature分支很相似但是你不可以在master分支重写提交,你最后需要用`git merge` 来并入这个feature。但是在merge之前执行一次rebase你可以确保merge是一直向前的最后生成的是一个完全线性的提交历史。这样你还可以加入pull request之后的提交
![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/10.svg) ![enter image description here](https://www.atlassian.com/git/images/tutorials/advanced/merging-vs-rebasing/10.svg)
@ -203,13 +199,13 @@ By default, the git pull command performs a merge, but you can force it to integ
git checkout feature git checkout feature
git checkout -b temporary-branch git checkout -b temporary-branch
git rebase -i master git rebase -i master
# [Clean up the history] # [清理目录]
git checkout master git checkout master
git merge temporary-branch git merge temporary-branch
``` ```
## 总结 ## 总结
你使用rebase之前需要知道的知识点都在这了。如果你想要一个干净的、线性的提交历史没有不必要的merge commit,你应该使用`git rebase` 而不是`git merge` 来并入其他分支上的更改。 你使用rebase之前需要知道的知识点都在这了。如果你想要一个干净的、线性的提交历史没有不必要的合并提交,你应该使用`git rebase` 而不是`git merge` 来并入其他分支上的更改。
另一方面如果你想要保存项目完整的历史并且避免重写公共分支上的commit 你可以使用`git merge `。两种选择都是完全有效的,但至少你现在可以想到`git rebase` 的好处了 另一方面如果你想要保存项目完整的历史并且避免重写公共分支上的commit 你可以使用`git merge`。两种选项都很好用,但至少你现在多了`git rebase`这个选择