注意:本文将逐渐完善更新…因为rebase在工作中多人合作开发中真的很重要

1:背景描述

rebase中最需要注意的地方就是代码冲突问题,产生冲突的原因在于merge的时候和他人修改了同一行代码,这就会导致使用git merge的时候不知道应该听谁的,使得git服务疑惑不知道谁是正确的。

废话不多说,如下案例
假设时间点今天上午10点,我从最新的代码仓中的master分支下在本地创建了一个新的开发分支,就命名为branch_1好了,然后我就着手在该分支下进行开发,然后到了中午12点,远程的代码仓master分支被其他同事merge了他人的分支,此时,命名更新的master分支为master_1,然后到了下午2点钟,又有新的他人的分支被merge到master分支了,此时master分支已经变成了master_2,为了减少和大家的代码冲突,我尽快开发完成,在下午4点完成了代码的开发和自测,这个过程的简图如下,
在这里插入图片描述
即你在开发后准备merge的时候,你的branch_1依赖的master已经不存在了,更新了很多个版本,在你准备merge时已经变为master_2了,此时你准备merge,就会存在和别人(master_1中提交的,或者master_2中提交的)修改同一行的情况,毕竟你的代码开发基准是master,所以在自己的代码能够安全的merge到当前的master分支中,我们需要对当前的master分支(master_2)进行rebase,即字面含义,重新定基准为当前的节点master_2。这时问题就来了,如果确实两次merge的时候有修改到同一行,那就要解决冲突,没有冲突那自然万事大吉。

2:操作过程

直接上操作,过程如下:

第一步:rebase前准备

git status    # 先查看下当前为提交的自己本地分支修改状态

结果如下,

On branch branch_1
Your branch is ahead of 'origin/master' by 75 commits.
  (use "git push" to publish your local commits)
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   file1.txt
        modified:   file2.txt
        modified:   readme.md
        modified:   test.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        test1.py

no changes added to commit (use "git add" and/or "git commit -a")

如上述结果显示,当前工作区有些明文件没提交,还有新增的文件没有add到工作区,先搁置,我们先保存一下当前的branch_1状态,

git stash

分支的状态被保存了

Saved working directory and index state WIP on vgw_config_by_cmd: 9c382fd Merge branch 'branch_1' of http://xxxxxxxxx/itemname into branch_1
HEAD is now at 9c382fd Merge branch 'branch_1' of http://xxxxxxxx/itemname into branch_1

此时,我们就可以在不提交当前本地开发代码的状态下安全的切换到其他分支,至于为什么要切换到其他分支,后面就清楚了。
接着,我们在当前远程的master_2的分支下在本地创建一个新的分支,为了方便,本地也叫master_2(为了避免误会,其实一个项目中只需要一个叫master分支就ok,只要定时pull到最新即可,无需每次递增序列创建很多个master分支,这里为了方便举例,命名为master_2),即

git checkout -b master_2 origin/master

此时本地就会存在master_2分支和自己的开发分支branch_1了,可以自行git branch -l查看。
然后请尽量通过以下步骤,将本地的master_2分支和远程分支保持一致和最新,

git fetch
git pull

第二步:开始rebase(注意rebase途中可以通过git rebase --abort终止rebase并回退到为rebase的状态)

这里需要注意的是,rebase可以直接和远程的master分支rebase,但是不建议这样操作,在第一步中我们在本地创建了和远程master分支一样的master_2分支,我们用这里的进行rebase也会有同样效果,更安全。
检查第一步的操作结果,发现只有两个分支,且当前所在分支为master_2

$ git branch -l
  * master_2
    branch_1

git pull后代码为最新状态,此时可以切换回branch_1分支上

git checkout branch_1

然后直接和本地的master_2分支进行rebase

git rebase master_2

没有冲突万事大吉,有冲突就要解决冲突,如下就是有冲突了

$ git rebase master_2
First, rewinding head to replay your work on top of it...
Applying: branch_1
Applying: low level function packing
Applying: move reload method to parent class
Applying: rebase and rebuild vgw command
Using index info to reconstruct a base tree...
A       filename1.conf  
A       filename2.conf
M       filename3.conf
Falling back to patching base and 3-way merge...
Auto-merging filename3.conf
Auto-merging filename4.conf
CONFLICT (content): Merge conflict in filename4.conf
error: Failed to merge in the changes.
Patch failed at 0004 rebase and rebuild
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

xxxxxx (branch_1|REBASE 4/8)

如上述结果显示,自动合并改动文件8个,但是执行到filename4出现了冲突导致rebase暂停,冲突出现在filename4.conf上,此时我们打开项目文件filename4.conf时会发现很多地方出现了如下的字样

HEAD
1+1=2
<<<
===
2+2=4
>>>>branch_1

注意,这里就是冲突的地方,一般情况下HEAD后等号前的都是被rebase的分支内容(例如本例中git rabase master_2中,HEAD和等号间的是master_2的内容,等号和>>间是本地分支的内容,请一定要注意不要都删掉了,找准哪个是自己应该保留的),等号后的是那个当前分支branch_1和master_2分支冲突的内容,说明我们的代码和已经稳定的master分支有了同一行代码的更改,
如果这个冲突的部分是都是自己写的,那么可以酌情直接删掉多出来的符号即可(删掉HEAD,=,<>)如果冲突的代码有其他人合作的,就要和对方沟通一下,哪些是需要保留的,以及如何安排branch_2分支的内容的位置。如果当前文件冲突已经解决,直接git status查看修改文件状态

$ git status
rebase in progress; onto ade5ada
You are currently rebasing branch 'branch_1' on 'ade5ada'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   filename1.conf
        modified:   filename2.conf
        modified:   filename3.conf

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add <file>..." to mark resolution)

        both modified:   filename4.conf


xxxxxxxxxxxxxxxxxxx(branch_1|REBASE 4/8)

如上结果说明,刚才修改了文件filename4.conf文件,此时仍处于rebase状态,卡在第4个文件上,所以我们要将解决冲突后的filename4文件add到工作区,然后继续rebase进程

git add filename4.conf
git rebase --continue

结果如下

Applying: rebase and rebuild
Applying: branch_1
Using index info to reconstruct a base tree...
M       filename3.conf
Falling back to patching base and 3-way merge...
Auto-merging filename3.conf
CONFLICT (content): Merge conflict in filename3.conf
error: Failed to merge in the changes.
Patch failed at 0005 branch_1
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".


xxxxxxxx(branch_1|REBASE 5/8)

上述结果说明filename3也有冲突,同样解决冲突后git add filename3.conf后继续rebase

git add filename3.conf
$ git rebase --continue
Applying: vgw config by cmd
No changes - did you forget to use 'git add'?
If there is nothing left to stage, chances are that something else
already introduced the same changes; you might want to skip this patch.

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

xxxxxxxxxx(branch_1|REBASE 5/8)

此时继续rebase后没有给出新的冲突信息,只是仍然处于rebase状态,这个时候可以直接git status一下

$ git status
rebase in progress; onto ade5ada
You are currently rebasing branch 'branch_1' on 'ade5ada'.
  (all conflicts fixed: run "git rebase --continue")

nothing to commit, working tree clean

xxxxxxxxx(branch_1|REBASE 5/8)

说明当前的工作区是干净的,没有需要add的文件,此时可以直接使用如下方法跳过当前的patch

$ git rebase --skip
Applying: low level function packing
Using index info to reconstruct a base tree...
M       filename2.conf
Falling back to patching base and 3-way merge...
Auto-merging filename2.conf
CONFLICT (content): Merge conflict in filename2.conf
error: Failed to merge in the changes.
Patch failed at 0006 low level function packing
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".


xxxxxxxxxx(branch_1|REBASE 6/8)

此时rebase开始继续下一个文件的检查,发现filename2文件冲突,同样解决冲突后

$ git add filename2.conf

xxxxxxxxxx (branch_1|REBASE 6/8)
$ git rebase --continue
Applying: low level function packing
Applying: move reload method to parent class
Using index info to reconstruct a base tree...
M       filename2.conf
Falling back to patching base and 3-way merge...
Auto-merging filename2.conf
CONFLICT (content): Merge conflict in filename2.conf
error: Failed to merge in the changes.
Patch failed at 0007 move reload method to parent class
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".


xxxxxxxxxx (branch_1|REBASE 7/8)

发现还是filename2有冲突,那么继续解决冲突,然后add,再continue

$ git add filename2.conf

xxxxxxxxxx(branch_1|REBASE 7/8)
$ git rebase --continue
Applying: move reload method to parent class
Applying: rebase
Using index info to reconstruct a base tree...
M       filename2.conf
M       filename5.conf
M       filename4.conf
Falling back to patching base and 3-way merge...
Auto-merging filename2.conf

xxxxxxxxxxx (branch_1)

惊讶不!!!冲突已经解决完了,说明rebase结束了,但是我们的任务还没结束,因为我们还没有提交我们本地代码呢,不知道还记得不当初保存了本地的开发代码了吗??

第三步:弹出本地代码缓存

在当前开发分支branch_1执行命令

$ git stash pop
On branch branch_1
Your branch and 'origin/master' have diverged,
and have 134 and 4 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   filename6.conf
        modified:   filename3.conf
        modified:   filename2.conf
        modified:   filename7.conf

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (36eaa1d566c24e7e1adbaef9df0be545c5f18716)

此时有可能还有冲突,不过上面的冲突都已经解决了,这里的应该都是小儿科了吧,解决完后再add,commit和push(注意push可能需要强制提交加个-f,前提是要确保安全的情况下)

后记

其实,也可以不事先git stash保存本地分支开发内容,直接git rabase master_2分支,但是这样的话容易导致解决冲突时分不清哪里是自己的新开发的内容,会一定程度上产生混乱,所以我个人都是先stash解决和最新的master分支冲突后,再git stash pop弹出缓存,再解决一遍冲突。

如果实在不想解决冲突,那就每次开发完后保存代码,然后每次下拉新的代码,再将自己的代码替换到新的分支上,这样也是一种逃避方法,虽然可耻,但是有用。

本文还会继续更新和完善,有疑问或者讨论的请评论区探讨。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐