目录
1、分支的基础知识
2、功能分支工作流介绍
3、分支管理命令
4、分支合并冲突
分支功能是可以同时拉出来多个代码副本,然后在不同的代码副本上进行对应功能的开发。完成开发之后,可以将多个分支合并在一起,形成最终的代码。
分支,其实就是指针
在git中,每一个项目,不管你有多少个分支,不管你在哪个分支上开发,最终都会形成一个完整的提交历史,树形结构
每个分支,其实就只是一个指针而已,分支就指向了提交历史中的某个commit object
每个commit object就代表了这个项目的所有代码在那次提交的时候一个完整的快照版本,包含了之前没有变更的代码文件,也包括了这次提交的最新的修改/新增/删除的代码文件
每个commit object就代表了项目的一个版本,所以你的分支实际上就是指向了历史上某个时刻的一个版本的代码,就是一个commit object
Git的分支功能是一个很大的特色,非常的轻量级,分支的来回切换速度是很快的,在实际的开发过程中,分支绝对是最重要的功能。
(2)commit object与分支的原理
Git并不是存储一系列的文件差异,而是存储一系列的文件快照的。实际上每次我们执行一次commit,git都会存储一个commit object,这个commit object中会包含一个指针,指向这次提交文件的快照。
这个commit object同时也包含作者的姓名和邮箱,提交说明,以及对上一次commit object的指针。
- 将一个文件版本放入暂存区的时候,就会计算一个校验和,然后提交的时候会将文件内容以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指针会指向那个分支。
(3)创建一个分支
git branch testing,可以创建一个新的分支,此时这个分支的指针会指向当前你所在的分支所指向的commit object上。
如何查看各个分支指向哪个commit object呢?使用git log --oneline --decorate命令即可,会给你显示出来。
(4)切换分支
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。所以创建分支的代价是很低的,不过就是创建这么一个文件罢了。而一些集中式版本控制系统,对于分支可能需要拷贝一个完整的文件副本,导致速度很慢,磁盘空间占用很大。
(5)远程分支
在本地你可以创建一个分支,开发代码,后续你肯定是要将本地分支推送到远程仓库里面去的
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/iss36 分支,对应的远程分支就是origin/feature/iss53。
假设我们公司的git服务器地址是git.bema.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删除本地分支。
git工作流,就是多个人如何同时基于git远程仓库,加上分支,去非常良好的协作开发,包括去执行各种集成测试,QA测试,预发布测试,生产环境上线
分类: 集中式工作流、功能分支工作流、GitFlow工作流、互联网公司一种GitFlow工作流的变种
工作流程(适合小的,独立的项目)
一般来说,都会准备一个master分支,作为你的稳定分支,这里的代码是随时可以上线;
有多个feature分支,每个feature分支用来开发一个功能。
如果线上有bug,一般分为两种,
一种是影响正常用户使用流程的紧急bug,就是hotfix,比如说电商网站无法下单了,门户网站无法查看新闻了;
一种是不影响正常用户使用流程的非紧急bug,比如说有某个广告显示的位置出现了偏差,就是bugfix。
一般线上发现了bug,就了一个hotfix或者bugfix分支,然后在分支上复现bug,修复bug,然后在测试环境进行验证以及回归测试,最后将分支合并到master去上线,修复线上问题。
git branch命令,直接执行,就会显示出当前所有分支列表,以及你在哪个分支上工作
git branch -v命令,可以显示出每个分支当前指向的commit object
git branch --merged,可以看到哪些分支被merge进了当前分支;git branch --no-merged,可以看到哪些分支还没有被merge进当前分支
如果git branch -d命令删除一个分支,可能会提示你那个分支还没merge到当前分支来,不让你删除该分支,此时可以使用git branch -D命令,强制删除一个分支。
(1)分支原理再次回顾
git checkout -b dev,这个命令就是创建dev分支,dev指针指向最新一个commit,同时HEAD指针指向dev指针
git checkout -b,相当于两个命令,git branch dev,git checkout dev,先创建分支,再切换到分支
接着用git branch命令,可以查看当前的所有分支以及我们目前所处的分支
接着我们可以在dev分支上修改代码,在HelloWorld.java中增加一行:System.out.println(“I like maven also.”),然后提交,这时commit时间轴就会长出来一个新的节点,同时dev指针指向最新commit。
这个时候master指针是指向上一个ccommit的,而且还没有做过任何修改,此时如果将dev分支合并到master分支,就是直接将master指针指向最新的一个commit而已,接着将HEAD指针重新指向master指针。画图说明。
用git checkout master,可以切换回master分支,这时会看到上一次commit的代码,因为commit还指向上一个commit。
用git merge dev,将dev分支合并到master分支,这个时候,master指针指向最新commit,HEAD重新指向master指针。
此时代码就是最新一次commit的代码了。
上面这种合并方式,实际上master是没有过任何修改的,合并dev到master,只不过相当于让master分支快进到dev分支指向的commit而已,就是fast-forward模式的merge。
合并完分支之后,可以删除dev分支,此时相当于就是删除dev这个指针而已。画图说明。
git branch -d dev,可以删除dev分支,此时就会删除dev这个指针了。
(2)解决分支冲突
如果在拉取了两个分支,然后两个分支并行开发,接着其中一个分支先合并到了master,相当于master分支的内容已经修改了,接着另外一个分支再合并到master,此时可能会出现,两个分支对同一行代码都做了修改,就会出现代码冲突问题!
在feature1分支将最后一行修改为System.out.println(“I like spark......”),提交代码
在feature2分支将最后一行修改为System.out.println(“I like storm......”),提交代码
这个时候的情况,在之前的最新commit,拉出来了两个并行的commit,同时两个分支的指针分别指向对应的commit
接着切换到master分支,将feature1先合并到master分支,那么此时master指针就会指向feature1指向的那个commit,同时HEAD指向master指针
接着继续将feature2合并到master分支,此时,因为master分支是无法执行fast-forward快进操作的,因为master也是有修改的,此时只能执行3-way merge。
而且执行git merge feature2命令后,会提示出现了confiict冲突,让我们先解决冲突。
此时我们可以查看HelloWorld.java代码,发现其中最后一行变成了这个样子
<<<<<<< HEAD
System.out.println(“I like spark......”);
=======
System.out.println(“I liike storm......”);
>>>>>>> feature2
上面的意思,就是说master分支和feature2分支出现了冲突,第一行是HEAD代码,因为HEAD目前指向master,也就是master的代码;
第二行是feature2的代码。
这个时候,我们可以将冲突的内容删除掉,保留正常内容即可。
接着提交HelloWorld.java代码,就解决了冲突并且提交了代码,同时完成了feature2到master的合并。
此时会自动长出来一个新的commit节点,这个commit就是解决了master和feature2冲突并且合并之后的commit。
同时master指向最新的这个commit,HEAD还是指向master。
最后删除feature1和feature2两个分支