几乎所有的VCS都支持分支的机制,到底什么是分支,有什么作用?
? ? ?分支是一份完整的项目代码,一个项目可以有很多分支。多数VCS工具的分支机制很昂贵,需要完整的拷贝项目代码,对于代码量大的目录往往需要很长的时间。Git用快照加校验码的方式,让分支模型变得异常轻便,创建和切换分支都很快。Git的每个分支都是一些校验码构成的树或者图,要想理解Git的分支模型,请仔细阅读Branches in a Nutshell或者中文版何谓分支。
如何创建分支呢?
? ? ?$ git branch testing
? ? ?这种方式是在当前分支基础上创建了testing分支。首先应该明白,如果项目有多个分支,当前只有一个分支位于工作区域。一般一份代码至少有一个主干分支,叫做master。如果想指定从某个分支基础上创建分支只需要在后面再加上分支名字即可,例如
? ? ?$ git branch testing master
? ? ?就从master新建分支,不管当前处于哪个分支下。这个命令的执行不会将新建的分支testing切换到工作区域。
如何切换分支呢?
? ? ?$ git checkout testing
? ? ?或者可以在创建分支的时候执行
? ? ?$ git checkout -b testing
? ? ?表示创建新的分支testing并且切换过去。
是否在分支中新加的代码不会加在主干中?
? ? ?是这样的。如果是在分支中做提交,那么只会影响分支,不会影响主干和其他的分支,可以保持主干的稳定性。
分支开发完后要怎么将代码加回主干?
? ? ?$ git checkout?master
? ? ?$ git merge testing
? ? ?注意切换到master时需要保证所有记录的修改都应该已经提交,否则切换会失败。加回主干一般称作合并,合并的过程是在被提交的分支上生成一个新的提交,将两个分支的代码差异合并起来的操作。当同一个文件在两个分支上都有修改时就会产生冲突,需要手动解决。详细的冲突解决的示例可以参考Basic Merge Conflicts。
应该如何查看当前有哪些分支呢?
? ? ?$ git branch
? ? ?会显示所有分支的名字,更详细的信息可以加-v选项。还有两个选项也常用,–merged显示已合并到当前分支上的那些分支,–nomerged显示还未合并到当前分支上的那些分支。
分支合并后该怎么删除呢?
? ? ?$ git branch -d <branch_name>
之前提到的是否都是本地仓库的操作,本地的提交怎么会影响到远端呢,也需要合并吗?
? ? ?对的,之前说的都是本地的操作,提交到远端也需要合并,但命令不太一样,原则上有些差别。
什么原则上的差别?
? ? ?前面提到过向远程提交代码是一个推送操作,需要执行
? ? ?$ git push
? ? ?而能直接执行这个操作,需要当前分支跟踪着远端的某个分支。
? ? ?$ git branch -vv
? ? ?可以查询本地跟踪远程的那些分支情况。不管用推送去合并哪个分支都需要本地的分支跟踪远程的某个分支。如果是要推送一个远程不存在的分支,那么直接会在远程新建一个对应名字的分支,并被本地分支跟踪,命令是
? ? ?$ git push <remote_name> <branch_name>
? ? ?所以本地和远程的差别在于,远程使用推送去完成合并和创建新的分支。
远端的分支在本地名字好像都是类似origin/master的格式?
? ? ?是的,origin是远端的名字,master是远端分支的名字。
前面好像有提过,从远程拉取代码使用fetch或者pull的命令,差别在哪里?
? ? ?$ git fetch origin
? ? ?会拉取所有在远端名为origin下的所有分支到本地,并且名字都叫origin/<branch_name>。这些拉取的分支是不能够直接切换和修改的,需要对它们用本地分支进行跟踪。如果本地已经有对应的跟踪分支,切换过去,直接进行合并,即执行
? ? ?$ git checkout tracking_branch
? ? ?$ git merge origin/tracking_branch
? ? ?命令pull将fetch操作和merge操作一并执行。
如果本地没有跟踪分支,那么怎么新建一个呢?
? ? ?$ git checkout -b <branch_name> <remote_name>/<branch_name>
这个命令看起来和本地新建一个分支好像没有差别?
? ? ?看起来是没有差别,但会让新建的分支跟踪远端的分支。如果想要显式地执行跟踪,可以执行
? ? ?$ git checkout –track <remote_name>/<branch_name>
? ? ?如果要改变本地分支追踪的远程分支,可以执行
? ? ?$ git checkout -u <remote_name>/<branch_name>
假如错误地推送给远程一个分支,怎么能够删除?
? ? ?$ git push <remote_name> –delete <branch_name>
命令和选项都太多了,平常应该怎么用这些命令来做开发?
? ? ?通常情况下,远端只会有一个分支,姑且叫做远端的主干分支。远端主干分支上的代码都是稳定的,经过测试,可以直接编译、执行、部署。本地开发时,需要有一个本地主干来跟踪远程的主干,因而这个主干通常不做开发,也一般都是稳定的。如果代码中有bug,那么从本地主干上切换出一个新的分支来做修改,修改完后再合并到主干,主干测试无误,推送到远端主干进行测试。如果代码要开发新的功能,也从主干上切换出一个新的分支来添加代码,添加完后,测试无误合并到主干,然后推送到主干进行测试。所以稳定的主干一般只有一个,不管是开发新功能还是修改漏洞都要在新分支上完成。
? ? ?需要注意的一点是多人使用远端分支时候,主干推送可能会出错。这种情况发生在本地主干与远程的主干不同步,合作开发者已经将更新的提交推送到了远端,这时候需要本地主干同步后,再做本地的合并,然后再推送。
意思是说每次合并主干前,都执行一次$ git pull吗?
? ? ?是的,最好这样。
之前有提到过$ git pull -r的操作,与上面有什么差别?
? ? ?这个命令会以rebase的方式合并,而不是merge。
什么是rebase的方式?
? ? ?假设在本地功能分支合并到主干分支的时候,忘记去同步远端主干到本地,这个时候直接执行$ git pull的话,可能会产生合并冲突,如果远端和本地的最新提交都修改了同一个文件的话。这种情况下,不仅需要手动合并,而且向远端提交分支的时候还后产生一个合并分支的分叉,提交历史看起来不太整齐。如果用rebase合并,Git会将本地合并的提交接在远端提交的后面,等价于先同步远端主干到本地,再去合并。
rebase可以单独使用吗?
? ? ?可以的。rebase也能够用在本地功能分支向主干分支合并的时候。本地的主干可能有多个分支同时进行开发,也有可能同步了远程的主干,继续向前,所有头部的提交已经不再是当前功能分支切换时的情况了,这个时候rebase就很有用,直接在当前的功能分支下执行
? ? ?$ git rebase master
? ? ?能够让当前功能分支的新提交追加在主干新提交之后。另外值得提一句rebase操作的-i操作还能够在合并的时候修改提交历史,这个功能其实很好用,但也引起了很多的争议。
什么样的争议?
? ? ?反对者的声音是提交历史记录了原本发生的历史,而历史不应该被改变,使用rebase等于在篡改历史,你相当于是在对过去说谎。支持者的声音是提交历史里应该记录的是项目如何被完成这个完整的故事,哪些保留,哪些删除,应该有所取舍,没有人会将一本书的草稿出版。
听起来挺有趣,那争议的结果怎样呢?
? ? ?其实就是我们现在看到的,两种方式都保留在Git中。有一点建议是只用rebase修改自己本地仓库的提交,千万不要修改本地之外的提交(Do not rebase commits that exist outside your repository)。
这段文字改写自《Pro Git》一书的第二版第三章Branching部分。这里的改写仅用作回顾,不推荐直接学习,原书中有很多图示的说明更加直观。第二章请参考Git基础。