##热身
该Blog起源于最近和朋友讨论关于Git的一些特性, 在讨论中发现他对Git多人协作当中的模型并不是很熟悉.
如果你是Git的初学者, 建议先去Git-Learn-Branching 玩玩前2~3个Level, 了解下rebase , pull
如果该项目只有 你一人开发, 那么git的pull是不会有问题的.
##背景介绍
当前, 我们有2位开发者: sunus , vivian
他们想进行pair programming一个项目. 并且该项目是由god 发起的,已有2次commits.
他们会将对新的代码提交到dev 分支上, 之后由god 将新代码合并到稳定分支master
sunus@mbp~[/private/var/tmp/git-pull/awesome-project] ( master ✔)
[ 22:36]:cat git.c
#include <stdio.h>
int main()
{
printf ( "Hello Git!" ) ;
return 0;
}
sunus@mbp~[/private/var/tmp/git-pull/awesome-project] ( master ✔)
[ 22:36]:git log
commit 163a6d700226b780b7852a79fe1370a6d38c819a
Author: god <god@mbp>
Date: Mon Dec 9 22:13:15 2013 +0800
remove FILE
commit d22bf163d093afb494ad619d8964572e55c73167
Author: god <god@mbp>
Date: Mon Dec 9 22:11:40 2013 +0800
write first lines of codes
commit 45f2016a51ce7b8317e074a961647c091a50cd94
Author: sunus <god@mbp>
Date: Mon Dec 9 22:04:41 2013 +0800
add first file
sunus@mbp~[/private/var/tmp/git-pull/awesome-project] ( master ✔)
[ 22:48]:git branch
dev
* master
PS, 在命令行的末端会显示我们当前所在的branch, 比如在这儿是master.
PSS, branch之后的符号是表示当前的branch是否有被修改但是还没commit的内容: ✔表示没有, ⚡表示有.
现在, sunus , vivian 他们分别将项目clone到他们的本地.
[ 22:52]:echo "I am sunus:)"
I am sunus:)
sunus@mbp~[/private/var/tmp/git-pull]
[ 22:52]:git clone awesome-project sunus
Cloning into 'sunus' ...
done .
Checking connectivity... done
sunus@mbp~[/private/var/tmp/git-pull]
[ 22:52]:cd sunus
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ✔)
[ 22:52]:ls
git.c
sunus@mbp~[/private/var/tmp/git-pull]
[ 22:54]:echo "I am vivian ^^"
I am vivian ^^
sunus@mbp~[/private/var/tmp/git-pull]
[ 22:55]:git clone awesome-project vivian
Cloning into 'vivian' ...
done .
Checking connectivity... done
sunus@mbp~[/private/var/tmp/git-pull]
[ 22:55]:cd vivian
sunus@mbp~[/private/var/tmp/git-pull/vivian] ( master ✔)
[ 22:55]:ls
git.c
好了, 现在开始Pair Progamming:)
####sunus 写了一些代码, 并且在本地分支 有2个commits.
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ⚡)
[ 23:07]:git diff
diff --git a/git.c b/git.c
index 7d26397..127e99a 100644
--- a/git.c
+++ b/git.c
@@ -2 ,6 +2,8 @@
int main()
{
+ void * p;
printf ( "Hello Git!" ) ;
+ printf ( "I am sunus and I am here with vivian" ) ;
return 0;
}
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ⚡)
[ 23:07]:git add git.c
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ⚡)
[ 23:09]:git commit -m 'I add a intro'
[ master 1c0b75b] I add a intro
1 file changed, 1 insertion( +)
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ✔)
[ 23:09]:
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ⚡)
[ 23:13]:git diff
diff --git a/git.c b/git.c
index 668a8f3..50aae80 100644
--- a/git.c
+++ b/git.c
@@ -1 ,8 +1,16 @@
#include <stdio.h>
+void * magic()
+{
+ return ( void * ) magic;
+}
+
int main()
{
+ void * p;
printf ( "Hello Git!" ) ;
- printf ( "I am sunus and I am here with vivian" ) ;
+ printf ( "I am sunus and I am here with vivian \n " ) ;
+ p = magic() ;
+ printf ( "I will show you a magic: %p" , p) ;
return 0;
}
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ⚡)
[ 23:13]:git add git.c
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ⚡)
[ 23:13]:git commit -m 'show you a magic'
[ master 6df90b8] show you a magic
1 file changed, 9 insertions( +) , 1 deletion( -)
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ✔)
以下是sunus 的log
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ✔)
[ 23:19]:git log
commit 6df90b8dc07988fb9590100338af6897d119ca1b
Author: sunus <sunuslee@gmail.com>
Date: Mon Dec 9 23:14:07 2013 +0800
show you a magic
commit 1c0b75b60d68ccc58eca5519f7fd15912277be84
Author: sunus <sunuslee@gmail.com>
Date: Mon Dec 9 23:09:41 2013 +0800
I add a intro
commit 163a6d700226b780b7852a79fe1370a6d38c819a
Author: god <god@mbp>
Date: Mon Dec 9 22:13:15 2013 +0800
remove FILE
commit d22bf163d093afb494ad619d8964572e55c73167
Author: god <god@mbp>
Date: Mon Dec 9 22:11:40 2013 +0800
write first lines of codes
commit 45f2016a51ce7b8317e074a961647c091a50cd94
Author: god <god@mbp>
Date: Mon Dec 9 22:04:41 2013 +0800
add first file
####vivian 也写了一些代码, 并且在本地分支 有1个commit
sunus@mbp~[/private/var/tmp/git-pull/vivian] ( master ⚡)
[ 23:25]:git diff
diff --git a/git.c b/git.c
index 7d26397..003e1ee 100644
--- a/git.c
+++ b/git.c
@@ -3 ,5 +3,6 @@
int main()
{
printf ( "Hello Git!" ) ;
+ printf ( "I am vivian, I am new to Programming in C:<" ) ;
return 0;
}
sunus@mbp~[/private/var/tmp/git-pull/vivian] ( master ⚡)
[ 23:25]:git add git.c
sunus@mbp~[/private/var/tmp/git-pull/vivian] ( master ⚡)
[ 23:25]:git commit -m 'vivian committttt^^'
[ master 1838ec2] vivian committttt^^
1 file changed, 1 insertion( +)
sunus@mbp~[/private/var/tmp/git-pull/vivian] ( master ✔)
[ 23:25]:git log
commit 1838ec2b16be49b5aa084eb463e8d03e3b1f47de
Author: vivian <vivian@gmail.com>
Date: Mon Dec 9 23:25:34 2013 +0800
vivian committttt^^
commit 163a6d700226b780b7852a79fe1370a6d38c819a
Author: god <god@mbp>
Date: Mon Dec 9 22:13:15 2013 +0800
remove FILE
commit d22bf163d093afb494ad619d8964572e55c73167
Author: god <god@mbp>
Date: Mon Dec 9 22:11:40 2013 +0800
write first lines of codes
commit 45f2016a51ce7b8317e074a961647c091a50cd94
Author: god <god@mbp>
Date: Mon Dec 9 22:04:41 2013 +0800
add first file
sunus@mbp~[/private/var/tmp/git-pull/vivian] ( master ✔)
[ 23:25]:
####现在是什么情况?
sunus , vivian 都在本地基于origin上的远程分支 编写了自己的代码.
但是他们不知道对方 干了什么. 于是, 他们需要合并两人的修改, 并且将更新提交到远程dev分支上
vivian 动作比较快, 什么也没想就push了.
sunus@mbp~[/private/var/tmp/git-pull/vivian] ( master ✔)
[ 23:34]:git push -u origin master:dev
Counting objects: 5, done .
Delta compression using up to 4 threads.
Compressing objects: 100% ( 2/2) , done .
Writing objects: 100% ( 3/3) , 343 bytes | 0 bytes/s, done .
Total 3 ( delta 0) , reused 0 ( delta 0)
To /private/var/tmp/git-pull/awesome-project
163a6d7..1838ec2 master -> dev
Branch master set up to track remote branch dev from origin.
这看起来是成功了, god 也能够看到vivian 的改动:)
sunus@mbp~[/private/var/tmp/git-pull/awesome-project] ( dev ✔)
[ 23:35]:cat git.c
#include <stdio.h>
int main()
{
printf ( "Hello Git!" ) ;
printf ( "I am vivian, I am new to Programming in C:<" ) ;
return 0;
}
sunus@mbp~[/private/var/tmp/git-pull/awesome-project] ( dev ✔)
[ 23:35]:git log
commit 1838ec2b16be49b5aa084eb463e8d03e3b1f47de
Author: vivian <vivian@gmail.com>
Date: Mon Dec 9 23:25:34 2013 +0800
vivian committttt^^
commit 163a6d700226b780b7852a79fe1370a6d38c819a
Author: god <god@mbp>
Date: Mon Dec 9 22:13:15 2013 +0800
remove FILE
接下来看以前的sunus 会怎么做(他要倒霉了)
##PULL
该是git初学者们常用的一个操作, 他们一般认为该操作知识将本地版本库 与远程的版本库 同步更新.
但是并不知道这背后实际发生了什么 , 这也是为什么pull在大多数情况下,单个/少数开发者合作能够work, 但是在实际和多人协作中会造成问题的原因.
下面是简单的workflow:
首先, sunus 并不知道origin是否有改动, 他也是直接push.
[ 23:42]:git push -u origin master:dev
To /private/var/tmp/git-pull/awesome-project
! [ rejected] master -> dev ( fetch first)
error: failed to push some refs to '/private/var/tmp/git-pull/awesome-project'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first merge the remote changes ( e.g.,
hint: 'git pull' ) before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
很明显, push不成功, 因为vivian 抢先一步对远程版本库做了修改. 所以, sunus看到了要先做
的hint.
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ✔)
[ 23:47]:git pull origin dev
From /private/var/tmp/git-pull/awesome-project
* branch dev -> FETCH_HEAD
Auto-merging git.c
CONFLICT ( content) : Merge conflict in git.c
Automatic merge failed; fix conflicts and then commit the result.
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ⚡)
[ 23:48]:cat git.c
#include <stdio.h>
void * magic()
{
return ( void * ) magic;
}
int main()
{
void * p;
printf ( "Hello Git!" ) ;
<<<<<< < HEAD
printf ( "I am sunus and I am here with vivian \n " ) ;
p = magic() ;
printf ( "I will show you a magic: %p" , p) ;
=======
printf ( "I am vivian, I am new to Programming in C:<" ) ;
>>>>>>> 1838ec2b16be49b5aa084eb463e8d03e3b1f47de
return 0;
}
好了, 接下来还是蛮常见的事情, sunus , vivian 都对相关的代码做了修改, 现在有冲突了, sunus需要手动解决.
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ✔)
[ 23:52]:cat git.c
#include <stdio.h>
void * magic()
{
return ( void * ) magic;
}
int main()
{
void * p;
printf ( "Hello Git!" ) ;
printf ( "I am sunus and I am here with vivian \n " ) ;
p = magic() ;
printf ( "I will show you a magic: %p" , p) ;
printf ( "I am vivian, I am new to Programming in C:<" ) ;
return 0;
}
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ✔)
[ 23:52]:git log
commit 135990d6a92554009966c7b88133501adba767f2
Merge: 6df90b8 1838ec2
Author: sunus <sunuslee@gmail.com>
Date: Mon Dec 9 23:51:59 2013 +0800
pull and resolved a conflict
commit 1838ec2b16be49b5aa084eb463e8d03e3b1f47de
Author: vivian <vivian@gmail.com>
Date: Mon Dec 9 23:25:34 2013 +0800
vivian committttt^^
ok,在这儿, sunus 看了把手上的工作也做完了, 可以把代码push交到远程origin了.(会发生什么事呢?)
我们先比较一下当前sunus , vivian 两人在本地的git仓库情况:
vivivan
sunus
sunus 开始push.
sunus@mbp~[/private/var/tmp/git-pull/sunus] ( master ✔)
[ 0:12]:git push -u origin master:dev
Counting objects: 13, done .
Delta compression using up to 4 threads.
Compressing objects: 100% ( 6/6) , done .
Writing objects: 100% ( 9/9) , 903 bytes | 0 bytes/s, done .
Total 9 ( delta 2) , reused 0 ( delta 0)
To /private/var/tmp/git-pull/awesome-project
1838ec2..135990d master -> dev
Branch master set up to track remote branch dev from origin.
push成功了, 接下来, 看看当前sunus , vivian , god 本地分支的情况:
sunus
vivian
god
看起来好似没有问题, 不就是有个环吗?
但是, 尝试下
会发现, 这儿根本没有 sunus push之后的详细日志, 不可思议吧?!
也就是说, 除了sunus , 别人并不知道sunus 和vivian 他们俩的代码, 最终是如何合并 的.
除非对单个commit依次进行diff
##Fetch + Rebase
让我们再来看看另一种做法, 也是我比较推荐的. 使用fetch 然后再进行rebase.
fetch: 只把origin源改动下载到本地, 但是并不进行合并.
rebase: 把当前的branch放到另一个branch的顶端, 体现的形式是开发的过程是线性 的, 而不是一个环(pull/merge)
我们回到刚才sunus 的情形: vivian 已经push了代码.
这次, sunus 使用fetch
sunus@mbp~[/private/var/tmp/git-fetch-rebase/sunus] ( master ⚡)
[ 11:15]:git fetch origin
remote: Counting objects: 5, done .
remote: Compressing objects: 100% ( 2/2) , done .
remote: Total 3 ( delta 0) , reused 0 ( delta 0)
Unpacking objects: 100% ( 3/3) , done .
From ../awesome-project
* [ new branch] dev -> origin/dev
* [ new branch] master -> origin/master
我们把新的改动下载后, 新的分支有:
origin/dev 该分支有vivian 的新改动
origin/master 远程origin的master 分支, 在这不需要理会.
接下来, 我们要做的事情是, 把我们的改动放在origin/dev 分支的最顶部, 即紧接着vivian 的改动. 这样看起来像一个人写的代码一样.
sunus@mbp~[/private/var/tmp/git-fetch-rebas/sunus] ( master ⚡)
[ 11:16]:git rebase origin/dev
First, rewinding head to replay your work on top of it...
Applying: I add a intro
Using index info to reconstruct a base tree...
M git.c
Falling back to patching base and 3-way merge...
Auto-merging git.c
CONFLICT ( content) : Merge conflict in git.c
Failed to merge in the changes.
Patch failed at 0001 I add a intro
The copy of the patch that failed is found in :
/private/var/tmp/git-fetch-rebase/sunus/.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" .
sunus@mbp~[/private/var/tmp/git-fetch-rebase/sunus] (( no ⚡)
[ 11:16]:git mergetool
Merging:
git.c
Normal merge conflict for 'git.c' :
{ local } : modified file
{ remote} : modified file
Hit return to start merge resolution tool ( vimdiff) :
4 files to edit
ok, 在这儿会遇到一次merge 的冲突, 我们使用mergetool 解决. 然后继续rebase
sunus@mbp~[/private/var/tmp/git-fetch-rebase/sunus] (( no ⚡)
[ 11:18]:git rebase --continue
Applying: I add a intro
Applying: show you a magic
Using index info to reconstruct a base tree...
M git.c
Falling back to patching base and 3-way merge...
Auto-merging git.c
CONFLICT ( content) : Merge conflict in git.c
Failed to merge in the changes.
Patch failed at 0002 show you a magic
The copy of the patch that failed is found in :
/private/var/tmp/git-fetch-rebase/sunus/.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" .
sunus@mbp~[/private/var/tmp/git-fetch-rebase/sunus] (( no ⚡)
[ 11:18]:git mergetool
Merging:
git.c
Normal merge conflict for 'git.c' :
{ local } : modified file
{ remote} : modified file
Hit return to start merge resolution tool ( vimdiff) :
4 files to edit
sunus@mbp~[/private/var/tmp/git-fetch-rebase/sunus] (( no ⚡)
[ 11:19]:cat git.c
#include <stdio.h>
void * magic()
{
return ( void * ) magic;
}
int main()
{
void * p;
printf ( "Hello Git!" ) ;
printf ( "I am sunus and I am here with vivian \n " ) ;
p = magic() ;
printf ( "I will show you a magic: %p" , p) ;
printf ( "I am vivian, I am new to Programming in C:<" ) ;
return 0;
}
sunus@mbp~[/private/var/tmp/git-fetch-rebase/sunus] (( no ⚡)
[ 11:19]:git rebase --continue
Applying: show you a magic
ok, rebase完成, 可以看到最后sunus 的2个commit: 5580/8318 是在当前log的最顶端.
sunus@mbp~[/private/var/tmp/git-fetch-rebase/sunus] ( master ✔)
[ 11:19]:git log
commit 5580978c60d157da68816644aba7afecd328a4be
Author: sunus <sunuslee@gmail.com>
Date: Mon Dec 9 23:14:07 2013 +0800
show you a magic
commit 8318c499e6d5c4d9fd9ba46c19994c326a6cb1c5
Author: sunus <sunuslee@gmail.com>
Date: Mon Dec 9 23:09:41 2013 +0800
I add a intro
commit 1838ec2b16be49b5aa084eb463e8d03e3b1f47de
Author: vivian <vivian@gmail.com>
Date: Mon Dec 9 23:25:34 2013 +0800
vivian committttt^^
commit 163a6d700226b780b7852a79fe1370a6d38c819a
Author: god <god@mbp>
Date: Mon Dec 9 22:13:15 2013 +0800
remove FILE
接下来, 把我们本地的变动提交到远程仓库.
sunus@mbp~[/private/var/tmp/git-fetch-rebase/sunus] ( master ✔)
[ 11:32]:git push -u origin master:dev
Counting objects: 8, done .
Delta compression using up to 4 threads.
Compressing objects: 100% ( 4/4) , done .
Writing objects: 100% ( 6/6) , 663 bytes | 0 bytes/s, done .
Total 6 ( delta 1) , reused 0 ( delta 0)
To ../awesome-project
1838ec2..5580978 master -> dev
Branch master set up to track remote branch dev from origin.
我们看看sunus , god 当前的历史情况.
sunus
god
ok, 看起来很不错!
然后看看vivian 需要做什么获取最新的代码
sunus@mbp~[/private/var/tmp/git-fetch-rebase/vivian] ( master ✔)
[ 11:56]:git fetch
remote: Counting objects: 8, done .
remote: Compressing objects: 100% ( 4/4) , done .
remote: Total 6 ( delta 1) , reused 0 ( delta 0)
Unpacking objects: 100% ( 6/6) , done .
From ../awesome-project
* [ new branch] dev -> origin/dev
* [ new branch] master -> origin/master
sunus@mbp~[/private/var/tmp/git-fetch-rebase/vivian] ( master ✔)
[ 11:57]:git diff master origin/dev
diff --git a/git.c b/git.c
index 003e1ee..0187070 100644
--- a/git.c
+++ b/git.c
@@ -1 ,8 +1,17 @@
#include <stdio.h>
+void * magic()
+{
+ return ( void * ) magic;
+}
+
int main()
{
+ void * p;
printf ( "Hello Git!" ) ;
+ printf ( "I am sunus and I am here with vivian \n " ) ;
+ p = magic() ;
+ printf ( "I will show you a magic: %p" , p) ;
printf ( "I am vivian, I am new to Programming in C:<" ) ;
return 0;
}
sunus@mbp~[/private/var/tmp/git-fetch-rebase/vivian] ( master ✔)
[ 11:57]:git log
commit 1838ec2b16be49b5aa084eb463e8d03e3b1f47de
Author: vivian <vivian@gmail.com>
Date: Mon Dec 9 23:25:34 2013 +0800
vivian committttt^^
commit 163a6d700226b780b7852a79fe1370a6d38c819a
Author: god <god@mbp>
Date: Mon Dec 9 22:13:15 2013 +0800
remove FILE
sunus@mbp~[/private/var/tmp/git-fetch-rebase/vivian] ( master ✔)
[ 11:57]:git merge origin/dev
Updating 1838ec2..5580978
Fast-forward
git.c | 9 +++++++++
1 file changed, 9 insertions( +)
sunus@mbp~[/private/var/tmp/git-fetch-rebase-bak/vivian] ( master ✔)
[ 11:58]:git log
commit 5580978c60d157da68816644aba7afecd328a4be
Author: sunus <sunuslee@gmail.com>
Date: Mon Dec 9 23:14:07 2013 +0800
show you a magic
commit 8318c499e6d5c4d9fd9ba46c19994c326a6cb1c5
Author: sunus <sunuslee@gmail.com>
Date: Mon Dec 9 23:09:41 2013 +0800
I add a intro
commit 1838ec2b16be49b5aa084eb463e8d03e3b1f47de
Author: vivian <vivian@gmail.com>
Date: Mon Dec 9 23:25:34 2013 +0800
vivian committttt^^
commit 163a6d700226b780b7852a79fe1370a6d38c819a
Author: god <god@mbp>
Date: Mon Dec 9 22:13:15 2013 +0800
remove FILE
嗯, vivian 这边也没什么问题, 也同步了本地的版本库.
最后看看她本地的历史:
嗯, 看起来好极了~
##总结
##Update:
经好友@hunt提醒, 对本文提出了一点看法(关于merge与rebase):
内核在更顶端的地方的开发者/维护者使用的是 merge,比如 Linus 合并网络模块
maintainer David Miller 的 tree(Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net),
David Miller 合并 OpenvSwitch 维护者 Jesse Gross 的 tree(Merge branch ‘master’ of
git://git.kernel.org/pub/scm/linux/kernel/git/jesse/openvswitch)。而在更为下游的地方,比如
OpenvSwitch 社区中,提交给内核模块的代码则是要求开发者使用 rebase 来形成一个线性的提交。这样子形成了一个
非常好的分工,Jesse Gross 负责 OpenvSwitch 的模块代码的维护,David Miller 则轻松地进行合并,并关注 net
模块核心的一些相关的改动,Linus 同样能轻松地合并 net 模块中的内容,只需要去关注主干树上对基础代码的改动。对
开发者来讲,也很容易能明白哪些代码应该提交到哪个列表中,并抄送改动涉及/波及到的相关列表。