左merge,右cherry-pick
之前在使用Git来管理代码时,团队成员基本上是在同一个远程某个分支下建立相应的本地分支后进行开发和提交代码,开发成员可能不会在本地仓库创建本地专属的分支,当然并不说要强制创建,不过还是建议这样处理,它会带来一些好处,后面会讲到。假设当前的代码库有master分支、develop分支,远程和本地都有这两个分支了;此时,在本地仓库创建一个feature分支,后续在feature分支上开发新的需求业务功能,然后合并到本地的develop分支,最后推送到远程的develop分支上入库。在自己本地分支上进行开发,从目前看来至少有两个好处:一是可以让代码不受干扰,专心地完成业务需求功能的实现;二是也是为了方便分支的管理,后期的分支合并会体现其优势。
(一)git merge合并分支非他不可
最近在处理合并分支的事,遇到这样的一个问题。在feature分支上提交了十多次的代码到本地库,每次都有Change-Id和commit id信息,而远程develop分支上已经合入了新的代码,此时从本地feature分支切换到本地develop分支后更新了一下远程develop分支的代码,若之前在feature分支中每次的代码提交都是保证有质量的、没有任何缺陷的,并且远程develop分支新增的代码与本地提交的代码没有任何关联的话,那么在在本地develop分支中输入git merge feature命令后自动完成合并,此时的commit记录没有Change-Id,只需要输入git commit --amend命令就可以对这次的合并生成Change-Id了。下面我将要说的是另外遇到的一种情况:
(1)在feature分支上提交的代码最开始的一次记录就出现了质量问题,导致CI失败,代码没法入库,那么feature分支上后面提交的代码都会在CI时失败,因为它们都是基于首次提交的一个临时分支上来记录数据的,即使回退到到首次提交的版本进行质量修复,然后重新git commit --amend,后面的commit记录也还是基于最原始的一个分支;那我们该如何处理呢?只能是一个一个的commit记录来合并。
- 在feature分支,开出git的命令行工具,输入git log 20 > log.txt,这里是将最近的20(数量可以根据自身的实际情况来设置)条记录日志导出到本地,方便后续git reset命令参考使用。
- 在feature分支上回退到本地首次提交的commit版本,修改代码质量问题,然后合并到本地develop分支,在本地develop分支提交这次合并的记录,然后提交至远程的develop分支;当然你也可以直接在feature分支将这次修改的commit记录提交到远程的develop分支,这种方式只能是在保证此次提交的代码与远程develop分支上的代码不存在冲突的情况下才有效,否则出现冲突是不能合入的,所以还是建议先合并到本地develop分支。
- 在将首次的提交记录合并并提交后,又进入feature分支,利用git reset --hard commitId命令,将代码回退到指定的commitId分支上,继续修改代码质量问题,然后git commit --amend命令重新生成commitId,保持原有的ChangeId;接着将这次的commitId合并并提交到develop分支,操作过程与前一步的操作相同。
- 后续就是不停地在feature分支和develop分支来回切换、合并、提交,操作过程与前面的步骤相同。
(2)本地develop分支合并本地feature分支时出现了代码冲突,在命令行中会出现(develop|MERGING) 的提示,此时你需要修改冲突的代码才能合并,处理步骤如下:
- 在出现冲突的命令行工具中输入git status命令,可以查看到“both modified”的信息,也就是说可以看到那些文件发生了冲突。
- git diff 冲突的问题,可以看到是在哪里发生了冲突,通过vim命令就可以进行修改,修改后git add 已修复的冲突问题,这样把修复的文件添加到修改记录中。
- 等每个冲突的问题修改好,输入git commit 命令完成合并,此时这条commit记录是没有ChangeId的,需要重新输入git commit --amend命令完成ChangeId的生成。
- 最后就是将合并的代码提交到远程develop分支,git push origin HEAD:refs/for/develop%r=评审人邮箱,进入gerrit流程。
(二)git cherry-pick抓取commit非他莫属
有这样一个场景,在feature分支上有了很多新的commit记录,而此时develop分支只想要从feature分支上抽取某个commit的提交内容,此时就需要借助cherry-pick命令了,它的用法是:
- 首先切换到feature分支,通过git log 查找到需要的commit-Id号,记录下来;
- 然后切换到develop分支,通过命令git cherry-pick commit-Id,此时会在develop分支上产生一条新的commit记录,这就相当于一条记录既在feature分支上进行了提交,又在develop分支上进行了提交,不过这两次的Change-Id是相同的;
- 若没有遇到冲突的话,就完成commit的抽取,若是遇到冲突的话,请按如下步骤操作:
- 首先,git status命令查看有哪些文件发生了冲突,然后进行修改;
- 接着,修改好冲突的文件后通过git add 命令将已修改的冲突文件添加到index区;
- 最后,通过git cherry-pick --continue命令,就可以完成这次commit的抽取。
在上述merge合并的场景中也有可能会运用cherry-pick命令的,比如从feature分支一次性merge了很多commit记录到develop分支,但是由于首次的commit记录出现了代码质量问题,导致develop分支走CI时不通过,此时必须要修复首次commit提交的内容后才能让后续合并的commit能够正常完成CI,若是采用(一)中方法的话,工作量大不说,还容易出错,并且也会多处好多merge的gerrit记录和CI记录;这个时候就该cherry-pick出场了,在这里它就发挥了明显的优势,在首次commit的代码修复后,后续只要在develop这个分支上通过循环使用git cherry-pick命令将feature分支上剩余的commit记录重新提交到develop分支上来,这样就不要修改太多的东西,并且都会根据原有的Change-Id进行CI流程,不会产生多余的gerrit记录和CI记录。
(三)merge和cherry-pick的差异
根据目前的应用来看,merge时每个commit记录有强耦合性,最新的一个commit都会基于前一个父commit;而cherry-pick时就可以灵活选取commit,并且还可以切换commit的父commit节点,不会让某个commit强依赖某个父commit,每次cherry-pick都是将某次的commit提交的内容重新提交到新的分支上来,也就是说它可以更改commit的父commit-Id。