git学习之三:分支管理

上一节介绍了git管理的文件状态,本节将介绍git版本控制的核心利器-分支(branch)。

第一节的内容讲了三种git管理的对象,可以知道只要知道一个commit对象的信息,我们就可以知道这个版本的内容。我们进行版本控制,也就是要管理这些commit对象。git使用分支这个概念来对commit进行管理,分支其实就是指向了某一个commit,一个分支来索引一个commit。

还是从.git文件夹出发,初始化一个本地仓库,看看.git下的目录:

objects保存了我们所有的对象,refs的文件夹全称references,这个文件夹里有会储存三类文件:本地分支、远程分支、tag。

现在看看refs文件夹的内容:

1.本地分支

heads文件夹里面的内容是本地分支文件tags文件夹里的文件也是标识一个commit,不过对这个commit进行了一些包装,取了一个更好的名字而已。这里如果关联了远程仓库,refs还存在一个remotes文件夹,用来储存远程分支

本地仓库初始化之后,heads文件夹为空,也就是目前没有分支,但是我们提交一个commit之后,git将建立一个默认的名为master的分支。现在随意往仓库加一个文件,使用git add将文件加入暂存区(对于暂存区,下一节将详细讲解),然后使用 git commit将文件提交(git commit -m "first commit"  -m参数可以对本次commit加上一个注释)。

现在看看refs/heads目录:里面多了一个名为master的文件。

这个master文件,就是我们的分支文件,他可以直接用文本打开,我们可以看看里面的内容:

使用git log 看看我们的commit日志:

这个分支文件,指向了当前的commit,可以看到创建一个分支的成本很低,一个文本文件,里面标识一个commit的key就完成了,git使用分支来管理版本的成本的花费是很低的,新建一个分支的消耗几乎不存在。

使用git branch 可以查看本地所有分支,git branch [分支名] 可以创建一个分支,每创建一个分支heads文件夹就会多一个文件

可以看到一开始只有一个master分支,然后建立一个名为develop的分支,分支前面的 * 号,表示目前所在的分支。目前版本的内容还是master分支,git branch只是新建了一个分支,并没有切换到新的分支,新建的分支文件里的内容,就是当前所在分支指向的commit。

git何如知道目前版本工作的分支是哪一个呢?我们需要看看.git目录下的HEAD文件,这个文件记录了当前版本所在的分支,这个文件也可以直接用文本打开:

git 通过这个文件来标识当前工作的分支。

git checkout [分支名] 可以在分支之间切换,通过分支的切换,也就对我们的版本的内容进行了切换。,每一次切换都会改变HEAD文件的内容,下面我们切换到develop分支:

git branch [分支名] 创建一个分支

git checkout [分支名] 切换到该分支

这两条git 命令可以用 git checkout -b [分支名]来完成 即创建一个分支 并且马上切换到该分支。


看看HEAD文件的内容

用一个简易的图来表示现在的状态:

master develop指向同一个commit HEAD目前指向develop,表示目前版本的分支是develop。

现在加一个文件,进行第二次提交,名称为second commit,每次commit之后,当前分支内容会更新,指向新的commit。

看看现在的状态:

如果进行很多次提交,develop会一直向前移动。只要切换到master分支,文件又回到了之前master分支所标识的commit的状态。回到master上之后

继续工作,进行新的提交之后,这时在分支上产生了一个分叉:


这样产生了两个方向,比如两个不同功能的开发,在完成之后需要进行分支合并(稍后讲解)


2.远程分支

本地分支只能对自己的本地仓库进行版本管理,但是多人合作必须利用到远程仓库。克隆一个远程仓库到本地,在

.git/refs文件夹会有remotes这个文件夹。这个文件我包含了远程仓库的信息

看看这个文件夹里的内容:

克隆之后远程仓库名默认为origin,这个origin文件夹里的内容就是远程分支文件。当我们克隆一个远程仓库时,我

们默认将远程仓库成为origin,一个本地仓库可以关联多个远程仓库,在这里对他们的以仓库名建立文件夹,该文件夹里

就是远程的分支文件

使用git branch -r 可以查看远程分支。

下面看看远程分支和本地分支的协作,对于远程仓库,每个开发人员都可以克隆一份本地的仓库,在本地仓库看

来,远程分支只是一个标识,告诉我们现在远程仓库的状态。

克隆之后本地只存在一个master分支,与远程master分支关联

现在假设远程仓库只要一个分支master,该分支只要一个commit A,我们克隆岛本地仓库后的状况如图:

经过本地经过B,C,D三次commit之后:

时我们本地的master分支已经有三次提交了,我们可以将新的内容push到远程仓库

git push origin [本地分支名]:[远程分支名] 这个命令来推送我们本地新加的内容到远程仓库。

如果远程分支名不存在,则功能变为创建一个新的远程分支。

如果我们的本地分支和远程分支关联,则可以省略后面的远程分支名

如果推送成功那么远程仓库就有了我们所提交的内容,同时远程master分支也会移动指向commit D。


假设还有另外一个人基于原来的远程仓库也创建了本地仓库,现在我们推送了新内容到了远程仓库,那么这个

人只需要使用git fetch把远程仓库新内容拉取到自己的本地仓库,本地状态如下:

这时使用git status查看本地的状态,会被提示在master分支落后origin/master 3个commit,可以使用git pull进行

fast-forward(快速移动,这个稍后讲解),使用git pull之后,第二个人的本地master会移动到commit D,这样两个人的代码

就同步了。


3.分支合并

我们通过分支来管理版本,现在假设有两个功能需要开发,我们有两个分支master和develop,完成开发后各自都进行了

很多次commit:


现在两个功能都开发完成了,这时需要合并两个分支的内容,合并分支有两种方式:rebase和merge。

为了区分两者,我们将git rebase 称作衍合,git merge 成为融合。

git命令:git rebase [分支名], git merge [分支名]可以将分支合并到工作的分支上,现在HEAD指向master分支,说

表明我们的目前的工作在master分支上,使用git rebase develop 就将develop分支的内容合并到了master分支上。

两个分支合并,git会寻找离两个分支最近的公共祖先,之前已经讲过每一个commit都能完整的表现出所有文

的状态,这样可以了解到两个分支分别都做了哪些改变,从何进行合并。

对于git rebase衍合有两个步骤:1.找到了公共祖先之后,会把develop的commit 依次加入到改祖先之后。2.将

master分支的commit依次加入到第一步执行完之后的commit上。如下图所示,这样就可以清晰的知道我们的提交历史:


对于git merge融合,比git rebase的处理方式要简单,找到公共祖先的commit,然后比较两个分支顶点的commit,

进行差异化的比较,生成一个新的commit加入到master分支上 ,这个commit比较特殊会指向两个父节点:

之前我们提到个fast forward 快速合并,如果不存在A,B,C三个commit,master分支指向init commit节点,这个

时候寻找到的公共祖先节点,其实就是master指向的init commit节点,这个时候就不存在分支的分叉,可以快速合并,

master分支讲直接指向commit F来完成分支合并。

分支合并会可能会产生冲突,这时需要解决冲突,才能继续合并。产生冲突的方式有很多种:如果两个分支都修

改的同一个文件的同一位值;一个分支删除了文件,一个分支对该文件进行修改,这样都会产生冲突。我们以一个具

体例子来解决一个冲突。

现在我们在master分支对1.txt末尾添加一行“master 1”, 在develop分支上在1.txt末尾加一行“develop 1”,这样两个

不同分支上的commit对同一个文件同一个位置进行了修改。我们在master分支进行git merch develop操作:

提示conflict,这个时候我们master分支的状态是merging,我们需要解决冲突,打开1.txt文件:

文件中用 <<< ==== >>>三个符号标识了冲突的内容,我们需要进行对这个文件进行修改,来确认最终的版本

解决冲突之后,需要将1.txt进行add一次,来告诉git我们解决了冲突[ git add 1.txt],这样就可以接着合并了,对于

merge来说其实是进行新的提交,git commit一次之后就完成了合并。对于git rebase则使用git rebase --continue来完

成合并


4.分支管理

我们在多人合作开发当中,会遇到很多复杂的分支情况。

本地分支的创建 :git branch [分支名](上面的内容已经讲解过)

本地分支的删除 :git branch -d|-D [分支名] 。-d删除分支的时候,也许该分支还有内容没有合并,会拒绝删除分支,如果

确认该内容无用,使用-D进行强制删除(小心使用)


git fetch ,git pull 两者都是讲远程分支的代码拉取到本地,但是fetch只是拉取,不会合并,git pull则会自动merge远程分支

的内容到自己的分支,也就是git pull = git fetch + git merge. 直接使用git pull可能使自己的分支与远程分支产生偏离。下面

具体看一个例子。

用一种蠢的情况来说明如何管理分支,现在有多个人进行开发,远程分支为origin/master,很多个人都克隆了,

远程仓库,在自己本地的master进行开发各自的功能。

现在有人进行了提交,将自己的代码推送到了远程分之master上。这时如果我们没有如何提交,直接git pull 就可以把

别人代码合并到自己的代码,自己的本地分支还是与远程分支对应。这个时候就是快速合并(fast forward)

当然这个时候我们自己很可能在本地分支已经有了自己的提交,因为本地master已经与远程master的commit已经不对

应,这个时候我们推送自己的修改到该远程分支会被拒绝。这时必须把拉取远程分支的代码拉取到本地:看看现在的

远程仓库情况:commitA 是最原始的提交,现在一个开发人员向远程分支推送了commit B 和commit C

 

而我们自己本地也进行了提交:本地的状态,提交了commit D和commit E:


这个时候我们的本地的master分支已经和远程的master分支不一致了,这个时候如果直接git fetch一次,将远程仓库的

修改拉取到本地,git fetch之后:

这个时候为了与远程分支保持一致,需要使用git rebase来衍合我们的修改,也就是将B C加入到D之前,如果使用

git merge来进行融合,那么我们的本地master分支将与远程的分支产生偏离:这个时候我们本地的master分支将无法

与远程master关联了:

直接使用git pull就会造成这种情况,可以使用git pull --rebase 这样告诉git拉取代码之后进行的是rebase操作。

这是一种糟糕的合作方式,因为rebase需要对每一个commit进行操作,产生冲突的概率要大于merge,同时rebase会

改变commit 的sha-1值,这样的管理很容易造成混乱。


合理的管理方式是,我们对自己的开发内容在原始分支上新建一个分支,完成后将自己的本地分支推送到远程仓库,由

git 管理员来管理远程分支,开发人员只需要在本地拉取远程仓库代码就好了。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章