之前写了一片介绍git基本用法的文章,涵盖了我们平时常用的80%的应用场景。这里介绍rebase和cherry-pick命令的运用及一些应用场景。
rebase
在我的理解里面,rebase主要作用就是让git日志变的好看,他主要有以下两个应用场景:
应用场景1:把多条提交日本合并成一条提交日志。
我们现在要做一个把大象装进冰箱的功能。 首先先从master分支上面切一个叫做 "elephant" 的分支。
git checkout -b elephant
然后新建文件"e.txt" ,并且编写第一步代码:“打开冰箱门”,提交git,提交信息为"first"。
echo "open the fridge's door" > e.txt
git add .
git commit -m "first"
再编写第二部代码:"把大象塞进冰箱",提交git,提交信息为"second"。
echo "get the elephant into the fridge" >> e.txt
git add .
git commit -m "second"
再编写第二部代码:"关上冰箱门",提交git,提交信息为"third"。
echo "close the fridge's door" >> e.txt
git add .
git commit -m "third"
我们查看一下log日志

当我们把这个分支合并到master上之后,这三个日志会在master的历史纪录中,但这样看起来语意并不明确。如果我们把这三个提交合并成一个,然后修改信息为“elephate into the fridge” 是不是会更加明确。如何做呢?
git rebase -i HEAD~3
合并最后三个提交的历史纪录,这时会产生一个临时文件。

红框中表示需要修改的历史版本信息(第一个参数为命令,第二个参数为版本号,第三个参数为版本说明),绿框中的为可以使用的命令。
我们把红框中的第一行第一个参数改成"r" 与绿框中的"# r, reword = use commit, but edit the commit message" 对应,表示使用此次提交,并且编辑提交信息。
第二行第三行的第一个参数改成"f", 与绿框中的 "# f, fixup = like "squash", but discard this commit's log message"对应,表示合并到上一次提交,并且丢弃提交的信息。
修改完后文件如下:

保存文件。 然后出现第二个临时文件

修改绿色框中的内容: “elephate into the fridge”

保存文件

应用场景2: 合并分支,让日志显示更加人性化,方便回滚
一般我们合并分支,会使用merge命令,merge命令的原理如下,会在提交的分支后面再增加一个合并分支:

而如果我们使用rebase命令,会把公共分支放在当前分支之前。

rebase相对于merge的好处是日志更加清晰了,便于回滚。而坏处在于更容易引起冲突。
接下来我们做一个栗子,来说明一下情况。 我们新建一个分支,然后新建一个"readme.md"文件,写入一行内容,并且提交到资源库。
git init
echo readme > readme.md
git add .
git commit -m "init data"
使用更新日志如下:

接下来我们创建两个分支,分别取名 "merge-test"和"rebase-test"
git branch merge-test
git branch rebase-test
在master分支新增三行"11","22","33", 每一行提交一次,日志信息与修改的信息相同。
echo '11' >> readme.md
git commit -am "11"
echo '22' >> readme.md
git commit -am "22"
echo '33' >> readme.md
git commit -am "33"
readme.md文件内容如下:

master分支的日志如下:

现在我们切换到merge-test分支,和master上做类似的操作,新增三行"11","22","33", 每一行提交一次,日志信息与修改的信息相同。 然后合并到 rebase-test 分支。
git checkout merge-test
echo 'aa' >> readme.md
git commit -am "aa"
echo 'bb' >> readme.md
git commit -am "bb"
echo 'cc' >> readme.md
git commit -am "cc"
git checkout rebase-test
git merge/rebase merge-test
readme.md文件内容如下:

merge-test和rebase-test分支日志如下:

ps:当被合并分支(rebase-test)未做修改,则rebase和merge分支产生的日志一样。
好了,我们现在准备工作已经做好了,现在我们分别用merge和rebase的方式把master分支合并到merge-test和rebase-test中。
merge的栗子
先看merge合并, 会产生冲突。
git checkout merge-test
git merge master

我们解决一下冲突,并查看一下日志:
git add .
git commit -m "merge"
git log --oneline --graph

我们发现这个日志文件分叉了,现在我们试着回滚到 "bb", 看会发生什么。
git reset --hard febfb96
git log --oneline --graph
日志文件如下:

readme.md文件如下:

我们发现之前从master分支合并的代码没有了,需要再合并一次。如果合并的分支越多,情况很复杂,可能就比较难找到到底需要回滚到哪个分支了。
rebase的栗子
现在我们来研究一下"rebase"。一样切换到rebase-test分支, 使用rebase 合并 master分支。
git checkout rebase-test
git rebase master
有冲突,我们发现是“aa”的提交与 master上的提交相互比较

我们选择接受全部,并且把"aa" 放在 "11" 之前, 调整成如下样子:

git add .
git rebase --continue
然后因为我们修改完的代码和 "bb"比较,继续会有冲突:

继续解决

git add .
git rebase --continue
然后和"cc"比较,又有冲突

继续解决:
git add .
git rebase --continue
解决好了之后,我们查看一下日志:

我们发现日志并没有分叉。现在我们试着回退到 "bb"
git reset --hard fe97c31

我们发现master分支上的内容依然保留着。
小结
-
rebase 可以把多次提交合并成1次
-
rebase 和 merge 都可以用来合并分支
2.1)当被合并分支没有修改时,rebase和merge效果一样
2.2)如果两个分支都有修改,使用merge会在被合并分支后面增加一次提交, 冲突也只需要解决1次,但日志分叉,不对回滚不友好。
2.3)如果两个分支都有修改,使用rebase在把合并分支与被合并分支的提交逐个合并, 冲突有可能需要解决多次,但日志无分叉,对回滚友好。
-
rebase 和 merge如何区分使用 3.1) 上游分支往下游分支更新代码时使用 "rebase" (master 往 dev合并时) 3.2) 下游往上游更新代码时使用 "merge" (dev 往 master 合并时)
-
使用git pull 默认使用merge方式,如果需要使用 rebase 方式,使用命令 " git pull --rebase"
-
推荐使用的合并分支的命令(当工作分支为tmp)
5.1)把本地的几次提交合并为一次,减少解决冲突的次数
git rebase -i HEAD~n (其中n看情况而定)5.1)从远程合并分支到本地,这里分为两种情
5.1.1)从线上远程tmp分支合并到本地tmp分支git pull --rebase5.1.2) 从远程master分支合并到本地master分支,再把master合并到本地tmp分支
git checkout master git pull --rebase git checkout tmp git rebase master5.2) 更新远程的tmp
git push5.3) 把tmp分支合并到master上
git checkout master git merge tmp
cherry-pick
此命令英译过来是挑樱桃的意思,就是在一些樱桃里面,挑选出好的樱桃。在git中,就是把A分支中一些好的提交挑选出来合并到B分支上面。
我们先来用一个栗子,来看一下这个命令是怎么用的。 我们还是用上一个节提到的master分支,先新建两个分支 cp-master 和 cp-dev, 然后修改cp-dev,再把cp-dev中的某些修改合并到cp-master上。
git branch cp-master
git checkout -b cp-dev
然后新增三个次提交
1) 最后一行新增 “xyz” 2) 把 "xyz" 改成 "xaz" 3) 第二行新增 "789"
echo xyz >> readme.md
git add .
git commit -m "xyz"
vi readme.md
## edit start ##
readme
11
22
33
xaz
## edit end ##
git commit -am "xyz to xaz"
## edit start ##
readme
789
11
22
33
xaz
## edit end ##
git commit -am "789"
最终的日志为如下

最终的文件内容如下:

接下来我们切换到 "cp-master"分支。
1)把第三次提交(789)更新到cp-master上
git checkout cp-master
git cherry-pick 6830e28
git log --oneline --graph

“789”这次提交已经更新上去了,文件内容为:

2)把第二次提交更新到cp-master上
git cherry-pick 20ee544
我们发现有冲突

解决一下冲突
git add .
git cherry-pick --continue
git log --oneline --graph

最后的文件如下:

我们对比发现两个分支的最后的文件基本相同,但是提交历史完全不一样。
cherry-pick 使用场景
cherry-pick使用场景主要有2个
1) 切磋分支的情况,比如上游分支原来是master,不小心切了dev分支,现在要更新到master上面,那只能用cherry-pick把需要提交找出来更新上去。
2) 类似svn模式提交。 svn模式没有分支的概念,所有的代码都在一起,只能通过版本号来区分环境,所以需要挑选特定的版本号来更新。当然这种方式比较out,不建议使用。
注意:cherry-pick不建议在一般场景下使用。