08.分支

13 阅读17分钟

分支

1.介绍

分支是Git使用过程中非常重要的概念。使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线

同一个仓库可以有多个分支,各个分支相互独立,互不干扰

通过git init命令创建本地仓库时默认会创建一个master分支

HEAD是一个活动指针,指向的是正在活跃的分支

1769701832953.png

2.优势

1755781558567.png

3.案例理解

在Git中使用分支的主要目的是为了合并分支,基于新分支来开发项目并不会影响主线开发。当其他分支的代码确认无误需要集成到主线(master)分支时,我们需要进行分支的合并,即将主线分支合并到其他分支中。这样,一个完整的功能就集成到主线代码中了

FigureP974071.jpg

1755780751353.png

5.常用命令总览

FigureT994702.jpg

1755780815485.png

注意:git checkout命令可用作分支切换和文件恢复。在Git2.23版本后引入了git switch和git restore命令。其中,git switch命令专门用于分支切换,git restore命令专门用于文件恢复,以提供更清晰的语义和错误检查。如果使用较新的Git版本,可以考虑使用这些命令来代替git checkout

6.原理

介绍

在Git中,分支实质上仅仅是一个指向Commit对象的可变指针。默认情况下,每个Git仓库都会有一个名为master的默认分支。在.git/refs/heads目录存储着当前仓库的所有分支。每一个分支都有一个以分支名命名的文件,该文件存储着当前分支指向的Commit对象的哈希值

案例解析

初始化仓库,命令如下:

git init
echo "111" > aaa.txt
git add .
git commit -m "111" ./

查看分支,命令如下:

git log --oneline
ceca35b(HEAD -> master)111

# 查看 master 文件
cat .git/refs/heads/master
# 文件中保存着当前分支指向的 Commit 对象的哈希值
ceca35b10c0495e852cbf26205fb5a5af409b70e

git cat-file -p ceca35b
tree 8f96f2f60c766a6a6b78591e06e6c1529c0ad9af
author xiaohui <xiaohui@aliyun.com> 1697097246 +0800
committer xiaohui <xiaohui@aliyun.com> 1697097246 +0800

111

继续开发,观察master分支的变化,命令如下:

echo '222' > aaa.txt
git commit -m '222' ./
git log --oneline
69372ca(HEAD -> master)222
ceca35b 111

# 再次查看 master文件
cat .git/refs/heads/master
# 文件内容变为了最新的 Commit 对象的哈希值
69372ca265e6a98c8a7f839b8760f98b80bbf4fe

我们可以看到,在Git中,分支仅仅是一个指向Commit对象的可变指针。当执行commit提交后,当前分支会指向最新的Commit对象的哈希值。这样,通过Commit对象的哈希值即可找到对应版本的内容

在Git中,每个分支都会保存一个Commit对象的哈希值,如下图所示:

FigureP9914393.jpg

6.git stash

介绍

有时,当我们在项目的一部分工作了一段时间后,可能会发现所有东西都进入了混乱的状态。而在此时,如果想要切换到另一个分支去处理其他事务,就必须将当前工作空间所做的操作提交到版本库。否则,Git将不允许我们切换分支

当当前的操作还不足以生成一次版本快照时,git stash命令便派上用场了。我们可以使用这个命令将当前工作状态存储起来,然后再切换到其他分支进行工作。待工作完毕后,再次切回当前分支时,我们可以从Git存储中取出之前保存的工作内容

git stash命令能够将当前工作目录中尚未提交的所有更改(包括暂存区和未暂存的修改)临时存储到stash堆栈中,从而让用户在不影响当前工作进度的前提下,轻松切换到其他分支处理问题、合并代码或恢复到干净的工作状态

注意

通常情况下,如果当前分支存在未提交的操作,则无法切换到其他分支。因此,当我们切换分支时,最好保证当前工作空间的状态为nothing to commit,即所有操作均已提交

如果当前分支存在未提交的操作,Git不允许切换分支

命令格式

FigureCC1184763.jpg

使用步骤

在编辑文件过程中,若已完成编辑但尚未打算提交,此时突然接到了一项新的“临时任务”。当想要切换到其他分支继续操作时,请使用git stash

  1. 存储状态:git stash
  2. 切换分支:git switch
  3. 读取存储状态:git stash apply
  4. 删除存储状态(可选):被读取后stash不会被删除,可以删除掉,git stash drop

暂存区与git stash

即便某个操作已经添加到暂存区,我们同样可以使用Git存储将其存储起来,从而使工作空间进入“nothing to commit”状态

需要注意的是,尽管使用git stash命令将当前状态存储起来后,当前工作空间的暂存区变为“nothing to commit”状态,但当后续将该存储读取出来时,暂存区并不会回到之前存储时的状态

7.工作树

为什么需要

使用git stash命令,我们可以将当前的分支状态存储起来,从而抽身处理一些相对紧急的任务。然而,gitstash存在一个天然的弊端——串行化开发。试想一下,如果我们希望能够在保持当前工作空间继续开发的同时,还能并行处理紧急任务,这种基于并行的工作模式显然是gitstash所无法直接支持的。在这种情况下,我们通常需要切换到另一个分支来完成紧急任务,然后再切回原分支继续之前的工作。而工作树(worktree)的概念允许我们实现真正的并行开发,使得多个任务可以同时进行而互不干扰

介绍

工作树,即用来存储工作的树,这个树也可以被称为新的工作副本。当正常开发临时出现了紧急任务时,我们可以使用工作树来创建一个新的工作副本,该工作副本是基于原来的工作副本而创建出来的,这个新的工作副本就被称为工作树。这样,就存在了两个工作副本,让两个任务得以并行开发。有了工作树,我们就可以在多个工作副本之间并行开发

当在工作树中提交了版本时,原来的工作副本会即时感知这些变化。同样地,若原来的工作副本提交了版本,这一变动也会在工作树中即时显现

为同一个仓库创建多个工作区(工作副本),每个工作区可检出不同分支/提交,同时开发互不干扰

命令格式

FigureT13284792.jpg

使用步骤

1755789851743.png

8.合并

介绍

分支合并是版本控制系统中的核心概念之一,它指的是将不同分支上的修改合并到一起,以便整合各自独立的开发工作。在软件开发中,分支常被用来支持并行开发、功能隔离、bug修复等活动。当一个特定的开发任务(如一个新功能或bug修复)在单独的分支上完成之后,就需要将这个分支的变更合并到主线或其他目标分支,以使所有团队成员共享最新的成果

建立好分支后,我们可以使用分支进行开发功能,待开发功能趋于成熟且稳定后,我们可以将其他分支上的功能合并到master分支。这样,其他分支的功能就集成到主分支上了。分支的工作流程示意图如下图所示:

FigureP13725223.jpg

开发路线

介绍

分支的好处在于可以定制不同的开发路线,但项目的主线一般只有一个,即master分支。当其他分支的功能趋于完善后,我们会将功能合并到master分支。有了多个分支,即拥有了多条开发路线。在Git中,开发路线分为同轴开发路线和分叉开发路线。在学习分支合并之前我们必须对此概念有所了解

同轴开发路线

同轴开发,即创建一个新分支,在使用新分支开发时,master分支并没有任何操作。待开发功能趋于成熟且稳定后,我们可以将新分支与master分支进行合并。这样,其他分支的功能就集成到主分支上了。同轴开发工作的示意图如下图所示:

FigureP13825418.jpg

分叉开发路线

分叉开发,即创建一个新分支,使用新分支开发,与此同时,master分支也在进行开发。新分支与master分支属于不同的开发路线,这是我们在实际开发中经常遇到的情况。分叉开发工作的示意图如下图所示:

FigureP13925492.jpg

语法

FigureT14084808.jpg

分类

路线关系
  • 同轴开发路线:后面的版本必定包含了前面版本的内容。如果前面版本需要后面版本的内容,只需要使用快进式合并即可
  • 分叉开发路线:有时会出现后面的版本合并前面版本的情况,这种合并方式被称为典型式合并
快进式合并
介绍

快进式合并是Git在分支合并时的一种方式,指的是前面的版本(旧版本)合并后面的版本(新版本),只存在于同轴开发路线的合并

同轴开发时只存在快进式合并,因为多个分支处于同一根开发轴上,后面的版本必定包含前面版本的内容。因此,后面的版本无须合并前面版本的内容

工作流程

FigureP14125741.jpg

案例

初始化项目

git init
echo "用户名+密码登录" >> project.txt
git add ./
git commit -m "用户名+密码登录功能完成"

创建login分支,并切换到login分支

git branch login
git switch login
git log --online --all

使用login分支开发集成QQ登录

echo "集成QQ登录" >> project.txt
git commit -m "集成QQ登录"
git log --online --all

使用login分支开发集成微信登录

echo "集成微信登陆" >> project.txt
git commit -m "集成微信登陆" ./
git log --online --all

切换到master分支,将login分支的代码合并到master分支

git switch master
cat project.txt
git merge login
cat project.txt
典型式合并
介绍

典型式合并分支是后面的版本要合并前面的版本的情况。需要注意的是,典型式合并分支只存在于分叉开发路线中,因为,在同轴开发路线时,后面的版本必定包含前面版本的内容。所以,同轴开发是不需要后面的版本来合并前面的版本的,即同轴开发只存在快进式合并

在分叉开发路线中,后面的版本(新版本)不一定包含前面的版本(旧版本)的内容,前面的版本(旧版本)也不一定包含后面的版本(新版本)的内容。因此,在分叉开发路线中,既存在快进式合并,也存在典型式合并

工作流程

FigureP14326102.jpg

案例

初始化项目

git init
echo "项目初始化" >> project.txt
git add .
git commit -m "项目初始化" ./
echo "开发了一个基础的登录功能" >> project.txt
git commit -m "开发了一个基础的登录功能" ./

创建并切换到login分支,使用login分支开发

# 创建login分支
git branch login
# 切换到login分支
git switch login
# 查看日志
git log --oneline --all --graph
# 开发QQ登录
echo "集成QQ登录" >> project.txt
git commit -m "集成QQ登录" ./
# 查看日志,当前login分支与master分支属于同轴状态
git log --oneline --all --graph

切换到master分支并添加上传头像功能

# 切换到master分支
git switch master
# 开发上传头像功能
echo "添加上传头像功能" >> project.txt
git commit -m "添加上传头像功能" ./
# 查看日志,发现处于分叉开发路线
git log --oneline --all --graph
# 查看master分支的project.txt文件,发现并没有包含login分支的代码
cat project.txt

将login分支的代码合并到master分支

# 在master分支使用merge合并login分支(会出现代码冲突)
git merge login
# 编辑文件解决冲突
vi project.txt
# 查看日志
git log --oneline --all --graph
# 提交解决冲突的操作
git commit -a -m "合并login分支,并解决代码冲突"
# 查看日志,发现合并成功后产生一个新的版本
git log --oneline --all --graph

master合并了login分支的代码,此时master和login处于同轴,login分支可以直接合并master分支的代码(快进式合并)

# 切换到login分支
git switch login
# 查看日志
git log --oneline --all --graph
# 在login分支合并master分支内容,会出现Fast-forward关键字,就是快进式合并的意思
git merge master
# 查看日志
git log --oneline --all --graph

9.代码冲突

介绍

项目进度的推进是团队配合的结果,这意味着团队中的每一位开发者都可以随时随地对文件进行任何的修改,这时候代码冲突难以避免。代码冲突是每一个版本控制工具都要解决的核心问题

分类

aasdjioasdjoadjj12io3j12o3.jpg

快进式合并代码冲突

介绍

快进式合并是指前面的版本(旧版本)合并后面的版本(新版本)

注意

后面的版本并非一定包含前面版本的内容,这主要取决于分支开发的路线。对于同轴开发路线而言,其后面的版本肯定包含前面版本的内容。但对于分叉开发路线而言,后面的版本是不包含前面版本的内容的

在同轴开发情况下,快进式合并不会产生代码冲突,如果在非同轴开发情况下,快进式合并则可能会产生代码冲突

示例

asdasdasdjioiasd12j3io13jo.jpg

典型式合并代码冲突

介绍

典型式合并是指后面的版本(新版本)合并前面的版本(旧版本),只存在于分叉开发路线。在同轴开发路线中,后面的版本必定包含前面版本的内容,因此同轴开发路线不存在典型式合并,只存在快进式合并

示例

FigureT15287782.jpg

原理

  • 两路合并算法
  • 三路合并算法
  • 递归三路合并

10.merge命令

介绍

git merge用来合并分支,在使用git merge命令时,可以携带一些参数来添加一些额外的功能

其他用法

介绍

FigureT16884869.jpg

--continue

当分支合并产生冲突时,我们可以编辑出现冲突的文件,待冲突解决后继续提交。这是我们之前解决冲突的步骤。--continue的作用也是如此

--abort

当分支合并后出现冲突时,我们可以利用该命令让当前分支回退到没有合并之前的状态。该命令同样适用于当解决完冲突后不满意时,需要回退到没有合并之前的状态的场景

可选参数

介绍

git merge命令的常用其他可选参数如下表所示,其他更详细的说明可参考git merge -h来查询Git官方对merge命令的介绍

FigureT16984874.jpg

--no-ff

默认情况下,只要是在分叉开发路线的合并,最终都会产生一个提交节点来记录本次合并。如果是同轴开发路线的合并,并不会产生一个合并提交节点。这种情况也被称为快速合并(fast-forward)。--no-ff参数将禁用快速合并,只要进行了分支合并操作,必定会产生一个新的Commit对象来记录本次合并

--squash
介绍

使用--squash参数可以将某个提交对象的内容合并到当前分支。使用当前分支提交一次时,只会产生一个Commit对象,该提交含有之前多次的提交内容

生成一个简化的历史,只包含最终的合并提交

工作流程

1769767558999.png

优缺点
  • 优点:简化历史记录,提供更干净的视图,减少冗余
  • 缺点:丢失了每个提交的详细信息,不适用于公共分支
-s

Git提供了多种合并策略,允许用户根据实际情况选择最合适的合并方法。git merge命令通过-s或--strategy可以指定分支的合并策略

  1. recursive:这是最常用的合并策略,也是Git在合并单个分支时默认使用的合并策略,底层采用递归三路合并算法
  2. ours:这是一种特殊的策略,不是真正意义上的合并,而是简单地将当前分支的版本作为最终结果,完全忽略合并进来的分支的所有更改
  3. octopus:这种策略是Git在合并多个分支时的默认策略,主要用途是将多个分支捆绑在一起。octopus策略的实现方式是在内部多次调用recursive策略,对要合并进来的每个分支都调用一次,但只产生一个唯一的合并提交
  4. resolve(已被recursive替代):这种策略也是基于三路合并算法,但它不采用递归的方式去处理多个共同祖先的情况,而是更倾向于一次性找到一个合适的共同祖先来进行合并

11.rebase命令

介绍

在实际应用中,分支合并非常常见,尤其是在分叉开发路线时的分支合并。这意味着多个分支可以同时进行项目功能的开发,完成开发后,我们可以使用git merge命令来合并分支。合并完成后,通过git log命令可以查询到历史开发记录。然而,分叉开发使用得非常频繁,如果我们不对历史记录进行整理和优化,那么,一个项目的历史记录将会变得庞杂且混乱。为此,git rebase命令提供了有效的解决方案

rebase可译为重做,该命令用于改变某一个分支的基底,将分支迁移到另一个分支,这个过程也是一种代码的合并(也有可能会产生代码冲突)。其中,源分支被称为待变基分支,目标分支被称为基分支

语法

asiodjaoisdjiajdoajdioa.jpg

工作流程

1769767190113.png

FigureP17634180.jpg

1769768793072.png

应用场景

1769767296543.png

1769767324054.png

优缺点

  • 优点:历史记录干净,线性,减少不必要的合并提交,适用于已经完工的分支与主干合并
  • 缺点:可能引起冲突,不推荐对已经分享的提交进行变基

交互式

省略

安全守则

1769769721545.png

1769769749901.png

12.cherry-pick命令

介绍

git cherry-pick命令主要用于将历史记录中的任意一个提交变更到当前工作分支上,这对代码来说也是一种合并。这个命令可以让我们在不合并整个分支的情况下,挑选出某个特定的提交并合并其更改。当执行此命令后,Git会创建一个新的提交,然后将这些更改应用于当前分支的HEAD。这样,就可以在不影响其他提交的情况下,只选取并应用所需的部分更改

此命令在处理bug修复、功能移植等场景时非常有用,因为它允许将某个分支上的个别更改应用到另一个分支,而无须进行完整的分支合并

工作流程

FigureP19037729.jpg

语法

FigureT19084919.jpg