图解Git的分支管理

目录

Git 分支模型被称为“必杀技特性”,与其他版本控制系统相比可谓是难以置信的轻量级,本文是 《Git 使用指南》 后续:图解Git的分支管理。

本文是 《Pro Git》 阅读笔记整理,文中不详细之处,可阅读原著补充。

一、Git 分支本质

1.1、首次提交后,各对象间关系

  • 生成blob类型对象,它存储着当前文件状态的快照。(暂存操作)
  • 生成tree对象,该对象中的每个文件,包含着指向blob对象的索引。(提交)
  • 创建commit对象,它包含指向tree对象的索引和其他元数据。(提交操作)

https://img.cdn.wangyan.cloud/g/git-1-1.jpg

图1-1:Git仓库各对象间关系

1.2、多次提交后,提交对象间关系

修改后再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针。

https://img.cdn.wangyan.cloud/g/git-1-2.jpg

图1-2:多次提交后,提交对象间的关系

1.3、本地master主分支

Git分支本质:是一个指向commit 对象的可变指针,例如master为默认分支名称。

https://img.cdn.wangyan.cloud/g/git-1-3.jpg

图1-3:指向提交历史的master分支

1.4、创建新分支

创建新分支本质:是创建一个指向当前commit对象的指针。

bash
git branch testing #创建testing分支

https://img.cdn.wangyan.cloud/g/git-1-4.jpg

图1-4:指向同一提交历史的多个分支(master、testing)

1.5、当前分支

HEAD 指针指向当前分支,默认指向 master 分支。

https://img.cdn.wangyan.cloud/g/git-1-5.jpg

图1-5:HEAD 指向当前所在的分支

1.6、切换分支

checkout 切换至 testing 分支,于是 HEAD 指针也指向了testing分支。(图1-6-1)

bash
git checkout testing #切换至testing分支

https://img.cdn.wangyan.cloud/g/git-1-6-1.jpg

图1-6-1:HEAD 指向了testing分支

修改后再提交,HEAD 依然随着testing分支一起向前移动(图1-6-2)

bash
vim test.rb
git commit -a -m 'made a change'

https://img.cdn.wangyan.cloud/g/git-1-6-2.jpg

图1-6-2:每次提交后HEAD 随着分支一起向前移动

返回原来的master主分支继续工作。(图1-6-3)

bash
git checkout master

https://img.cdn.wangyan.cloud/g/git-1-6-3.jpg

图1-6-3:返回原来的master主分支

当在主分支中修改提交后,分叉出"87ab2"的提交对象,但不会干扰到testing分支。(图1-6-4)

bash
vim test.rb
git commit -a -m 'made other changes'

https://img.cdn.wangyan.cloud/g/git-1-6-4.jpg

图1-6-4:分叉了的分支历史

二、分支工作流程

  1. 开发某个项目 (master主分支,生产服务器)
  2. 为该项目增加某个新需求 (iss53分支)
  3. 已上线项目需要修补,于是为本次修补建立新分支(hotfix分支)
  4. 合并补丁(hotfix分支)到master分支,完成已上线项目Bug修补
  5. 继续开发新需求(iss53分支),并且纳入已发布的补丁

2.1、开发某个项目(master分支)

首先,我们正在开发某个项目,并推送到了生产服务器。

https://img.cdn.wangyan.cloud/g/git-2-1.jpg

图2-1:一个简单的提交历史

2.2、为该项目增加某个新需求 (iss53分支)

创建并切换至iss53分支,注意要加"-b"参数。

bash
git checkout -b iss53

https://img.cdn.wangyan.cloud/g/git-2-2-1.jpg

图2-2-1:创建一个新的分支(iss53)

修改后再次提交。

bash
vim index.html 
git commit -a-m 'addedanewfooter [issue53]' 

https://img.cdn.wangyan.cloud/g/git-2-2-2.jpg

图2-2-2:iss53 分支随工作进展向前推进

2.3、修补上线项目Bug(hotfix分支)

已上线项目需要修补,于是为本次修补建立新分支(hotfix分支)

bash
git checkout master #首先切换到主分支
git checkout -b 'hotfix' #然后创建hotfix分支

https://img.cdn.wangyan.cloud/g/git-2-3.jpg

图2-3:hotfix分支是从master(c2位置)分出

2.4、合并补丁(hotfix分支)到master分支

补丁(hotfix分支)测试完毕,于是合并到master分支,完成Bug修补。

bash
git checkout master  #首先切换到主分支
git merge hotfix #测试完毕后,合并hotfix分支
git branch -d hotfix #删除已合并的hotfix分支

合并时会出现“Fast forward”(快进)提示,表示无分歧需要解决,直接向前移动指针。

https://img.cdn.wangyan.cloud/g/git-2-4.jpg

图2-4:合并hotfix分支后指针位置

2.5、继续开发新需求,并纳入已发布的补丁

bash
git checkout master #切换至master 
git merge iss53 #合并iss53到master

注意:这次与之前hotfix合并不同,由于当前master分支直接祖先是C2,iss53分支直接祖先是C3,所以Git需要和它们的共同祖先C2进行一次简单的三方合并计算。

https://img.cdn.wangyan.cloud/g/git-2-5-1.jpg

图2-5-1:合并iss53分支后指针位置

解决合并冲突

bash
git status #合并失败的文件以"unmerged"标出
git add . #标记冲突已解决,并暂存文件
#git mergetool Git默认图形化冲突解决工具

https://img.cdn.wangyan.cloud/g/git-2-5-2.jpg

图2-5-2:Git 自动创建了包含合并结果的对象(C6)

三、远程分支

远程分支(remote branch)是对远程仓库状态的索引,格式如下:

bash
格式:远程仓库名/分支名
默认:origin/master

3.1、克隆远程分支

Git clone 会克隆远程origin/master分支,并建立一个相同位置的本地master分支。

https://img.cdn.wangyan.cloud/g/git-3-1-1.jpg

图3-1-1:Git clone 建立本地master和远程origin/master分支

正在本地master分支工作,与此同时,其他人向远程仓库推送了内容,从f42c5更新到了190a3,但是本地origin/master指针依然会停留在f42c5位置。

https://img.cdn.wangyan.cloud/g/git-3-1-2.jpg

图3-1-2:远程仓库更新,但本地origin/master指针不改变

3.2、更新本地分支

git fetch 从远程仓库更新你本地还没有的数据。(新增了31b8e,190a3)

https://img.cdn.wangyan.cloud/g/git-3-1-3.jpg

图3-1-3:运行 git fetch origin 同步远程仓库至本地

注意:在fetch操作抓来新远程分支XX之后,本地不会新增XX分支,有的只是一个你无法移动的origin/XX指针。

如果要把远程分支合并到当前分支,可以运行:

bash
git merge [远程仓库名]/[分支名]
git merge origin/serverfix。

3.3、跟踪分支

从远程分支检出的本地分支,称为跟踪分支(tracking branch)。在跟踪分支里git push可推送本地分支到相应的远程仓库,反过来可直接运行git pull,可获取远程分支所有更新,并且合并到当前本地分支。

注:克隆操作会自动创建一个master分支来跟踪origin/master。

如果想要在远程分支的基础上分化出一个新分支,可以运行:

方法一:

bash
git checkout -b [分支名] [远程名]/[分支名]
git checkout -b serverfix origin/serverfix

方法二:(适用1.6.2以上版本Git)

bash
git checkout --track [远程名]/[分支名] 
git checkout --track origin/serverfix

3.4、推送本地分支

方法一:

bash
语法:git push (远程仓库名) (本地分支名)
实例:git push origin serverfix

方法二:(可自定义远程分支名)

bash
语法:git push (远程仓库名) (本地分支名):(远程分支名)
实例:git push origin serverfix:serferfix

四、衍合

把一个分支整合到另一个分支的办法有两种:merge(合并) 和rebase(衍合),两者得到的结果一样,但衍合能产生一个更为整洁的提交历史。

4.1、合并与衍合区别

合并(merge):通过C2-C3-C4三方合并计算生成C5。

https://img.cdn.wangyan.cloud/g/git-4-1-1.jpg

图4-1-1:合并(merge)分支后的提交历史

衍合(rebase):把在C3 里产生的变化补丁重新在C4 的基础上打一遍。

衍合原理:从两个分支共同祖先(C2)开始,提取你所在分支(experiment)每次提交时产生的差异(diff),然后回到需要衍合入的分支(master),按顺序施用每一个差异补丁文件。

bash
git checkout experiment
git rebase master

https://img.cdn.wangyan.cloud/g/git-4-1-2.jpg

图4-1-2:把C3 里产生的改变衍合到C4 中

最后回到master分支进行一次快进合并,结果同三方合并例子中的C5内容一模一样,但衍合能产生一个更为整洁的提交历。

https://img.cdn.wangyan.cloud/g/git-4-1-3.jpg

图4-1-3:master 分支的快进。

4.2、衍合(rebase)风险

注释事项:永远不要衍合那些已经推送到公共仓库的更新。

常见用法:一般把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些永远不会公开的commit

五、小结

5.1、新建本地分支

bash
git branch testing #创建testing分支
git checkout testing #切换至testing分支
git checkout -b iss53 #创建并切换至iss53分支。

5.2、合并本地分支

bash
git checkout master  #首先切换到主分支
git merge hotfix #测试完毕后,合并hotfix分支
git branch -d hotfix #删除已合并的hotfix分支

5.3、解决合并冲突

bash
git status #合并失败的文件以"unmerged"标出
git add . #标记冲突已解决,并暂存文件
git mergetool #Git默认图形化冲突解决工具

5.4、合并远程分支

bash
origin/master #格式:远程仓库/分支名
git merge origin/serverfix #合并远程分支serverfix到当前分支

5.5、删除远程分支

bash
#git push [远程仓库名] :[远程分支]
#注意区别:git push [远程名] [本地分支]:[远程分支]
git push origin :serverfix #删除远程分支

5.6、创建跟踪分支

方法一:

bash
git checkout -b [分支名] [远程名]/[分支名]
git checkout -b serverfix origin/serverfix

方法二:(适用1.6.2以上版本Git)

bash
git checkout --track [远程名]/[分支名] 
git checkout --track origin/serverfix

5.7、查看分支

bash
git branch #列出所有分支,星号"*"表示当前所在分支。
git branch -v #查看各分支最后一次提交信息

#查看已和当前分支合并的分支
#可用git branch -d 删掉未标星号的分支,因为已经合并了。
git branch --merged 

#查看未和当前分支合并的分支
#强制删除未合并的分支git branch -D
git branch --no-merged 

5.8、衍合所在分支

bash
git checkout experiment #切换到需要衍合的分支
git rebase master #衍合experiment分支到主分支
ESC