[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、长期分支

软件常见的nightlydaily等等往往采用的都是这个模式,常用于需求开发的大型项目。

开发模式一般为:只在master分支上保留稳定的代码,在开发分支beta分支上开发新功能并测试稳定性,测试完成后再并入master分支。

在一些大型项目中,往往还有一个 proposed(建议) pu: proposed updates(建议更新)分支,它可能因包含一些不成熟的内容而不能进入 next 或者 master 分支。

2、主题分支

主题分支是一种短期分支,它被用来实现单一特性或其相关工作。适用于任何规模的项目。

在上面的iss88hotfix分支中就用到了这种模式,将工作分散在不同的流水线中,每个流水线都有自己的短期目标和主题。更适用于临时改动较多或短期需求较频繁的项目。

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指针到更新后的位置。
  • 如果teamoneorigin服务器上的一个子集,此时不会抓取数据,而是直接设置teamone/master指针指向本地teamonemaster分支,如下图所示:

(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是这两个步骤的合并,然而实际使用中合并会有一些异常情况,最好还是单独使用fetchmerge命令。

(5)删除远程分支

服务器上某个分支已经合并到了主分支之后,可以通过以下命令删除它:

$ git push <remote> --delete 远程分支名

基本上这个命令做的只是从服务器上移除这个指针。Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。

最后修改:2023 年 04 月 06 日
如果觉得我的文章对你有用,请随意赞赏