Git 作为版本控制工具,另一个强大之处就是分支的管理;
而分支管理对于团队的合作至关重要,它可以让不同的团队在不同的分支上进行工作,这样团队之间就不会相互干扰;最后将分支进行合并,这样就愉快的完成了团队合作的任务
相比于其他的版本控制工具,Git 的分支模型是“必杀技”,因为 Git 处理分支的方式相当的轻量,创建以及在分支之间的切换都能很快的完成;因此,Git 实际上鼓励在工作流中使用分支;
一、分支简介
Git 保存数据的时候,实际上保存并不是文件的变化或者差异,而是一系列不同时刻的快照(snapshot)。
在进行提交(commit)操作的时候,这个提交会包含一个指向暂存内容快照的指针,同时还包含作者信息、提交时输入的信息以及指向它的父对象的指针(首次提交没有父指针)
-
实际上,每一次提交,产生的提交对象都会包含指向父对象的指针和指向快照的指针,如下图所示
-
分支的本质:仅仅是指向提交对象的可变指针
- 使用 HEAD 这个特殊指针来确定当前处于哪一个分支
- 当我们分别在两个分支上作出一些修改:
二、分支的基本操作
1)新建 & 切换分支 & 删除分支 & 查看分支
假设开始时,分支状态如图所示(当前处于 master 分支):
-
新建并切换分支:这样我们就可以在 test 分支上进行修改,而不会影响 master 分支了
# 新建 test 分支 git checkout -b test # 上面的命令是下面两个命令的简写 git branch test git checkout test
-
进行一系列的修改之后,分支情况如下:
-
删除分支
# 删除一个分支 git branch -d hotfix
-
查看分支(包括远程分支)
# 只查看本地分支 git branch # 查看包括本地和远程的所有分支 git branch -a
2)合并分支
合并分支有两种情况:
Fast-forward (--ff)
和No-fast-forward (--no-ff)
;
-
fast-forward (–ff):当顺着一个分支走下去能够达到另外一个分支,那么 Git 在合并的时候,只会简单地将指针向前移动;这个类型的合并不会产生新的提交
-
no-fast-forward (–no-ff):通常情况下,很少会出现上面的合并情况,因为我们多数情况下会在多个分支上同时进行修改,这里就是我们将要介绍的合并 no-fast-forward,这种情况将会创建新的提交(commit)
因为这时候在两个分支上分别进行了改动,两个分支分别做了哪些改动需要一个参考,这里就涉及到了三方合并,这里推荐腾讯官方文章
-
冲突合并
当两个分支在同一文件的同一行做了修改,在进行合并的时候就会产生冲突,因为即便有三方合并的比较,Git 还是不能确定哪一个版本是你想要保留的,这时候就需要我们手动进行选择
假设我们分别在 README.md 文件中进行了修改,则合并的过程如下图所示(注意到,这里我们解决冲突使用的是 vim,之后我们还需要添加到暂存区,然后添加到 Git 版本库):
3)变基操作
变基操作和合并分支都是将两个分支进行合并,但是变基操作通常很少用到,因为这样的操作会导致整个项目的历史的改变,尤其是大型的项目中比较少使用;
但是,变基操作有自己的使用场景:通常在 feature 分支上对文件进行了修改,同时 master 分支也进行了更新,这时候就可以进行变基操作来更新 feature 分支
-
变基操作的一个优点是不会产生冲突
-
使用变基的原则:只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作【如果没有彻底理解变基,那就不要用,只用合并操作即可】
4)Reverting
这个操作实际上是删除之前的修改,但是与我们在之前讲过的不太一样的地方是,我们并不是简单地回退到历史提交,而是创建新的提交回到历史状态
5)Cherry-pick
当另外一个分支的提交是我们当前分支想要的提交的时候,我们可以使用这个命令将另外一个分支的提交复制过来,这样在当前分支上,我们会创建新的提交
三、贮藏与清理
通常,当我们对一个分支进行了修改之后,如果我们想要切换分支,就必须让这个分支重新变的干净,所谓的干净就是没有未提交的文件;
那么,这时候就有两种方法,一种是直接提交,另一种就是贮藏;当我们不想提交的时候,贮藏是一个非常好的选择
贮藏(stash)会处理工作目录的脏的状态——即跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)
1)贮藏
将修改推送到栈上,就可以得到一个干净的分支
git stash
# or
git stash push
- 这个时候就可以切换到其他的分支了
2)查看贮藏
git stash list
3) 应用贮藏
# 默认应用最新的贮藏
git stash apply
# 指定应用的贮藏
git stash apply stash@{2}
# 应用并删除贮藏
git stash pop
- 应用贮藏不一定还是应用在原来的分支,可以是其他的分支;当然也不一定是干净的分支才能应用
4) 删除贮藏
删除指定的贮藏
git stash drop stash@{0}
四、版本回退
既然是版本控制工具,Git 的主要功能当然是控制版本,便于我们对于各个历史版本的管理;Git 可以清楚地记录我们提交的每一个历史版本,当我们想要回退到某一个历史版本的时候是非常方便的;
- 为了最简化,这里假设只有一个分支,所以不涉及分支的合并的问题;
1)查看历史版本
- 首先我们要查看历史版本,这样我们才能知道要回退到哪一个历史版本:
git log
- 当我们不是处于当前分支的最后一次提交的时候,会发现一些最新的提交并没有显示,别慌,使用下面的命令:
git reflog
2) 回退到指定的历史版本
从上面的 log 中我们可以查看每一次提交的 commit id,我们就是要使用这个 ID 来实现版本的回退;
-
以 HEAD 作为基准回退版本:
# 当前版本为 HEAD,则上一版本为 HEAD^, 上上版本为 HEAD^^, git reset HEAD^
-
以 commit id 来指定回退的版本
git reset commit_id_of_specific_version
这里需要注意的是
reset
后面可以加可选参数:# 仅仅是版本的回退,不会修改工作区的修改和暂存区的文件 git reset --soft commit_id_of_specific_version # 版本回退,会清除暂存区的文件,但是不会修改工作区的修改 【默认】 git reset --mixed commit_id_of_specific_version # 版本回退,同时清除工作区的修改和暂存区的文件 git reset --hard commit_id_of_specific_version
-
soft reset
: 版本回退,但是工作区和暂存区的内容都没有改变 -
hard reset:版本回退,暂存区和工作区的内容都清除(注意保存有用的修改)
-
另外一个需要注意的是: 即便使用
--hard
可选参数git reset
只能删除已经追踪的文件,不能删除未追踪的文件,比如新添加的但是还没有加入到暂存区的文件就是没有被追踪的,是不能删除的;这个时候就需要使用git clean
来删除没有跟踪的文件,但是使用这个命令的时候要小心,不要随意添加参数,导致.gitignore
中的文件都被删除;
-