上一篇中我们介绍了git的基础概念,这篇我们就来说说分支
分支
git中的分支其实只是一个指针指向一个commit对象,而不是像传统的版本控制系统一样把整个当前版本复制一份出来。它背后其实就是一个文件,我们可以去.git/refs/heads文件夹下面查看,里面的每个文件其实就是一个分支,而内容其实就是一串SHA1值,而这个SHA1值又是什么呢,其实就是一个commit id,以下图为例,其实此时master分支文件存放的就是master分支上的最后一次commit id,而分支背后代表的就是一条commit对象链。
而HEAD又是什么呢,其实它也是一个指针,指向着是当前分支,而不是commit对象。在.git目录下有一个HEAD文件,里面就是记录着HEAD指向的分支。
分支合并
接下来就用一个例子来介绍一下git分支合并的过程,首先假设我们处于master分支上
接着我们从当前master上创建一个新分支dev,并且换到当前分支上git checkout -b dev
下一步我们在dev分支上修改并提交一个新的commit
接着我们回到master分支并执行git merge dev
合并分支
这里要注意的是merge默认采用的是fast-forward快进模式,它代表着就是直接将master指针指向最新的commit id,中间master分支不会做任何的修改,而没有创建任何新的commit,但是实际上不是任何情况下我们都可以直接这样快进的,合并时修改的不是同一个文件的同一个内容是可以直接fast-forward合并,但是因为两个分支都有过commit,所以合并的时候就一定会产生一个新的commit对象,同时当我们修改的是同一个文件同一个内容时,合并是会产生冲突的,这是后我们就必须要手动修复冲突,解决后通过git add标示冲突已解决并用git commit提交,也一样不可避免会产生一个新的commit。在合并之后,反过来如果被merge的分支想要去merge已经merge完的分支,就直接fast-forward就可以了,以为两条分支已经有了一个交汇点,所以直接快进至交汇点即可,所以这里其实是可以总结一条规律的,什么情况下可以快进呢,就是当当前的commit对象链可以从当前想要merge的分支走到要merge到的commit点,那就可以直接快进了。
那说完快进模式后,自然也就有非快进模式,非快进模式模式在上面提到的情况时即便没有冲突,两个分支也没有同时进行修改也会产生一个新的commit,在我们merge时我们只要加上一个--no-ff
即可,git merge --no-ff dev
Reset
git reset其实有3中模式,mixed, soft和hard,默认不写的话会调用mixed模式,那么3者的区别是什么呢,mixed其实就是会将reset之后的commit与原commit之间的修改转到工作区,而soft则是转到暂存区,最后hard就是直接丢弃掉,所以可以看出其实reset与它的字面意思有些不同的,我们经常会误会它是重新设定把之后的commit砍掉,其实它只是回到之前的状态。
Stash
git stash的作用是什么呢,假设当我们在feature1上做事时,突然需要紧急去feature2上做事,此时就需要将feature1上的事情暂时用git stash先存起来然后去feature2上做事,做完再回feature1上通过git stash pop将之前保存的修改取出。需要注意的是git stash会记住目前的commit id,所以如果同一个commit下stash两次对于同一文件同一行内容的修改,恢复完第一次并且提交过后,在执行第二次恢复就会有冲突。
Tag
tag就是我们之前提到的其实就是一个标签对象,它也是指向某一个commit对象,它与分支的区别就在于当有commit发生时,分支会跟着一起向前移,但是标签一旦定下来就不会再移动了,所以它通常是用于我们项目到了一个milestone,需要发布一个新版本时使用。git标签分为两种轻量级标签(lightweight)和附注标签(annotated),就如同字面上的意思,轻量级于附注标签的区别就是一个有描述信息一个没有。
Diff
git diff背后其实就是利用了linux自带的diff模块用来比较文件之间的差别,有兴趣的可以去了解一下
指令
-
查看分支列表
git branch
-
创建分支
git branch 分支名
-
切换分支
git checkout 分支名
-
切换到上次处于的分支中
git checkout -
-
删除分支()
git branch -d 分支名
这里需要注意不能删除当前处于的分支,如果非master分支有改动但还未merge的话也不可以,除非使用git branch -D 分支名
-
创建新分支并切换到新分支
git checkout -b 分支名
-
显示当前分支最近的一条提交消息
git branch -v
-
将分支合并到当前分支
git merge 分支名
-
禁用fast-forward,会多一个commit id
git merge --no-ff 分支名
-
回退到上一次提交(基于当前的commit)
git reset --hard HEAD^
-
回退到上上一次提交(都是基于当前的commit)
git reset --hard HEAD^^
-
回退到当前分支上的前n次(从当前commit往前n次)的提交
git reset --hard HEAD~n
-
回退到指定commit
git reset --hard commit信息的前几位
-
修改分支名
git branch -m 原分支名 新分支名
-
将工作区的修改保存
git stash
-
列出所有的保存
git stash list
-
手动设置stash描述
默认执行git stash返回的描述信息是
其实就是包含了当前最新commit的消息,那么我们可以通过以下命令来修改描述信息git stash save 'hello basic'
-
恢复最近一次的保存,并且会把这次保存在列表中删除
git stash pop
-
恢复最近一次的保存,但是不会在列表中删除
git stash apply
-
apply特定一个版,并且会把这次保存在列表中删除
git stash apply stash@{0}
-
手动删除指定的一个保存版本
git stash drop stash@{0}
-
创建轻量标签
git tag v1.0.1
-
创建附注标签
git tag -a v1.0.2 -m 'release 1.0.2'
-
查看所有标签
git tag
-
查找标签
git tag -l 'v1.0'
里面可以使用pattern,例如'v*', 代表v开头的所有标签 -
删除标签
git tag -d 标签名
-
列出每一行都是谁在什么时间哪个commit修改的
git blame 文件名
-
比较算入暂存区修改的当前文件与工作区文件之间的区别
git diff
-
比较当前最新commit与工作区的区别
git diff HEAD
-
比较某个commit与工作区的区别
git diff commit_id
-
比较最新提交与暂存区的区别
git diff --cached
-
比较某个commit与暂存区的区别
git diff --cached commit_id
场景
-
当我们使用
git reset --hard
指令回退到之前的commit时,执行git log
只能看到当前commit及其之前的commit,那如果我想回到之后的某个commit,我要怎么获取commit id呢?我们可以通过执行
git reflog
来查看操作日志,看到我们使用git的指令的历史,这样就可以找回我们之前的commit id了
Q&A
-
git checkout commit-id
与git reset --hard commit-id
有什么区别呢?两者都可以回到对应的commit点,但是checkout与reset不同的是它会处于游离状态,任何的修改如果不做提交就会有警告不允许我们跳去其他的commit,同时修改完我们可以通过
git branch 分支名 当前checkout的commit-id
来创建一个新分支。