三、分支管理
1.创建和合并分支
①图解
每次提交,Git都会把它们串成一条时间线。这条时间线就是一个分支。截至到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:

每次提交,master分支都会向前移动一步,这样随着你不断提交,master分支的线也越来越长。
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,然后HEAD指向dev,就表示当前分支在dev上:

你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:

假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:

所以Git合并分支也很快!就改改指针,工作区内容也不变合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:

②命令
首先,我们创建dev分支,然后切换到dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
然后,用git branch命令查看当前分支:
$ git branch
* dev
master
git branch命令会列出所有分支,当前分支前面会标一个*号。
在dev分支修改、添加、提交readme.md,切换回master(git checkout master)分支后,再查看readme.md文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:

现在,我们把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.md | 1 +
1 file changed, 1 insertion(+)
git merge命令用于合并指定分支到当前分支,通过上面的Fast-forward信息,Git告诉我们这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。合并完成后,就可以放心地删除dev分支(git branch -d dev)。
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
③switch
我们注意到切换分支使用git checkout <branch>,而前面讲过的撤销修改则是git checkout -- <file>,同一个命令,有两种作用,确实有点令人迷惑,因此,最新版本的Git提供了新的git switch命令来切换分支。
创建并切换到新的dev分支:git switch -c dev
切换到master分支:git switch master
2.解决冲突
当master分支和dev分支各自都分别有新的提交,变成了这样:

这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
$ git merge dev
Auto-merging readme.md
CONFLICT (content): Merge conflict in readme.md
Automatic merge failed; fix conflicts and then commit the result.
通过git status可以告诉我们冲突的文件是哪个,然后查看readme.md:
Git is a distributed version control system.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存:
Creating a new branch is quick and simple.
其实这就是一个解决冲突的过程,然后提交修改。现在,master分支和feature1分支变成了下图所示:

最后,删除dev分支(git branch -d dev)
3.分支管理策略
合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。操作命令为git merge --no-ff -m "merge with no-ff" dev。
通过git log --graph --pretty=oneline --abbrev-commit以看到分支合并图。
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。所以,团队合作的分支看起来就像这样:

4.Bug分支
当工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
Git提供了一个stash功能,可以把当前工作现场“储藏”起来,命令为git stash,使用完该命令后用git status查看工作区是干净的。首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支issue-100:git checkout -b issue-100,修改完毕后,切换到master分支,并完成合并,最后删除issue-100分支。
然后我们切换到刚才工作的分支dev,使用git stash pop恢复刚才工作的内容,同时我们需要将issue-100修复的bug同步到dev分支,使用git cherry-pick <commitId>,这样就完成了dev上bug的修复,同时还还原了之前的正在进行的工作。
5.多人协作
当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。要查看远程库的信息,用git remote:
$ git remote
origin
或者,用git remote -v显示更详细的信息
$ git remote -v
origin https://github.com/hucheng1997/learngti.git (fetch)
origin https://github.com/hucheng1997/learngti.git (push)
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
推送master或其他分支到远程库:git push origin master/dev,实际开发中一般需要将master和dev分支推送到远程库。
抓取分支:git pull origin master/dev。
创建远程origin的dev分支到本地:git checkout -b dev origin/dev。
如果是直接创建的dev,在git pull的时候会失败,此时需要执行命令git branch --set-upstream-to=origin/dev dev。
多人协作的工作模式通常是这样:
- 首先,可以试图用
git push origin <branch-name>推送自己的修改; - 如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull试图合并; - 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用
git push origin <branch-name>推送就能成功!
如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>。
四、标签管理
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
1.创建标签
-
git tag <tagname> [commitId]:用于新建一个标签,默认为HEAD,也可以指定一个commit id;注:简洁查看历史记录
git log --pretty=oneline --abbrev-commit -
git tag -a <tagname> -m "blablabla...":可以指定标签信息; -
git tag:可以查看所有标签。 -
git show <tagname>:可以查看所有标签
2.操作标签
-
删除
tag:git tag -d v0.1 -
推送某个标签到远程:
git push origin v1.0 -
一次性推送全部尚未推送到远程的本地标签:
git push origin --tags -
删除远程标签
先删除本地的
git tag -d v0.9,然后删除远程:git push origin :refs/tags/v0.9