git补漏之rebase和cherry-pick

3,071 阅读8分钟

之前写了一片介绍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分支上的内容依然保留着。

小结

  1. rebase 可以把多次提交合并成1次

  2. rebase 和 merge 都可以用来合并分支

    2.1)当被合并分支没有修改时,rebase和merge效果一样

    2.2)如果两个分支都有修改,使用merge会在被合并分支后面增加一次提交, 冲突也只需要解决1次,但日志分叉,不对回滚不友好。

    2.3)如果两个分支都有修改,使用rebase在把合并分支与被合并分支的提交逐个合并, 冲突有可能需要解决多次,但日志无分叉,对回滚友好。

  3. rebase 和 merge如何区分使用 3.1) 上游分支往下游分支更新代码时使用 "rebase" (master 往 dev合并时) 3.2) 下游往上游更新代码时使用 "merge" (dev 往 master 合并时)

  4. 使用git pull 默认使用merge方式,如果需要使用 rebase 方式,使用命令 " git pull --rebase"

  5. 推荐使用的合并分支的命令(当工作分支为tmp)

    5.1)把本地的几次提交合并为一次,减少解决冲突的次数

        git rebase -i HEAD~n (其中n看情况而定)
    

    5.1)从远程合并分支到本地,这里分为两种情

    5.1.1)从线上远程tmp分支合并到本地tmp分支
    
        git pull --rebase
    

    5.1.2) 从远程master分支合并到本地master分支,再把master合并到本地tmp分支

        git checkout master
        git pull --rebase
        git checkout tmp
        git rebase master
    

    5.2) 更新远程的tmp

        git push
    

    5.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不建议在一般场景下使用。