【Git原理和使用】Git 分支管理(创建、切换、合并、删除、bug分支)

一、理解分支

我们可以把分支理解为一个分身,这个分身是与我们的主身是相互独立的,比如我们的主身在这个月学C++,而分身在这个月学java,在一个月以后我们让分身与主身融合,这样主身在一个月内既学会了C++,也学会了java。

在版本回退里,已经知道,每次提交,Git都把它们串成⼀条时间线,这条时间线就可以理解为是⼀个分支。截至目前,只有⼀条时间线,在Git⾥,这个分支叫主分⽀,即 master 分支。

再来理解⼀下HEAD,HEAD 严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD 指向的就是当前分支

每次提交,master分支都会向前移动⼀步,这样,随着你不断提交,master分支的线也越来越长,而 HEAD只要⼀直指向master分支即可指向当前分支

二、创建分支

Git支持我们查看或创建其他分支,在这里我们来创建第⼀个自己的分支 dev ,对应的命令为:

复制代码
git branch [分支名]

zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch  
* master
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch dev
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch
  dev
* master

git branch 指令可以查看本地仓库所有的分支,可以看到有的分支前面有一个 * 号,表示当前HEAD指向的该分支

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ ls .git/refs/heads/
dev  master
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat .git/refs/heads/*
fb025b3966df8e8808a120105cbf80440d26e6bb
fb025b3966df8e8808a120105cbf80440d26e6bb

可以发现当前dev和master指向的是同一个修改,并且也可以验证下 HEAD 目前指向 master 的。

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat .git/HEAD 
ref: refs/heads/master

三、切换分支

当我们创建好一个分支,发现HEAD还是指向原本的分支,想要HEAD指向目标分支可以使用指令:

复制代码
git checkout [分支名]

zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout dev
Switched to branch 'dev'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch
* dev
  master
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat .git/HEAD 
ref: refs/heads/dev

当然想让HEAD直接指向新创建的分支也是可以的,例如:

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout -b dev
Switched to a new branch 'dev'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch
* dev
  master

这样HEAD就直接指向dev分支了

接下来我们在dev分支下修改一下ReadMe文件,在提交试一下

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ vim ReadMe 
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat ReadMe 
hello git
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git add .
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git commit -m"md ReadMe"
[dev 1e9400d] md ReadMe
 1 file changed, 5 deletions(-)

然后我们切回master分支,会发现ReadMe文件并没有被改变,这是因为改动是在dev分支下提交的,master分支并不受影响

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout master 
Switched to branch 'master'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat ReadMe 
hello git
hello git
aaaaa
bbbbb
ccccc
ddddd

想要让master分支可以看到dev分支下的修改,就需要将master分支合并dev分支

四、合并分支

合并分支要用到指令:

复制代码
git merge [分支名]

zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch 
  dev
* master
#合并分支
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git merge dev
Updating fb025b3..1e9400d
Fast-forward
 ReadMe | 5 -----
 1 file changed, 5 deletions(-)
#可以看到文件的修改了
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat ReadMe 
hello git

Fast-forward 代表"快进模式",也就是直接把master指向dev的当前提交,仅改变了指向关系,所以合并速度非常快。 当然,也不是每次合并都能 Fast-forward,我们后面会讲其他方式的合并。

五、删除分支

合并完成后, dev 分支对于我们来说就没用了, 那么dev分支就可以被删除掉

复制代码
git branch -d [分支名]
#注意:不能再某分支下删除那个分支

在master分支下删除dev分支 :

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch -d dev 
Deleted branch dev (was 1e9400d).
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch 
* master

因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是⼀样的,但过程更安全,至于为什么安全,我们后续再讲

六、合并冲突

在实际开发过程中,并不是每次合并都能成功,有时候可能遇到代码冲突的问题,这就需要我们程序员手动解决了。

举个例子,dev分支修改了ReadMe文件,并且提交了,在这个期间,别人在master分支也修改了master分支,那dev分支在与master分支合并的时候,git就不知道应该保留dev分支下的修改还是保留master分支下的修改,就存在了合并冲突。

复制代码
#创建一个dev分支,并修改文件
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout -b dev
Switched to a new branch 'dev'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ vim ReadMe 
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat ReadMe 
hello git
aaa
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git add .
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git commit -m"dev commit"
[dev dcafaf1] dev commit
 1 file changed, 1 insertion(+)
#切换master分支也修改文件
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout master 
Switched to branch 'master'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ vim ReadMe 
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat ReadMe 
hello git
bbb
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git add .
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git commit -m"master commit"
[master f28d2fd] master commit
 1 file changed, 1 insertion(+)

现在的情况变为了这样:

当我们在master分支想要合并dev分支时,就会发生合并冲突

复制代码
#合并冲突
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git merge dev
Auto-merging ReadMe
CONFLICT (content): Merge conflict in ReadMe
Automatic merge failed; fix conflicts and then commit the result.

发现 ReadMe 文件有冲突后,可以直接查看文件内容,要说的是 Git 会用 >>>>>> 来标记出不同分支的冲突内容,如下所示:

复制代码
hello git
<<<<<<< HEAD
bbb
=======
aaa
>>>>>>> dev

此时我们必须要手动调整冲突代码,并需要再次提交修正后的结果(再次提交很重要,切勿忘 记)

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat ReadMe 
hello git
aaa
bbb
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git add .
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git commit -m"merge success"
[master 8d5cf2a] merge success

到这⾥冲突就解决完成,此时的状态变成了

用 带参数的 git log 也可以看到分支的合并情况:

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git log --graph --pretty=oneline --abbrev-commit
*   8d5cf2a (HEAD -> master) merge success
|\  
| * dcafaf1 (dev) dev commit
* | f28d2fd master commit
|/  
* 1e9400d md ReadMe
* fb025b3 delete file
* 4d9912b 测试删除
* 691a70b ReadMe version3
* e9c0a2d ReadMe version2
* 771fbc6 ReadMe version1
* bec8f39 modify ReadMe
* 33d37b6 add ReadMe

七、分支管理策略

通常合并分支时,如果可能,Git 会采用 Fast forward 模式。还记得如果我们采用 Fast forward 模式之后,形成的合并结果是什么呢?回顾⼀下

在这种 Fast forward 模式下,删除分支后,查看分支历史时,会丢掉分支信息,看不出来最新提 交到底是 merge 进来的还是正常提交的。但在合并冲突部分,我们也看到通过解决冲突问题,会再进行⼀次新的提交,得到的最终状态为:

那么这就不是 Fast forward 模式了,这样的好处是,从分支历史上就可以看出分支信息。例如我 们现在已经删除了在合并冲突部分创建的 dev1 分支,但依旧能看到 master 其实是由其他分支合并得到:

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git log --graph --pretty=oneline --abbrev-commit
*   8d5cf2a (HEAD -> master) merge success
|\  
| * dcafaf1 (dev) dev commit
* | f28d2fd master commit
|/  

Git 支持我们强制禁用 Fast forward 模式,那么就会在 merge 时生成⼀个新的 commit ,这样, 从分支历史上就可以看出分支信息。

下面我们实战⼀下 --no-ff ⽅式的 git merge 。首先,创建新的分支 dev2 ,并切换至于新的分支:

复制代码
#创建一个新分支
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout -b dev2
Switched to a new branch 'dev2'
#修改完代码提交
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ vim ReadMe 
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat ReadMe 
hello git
hello
nihao
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git add .
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git commit -m"dev2 commit"
[dev2 e0babc3] dev2 commit
 1 file changed, 1 insertion(+)
#切换master分支
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout master 
Switched to branch 'master'
#采用普通合并
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git merge --no-ff -m"no-ff merge dev2" dev2
Merge made by the 'recursive' strategy.
 ReadMe | 1 +
 1 file changed, 1 insertion(+)

请注意 --no-ff参数,表表示禁用 Fast forward 模式。禁用 Fast forward 模式后合并会创建 ⼀个新的 commit ,所以加上 -m 参数,把描述写进去。

合并后,查看分支历史:

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git log --graph --pretty=oneline --abbrev-commit
*   f780c2a (HEAD -> master) no-ff merge dev2
|\  
| * e0babc3 (dev2) dev2 commit
|/ 
*

可以看到,不使用 Fast forward 模式,merge后就像这样:

对比一下Fast forward 模式,采用普通合并可以清晰的看到合并的过程,而Fast forward 模式就看不出来,开发中一般都使用 --no-ff 方式合并,这样当某一块的代码出现问题时,就可以按照历史准确找到写这块代码的程序员

八、分支策略

在实际开发中,我们应该按照几个基本原则进行管理:

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本

你和你的同事们每个⼈都在dev分支上干活,每个⼈都有自己的分⽀,时不时地往dev分支上合并就可以了。 所以,团队合作的分支看起来就像这样:

九、bug分支

假如我们现在正在 dev2 分支上进行开发,开发到⼀半,突然发现 master 分支上面有 bug,需要 解决。在Git中,每个 bug 都可以通过⼀个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。

可现在 dev2 的代码在工作区中开发了一半,还无法提交,怎么办?

如果你直接从包含未提交更改的 dev 分支切换到主分支,Git 会阻止你这样做,以防丢失你的工作进度。这是因为未提交的更改可能与主分支上的文件冲突。

Git 提供了 git stash 命令,可以将当前的工作区信息进行储藏,被储藏的内容可以在将来某个时间恢复出来。

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git stash
Saved working directory and index state WIP on dev2: e0babc3 dev2 commit
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git status
On branch dev2
nothing to commit, working tree clean

用 git status 查看工作区,就是干净的(除非有没有被 Git 管理的文件),因此可以放心地创建分 支来修复bug

储藏 dev2 工作区之后,由于我们要基于master分支修复 bug,所以需要切回 master 分⽀,再新 建临时分支来修复 bug,示例如下:

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout master 
Switched to branch 'master'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout -b fix_bug
Switched to a new branch 'fix_bug'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ vim ReadMe    //假设这一步是修改bug
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git add .
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git commit -m"fix_bug"
[fix_bug d6a229a] fix_bug
 1 file changed, 1 insertion(+), 1 deletion(-)

修复完成后,切换到 master 分支,并完成合并,最后删除 fix_bug 分支:

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout master 
Switched to branch 'master'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git merge --no-ff -m"merge fix_bug branch" fix_bug 
Merge made by the 'recursive' strategy.
 ReadMe | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git branch -d fix_bug 
Deleted branch fix_bug (was d6a229a).

至此,bug 的修复工作已经做完了,我们还要继续回到 dev2 分支进行开发。切换回 dev2 分支

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git checkout dev2 
Switched to branch 'dev2'
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git status
On branch dev2
nothing to commit, working tree clean

当切换回dev2分支以后,会发现工作区是干净的,那之前我们写的代码去哪里了呢?因为我们之前使用git stash把他们保存起来了,可以使用 git stash list 来查看

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git stash list
stash@{0}: WIP on dev2: e0babc3 dev2 commit

工作现场还在,Git 把 stash 内容存在某个地方了,但是需要恢复⼀下,如何恢复现场呢?我们可以使用 git stash pop 命令,恢复的同时会把 stash 也删了,示例如下:

复制代码
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git stash pop   //恢复工作
On branch dev2
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   ReadMe

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (5cbc7f7aaeb9a758eac2d9dc5e908f721bec1424)
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ git stash list   //stash为空了
zyq@iZm5egpp4a85g2tfliaeikZ:~/gitcode$ cat ReadMe       //代码回来了
hello git
hello
nihao
I am coding......

另外,恢复现场也可以采用 git stash apply 恢复,但是恢复后,stash内容并不删除,你需要用 git stash drop 来删除; 你可以多次stash,恢复的时候,先用 git stash list 查看,然后恢复指定的stash,用命令 git stash apply stash@{0}

恢复完代码之后我们便可以继续完成开发,开发完成后便可以进行提交,但是要注意,修复 bug 的内容,并没有在 dev2 上显示。此时的状态图为:

Master 分支目前最新的提交,是要领先于新建 dev2 时基于的 master 分支的提交的,所以我们在 dev2 中当然看不见修复 bug 的相关代码。 我们的最终目的是要让 master 合并 dev2 分支的,那么正常情况下我们切回 master 分支直接合并即可。

但这样其实是有⼀定风险的。 是因为在合并分支时可能会有冲突,而代码冲突需要我们手动解决。如果在 master 上合并出现冲突,我们解决时无法保证对于冲突问题可以正确地⼀次性解决掉,因为在实际的项目中,代码冲突不只几行那么简单, 有可能几十上百行,甚至更多,解决的过程中难免出错,导致错误的代码被合并到 master 上。 此时的状态为:

解决这个问题的⼀个好的建议就是:最好在自己的分支上合并下 master ,再让 master 去合并 dev ,这样做的目的是有冲突可以在本地分支解决并进行测试,而不影响 master 。此时的状态为:

十、删除临时分支

软件开发中,总有新的功能要不断添加进来。 添加⼀个新功能时,你肯定不希望因为⼀些实验性质的代码,把主分支搞乱了,所以,每添加⼀个新 功能,最好新建⼀个分支,我们可以将其称之为 feature 分支,在上面开发,完成后,合并,最后,删除该 feature 分支。

可是,如果我们今天正在某个 feature 分支上开发了一半,被产品经理突然叫停,说是要停止新功 能的开发。虽然⽩白干了,但是这个 feature 分支还是必须就地销毁,留着没用了。这时使用传统 的 git branch -d 命令删除分支的方法是不行的,会报错,因为这个分支还没有被合并过

如果我们确实想删掉这个分支,可以使用 git branch -D 选项强制删除

小结:

分支在实际中有什么用呢?

假设你准备开发⼀个新功能,但是需要两周才能完成,第⼀周你写了50% 的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全 部写完再⼀次提交,又存在丢失每天进度的风险。

现在有了分支,就不用怕了。你创建了⼀个属于你自己的分支,别人看不到,还继续在原来的分⽀上 正常⼯作,而你在自己的分支干活,想提交就提交,直到开发完毕后,再⼀次性合并到原来的分支上,这样,既安全,又不影响别人工作。 并且Git无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。

相关推荐
自学也学好编程1 小时前
Git远程仓库与协作技巧详解
git
测试开发技术3 小时前
git rm 命令与系统的 rm 命令有什么区别?
git·gitlab·github·面试题
longze_74 小时前
Jenkins credentials 增加了github credential 但是在Git SCM 凭证中不显示
git·github·jenkins
<但凡.7 小时前
Git 完全手册:从入门到团队协作实战(1)
linux·git
大卫小东(Sheldon)16 小时前
面向 Git 用户的 jujutsu 使用入门
git
大飞pkz20 小时前
【Git】git lfs自动跟踪大文件
git·lfs·git lfs·大文件传入github·lfs大文件自动跟踪
自学也学好编程21 小时前
Git分支管理与工作流详解
git
自学也学好编程1 天前
Git基础概念与常用命令详解
git
linrunxinnn1 天前
Git 团队协作总结 —— 不只是版本控制的工具
git
吱吱02号机1 天前
<Git>从零创建远程新仓库(最小操作)
git