对Git Fast-Forword和Rebase理解

四 24 九月 2015 | tags: git

问题

遇到什么问题?

之前日常操作都是用git pull来获取远程代码并且自动合并,如果和远程代码有分叉的话,就会出现一些奇怪的提交,如下面的提交记录:

geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit 0c2df7467d30c46ee4884eb170cf4a521918e7d3
Merge: 2ce37bb 236e1d3
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:19:49 2015 +0800

    Merge branch 'master' of /home/geesun/test/remote

很多情况下,我们并不希望有这样的自动合并产生,因为他产生了一个自动提交,会让版本变得交叉,不清晰。

问题如何产生?

Step 1:本地代码从远程服务器的同步完成后又提交了B,这是本地代码提交记录:

geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit 2ce37bbfd25d5e0265cda4949b16f0110a4f45e7
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:40 2015 +0800

    Check in by B

commit 9d34ed98d4478fa52424498940c962f9b0d921e2
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:17:02 2015 +0800

    base 

Step 2: 远程服务后来又被另外一个人提交了A,远程服务器的提交记录如下:

geesun@geesun-OptiPlex-3010:~/test/src/remote$ git log
commit 236e1d369761e180e4928a717c180d5cb776ba02
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:19 2015 +0800

    Check in by A

commit 9d34ed98d4478fa52424498940c962f9b0d921e2
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:17:02 2015 +0800

    base

Step 3:这个时候我要在本地push代码到远程服务器,会被reject,必须先同步才能push,就用git pull同步,操作结果如下:

geesun@geesun-OptiPlex-3010:~/test/src/local$ git pull 
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
来自 /home/geesun/test/remote
   9d34ed9..236e1d3  master     -> origin/master
Merge made by the 'recursive' strategy.
 local1 | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 local1
geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit 0c2df7467d30c46ee4884eb170cf4a521918e7d3
Merge: 2ce37bb 236e1d3
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:19:49 2015 +0800

    Merge branch 'master' of /home/geesun/test/remote

commit 2ce37bbfd25d5e0265cda4949b16f0110a4f45e7
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:40 2015 +0800

    Check in by B

commit 236e1d369761e180e4928a717c180d5cb776ba02
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:19 2015 +0800

    Check in by A

commit 9d34ed98d4478fa52424498940c962f9b0d921e2
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:17:02 2015 +0800

    base

这个时候别人在从远程服务器同步代码之后,都会看到开始那条本不该存在的提交记录.

如何解决这个问题

在本地代码推送到远程服务器之前,对本地代码进行rebase即可,操作记录:

geesun@geesun-OptiPlex-3010:~/test/src/local$ git pull -r 
首先,重置头指针以便在上面重放您的工作...
正应用:check in by B
geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit 2ce37bbfd25d5e0265cda4949b16f0110a4f45e7
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:40 2015 +0800

    check in by B

commit 236e1d369761e180e4928a717c180d5cb776ba02
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:19 2015 +0800

    Check in by A

commit 9d34ed98d4478fa52424498940c962f9b0d921e2
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:17:02 2015 +0800

    base check in

什么是Fast-Forward

要理解rebase,就先要搞清楚Fast-Forward的概念. 如果本地代码在跟远程同步之后,本地没有新的提交,而远程代码有新的提交,这就是说本地代码和远程的代码没有出现分叉,只不过是本地代码指向比较旧的版本而已.这种情况我们就称为本地代码可以做Fast-Forward.

如下:

BASE--->A      <--- [Local master]
                \
                 \
                  B   <--- [Remote master]

操作记录:

远端:

geesun@geesun-OptiPlex-3010:~/test/src/remote$ git log 
commit 257d73fdbbfbaaaac18a398e46bf296cc01436ca
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:00:40 2015 +0800

    check in by B

commit 236e1d369761e180e4928a717c180d5cb776ba02
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:19 2015 +0800

    check in by A

commit 9d34ed98d4478fa52424498940c962f9b0d921e2
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:17:02 2015 +0800

    base

本地:

geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit 236e1d369761e180e4928a717c180d5cb776ba02
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:19 2015 +0800

    check in by A

commit 9d34ed98d4478fa52424498940c962f9b0d921e2
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:17:02 2015 +0800

    base
geesun@geesun-OptiPlex-3010:~/test/src/local$ git pull 
更新 236e1d3..257d73f
Fast-forward
 local2 | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 local2
geesun@geesun-OptiPlex-3010:~/test/src/local$ git log
commit 257d73fdbbfbaaaac18a398e46bf296cc01436ca
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:00:40 2015 +0800

    check in by B

commit 236e1d369761e180e4928a717c180d5cb776ba02
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:18:19 2015 +0800

    check in by A

commit 9d34ed98d4478fa52424498940c962f9b0d921e2
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 14:17:02 2015 +0800

    base 

这里的git pull相当于 git fetch和git merge origin/master的组合.

因为这种情况是可以做fast-forward,所以git merge就相当于只是做了fast-forward而已.

开始问题描述的就是一种不可以做fast-forward的情况,如果再用git merge则就会产生一个自动提交,如开始问题描述的一样.

有些时候我们希望能fast-forward就fast-forward,否则我们用后面会讲的rebase命令合并。可以这样:

git pull --ff-only

或者

git fetch
git merge --ff-only origin/master

如果不能fast-forward, merge 操作会终止。

什么是Rebase

Rebase就是同步本地和远程的分支,使它们的base一样,然再应用本地没有推送的提交.

git rebase是时做了如下三件事情:

  1. 把本地未推送到远端的所有提交,放到暂存区。
  2. 然后把本地的指针指向远端的最新提交,也就使它和远端的提交一样.
  3. 然后将暂存区的提交挨个挨个提交到本分支. 如果出现冲突,会需要手工merge.

远端提交记录:

geesun@geesun-OptiPlex-3010:~/test/src/remote$ git log 
commit 0ab394dc2ac5a4d186eb9be86da4c72987d3023a
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:37:37 2015 +0800

    Check in by A

commit 88e8afd5519fe8eaf7b9e3bb04635567d83eeeee
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:36:30 2015 +0800

    base

本地提交记录:

geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit df94a3c6cf05c63a7b9db6fd753ef70b1d9a40df
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:45:21 2015 +0800

    Check in by B

commit 88e8afd5519fe8eaf7b9e3bb04635567d83eeeee
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:36:30 2015 +0800

    base

对本地进行rebase:

geesun@geesun-OptiPlex-3010:~/test/src/local$ git pull -r 
首先,重置头指针以便在上面重放您的工作...
正应用:Check in by B
geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit 59e14ba0bb2e55401ca0b2d8c309f520843e8144
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:45:21 2015 +0800

    Check in by B

commit 0ab394dc2ac5a4d186eb9be86da4c72987d3023a
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:37:37 2015 +0800

    Check in by A

commit 88e8afd5519fe8eaf7b9e3bb04635567d83eeeee
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:36:30 2015 +0800

    base

Rebase后的结果也许是我们希望的结果,而不是开始问题描述的那样.

这里的git pull -r 相当于 git fetch和git rebase的组合.

如果rebase出现冲突,会需要手工merge

手工merge操作日志:

geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit e6bb6173b626f8ef599b86516a0e35e4cbb7bc5a
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:51:02 2015 +0800

    Check in by B

commit 88e8afd5519fe8eaf7b9e3bb04635567d83eeeee
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:36:30 2015 +0800

    base
geesun@geesun-OptiPlex-3010:~/test/src/local$ git rebase
首先,重置头指针以便在上面重放您的工作...
正应用:Check in by B
使用索引来重建一个(三路合并的)基础目录树...
转而在基础版本上打补丁及进行三路合并...
自动合并 test.txt
冲突(添加/添加):合并冲突于 test.txt
无法合并变更。
补丁失败于 0001 Check in by B
失败的补丁文件副本位于:
   /home/geesun/test/src/local/.git/rebase-apply/patch

当您解决了此问题后,执行 "git rebase --continue"
如果您想跳过此补丁,则执行 "git rebase --skip"
要恢复原分支并停止变基,执行 "git rebase --abort"

geesun@geesun-OptiPlex-3010:~/test/src/local$ vi test.txt
geesun@geesun-OptiPlex-3010:~/test/src/local$ git add test.txt
geesun@geesun-OptiPlex-3010:~/test/src/local$ git rebase --continue
正应用:Check in by B
geesun@geesun-OptiPlex-3010:~/test/src/local$ git log 
commit 0a68c730ee96d4dd37d1dd97f52a5fcb4be383e4
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:51:02 2015 +0800

    Check in by B

commit 0ab394dc2ac5a4d186eb9be86da4c72987d3023a
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:37:37 2015 +0800

    Check in by A

commit 88e8afd5519fe8eaf7b9e3bb04635567d83eeeee
Author: Geesun Xu <geesun@gmail.com>
Date:   Thu Sep 24 16:36:30 2015 +0800

    base

不同分支之间也可以做rebase,应该不太常用,就不做例子了.

总结

在本地很远端不能做fast-forword的情况下,是采用git merge 还是采用git rebase要看项目的要求. 如果想保持完整的提交记录,可以考虑用git merge. 要想保持清晰的提交记录,采用git rebase会比较好.

git pull默认的行为是merge,如果要更改这种默认行为为rebase,可以用

git config --global branch.autosetuprebase always

Comments !

个人链接