Git分支

分支原理简介

所谓的分支功能,就是可以同时拉出来多个代码副本,然后在不同的代码副本上,可以进行对应功能的开发。完成开发之后,可以将多个分支合并在一起,形成最终的代码。
分支,其实就是指针

commit object

在git中,每一个项目,不管你有多少个分支,不管你在哪个分支上开发,最终都会形成一个完整的提交历史,树形结构
每个分支,其实就只是一个指针而已,分支就指向了提交历史中的某个commit object
每个commit object就代表了这个项目的所有代码在那次提交的时候一个完整的快照版本,包含了之前没有变更的代码文件,也包括了这次提交的最新的修改/新增/删除的代码文件
每个commit object就代表了项目的一个版本
所以你的分支实际上就是指向了历史上某个时刻的一个版本的代码,就是一个commit object
Git的分支功能是一个很大的特色,非常的轻量级,分支的来回切换速度是很快的,在实际的开发过程中,分支绝对是最重要的功能。
Git并不是存储一系列的文件差异,而是存储一系列的文件快照的。实际上每次我们执行一次commit,git都会存储一个commit object,这个commit object中会包含一个指针,指向这次提交文件的快照。这个commit object同时也包含作者的姓名和邮箱,提交说明,以及对上一次commit object的指针。

commit 树的形成

将一个文件版本放入暂存区的时候,就会计算一个校验和,然后提交的时候会将文件内容以blob的方式放入版本库中,同时在暂存区放入这个文件版本的校验和。接着git会创建一个commit object,其中会包含元数据,以及一个指针指向版本库中的文件快照。
也就是说,每次执行一次提交,都会在版本库中包含这么几个东西:一个blob,每个文件都会有一个blob来存储这个文件的本次提交的快照;一个tree,这个tree会包含对本次提交的所有文件的blob的指针;一个commt object,指向了tree的指针,作者,等信息。
接着如果再次执行一个提交,那么下一个提交同样会包含那些东西:每个文件一个blob,一个tree指向所有blob,一个commit object指向那个tree,同时这个commit object会有一个指针,指向上一个commit object。
最后多次提交,就会得到一颗完整的commit树。
分支是啥?分支就是一个轻量级的指针,默认的分支是master,那么每次提交,master分支的指针默认就是指向最新的那个commit object的。每次提交一次,master指针就会挪动,继续指向最新的commit object。
同时如果你当前工作在某个分支上,比如工作在master分支上,那么git还维护了一个特殊的指针,HEAD,这个指针指向master分支指针。如果你创建了其他的分支,那么其他的分支也会指向某个commit object,而且此时如果工作在那个分支上,那么HEAD指针会指向那个分支。

分支命令

  1. 创建一个分支
    1. git branch testing,可以创建一个新的分支,此时这个分支的指针会指向当前你所在的分支所指向的commit object上。
      如何查看各个分支指向哪个commit object呢?使用git log --oneline --decorate命令即可,会给你显示出来。
      git branch命令,直接执行,就会显示出当前所有分支列表,以及你在哪个分支上工作
      git branch -v命令,可以显示出每个分支当前指向的commit object
      git branch --merged,可以看到哪些分支被merge进了当前分支;git branch --no-merged,可以看到哪些分支还没有被merge进当前分支
      如果git branch -d命令删除一个分支,可能会提示你那个分支还没merge到当前分支来,不让你删除该分支,此时可以使用git branch -D命令,强制删除一个分支。
  1. 切换分支
    1. git checkout testing,此时就可以切换到testing分支,此时HEAD指针会指向testing分支指针。
      此时如果在testing分支上提交代码,那么会commit树会长出来一个新的commit object,而testing分支指正会指向最新的commit object,HEAD继续指向testing分支指针,而master指针还是指向之前的那个commit object。
      git checkout master,会切换回master分支,此时HEAD指针会指向master指针,同时将master指针指向的那个commit object,对应的tree和其中的blob,也就是对应的文件快照恢复到工作区中来。
      再次在master分支上提交一个修改,此时从这个commit object会再长出来一个新的commit object,看起来就是跟testing分支当前指向的commit object形成了两个分叉。
      此时如果要查看commit树,可以用命令:git log --oneline --decorate --graph --all,这个命令很有用,可以打印出整颗commit树,同时告诉你各个分支当前指向哪个commit object。
      在Git中,分支升级上就是一个很简单的文件,其中包含了一个40位的SHA-1校验和,就是分支指向的那个commit object的SHA-1。所以创建分支的代价是很低的,不过就是创建这么一个文件罢了。而一些集中式版本控制系统,对于分支可能需要拷贝一个完整的文件副本,导致速度很慢,磁盘空间占用很大。
 
 

远程分支

 
在本地你可以创建一个分支,然后开发代码,但是后续的话,你肯定是要将本地分支推送到远程仓库里面去的
git push -u origin 分支名称
这个的意思,就是将本地分支推送到远程仓库里,形成一个同名的远程分支,同时-u这个选项就是将本地分支和远程分支关联起来
下一次如果修改了这个分支的代码,直接执行git push origin 分支名称,就直接可以将本地分支的代码推送到远程分支
如果其他人要将某个远程仓库的分支拉取下来,应该执行一个命令,叫做git fetch origin,就会抓取下来远程仓库新增了哪些分支
接下来可以执行一个命令,git checkout -b 本地分支 origin/远程分支,用这个命令,origin/远程分支,就代表了远程仓库里的那个分支,然后这个命令一执行,就是在本地创建一个远程分支对应的本地分支,互相关联起来
以后呢,每次如果别人更新了那个分支的代码,push到了远程仓库,你可以执行git pull命令,将这个分支在远程仓库的代码拉取下来,跟本地分支的代码进行合并
远程版本库的分支,在本地都有追踪分支,remote-tracking 分支。
比如说本地的master分支,对应的远程分支就是origin/master。比如本地的feature/iss53分支,对应的远程分支就是origin/feature/iss53。
假设我们公司的git服务器地址是git.zhss.com,然后如果我们用git clone命令从这个git服务器克隆了一个版本库下来,git默认会将远程版本库命名为origin,同时在本地创建一个指向远程版本库的master分支的本地分支,叫做origin/master。此外,git也会给你在本地创建一个master分支,内容就是跟origin/master分支一样的。
从git服务器克隆版本库下来的时候,远程版本库的commit树会一同拷贝下来,然后origin/master指向的commit,就是远程版本库的master指向的commit,同时给本地创建的commit也是指向这个commit。
此时,如果在本地你做了不少开发,然后本地master移动了好几个commit,同时origin/master还是指向最开始的那个commit;而同时,远程版本库上,其他同事也提交了几次代码,因此远程版本库上的commit也移动了几个commit。
此时如果要让本地和远程保持同步,需要使用git fetch origin命令,该命令会将远程版本库的commit树和所有的分支都拉取下来,跟本地的commit树进行合并,此时可能就会在本地形成一棵有两个分叉的commit树。
本地的origin/master会指向远程版本库的master指向的那个commit,本地的master继续指向之前本地最新的那个commit。
使用git push origin serverfix,可以将你在本地的分支推送到远程版本库上去,此时你本地的commit树也会推送到远程版本库,跟远程版本库的commit树进行合并。
下一次别人执行git fetch origin的时候,就会获取到一个remote -tracking分支,origin/serverfix分支,这个远程分支是只读的。接着你使用git checkout -b serverfix origin/serverfix命令,就可以在本地获取到一个分支跟origin/serverfix关联起来,然后就可以跟你一起对一个分支进行修改了。
使用git checkout -b serverfix origin/serverfix命令,创建出来的本地分支叫做tracking brach,而这个本地分支track的远程分支叫做upstream branch。此时本地分支就会track那个远程分支,跟远程分支之间会建立关联关系。
如果我们在tracking分支上,执行git pull命令,git就会自动将对应的远程分支在远程版本库上的代码拉取下来跟本地的tracking分支做合并,如果有冲突的话,还需要解决冲突。
如果我们是使用clone命令克隆的远程版本库,那么默认就会将本地的master分支创建为追踪origin/master远程分支的。
使用git branch -u origin/serverfix,可以让当前本地分支track某个指定的远程分支。
使用git branch -vv,可以查看每个本地分支track的远程分支。
git fetch origin命令,仅仅就是将远程版本库的commit树拉取下来,同时拉取下来所有最新的远程分支,给我们使用。
如果我们已经建立起来了本地分支和远程分支的track关系,那么就可以直接使用git pull命令,将远程分支的代码拉取下来合并到本地分支。但是通常来说,我们也可以先git fetch origin,然后git merge 远程分支,一样可以将远程分支代码合并到本地分支上去。
删除远程分支,比如一个远程分支,已经结束了工作了,可以删除一个远程分支,使用git push origin --delete serverfix即可。同时可以使用git branch -d serverfix删除本地分支。

功能分支工作流

notion image
工作流,git工作流,简单来说,就是多个人如何同时基于git远程仓库,加上分支,去非常良好的协作开发,包括去执行各种集成测试,QA测试,预发布测试,生产环境上线
第一个工作流,功能分支工作流,比较适合用于5人左右或者以内的小团队
集中式工作流、功能分支工作流、GitFlow工作流、互联网公司一种GitFlow工作流的变种
每种工作流都没有绝对的好坏,主要是适用于某个场景下,某种类型的项目
一般来说,都会准备一个master分支,作为你的稳定分支,这里的代码是随时可以上线的;有多个feature分支,每个feature分支用来开发一个功能。
如果线上有bug,一般分为两种,一种是影响正常用户使用流程的紧急bug,就是hotfix,比如说电商网站无法下单了,门户网站无法查看新闻了;一种是不影响正常用户使用流程的非紧急bug,比如说有某个广告显示的位置出现了偏差,就是bugfix。
一般线上发现了bug,就了一个hotfix或者bugfix分支,然后在分支上复现bug,修复bug,然后在测试环境进行验证以及回归测试,最后将分支合并到master去上线,修复线上问题。
这边,我根据过往的经验,给大家说一下,我觉得这个工作流比较适合什么样的项目?
非常适合对公司内部的系统、平台或者工具,5人以内小团队,3人左右,去开发一个比如公司内使用工具,报表平台
报表平台,不是什么核心的系统,可能就是投入两三个人,就维护一个系统,各种在里面加入功能,满足大家看报表的需求,平时有bug就修复一下。
新的需求频率也不是很高,可能就是比较稳定的新需求一步一步提出来,大家就比较稳的在哪儿维护这个报表平台就可以了
分布式爬虫系统,3个人在搞,也是这样子,就是3个人开发和维护一个小爬虫的系统,然后有需要就在里面加入功能,有bug就修复
没有什么版本的概念,对公司内部,1.0,1.1.2,1.5.3,不需要这么复杂的版本
就是就很简单,就是一个系统,比如说,你的需求方,某个看数据的运营同学,或者是产品经理同学说,好,现在你的报表系统能不能给我显示一个折线图,因为之前只能是柱状图
然后你说,好,没问题,拉一个feature分支下来,feature/line_graph,开发开发,测试,最后feature/line_graph合并到master分支,上线。。。
然后平时有人突然说,饼状图显示不出来了,有bug,bugfix/bar_graph_cannot_show,复现,修复,测试,最后bugfix/bar_graph_cannot_show合并到master分支,上线
 

功能分支工作流实战

功能分支工作流实战

分支分叉点

notion image
在hello 2.0 的这个提交对象之后分别在dev和master分支对相同文件进行了不同的修改操作
dev分支
public class Test { public static void main(String[] args) { System.out.println("hello world 2"); System.out.println("dev"); } }
 
master分支
public class Test { public static void main(String[] args) { System.out.println("hello world"); System.out.println("master"); } }
此时出现分支分叉

分支合并

将dev合并到master ,因为对同一个文件进行修改, 出现冲突,解决冲突后合并
public class Test { public static void main(String[] args) { System.out.println("hello world"); System.out.println("master"); System.out.println("dev"); } }
notion image