注:译文出自 GitHub edx,经作者 @whilgeek 提醒,对一些错误已纠正。
一些说明
一些词汇表
- rebase 变基
- pull request 拉取请求
- merge 合并
- repository 仓库
- commit 提交
- squash 压缩,挤压
另外
有些词翻译过来反而不如原文精彩,就保留了,比如fork
,push
等等。
(OK正文开始)
当很多人同时在一个工程上工作的时候,一个拉取请求也许很快就会过时。一个“过时”的拉取请求就是一个不再和开发主线保持同步的开发分支,它在合并到工程里面之前需要被更新。拉取请求之所以会过时的最常见原因是因为冲突的存在:如果两个拉取请求修改了同一个文件中的相同的几行,一个拉取请求被合并之后,没被合并的拉取请求和工程之间就会存在一个冲突。有时候,一个拉取请求在没有冲突的情况下也会过时。或许是代码基础中的一个不同文件发生了改动,需要你在拉取请求中做出相应的改变,以保证和新架构一致。又或许是某人意外将失败了的单元测试代码合并进了主分支时创建了新分支。不管是什么原因,如果你的拉取请求已经过时了,在你的分支能被合并之前,你需要将你的分支变基到主分支最新版本。
什么是变基呢?
为了理解这个,我们需要首先理解一点git运行的原理。一个git仓库是一个树形结构,树上的每个点都代表一个提交。这里有一个简单的关于仓库的例子:在主分支上它有4个提交,每个提交都有一个ID(在这里个例子中,ID是a
,b
,c
和d
)。你会注意到d
目前是主分支中最新的提交(或者称为HEAD
)。
这里我们有两个分支,master
和my-branch
。你会看到master
和my-branch
中都包含a
和b
提交。然后它们开始分叉:master
包含c
和d
,而my-branch
包含e
和f
。b
就是所谓的my-branch
相对于master
的合并基础
,或者更一般的说,就是基础
。这很有意义:你可以看到my-branch
是基于master
的一个早期版本。
所以我们称my-branch
是过时的,而且你想让它和master
分支的最新版本保持同步。换种说法,my-branch
需要包含c
和d
。你可以做一个合并操作,但是这会让这个分支包含一些怪异的合并提交,让代码审查更为困难。为了不这样,你可以做一个rebase
操作。
当你变基的时候,git会找到你分支的base(在这个例子中是b
)以及base和HEAD中的所有提交历史(在这个例子中是e
和f
)。并且将这些提交历史在你要变基进去的分支(在这个例子中是master)的HEAD(头提交)上重演一遍。参照你的修改,git创建了一些看起来就像是在master分支顶部进行修改的提交历史:在这个图中,这些提交被叫做e
和f
。git并不会擦除你之前的提交:e
和f
不会有人动的,而且如果在变基过程中出现了某些错误,你完全可以回到之前的状态,就像没变过基一样。
然而,另外需要注意的一点就是,git只把分支当作是标签。主分支就是主标签指向的,同时也是所有提交的祖先。当你给一个分支变基的时候,git移动分支标签指向新创建的分支,my-branch
现在不再指向f
,而是指向f'
。退回到之前状态的方式,仅仅是改变了分支标签,所以它后退并指向了f
。
修改历史
你会注意到从f
到f'
没有一条直接的通路,在其他人看来,历史好像被突然改变了。c
和d
被有效注入了my-branch
分支,就好像它们一直在那里一样。不像其他的版本控制系统,git允许你修改你的项目的历史 -- 但是对这种允许一定要谨慎。我们稍后会讲到这一点。
我怎么才能变基呢?
好吧,现在你知道变基是什么了,下一步就是学习如何去变。假定你已经fork了edx-platform
,而且你创建了一个分支。就像这样:
git clone https://github.com/my-username/dex-platform.git
cd edx-platform
git checkout -b my-branch
在你的分支里你已经做了一些提交,并且将它们push到了Github上,并创建了一个拉取请求。你经历了代码审查,回应了别人的评论,然后某人要求你将你的拉取请求变基,下面是你要做的:
将官方repo作为一个远程分支(仅仅在第一次的时候这样)
在git的定义中,远程分支就是你能提交修改的仓库的克隆。当你从官方的edx/edx-platform
中克隆出一份后。你创建了一个新的,名叫your-username/edx-platform
的仓库
,但是这两个仓库可以共享所作的提交。
为了将edx
添加为一个远程分支,在你的本地仓库中运行:
git remote add edx https://github.com/edx/dex-platform.git
你可以运行git remote -v
来验证是否成功,你应该看到edx
在你的远程分支列表中。记住:这一步在每个克隆过程中只需要执行一次。
抓取主分支的最新版本
你的计算机需要从Github上下载关于这个官方仓库的信息,这样它就会知道主分支的最新版本。然后你的远程分支就建立好了,这很简单。在你的本地仓库中运行:
get fetch edx
Squash你做的改变(可选的)
这一步是可选的,但是很建议你走这一步。这涉及到你在你的分支上所作的所有提交,然后将他们压缩成一个大点的提交。这样做的目的是为了在变基的时候可以更简单地结局冲突,以及让我们来审查你的拉取请求。
为了这样做,我们将会做一个交互式的变基。首先,找到你分支的基础。你可以这样来找:
git merge-base my-branch master
这条命令会返回一个提交哈希。在下面这条命令中使用你得到的哈希:
git rebase --interactive $${HASH}
举例来说,如果你合并的基础是abc123
,你会运行git rebase --interactive abc123
。你的文本编辑器会打开一个文本文件,其中列举了你对你的分支所作的所有提交。 而且,在每个提交之前都有一个写作pick
的单词。就像这样:
pick 1fc6c95 do something
pick 6b2481b do something else
pick dd1475d changed some things
pick c619268 fixing typos
你需要将除了第一行外所有的行之前的“pick”换做“squash”,做完的时候,应当是这样的:
pick 1fc6c95 do something
squash 6b2481b do something else
squash dd1475d changed some things
squash c619268 fixing typos
保存并关闭这个文件,稍等片刻,一个新的文件会在你的编辑器中弹出来:包含着所有提交的所有信息。随意修改这些提交信息,同样的保存并关闭这个文件。保存的这些提交信息会变成一条提交信息,这是由那多条合并而来的。一旦你保存并关闭了这个文件你的这些提交就要被压缩成一个,这一步就完成了!
终于开始变基了!
在你本地分支中运行
git rebase edx/master
git会开始在主分支的最新版本上重演你的提交。这一步你可能会遇到冲突:如果确实遇到了的话,git会暂停并让你在继续之前先解决冲突。就像在合并的时候解决冲突一样:你可以用git status
来看哪些文件存在冲突,编辑这些文件以解决冲突,然后git add
来表示冲突已经解决了。然而,你要运行git rebase --continue
而不是git commit
来告诉git可以继续来重演提交了。如果在做这一步之前,你已经压缩了你的提交,你可以一次性地解决冲突 -- 如果你没有压缩,你会多次解决冲突。
用强推的方式来更新你的拉取请求
就像上面解释的,当你变基的时候,你在修改你的分支的历史。结果就是,如果你在变基之后做一个普通的git push
,git会拒绝它,因为在服务器的分支和你的分支之间没有一条直接的通路。所以,你要使用-f
或--force
标志来告诉git你知道你正在做什么。当你在做强制推送的时候,强烈建议你将你的push.default
设置改为Git2.0中默认的simple
。为了确保你的配置是正确的,运行:
git config --global push.default simple
一旦修改正确,你就可以运行:
git push -f
然后你检查一下你的拉取请求,它应该已经更新啦!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。