Posts Git基本使用方法(二):本地多分支篇
Post
Cancel

Git基本使用方法(二):本地多分支篇

一、 前言

这一篇着重介绍git分支管理的内容, 内容不再聚焦于单分支内的工作树操作

分支管理是git中非常重要的部分, 下图描述了分支的部分表现

分支图片

分支在被创建后独立, 可以合并到其他分支、也可以把其它分支合并

二、 基础知识: 分支的工作模式

分支的创建本质上是创建了一个指向当前版本的指针

在处于master分支时, 我们创建了一个名为test的分支, 现在出现了一个指向当前版本的指针

p1

我们需要知道的是, 有一个叫做HEAD的指针, 指向了当前分支的指针(即指针的指针), HEAD指向的分支便是当前所处分支

head

很容易想到, 切换分支的操作, 本质上就是改变了HEAD的指向

三、 基本操作

我们在初始化仓库之后便可以自动获得默认的分支master, 除此之外我们可以创建自己的分支

创建新分支

1
git branch [新分支名] (源分支名)

查看现有分支:

1
git branch

切换分支:

1
git checkout [分支名]

创建并切换到分支:

1
git checkout -b [新分支名] (源分支名)

删除分支:

1
git checkout -d [分支名]

合并分支:

1
git merge [分支名]

四、 合并分支(merge)详解

git-merge命令是将一个或多个分支, 合并到当前分支的操作

在有关分支的各种操作中, 合并分支是最为让人感到疑惑和畏惧的. 它是一个比较复杂和危险的操作, 怎样合并、怎样解决合并时发生的冲突, 这些都是我们所关心的

4.1 合并前的检查

  • 因为当前分支有未提交的改动时不允许checkout到其它分支, 所以被合并的分支不存在有改动没有commit的情况
  • 发起合并的分支有未提交改动时, 若无冲突则可合并, 有冲突时须先commit改动再合并
  • 可以用暂存(stage)代替上条的commit

原则上, 在进行合并前要尽量保证当前分支的整洁(尽量保持工作区、暂存区、仓库的一致性), 合并已经是复杂的操作了, 还是不要再给自己增加难度了

4.2 放弃(abort)合并/撤回合并

当合并出现失误时, 我们可以进行一些补救

  • 当合并出现冲突、没有完成合并时, 可以使用git merge --abort放弃合并, 并回到合并前的状态
  • 当合并成功, 但又想撤回操作时, 可以使用git reset返回指定的版本, 这个命令的具体解释请看第一篇文章

4.3 合并策略

所谓不同的策略, 就是不同的合并方法、在合并中遵守不同的约定

git可以在合并命令中指定策略, 方法如下

1
2
git merge -s [策略名] [分支名]
git merge --strategy=[策略名] [分支名]

支持的策略有:

  • resolve(三路合并)
  • recursive(递归三路合并)
  • octopus(八爪鱼合并)
  • ours
  • subtree

默认的合并策略是:

  • 合并两个分支, 采用recursive
  • 合并两个以上分支, 采用octopus

这里需要介绍一种合并模式叫做fast-forward合并(快进合并), 它不属于合并策略(不能通过-s指定), 若不禁用则会在满足条件时自动触发

这种模式是: 当发起合并的分支被合并分支分出去到被合并的区间内未作出任何改动, 把发起合并的分支的指针直接指向被合并分支的最新提交节点, 并不会在合并时创建新的提交节点

如下图, 发起合并的分支为master, 被合并分支为develop

ff演示

我们可以使用--no-ff选项来禁用fast-forward合并, 这也是大多数人推荐的做法, 因为这种合并模式虽然快速, 但是容易导致逻辑上的混乱

1
git merge --no-ff [分支名]

no-diff

可以看到, 禁用fast-forward合并之后不会让master分支乱飘, 更符合逻辑

4.3.1 resolve(三路合并)

我们进行两个分支的合并, 为什么叫做三路呢? 我们先看一下传统的二路合并算法

二路合并

二路合并采用逐行比对的模式, 一旦相同行内容不同就会报告冲突, 非常不方便

git采用三路合并, 所谓三路合并就是增加了它们共同的父节点作为基准

三路合并

如果两个分支相对于父节点都做出了改变, 并且改变不一样的话, 就报告冲突

若其中一个分支没有改变, 则取变化了的那个分支作为最终结果

三路合并适用于两条分支有共同且唯一的父节点的情况

但如下图: 如果共有两个父节点, 也就是所谓的交叉合并的情况, 我们又该怎么做呢?

交叉合并

4.3.2 recursive(递归三路合并)

递归三路合并就是解决交叉合并的问题的

在我们初始化git的时候, 只有一个master分支, 这就意味着: 遇到交叉合并的情况时, 我们可以不断”回溯”, 最终一定会找到满足三路合并的情况

找到三路合并

比如我们恰好找到了公共父节点A和B的一个公共且唯一的父节点E, 满足了三路合并的条件, 将其合并成节点ABE, 这样就巧妙地消除了C和D的交叉合并

大多数情况下, git merge默认采用这样的策略

另外, 递归三路合并也支持使用一些参数:

1
git merge -s recursive (-X [参数] -X [参数] ...)

一条命令的参数可以有多个, 但每一个参数前的-X都是必不可少的

这些参数可以是:

  • 发生冲突时采用当前分支的方案: ours
  • 发生冲突时采用被合并分支的方案: theirs
  • 更加细致的合并, 通常用于分支分离非常大时: patience
  • 不同的比较算法: diff-algorithm=[patience|minimal|histogram|myers]
  • 不同的对待空格冲突的方法: ignore-space-change, ignore-all-space, ignore-space-at-eol
    • ignore-space-change: 比较时忽略空格数量的更改(把多个空格与一个空格看做等价)
    • ignore-all-space: 比较时忽略所有空格
    • ignore-space-at-eol: 比较时忽略行尾(end of line)的空格
  • 关闭重命名检测: no-renames
  • subtree合并策略的高级形式 subtree[=<path>]

关于前三个参数ours, theirs, patience不作更多解释

不同的比较算法会有不同的结果, git默认的算法叫做myers, 绝大多数情况下适用, 没有特殊情况我们不需要特地指定; 另外强烈推荐描述这个算法的一篇博客, 写得简明清晰, 如果有兴趣可以了解一下cjting.me

忽略空格的选项也比较直观, 需要注意的是空行≠空格, 空行是回车(和换行)组成的, 忽略空格的选项无法对空行起作用

4.3.3 octopus(八爪鱼合并)

顾名思义, 用于大于2个分支同时合并, 当出现冲突需要手动合并的时候会放弃合并

4.3.4 ours

注意与前文中recursive合并的选项-X ours区分开来, 它们发挥的是完全不同的作用

  • 前文-X ours是在发生冲突时, 采用当前分支的方案
  • 作为一个策略-s ours, 在合并时无视被合并分支的内容

这种策略可以理解为假合并, 在git log中有所体现, 除此之外没有影响; 可以同时合并多个分支

这种策略的用途自然也是令人疑惑, 我翻找的资料中, 有一位博主说出了一种可能性, 可以去看一下waterlv的博客

4.3.5 subtree

这个策略我没有看懂, 暂且在这里放上文档的一部分

This is a modified recursive strategy. When merging trees A and B, if B corresponds to a subtree of A, B is first adjusted to match the tree structure of A, instead of reading the trees at the same level. This adjustment is also done to the common ancestor tree.

五、 一个成功的分支模型

规则让我们可以预测我们的行为会发生什么, 而前人总结的模型则对我们做什么有指导意义

以下内容改编自Chuckiefan的文章: 一个成功的Git分支模型, 这篇文章我看了之后很受启发, 推荐一下

这个模型有两个主分支:

  • 主干分支(master)
  • 开发分支(develop)

有三类辅助分支:

  • 需求分支(feature)
  • 发布分支(release)
  • 修复分支(hotfix)

5.1 master(主干)分支

这个分支是随时保持完美状态的、 可以随时投入生产的分支

当其他分支的代码版本合并到master分支时, 通常意味着一个新的正式版本已经发布

5.2 develop(开发)分支

这个分支用于开发新功能、新特性, 但主要代码是在需求分支(feature)中完成的, 为了保证开发的条理性, develop分支只是起整合新功能、新特性的作用, 故其也常被称为”整合分支”

主分支的生命周期贯穿开发始终, 而辅助分支往往在发挥作用之后就会被删除, 以保证分支管理的整洁

5.3 feature(需求)分支

需求分支源于开发分支、归于开发分支, 用于新功能、新特性的开发, 当开发结束并被合并到开发分支之后, 其生命周期也结束了

5.4 release(发布)分支

发布分支源于开发分支、归于主干分支

这个分支不是用于生产的环境(虽然名字具有误导性), 而是为发布准备的一个”缓冲区”, 做一些小bug的修复等发布前的工作, 这样在不影响发布的情况下, 保证了开发分支的进度

注意: 在修复bug后需要合并到开发分支以修复开发分支的bug

5.5 hotfix(修复)分支

修复分支源于主干分支, 归于主干分支和开发分支

这个分支用于对正式版本的紧急修复, 在修复分支完成修复之后合并到主干分支, 同时合并到开发分支、同时对其进行修复

5.6 模型示意图

很有参考价值的一张图

model

以上

This post is licensed under CC BY 4.0 by the author.

Git基本使用方法(一):本地单分支篇

Git基本使用方法(三):远程仓库篇