[TOC]
Git 鼓励在工作流 程中频繁地使用分支与合并,哪怕一天之内进行许多次。分支可以让你在进行一个需求开发时,从容进行突发事件或故障的处理,而并不影响当前的开发进度。同时,在多人开发时,可以通过不同分支保存每个人的开发方向,挑选合适的并入主分支。
一、分支简介
当使用 git commit 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, 然后在 Git 仓库中这些校验和保存为树对象。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针,指向这些对象。 Git 的默认分支名字是master
。
Git 的 master 分支并不是一个特殊分支, 它就跟其它分支完全没有区别。 之所以几乎每一个仓库
都有 master 分支,是因为 git init
命令默认创建它,并且大多数人都懒得去改动它。
二、分支操作
1、分支创建
通过下面的命令可以创建一个新分支:
$ git branch 新分支名
此命令仅仅创建一个新分支,并不会自动切换到新分支中去。如果要创建后立即切换过去,则应用下面的命令:
$ git checkout -b 新分支名
当多个分支同时存在时,Git 通过一个名为HEAD
的特殊指针指向当前所在的分支。可以用下面命令查看各个分支当前所指的对象:
$ git log --oneline --decorate
以上图为例,则会输出以下结果,即当前 master 和 testing 分支均指向校验和以 f30ab 开头的提交对象:
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) add feature #32 - ability to add new
formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project
2、分支切换
$ git checkout 分支名
当切换分支后再提交,新分支将随着提交操作自动向前移动,在新分支上做的任何改动都不会影响到切换前的master
分支。
如果此时再切回master
分支,则有两个效果:一是使 HEAD
指回master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。
在新分支和master
分支都做了改动之后,这个项目的提交历史将产生分叉,如下图所示:
此时,可以通过`git log --oneline --decorate --graph
--all`命令查看项目分支的提交历史和分叉情况。
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project
3、实例体验分支的新建与合并
- 过程1:正在
master
主分支上开发且已提交内容 过程2:PM发来一个优先级极高的需要,要尽快上线,此时转到新建的
iss88
分支上进行开发,过了一个多小时,已提交了一些内容# 切换分支前要注意工作目录和暂存区里没有未提交的修改 # 否则它们可能会和要切换的分支冲突从而阻止Git切换到该分支 $ git checkout -b iss88 $ git commit -a -m '修改了一些内容[iss88]' # git commit -a在修改文件时帮忙省一步git add的操作
过程3:测试发现一个严重bug,影响程序运行,要马上处理,此时先转回
master
主分支,再去新建的hotfix
分支上处理bug# 先转回 master 主分支 $ git checkout master Switched to branch 'master' # 现在,工作目录和文件回到了在开发 iss88 前的状态 $ git checkout -b hotfix Switched to a new branch 'hotfix' # 之后再 hotfix 分支上处理bug $ git commit -a -m "bug已修复"
过程4:将
hotfix
分支,合并回master
主分支,提交发布此版本。当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进 (指针右移
),因为这种情况下的合并操作没有需要解决的分歧
——这就叫做“快进(fast-forward)”
。# 合并分支用 git merge 命令 $ git checkout master $ git merge hotfix Updating f42c576..3a0874c Fast-forward ...... 1 file changed, 2 insertions(+) # 修改发布之后,就可以删除 hotfix 分支 $ git branch -d hotfix Deleted branch hotfix (3a0874c).
过程5:删除
hotfix
分支,切换回iss88
分支上继续开发。$ git checkout iss88
3.5、补充知识:切换分支前工作区的暂存
4、分支的合并
(1)无冲突时的分支合并
在上面的例子中,过程5时iss88
分支上还没有合并入已经发布的hotfix
相关内容,当iss88
开发完毕要合入master
时,会有以下区别于上面合并hotfix
时的提示:
$ git checkout master
Switched to branch 'master'
$ git merge iss88
Merge made by the 'recursive' strategy.
......
1 file changed, 1 insertion(+)
这是因为,此时master
的直接祖先是第三次提交
,而iss88
的直接祖先还是之前的第二次提交
,因此 Git 会做一个三方合并
,也就是将iss88
、第二次提交
、第三次提交
合并的结果做一个新的快照并自动创建一个新的提交指向它。即现在就有了一个新的第四次提交
。
iss88
的开发合并入之后,也就可以删除掉这个分支了。
$ git branch -d iss88
(2)有冲突时的分支合并
实际应用中难免会存在这样的情形:如果在修改hotfix
和开发iss88
时对同一个文件的同一个部分进行了不同的修改,合并时就会产生冲突。
此时,Git 会做合并,但不会创建新的合并提交,会暂停下来等待手动解决冲突。具体查看及解决冲突的方法如下:
# 查看因冲突而处于未合并状态(unmerged)的文件
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
# 用图形化工具解决冲突,可以可视化解决冲突问题
$ git mergetool
# 在手动解决了文件冲突之后,重新暂存这些文件,Git就会标记为冲突解决
$ git add 冲突文件
# 再次查看是否还有冲突的文件
$ git status
On branch master
All conflicts fixed but you are still merging.
# 完成合并提交
$ git commit
三、分支管理
1、查看所有分支列表
$ git branch
iss53
* master //*号代表当前 HEAD 指针所在分支
testing
2、查看每个分支的最后一次提交
$ git branch -v
iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes
3、查看已经合并到当前分支的分支
$ git branch --merged
iss53
* master
已合并的分支(如这里的iss53
)往往就可以通过git branch -d
删除了。
4、查看未合并到当前分支的分支
$ git branch --no-merged
testing
如果要删除包含未合并工作的分支,会报错。当然,如果你了解这个分支确实无用,也可以通过git branch -D testing
强制删除。
5、查看(未)合并到指定分支的分支
# 已合并到指定分支的分支
$ git branch --merged 指定分支名
# 未合并到指定分支的分支
$ git branch --no-merged 指定分支名
四、常见分支开发工作模式
1、长期分支
软件常见的nightly
、daily
等等往往采用的都是这个模式,常用于需求开发的大型项目。
开发模式一般为:只在master
分支上保留稳定的代码,在开发分支
或beta分支
上开发新功能并测试稳定性,测试完成后再并入master
分支。
在一些大型项目中,往往还有一个 proposed(建议)
或 pu: proposed updates(建议更新)
分支,它可能因包含一些不成熟的内容而不能进入 next
或者 master
分支。
2、主题分支
主题分支是一种短期分支,它被用来实现单一特性或其相关工作。适用于任何规模的项目。
在上面的iss88
和hotfix
分支中就用到了这种模式,将工作分散在不同的流水线中,每个流水线都有自己的短期目标和主题
。更适用于临时改动较多或短期需求较频繁的项目。
3、远程分支
上面的两种工作模式一般在本地进行,没有与服务器进行交互。而远程引用是对远程仓库的引用(指针),包括分支、标签等等。可以通过 git remote show <remote>
获得远程分支的更多信息。
远程跟踪分支以 remote/branch
的形式命名。从远程服务器克隆时,Git 的 git clone
命令会为你自动将其命名为 origin
,拉取它的所有数据, 创建一个指向它的 master
分支的指针,并且在本地将其命名为 origin/master
,这类似于快捷方式或书签的运行模式。同时,也会生成一个本地的master
分支与之对应。
origin
并无特殊含义。是当你运行git clone
时默认的远程仓库名字。 如果你运行git clone-o wahahah
,那么你默认的远程分支名字将会是 wahahah/master。
(1)抓取远程跟踪分支
Git 中通过git fetch <remote>
来抓取一个新的远程跟踪分支,此时本地不会自动生成一份可编辑的副本,只有一个不可以修改
的<remote>/分支名
的指针。
注意:抓取fetch
并不会更改本地工作目录的内容,需要再通过merge
手动合并。
例如,在通过 git fetch origin
已经连接了origin
服务器之后,又通过 git remote add
命令添加了一个名为teamone
的远程仓库。
此时运行git fetch teamone
即可抓取远程仓库teamone
有而本地没有的数据。
- 如果
teamone
是一个独立仓库,则更新本地数据,移动teamone/master
指针到更新后的位置。 - 如果
teamone
是origin
服务器上的一个子集,此时不会抓取数据,而是直接设置teamone/master
指针指向本地teamone
的master
分支,如下图所示:
(2)推送分支
推送分支可以把想要与别人协作的内容推送到公开分支。命令如下:
$ git push <remote> 分支名
# 这条命令默认远程分支名与本地分支名是一样的
# 如果想要推送本地分支到一个命名不相同的远程分支
# 或者不想让远程仓库的分支名称跟本地一样,可以用下面的命令
$ git push <remote> 本地分支名:远程分支名
此时,别人抓取到推送的分支后,需要用git merge <remote>/分支名
手动将新内容合并到当前所在分支。
(3)跟踪分支
①创建跟踪分支
跟踪分支是与远程分支有直接关系的本地分支。当克隆
一个仓库时,它通常会自动地创建一个跟踪 origin/master
的 master
分支,也可以用以下命令手动创建跟踪分支:
$ git checkout -b 本地分支名 <remote>/远程分支名
# 如git checkout -b serverfix origin/serverfix
# 当本地分支与远程分支名称一致时,以上命令也等同于以下命令:
$ git checkout --track <remote>/远程分支名
# 如果本地分支不存在且远程分支名称唯一时,甚至还可以简化为:
$ git checkout 远程分支名
执行命令后,本地分支
就会自动从<remote>/远程分支名
拉取数据。
②让本地分支跟踪新的远程分支
如果想要设置已有的本地分支跟踪一个刚拉取下来的远程分支,可以用以下命令:
$ git branch -u <remote>/远程分支名
③查看所有跟踪分支
$ git branch -vv
结果中有两个主要参数,ahead
指本地还没有推送到服务器上的提交数量,behind
指已经在服务器上但还没有合并的提交数量。
这些数字的值来自于本地从服务器上最后一次抓取的数据,如果想要获取服务器上的最新状态,需要先抓取所有的远程仓库:
$ git fetch --all; git branch -vv
(4)拉取分支
当 git fetch
命令从服务器上抓取本地没有的数据时,还需要执行一步手动合并git merge
的操作。拉取git pull
是这两个步骤的合并,然而实际使用中合并会有一些异常情况,最好还是单独使用fetch
和merge
命令。
(5)删除远程分支
服务器上某个分支已经合并到了主分支之后,可以通过以下命令删除它:
$ git push <remote> --delete 远程分支名
基本上这个命令做的只是从服务器上移除这个指针。Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。