Git:分支管理
目录
理解分支
初步了解创建、切换、合并分支
删除分支
合并冲突
合并模式
分支策略
bug 分支
强制删除分支
理解分支
在版本回退里,我们知道每次的提交,Git 都把它们串成一条时间线,这条时间线就可以理解为是一个分支
截止到目前,我们操作的只有一条时间线,叫做主线,在 Git 里这个分支叫 主分支,即 master分支
版本库 中有 HEAD指针,指向 master,而 master 又指向最新的提交(commit ID),所以 HEAD 指向的就是当前分支
初步了解创建、切换、合并分支
创建新的分支就像下图的红色线路一样,可以将新分支与老分支合并:
首先,我们可以通过 git branch 命令,查看当前本地仓库中有哪些本地分支:
- 其实 HEAD 指针,也是可以指向其他分支的,被 HEAD 指向的分支就是当前正在工作的分支
- 这里 * 出现在 master 前面,是因为我们目前正在 master 分支上工作
下面使用 git branch 分支名 创建新分支,此时就能够看到创建出了 dev分支:
这时的 HEAD 还是指向 master 分支的:
并且执行 tree .git,观察 .git 文件内容,也能看到新增一个 dev分支:
此时打印 master 和 dev 存放的内容,发现存放的都是最新的一次提交:
此时也就是下面的状态图,因为创建 dev 分支时,其实是站在当前最新的版本上去创建的 dev 分治,所以 dev 分支指向了最新的提交:
将 HEAD 切换到 dev 分支上去
通过 git checkout 分支名 命令来切换分支,下面我们将 HEAD 切换到 dev 分支上去,再通过 git branch 命令查看,发现 * 出现在了 dev 分支前,表示切换成功了:
观察到 HEAD 指针也指向了 dev 分支:
这时状态图就变为了:
下面在 dev 分支上,对 ReadMe 文件修改:
接着将 ReadMe文件 进行 add、commit 提交到暂存区:
此时打印 dev 分支的 ReadMe 文件,发现是有新增的这行代码的:
下面重新切换回 master 分支:
此时打印 master 分支下 ReadMe 文件的内容,发现并没有我们刚刚新增的代码:
打印 master 分支当前存储的内容,是 d431 开头的 commit ID:
再执行 git cat-file -p 打印 dev 分支存储的 commit ID 的具体内容,是 6773 开头的 commit ID
并且 parent 指向的 commit ID 正是 master 分支中存储的 d431 开头的 commit ID:
所以当前状态图中,dev 分支就指向了最新的提交:
下面实现让 master 分支合并 dev 分支的内容
先切换到 master 分支上:
再通过 git merge 需要合并的分支名 命令合并 dev 分支的内容:
再打印 master 分支下的 ReadMe 文件,发现有了在 dev 分支下新增的内容:
此时观察 master 分支存储的 commit ID,发现正是刚刚 dev 分支存储的 6773 开头的 commit ID:
最终的状态图是:
删除分支
在刚刚我们创建的 dev 分支,所做的工作就是在 ReadMe 文件中新增了一行内容,最后 master 分支也合并了这行内容,那么这里的 dev 分支就相当于已经完成了作用,此时就需要将 dev 分支删除
此时我们就可以在 master 分支下,通过 git branch -d 需要删除的分支名 来删除分支:
需要注意:如果我们想删除 dev 分支,是不能在 dev 分支上删除 dev 分支,这就相当于自己删除自己,会有问题
下面观察本地分支,就只有一个 master 分支了:
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全
合并冲突
在未来的工作中,执行合并操作时最容易出现问题,因为可能会出现合并冲突
合并冲突,需要手动解决,并进行一次提交操作
例如:此时 ReadMe 文件内容是:
master 分支将 aaa on dev branch 改为 ccc on dev branch
创建一个 dev 分支,dev 分支将 aaa on dev branch 改为 bbb on dev branch
此时进行合并操作,git 并不知道到底是合并为 bbb 还是 ccc,所以这里就存在了合并冲突问题,需要人为去解决
在前面学习的 创建dev分支 是执行:git branch dev
切换到 dev 分支是执行:git checkout dev
下面可以执行 git checkout -b dev 命令,完成上面的两个动作:
下面将 dev 分支上的 ReadMe 文件的第三行内容改为 bbb on dev branch:
接着执行 add、commit 操作:
下面切换到 master 分支,修改为 ccc on dev branch:
继续执行 add、commit:
此时本地仓库的状态如下所示:
我们目前是 master 分支,合并 dev 分支,告诉我们合并冲突了:
还告诉我们:自动合并失败;解决掉冲突后,将结果再进行一次提交
下面 vim 打开 ReadMe 文件:
注意:
- 在 <<<<<<< HEAD 和 >>>>>>>> dev 之间的都是冲突代码
- <<<<<<< HEAD 和 ======== 之间的是 当前分支 上的冲突代码
- >>>>>>>> dev 和 ======== 之间的是 dev 分支上的冲突代码
此时 git不能替我们决定 保留 bbb 还是 ccc。需要开发人员自己判断,假设我们想要保留 bbb,只需要手动将 其余内容删除掉即可:
下面执行 add 将变化提交到 暂存区,再执行 commit 将变化提交到 版本库:
此时 ReadMe 文件保留的就是 bbb on dev branch 内容:
此时的状态图为:
下面可以通过 dev 和 master 中存储的 commit ID,来确定上述的状态图
先看 master 和 dev 存储的 commit ID:
master:89aa 开头的 commit ID
dev:a959 开头的 commit ID
下面通过 git log 查看 git 的提交日志:
89aa 开头的 commit ID 存储的就是最新的 merge dev
a959 开头的 commit ID 存储的就是 dev 分支最新的修改
下面介绍几个 git log 的参数,可以更清楚的看到提交信息:
git log --graph --abbrev-commit
最前面的红色表示 master 分支,绿色表示 dev 分支,* 出现在哪个分支的线上,就表示在 哪个分支上进行的操作
合并模式
我们上述的学习中,其实是进行了两种 merge 操作的,第一次非常顺利,第二次出现了合并冲突,这其实就对应两种 合并模式,分别是 ff模式 和 no-ff模式
下面复现一下第一次合并顺利的场景,先创建一个 dev2 分支,并直接切换到 dev2 分支:
此时给 ReadMe 文件新增一行内容 abc:
接着执行 add、commit:
再切换回 master 分支,并合并 dev2 分支的内容:
上面成功合并,并显示 Fast-forward 模式
下面使用 git log --graph --abbrev-commit:
我们观察刚刚下面框住的图示,可以明显看出来是进行了 merge 操作的
而上面框起来的部分,是刚刚合并的 Fast-forward 模式,并不能清楚的得知是 merge 进来的,还是 master 自己提交的
使用 ff 模式(简称)的状态图如下:
看不出来最新提交到底是 merge 进来的,还是正常提交的
而前面合并冲突后提交的,就能清楚的看到是 merge 进来的,称之为 no-ff 模式
那么如何使用 no-ff 模式,而不使用 ff 模式呢,方法如下:
首先切换到 dev2 分支,在 abc 后面再加上 def:
修改完后,继续 add、commit:
此时切换回 master 分支,执行 git merge --no-ff -m "新提交的注释" dev2
这个选项就表示,合并时不使用 ff 模式,而是使用 no-ff 模式:
下面观察打印的日志,此时也可以通过前面的图示明白,是 master 分支 merge 进来的内容,而不是 master 分支自己提交的:
此时的状态图如下所示:
上述的 git merge --no-ff -m "新提交的注释" dev2 ,中 -m "新提交的注释" ,就表示最后一个提交的内容
综上所述,比较建议合并分支时,采用 no-ff 模式
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定,也就是仅用来发布新版本,平时不能在上面干活
日常开发环境:开发人员提交的代码,还未经过测试验证,是不稳定的,且可能存在 bug 的
经过一系列测试,最终将稳定的代码合并到 master 上
下面举例简易说明分支所带来的好处:
假设最初 master 分支有两个稳定的版本,用红色替代,这时有了两个新的需求,分别由张三(蓝色)和李四(绿色)进行开发,此时状态图就是:
在张三和李四完成任务后,经过测试没有问题后,往 master 分支中进行合并
没有合并冲突的话直接合并,有合并冲突的话解决完冲突再合并
此时 master 分支就会新增了张三和李四实现的功能,如果在这个过程中,还有其他需求,也可以继续开分支,实现需求后再往 master 分支中合并
这也就初步体现了 git多人协作 的优势
bug 分支
我们所使用的线上环境其实是一个比较稳定的环境,因为线上环境部署的是 master 主分支上的代码,而 master 主分支上的代码其实是比较稳定的代码
但是在我们之前使用 APP 时,也可能出现 卡死或闪退 的情况,出现这些情况就是因为线程环境其实也不是百分百稳定的,一旦有这种问题,就说明是我们的 master 主分支的代码出现了 bug
出现这种问题后,我们不能直接在 master 主分支上进行代码修改,因为我们既然在这里学习分支这个概念,就要遵守分支的策略,并且在 master 分支上修改 bug,如果改出了更大的 bug,那就问题更加严重了,所以不能直接在 master 分支上修改代码
我们需要在本地去创建一个专门修复 master 分支上 bug 的一个分支,最后修改完后,通过测试将稳定的代码再合并到 master 分支上去,从而解决这个 bug
下面所讲的前提条件是:我们新增了一个 dev2 分支,并且在 dev2 分支上已经进行了部分代码的开发,还未提交时,突然发现了 master 分支上的一个 bug,此时该如何解决
首先切换到 dev2 分支上,对ReadMe 文件进行操作,在最后新增一行内容:
此时 dev2 分支不进行 add、commit 操作,直接切换到 master 分支,打印 master 分支的 ReadMe 文件,发现 master 分支能看到 dev2 分支新增的内容:
这是因为 dev2 分支虽然修改了 ReadMe 文件,新增了一行内容,但没有 git add、commit,所以这些更改仍然只是工作区的修改,此时的状态图是这样的:
只有当 dev分支修改内容后,执行了 add、commit,这时状态图就变为这样了,master 分支就看不到 dev2 分支所做的修改了:
所以基于以上的知识,我们不想让 dev2 分支修改的工作区的内容影响 master 分支,所以可以使用命令:git stash,表示将工作区中的内容进行保存到了 stash 中
这里的 stash 就是在 .git 仓库中存在的一块区域,通过 tree .git/ 查看,发现在 refs 下新增了 stash
这里需要注意:
ReadMe 文件已经被 git 追踪管理了,所以 ReadMe 文件在工作区的修改,git 是可以保存起来的,而如果只是在工作区中创建一个文件,并没有进行 add、commit 操作,这时是不能进行 git stash 的操作的
所以在我们将 dev2 分支新增的内容保存到 stash 中后,再切换到 master 分支,这时 master 分支就不会看到 dev2 分支新增的内容了:
下面开始正式创建并切换一个新分支,解决发现的 master 分支的 bug,叫做 fix_bug:
我们假设所发现的 master 分支上 ReadMe 文件的 bug 是:最后一行的内容少了一个 字母g,当我们加上 字母g 以后就修复成功了,所以下面新增一个 字母g:
接着将修改后的 ReadMe 文件,进行 add、commit 操作:
当前的状态就是 bug 已经修复了,但是还没有合并到 master 分支中
所以接下来切换到 master 分支上,将 fix_bug 分支合并到 master 分支上:
此时打印 ReadMe 文件,发现已经被修复了 bug,最后一行内容新增了一个 字母g:
到此 master 分支的 bug 被解决了,但是最开始的 dev2 分支开发还没有完成,需要继续开发
下面继续切换到 dev2 分支上,并将 stash 刚刚存储的内容再拿回来
执行 git stash list,可以查看 stash 中存储了哪些内容:
执行 git stash pop,将 stash 的内容拿出来:
此时再打印 ReadMe 文件,发现 I am coding 内容拿回来了,倒数第二行的 abcdef 后面还没有 字母g,表明在当前的 dev2 分支上并没有修复 bug:
下面继续对 dev2 分支进行开发,下面就表示开发完成:
在 dev2 分支进行 add、commit操作:
此时的状态图是:
此时因为 master 分支多了一次 fix_bug 分支的合并代码,如果在 master 分支上,直接合并 dev2 分支,会发生合并冲突
这时就需要人为的解决冲突,而人为手动解决冲突,就可能会改出 bug,导致 master 分支的代码又出错,所以这不是我们想要的结果
所以为了避免这种情况,不想影响 master 主分支,我们可以反过来合并,也就是在 dev2 分支上合并 master 分支,合并后就算有问题也可以在本地分支多次修改测试,不会影响 master 分支的代码
在 dev2 分支上合并完 master 分支,解决完 bug 后,再在 master 分支上合并 dev2 分支,此时就不会有 合并冲突 了,也就不会影响 master 分支的代码了
此时的状态图就变为了:
下面实现上述的操作:
在 dev2 分支上,合并 master 分支,显示在 ReadMe 文件上有冲突:
查看此时的 ReadMe 文件:
下面在 dev2 分支上手动修改 合并冲突 问题,ReadMe 文件内容变为:
解决完冲突后,再进行一次提交:
接下来切换到 master 分支,让 master 合并 dev2 分支,这次就是没有 合并冲突 的问题了:
打印 ReadMe 文件内容,发现确实有了 I am coding... Done! 内容:
此时 master 完成了 bug 的修改,也合并了 dev2 分支提交的需求代码,所以删除掉 dev2 和 fix_bug 分支即可,就只剩下主分支 master 分支了:
至此,master 分支就是最新最全,且没有 bug 的分支了
强制删除分支
在实际工作中,还可能存在产品经理一周前提出一个需求,我们就拉一个 dev 分支去开发,结果一周后产品经理说不需要做这个需求了
针对这种情况,我们不能直接 git branch -d dev 删除 dev 分支,因为我们在这一周的开发过程中,有提交代码,并且也没有进行 merge合并,所以 git 会默认 dev 分支上提交的代码是有用的,不能随便删除,所以会删除失败
此时我们如果百分百决定,将 dev 分支的内容进行删除操作的话,只需要将 -d 改为 -D,也就是执行:git branch -D dev 强行删除
下面演示上述情况,先创建并切换一个 dev 分支,在 ReadMe 文件中新增一行内容:
再执行 add、commit:
此时产品经理说,当前功能不需要了,我们就想要删除 dev 分支,因为不能在自己分支上删除自己,所以先切换到 master 分支再删除
这时就会报错,告诉我们 dev 分支的内容还没有完全 merge
如果我们想强制删除,就将 -d 改为 -D,此时就只剩下 master 分支了:
Git的分支管理学习到此结束啦