news 2026/9/26 2:58:06

School of SRE 的 Git 分支实战指南:从分支创建到 Merge 与 Rebase 的完整解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
School of SRE 的 Git 分支实战指南:从分支创建到 Merge 与 Rebase 的完整解析
  • 教程

【免费下载链接】school-of-sre

At LinkedIn, we are using this curriculum for onboarding our entry-level talents into the SRE role.

项目地址:https://gitcode.com/gh_mirrors/sc/school-of-sre
点击查看免费下载

Git 分支是并行开发多个特性、隔离不同工作流的核心机制。本文基于 LinkedIn School of SRE 课程(课程大纲)中 "Working With Branches" 一节的完整内容,从底层提交树模型讲起,逐步演示分支的创建、切换、并行提交,并深入对比直接合并(merge commit)与「Rebase + Fast-forward」两条合并路线。读完本文,你将能够用git log --oneline --graph --all透视仓库的全貌,理解HEAD与分支引用(reference)的本质,并针对不同场景选择最合适的集成策略。

为什么需要分支:从单行历史到树状历史

假设我们的本地仓库已经有两次提交,此时提交历史是一条单行链:提交df2fb7a(adding file 1)是7f3b00e(adding file 2)的父提交,master指向最新的7f3b00e。

$ git log --oneline --graph * 7f3b00e (HEAD -> master) adding file 2 * df2fb7a adding file 1

单行历史固然清晰,但它意味着同一时刻仓库只有一个「前进方向」。当需要在同一个仓库里并行开发两个不同的特性时,直观但笨拙的办法是复制一份代码到新文件夹、再单独git init一份仓库——代码重复、历史割裂、同步困难。

更好的答案是分支(branch)。正如Git 基础章节中所强调的,Git 内部以树状结构存储提交(commit),一个提交可以有两个或多个子提交,而非只能单线串联。基于这种树状结构,我们可以:

  • 从任意一个提交上分出两条或多条分支;
  • 每条分支独立承载一套特性的开发;
  • 分支之间还可以通过合并(merge)重新汇聚。

借助分支,仓库中可以同时存在多条历史线,我们可以随时checkout到其中任意一条上继续工作。所谓 checkout,本质上就是用被检出版本(snapshot)的内容替换当前工作目录(repo)的内容——这一点在Git 基础章节中通过git checkout df2fb7a回到旧版本、ls只看到file1.txt已经得到验证。

分支的本质:指针,而非代码副本

在动手之前,先建立正确的心理模型:Git 内部就是一棵提交树,分支名(人类可读的名字)是指向树中某个提交的指针(pointer)。我们使用各种 git 命令与这棵树和引用打交道,Git 据此修改仓库内容。

这个说法不是比喻,而是有据可查的实现事实。Git 基础章节中的「The Magic」一节直接展示了引用的落盘形式:

$ cat .git/refs/heads/master 7f3b00eaa957815884198e2fdfec29361108d6a9

master这个分支引用指向的提交 ID 就存在.git/refs/heads/master这个文件里。每当 Git 需要知道master指向哪里,或需要更新master的指向,都只需读写这个文件。同理,HEAD也只是一个引用:

$ cat .git/HEAD ref: refs/heads/master

HEAD里存的是ref: refs/heads/master,也就是说HEAD会跟随master指向的位置。无论你 checkout 到哪个提交或分支,HEAD就指向哪里。理解了「分支=指针」这一点,下面所有命令的行为就顺理成章了。

创建分支:git branch b1

回到当前仓库(master指向7f3b00e),创建一个名为b1的分支:

$ git branch b1 $ git log --oneline --graph * 7f3b00e (HEAD -> master, b1) adding file 2 * df2fb7a adding file 1

git log告诉我们两件事:

  • b1同样指向最后一次提交7f3b00e(因为新分支默认从当前提交创建);
  • 但HEAD仍然指向master,即当前仍停留在master分支上工作。

注意git branch只是创建分支,并不会切换过去。

切换分支:git checkout b1

将工作区切换到b1:

$ git checkout b1 Switched to branch 'b1' $ git log --oneline --graph * 7f3b00e (HEAD -> b1, master) adding file 2 * df2fb7a adding file 1

对比上一节输出:b1仍指向同一个提交7f3b00e,但HEAD的箭头从master移到了b1。由于我们从提交7f3b00e分叉,从这个提交开始将出现两条历史线——你 checkout 在哪条分支上,那条分支的历史线就会继续向前推进。

并行开发:两条独立的历史线

在 b1 分支上提交

此时我们处于b1分支,新提交会把分支引用b1向前推进,而当前b1的提交7f3b00e成为新提交的父提交:

# 创建文件并提交 $ echo "I am a file in b1 branch" > b1.txt $ git add b1.txt $ git commit -m "adding b1 file" [b1 872a38f] adding b1 file 1 file changed, 1 insertion(+) create mode 100644 b1.txt # 新的历史线 $ git log --oneline --graph * 872a38f (HEAD -> b1) adding b1 file * 7f3b00e (master) adding file 2 * df2fb7a adding file 1

注意master仍然指向它原来指向的旧提交7f3b00e——两条分支此刻已经分道扬镳。

回到 master 分支继续提交

切换到master并做一次新提交,会从7f3b00e出发形成另一条历史线:

# 切换到 master 分支 $ git checkout master Switched to branch 'master' # 在 master 分支上创建新提交 $ echo "new file in master branch" > master.txt $ git add master.txt $ git commit -m "adding master.txt file" [master 60dc441] adding master.txt file 1 file changed, 1 insertion(+) create mode 100644 master.txt # master 的历史线 $ git log --oneline --graph * 60dc441 (HEAD -> master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1

注意:在master上执行git log时看不到b1分支的提交872a38f,因为当前历史线不包含它。

用 --all 查看全貌

要同时看到两条历史线,需要加上--all参数:

$ git log --oneline --graph --all * 60dc441 (HEAD -> master) adding master.txt file || * 872a38f (b1) adding b1 file ||/ * 7f3b00e adding file 2 * df2fb7a adding file 1

上图的树形结构一目了然:在提交7f3b00e处出现了一个清晰的分叉(fork)。这就是创建分支的结果——现在b1与master是两条相互独立的历史线,可以互不干扰地进行特性开发。

合并(Merge):把特性带回主分支

假设b1上的特性开发完成,需要合并到master(所有最终版本代码存放的分支)。标准流程是:先checkout到master,再从upstream(例如 GitHub 远端仓库)pull最新的代码,然后把自己的b1代码合并进来。合并有两条路线,先回顾当前历史:

$ git log --oneline --graph --all * 60dc441 (HEAD -> master) adding master.txt file || * 872a38f (b1) adding b1 file ||/ * 7f3b00e adding file 2 * df2fb7a adding file 1

路线一:直接合并(产生 merge commit)

在master上直接执行git merge b1,Git 会采用'recursive'(递归)策略合并两条历史线的改动,并产生一个新的合并提交(merge commit):

$ git merge b1 Merge made by the 'recursive' strategy. b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt $ git log --oneline --graph --all * 8fc28f9 (HEAD -> master) Merge branch 'b1' |\ || * 872a38f (b1) adding b1 file * | 60dc441 adding master.txt file ||/ * 7f3b00e adding file 2 * df2fb7a adding file 1

可以清楚看到一个新的合并提交8fc28f9诞生,它有两个父提交(872a38f与60dc441)。执行合并时 Git 会提示输入提交信息(commit message)。

这种方式的缺点也很明显:如果仓库里分支很多,会产生大量 merge commit,历史图上布满分叉与汇合,观感远不如一条干净的单线历史。下面看替代方案。

撤销合并:git reset --hard

先撤销刚才的合并,回到合并前的状态。此处使用git reset --hard <commit>将master硬重置到合并前的提交60dc441:

$ git reset --hard 60dc441 HEAD is now at 60dc441 adding master.txt file $ git log --oneline --graph --all * 60dc441 (HEAD -> master) adding master.txt file || * 872a38f (b1) adding b1 file ||/ * 7f3b00e adding file 2 * df2fb7a adding file 1

注意:--hard会丢弃工作区与暂存区中未提交的改动,使用时需确认没有需要保留的本地修改。

路线二:Rebase + Fast-forward(保持单线历史)

与其合并两条「底子相近」(共同祖先提交7f3b00e)的分支,不如先把b1变基到当前master之上。所谓 rebase,就是取出b1从共同祖先提交7f3b00e到872a38f之间的所有提交,把它们重新「播放」在master(60dc441)的顶端:

# 切换到 b1 $ git checkout b1 Switched to branch 'b1' # 将当前分支(b1)变基到 master 上 $ git rebase master First, rewinding head to replay your work on top of it... Applying: adding b1 file # 结果 $ git log --oneline --graph --all * 5372c8f (HEAD -> b1) adding b1 file * 60dc441 (master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1

观察结果:b1原本只有一个提交872a38f,其父提交是7f3b00e;rebase 到master之后,提交变成了全新的5372c8f,其父提交变成了60dc441。作为副产品,整个历史被整理成了一条单线。

此时再合并b1到master,就不再需要创建 merge commit——只需把master指针移动到5372c8f(即b1指向的位置):

# 切回 master,因为我们要把代码合并进 master $ git checkout master Switched to branch 'master' # 当前历史:b1 已基于 master $ git log --oneline --graph --all * 5372c8f (b1) adding b1 file * 60dc441 (HEAD -> master) adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1 # 执行合并,注意输出中的 "Fast-forward" 字样 $ git merge b1 Updating 60dc441..5372c8f Fast-forward b1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 b1.txt # 结果 $ git log --oneline --graph --all * 5372c8f (HEAD -> master, b1) adding b1 file * 60dc441 adding master.txt file * 7f3b00e adding file 2 * df2fb7a adding file 1

输出中的Updating 60dc441..5372c8f与Fast-forward表明:由于b1已经领先于master且没有分叉,Git 只需把master引用快进到5372c8f,无需产生新的合并提交。最终b1和master指向同一个提交,代码成功合并进master,可以 push 到远端,并且历史是一条干净的单线。

两种合并路线如何选择

两条路线的取舍,在分支章节中可以总结为:

维度直接git mergegit rebase+ Fast-forward 合并
产物产生 merge commit,历史呈树状分叉单线历史,干净直观
历史可读性分支多时 merge commit 堆积、观感杂乱始终是一条直线,便于回溯与排查
适用场景需要保留「何时合并、合并了谁」的真实汇合信息(如发布分支)个人特性分支、希望主分支保持整洁的日常开发

两条路线各有权衡,实践中可根据团队约定选择;Git 也允许在git merge时通过--no-ff强制创建合并提交、或用--ff-only只允许快进合并来固化团队策略。

从分支到协作:与仓库其他模块的衔接

分支、合并只是 Git 协作流程的一环。在 School of SRE 的课程体系中,后续内容与分支直接相关:

  • Git 与 GitHub / Hooks 章节:本地开发完成并通过 merge/rebase 之后,还需要push到 GitHub 中央仓库、pull远端最新改动。该章节还介绍了pre-push、pre-commit等 hooks——例如可以配置pre-commit钩子在每次提交前执行脚本(如代码检查),这与「提交是否干净」直接相关。
  • CI/CD 章节:持续集成要求所有成员频繁地把改动推到各自的 feature 分支,再由 CI 服务器在代码 push 后自动触发构建与测试。这正依赖分支机制提供的「独立开发、随时集成」能力——每个开发者一条分支,改动随时可 push、可快速合并回主线。
  • Git 结论章节:掌握分支与合并之后,还可以继续探索 cherry-pick、squash、amend、stash、reset 等进阶命令,它们大多与「如何整理提交树、移动指针」这一核心模型同源。

小结:把分支当作「指针操作」

贯穿本文的核心结论,也是分支章节反复强调的:

Git 内部就是一棵提交树。分支名(人类可读的名字)是指向树中某个提交的指针。我们使用各种 git 命令来操作这棵树和引用,Git 据此修改仓库内容。

  • git branch b1:从当前提交创建一个新指针,不切换HEAD;
  • git checkout b1:让HEAD指向b1,并用b1指向提交的快照替换工作目录;
  • 在两条分支上分别提交,就形成从共同祖先分叉的两条独立历史线;
  • git merge:直接汇合两条历史线,产生 merge commit;
  • git rebase master:把当前分支的提交搬到master顶端,然后git merge即可 Fast-forward,得到干净的单线历史。

从源码结构看,这一切的「魔法」都沉淀在.git目录——refs/heads/下的文件记录每个分支指向的提交 ID,HEAD文件记录当前检出位置(详见 git-basics.md)。理解了指针与树这两个概念,再面对任何 Git 操作都会更有底气。

  • 教程

【免费下载链接】school-of-sre

At LinkedIn, we are using this curriculum for onboarding our entry-level talents into the SRE role.

项目地址:https://gitcode.com/gh_mirrors/sc/school-of-sre
点击查看免费下载

相关推荐

上一篇:如何永久保存微信聊天记录:WeChatExporter开源工具使用指南
下一篇:Microduck RL的sim2real完整配方:为什么"仿真会跑"不等于"真机会跑"

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 2:57:26

Mac装Photoshop‘已损坏’报错终极解决方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 2:56:36

Baserow 文件上传与存储指南:从文件入库到访问控制

Baserow 文件上传与存储指南&#xff1a;从文件入库到访问控制 【免费下载链接】baserow Build databases, automations, apps & agents with AI — no code. Open source platform available on cloud and self-hosted. GDPR, HIPAA, SOC 2 compliant. Best Airtable alte…

作者头像 李华
网站建设 2026/9/26 2:54:48

浏览器里修图:WebGPU 图像修复工具 inpaint-web 上手记

浏览器里修图&#xff1a;WebGPU 图像修复工具 inpaint-web 上手记 【免费下载链接】inpaint-web A free and open-source inpainting & image-upscaling tool powered by webgpu and wasm on the browser。| 基于 Webgpu 技术和 wasm 技术的免费开源 inpainting & ima…

作者头像 李华
网站建设 2026/9/26 2:53:42

SQL Server数据库实验实战:约束、触发器与游标避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 2:52:36

SoLab AI逆向工作台实战:DEX/SO/Flutter分析一体化

做安卓逆向的朋友应该都有过这样的经历&#xff1a;桌面上堆着七八个工具&#xff0c;Jadx看DEX、IDA看SO、Frida做动态验证、Apktool拆包重打包&#xff0c;每个工具都有自己的操作习惯和依赖环境&#xff0c;项目一多光是在工具之间来回切换就消耗掉大半精力。我第一次接触So…

作者头像 李华