git rebase命令常常被认为是Git巫术,初学者应该远离它,但它实际上可让开发团队在使用时更加轻松。在本文中,咱们将git rebase与相关git merge命令进行比较。git
首先要理解的是git rebase和git merge解决了一样的问题。这两个命令都旨在将更改从一个分支集成到另外一个分支 - 它们只是以不一样的方式进行。安全
试想一下当你开始在专用分支中开发新功能时另外一个团队成员以新提交更新master分支会发生什么。这会出现分叉历史记录,对于使用Git做为协做工具的任何人来讲都应该很熟悉。编辑器
如今,咱们来讲说当master新提交与你正在开发的功能相关。要将新提交合并到你的feature分支中,你有两个选择:merge或rebase。工具
最简单的是将master分支合并到feature分支中:spa
git checkout feature
3d
git merge master
code
或者,你能够简化为一行:cdn
git merge master feature
blog
这会在feature分支中建立一个新的“merge commit”,它将两个分支的历史联系在一块儿,为你生成以下所示的分支结构:排序
合并很好,由于它是一种非破坏性的操做。现有分支结构不会以任何方式更改。这避免了rebase的全部潜在缺陷(下面讨论)。
另外一方面,这也意味着每次上游更改时feature都须要合并,且有无关的合并提交。若是master改动很是频繁,这可能会严重污染你分支的历史记录。尽管可使用高级git log选项减轻此问题的影响,但它可能使其余开发人员难以理解项目的历史更改记录。
做为merge的替代方法,你可使用如下命令将feature分支rebase到master分支上:
git checkout feature
git rebase master
这会将整个feature分支移动到master分支的顶端,从而有效地整合了全部master的新提交。可是,rebase不是使用merge commit,而是经过为原始分支中的每一个提交建立全新的提交来重写项目历史记录。
rebase的主要好处是能够得到更清晰的项目历史记录。首先,它消除了没必要要的git merge产生的merge commit。其次,正如在上图中所看到的,rebase也会产生完美线性的项目历史记录 - 你能够从feature分支顶端一直跟随到项目的开始而没有任何的分叉。这使得它比命令git log,git bisect和gitk更容易导航项目。
可是,对这个原始的提交历史记录有两个权衡:安全性和可追溯性。若是你不遵循rebase的黄金法则,重写项目历史记录可能会对你的协做工做流程形成灾难性后果。其次rebase会丢失merge commit提供的上下文 - 你没法看到上游更改什么时候合并到功能中。
Interactive rebase使你有机会在将提交移动到新分支时更改提交。这比自动rebase更强大,由于它提供了对分支提交历史的彻底控制。一般,这用于在合并特征分支到master分支以前清理杂乱的历史记录。
要开始基于交互式会话,请将i选项传递给git rebase命令:
git checkout feature
git rebase -i master
这将打开一个文本编辑器,列出即将移动的全部提交:
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
列表准确给出了执行rebase后分支的概况。经过更改pick命令和(或)从新排序,可使分支的历史记录成为你想要的内容。例如,若是第二次提交修复了第一次提交中的一个小问题,你可使用如下fixup命令将它们压缩为单个提交:
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
保存并关闭文件时,Git将根据你的指令执行rebase,从而产生以下所示的项目历史记录:
消除这种无心义的提交使你的历史记录更可读。这是git merge没法作到的事情。
一旦你理解了什么是rebase,最重要的是了解何时不使用它。git rebase的黄金法则是永远不要在公共分支使用它。
例如,想一想若是你把master分支rebase到你的feature分支会发生什么:
rebase将master全部提交移动到feature顶端。问题是这只发生在你的仓库中。全部其余开发人员仍在使用原始版本master。因为rebase致使全新的提交,Git会认为你的master分支的历史与其余人的历史不一样。
同步两个master分支的惟一方法是将它们合并在一块儿,从而产生额外的合并提交和两组包含相同更改的提交(原始提交和来自rebase分支的更改)。这将是一个很是使人困惑的状况。
所以,在你运行git rebase以前,老是问本身,“还有其余人在用这个分支吗?”若是答案是确定的,那就把你的手从键盘上移开,考虑使用非破坏性的方式进行(例如,git revert命令)。不然,你能够为所欲为地重写历史记录。
若是你尝试将rebase过的master分支推到远程仓库,Git将阻止你这样作,由于它与远程master分支冲突。可是,你能够经过传递--force标志来强制推送,以下所示:
#这个命令要很是当心!
git push --force
这将覆盖远程master分支以匹配rebase过的分支,并使团队的其余成员感到困惑。所以,只有在确切知道本身在作什么时才能很是当心地使用此命令。
rebase能够根据你团队的须要尽多地或少许地整合到你现有的Git工做流程中。在本节中,咱们将了解rebase在功能开发的各个阶段的好处。
任何工做流程git rebase的第一步是为每一个功能建立专用分支。这为你提供了必要的分支结构,以安全地利用rebase:
将rebase加入工做流程的最佳方法之一是清理本地正在进行的功能。经过按期执行交互式rebase,你能够确保功能中的每一个提交都具备针对性和意义。这使你能够写代码而无需担忧将其分解为隔离多个的提交 - 你能够在过后修复它。
调用git rebase时,有两个基(base)选项:feature的父分支(例如master),或feature中的历史提交。咱们在Interactive Rebasing部分看到了第一个选项的示例。当你只须要修复最后几回提交时,后一种选择很好。例如,如下命令仅针对最后3次提交的交互式rebase。
git checkout feature
git rebase -i HEAD~3
经过指定HEAD~3为新的基,你实际上并无移动分支 - 你只是交互式地重写其后的3个提交。请注意,这不会将上游更改合并到feature分支中。
若是要使用此方法重写整个功能,git merge-base命令可用于查找feature分支的原始基。如下内容返回原始基础的提交ID,而后你能够将其传递给git rebase:
git merge-base feature master
交互式rebase的使用是引入git rebase工做流的好方法,由于它只影响本地分支。其余开发人员惟一能看到的就是你的成品,这应该是一个简洁易懂的分支历史记录。
但一样,这仅适用于私有功能分支。若是你经过相同的分支与其余开发人员协做,则该分支是公共的,而且你能重写其历史记录。
在概念部分中,咱们了解了feature分支如何使用git merge或git rebase合并master上游更改。merge是一个安全的选择,能够保留仓库的整个历史记录,而rebase则经过将feature分支移动到master顶端来建立线性历史记录。
这种使用git rebase相似于本地清理(而且能够同时执行),但在此过程当中它包含了那些master上游提交。
请记住,rebase到远程分支而不是master。当与另外一个开发人员协做使用相同的功能而且你须要将他们的更改合并到你的仓库时,就会发生这种状况。
例如,若是你和另外一个名为John的开发人员新增了对feature分支的提交,从John的仓库中获取远程分支后,你的仓库可能以下所示:
你能够用从master集成上游更改相同的方法来解决这个分叉问题:要么用john/feature合并本地feature,或rebase本地feature到john/feature。
请注意,此rebase不违反黄金法则,由于只有你的本地feature提交被移动 - 以前的全部内容都不会受到影响。这就像是“将个人更改添加到John已经完成的工做。”在大多数状况下,这比经过merge与远程分支同步更直观。
默认状况下,git pull命令执行合并,但你能够经过向其传递--rebase选项来强制它与远程分支rebase集成。
在你的团队经过某feature后,你能够选择将该feature rebase到master分支的顶端,而后git merge再将该功能集成到主代码库中。
这是将上游更改合并到功能分支中的相似状况,但因为你不容许在master分支中重写提交,所以你必须最终使用git merge该功能进行集成。可是,经过在合并以前执行rebase,你能够确保合并产生完美的线性历史记录。这也使你有机会压缩在拉取请求期间添加的任何后续提交。
若是你不熟悉git rebase,能够随时在临时分支中执行rebase。这样,若是你不当心弄乱了feature的历史记录,能够查看原始分支,而后重试。例如:
git checkout feature
git checkout -b temporary-branch
git rebase -i master
#[清理历史]
git checkout master
git merge temporary-branch
这就是你须要知道的关于rebase你的分支。若是你更喜欢提交的干净,消除没必要要合并的线性历史记录,那么你在继承另外一分支的更改时应该使用git rebase 而不是git merge。
另外一方面,若是你想保留项目的完整历史记录并避免重写公共提交的风险,你能够仍然使用git merge。这两种选择都是彻底能够的,但至少能够选择利用git rebase有它的好处。