mobile wallpaper 1
mobile wallpaper 2
mobile wallpaper 3
mobile wallpaper 4
mobile wallpaper 5
mobile wallpaper 6
50966 字
135 分钟
代码版本管理与Git学习笔记(AI整理)
2026-04-15

代码版本管理与 Git 学习笔记#

1. 什么是代码版本管理#

代码版本管理,也叫版本控制,是对项目文件变化进行记录、比较、回退、审查和协作管理的过程。

它不是简单地“备份一份代码”,而是把软件开发中的每一次有效变更都整理成可追踪的历史记录。通过版本管理,团队可以知道代码从哪里来、为什么变成现在这样、某个问题是从哪次修改开始出现的,以及需要时如何恢复到某个稳定状态。

可以把代码版本管理理解为软件项目的“时间线系统”:

初始代码 -> 第一次提交 -> 第二次提交 -> 修复 bug -> 新增功能 -> 发布版本

每一个节点都记录了项目在某个时刻的状态。

1. 版本管理到底管理什么

版本管理工具通常管理的不只是代码文件,还包括和项目交付有关的文本型工程资产。

常见管理对象:

  • 源代码:如 .kt.java.py.js.cpp
  • 配置文件:如 ymljsonxmlproperties
  • 构建脚本:如 GradleMavenMakefile
  • 文档:如 README.md、接口文档、设计文档
  • 测试代码:单元测试、集成测试、自动化测试脚本
  • 脚本工具:部署脚本、数据迁移脚本、辅助工具
  • 项目元信息:依赖版本、CI/CD 配置、代码规范配置

一般不建议直接用普通 Git 仓库管理频繁变化的大型二进制文件,例如:

  • 视频
  • 大型图片源文件
  • 模型文件
  • 编译产物
  • 压缩包
  • 临时缓存

这类文件要么放到制品仓库,要么使用 Git LFS、对象存储或专门的资产管理系统。


2 版本管理解决的核心问题

它解决的问题包括:

  • 谁改了代码
  • 什么时候改的
  • 为什么改
  • 改了哪些文件
  • 如何回到旧版本
  • 多人同时开发如何合并
  • 如何把开发、测试、发布流程串起来

这些问题背后,其实对应软件工程中的几个核心能力:

能力说明
可追踪能知道每次修改的作者、时间、原因和内容
可回退发现问题后可以恢复到旧版本,或者撤销某次错误修改
可比较能比较两个版本之间改了什么
可协作多个人可以并行开发,最后合并成果
可审查合并前可以通过 Pull Request / Merge Request 审查代码
可发布可以给稳定版本打标签,形成清晰的发布记录
可自动化可以触发 CI/CD、测试、构建、部署和 changelog 生成

3. 为什么不能只靠复制文件备份

很多初学者一开始会用复制文件夹的方式保存版本:

project/
project_backup/
project_2026_07_16/
project_final/
project_final_2/

这种方式看起来简单,但很快会失控。

主要问题:

  • 不知道每个版本具体改了什么
  • 不知道哪个版本是稳定的
  • 不知道某个 bug 是什么时候引入的
  • 多人协作时无法合并修改
  • 文件夹越来越多,命名越来越混乱
  • 很难只回退某一次局部修改

版本控制系统相比普通备份的关键区别是:

对比项普通备份版本控制
记录粒度整个文件夹或文件每一次明确提交
修改说明通常没有每次提交都有说明
差异比较手动比较困难内置 diff
回退能力粗糙可精确回退
多人协作几乎不支持原生支持
历史查询混乱可按作者、时间、内容查询

所以,备份解决的是“文件丢了怎么办”,版本管理解决的是“软件如何持续演进并保持可控”。


4. 版本管理在开发流程中的位置

版本管理贯穿软件研发全过程。

典型流程:

需求提出
-> 创建分支
-> 编写代码
-> 本地提交
-> 推送远程仓库
-> 创建 PR/MR
-> 代码审查
-> 自动化测试
-> 合并主分支
-> 打 tag 发布
-> 线上问题追踪和回滚

在这个流程里,Git 不只是保存代码,它连接了需求、开发、测试、评审、发布和维护。

例如:

  • Issue 描述需求或 bug
  • Branch 隔离开发任务
  • Commit 记录具体变更
  • Pull Request 组织评审
  • CI 验证代码质量
  • Tag 标记发布版本
  • Revert 处理线上回滚

5. 代码版本管理的工程价值

对个人开发者:

版本管理可以帮助个人开发者:

  • 保存每个阶段的进度
  • 不怕大胆重构
  • 快速回退错误修改
  • 对比自己的改动
  • 整理学习和项目历史

比如做实验性重构时,可以先提交一个稳定版本,再放心修改。如果改坏了,随时回退。

对团队协作

版本管理可以帮助团队:

  • 分工开发
  • 合并代码
  • 审查变更
  • 避免覆盖彼此工作
  • 统一发布节奏
  • 追踪线上问题来源

在团队中,版本管理不是可选项,而是基础设施。

对项目交付

版本管理可以支撑:

  • 自动化构建
  • 自动化测试
  • 自动化部署
  • 版本发布
  • 回滚方案
  • 变更日志生成

现代 CI/CD 基本都依赖 Git 事件,例如:

  • push 触发测试
  • PR 触发代码检查
  • tag 触发发布
  • main 分支合并触发部署

6. 学习版本管理时要建立的思维

学习版本管理,不要只背命令,而要建立几个思维:

  1. 每次提交都应该表达一个明确目的。
  2. 分支是隔离工作内容的工具。
  3. 提交历史是项目的工程档案。
  4. 公共历史要谨慎修改。
  5. 主分支应该尽量保持稳定。
  6. 版本发布要有明确标记。
  7. 提交信息应该能解释变更原因。

后面学习 Git 命令、分支策略和 Commit Message 规范,本质上都是围绕这些思维展开。


2. 版本控制系统分类#

版本控制系统可以按架构和协作方式分为三类:

  • 本地版本控制
  • 集中式版本控制
  • 分布式版本控制

这三类不是简单的新旧替代关系,而是代表了不同阶段的软件协作方式。


2.1 本地版本控制#

最早的方式是在本地保存多个版本,例如:

project_v1.zip
project_v2.zip
project_final.zip
project_final_final.zip

这严格来说还不是现代意义上的版本控制系统,更像是人工备份。后来也出现过一些本地版本数据库工具,用来在单台机器上记录文件变化。

本地版本控制的核心特点是:

  • 版本历史只保存在当前机器
  • 不依赖服务器
  • 主要面向个人使用
  • 协作能力很弱

工作方式可以简单理解为:

本地文件 -> 本地版本记录 -> 本地恢复

这种方式简单,但问题很多:

  • 不适合多人协作
  • 难比较差异
  • 难追踪修改原因
  • 容易丢失文件
  • 机器损坏时历史可能一起丢失
  • 无法自然支持代码审查和远程发布

适用场景:

本地版本控制只适合:

  • 个人临时草稿
  • 不需要协作的小脚本
  • 简单文档备份
  • 学习版本概念的早期阶段

对正式软件项目来说,本地版本控制远远不够。


2.2 集中式版本控制(SVN、CVS)#

代表工具:

  • SVN
  • CVS

集中式版本控制系统有一个中央服务器。开发者从服务器拉代码,再把修改提交回服务器。

典型结构:

开发者 A \
开发者 B -> 中央版本库 -> 统一保存历史
开发者 C /

所有人的提交都进入中央服务器。中央服务器保存完整版本历史,本地工作副本通常只保存当前版本和少量元信息。

工作流程:

集中式版本控制的常见流程:

从服务器 checkout/update
-> 本地修改
-> 解决可能的冲突
-> commit 到中央服务器

以 SVN 为例:

svn checkout <repo-url>
svn update
svn commit -m "fix login validation"

优点:

  • 模型简单
  • 权限控制集中
  • 适合传统企业流程
  • 服务器上可以做目录级权限控制
  • 管理员更容易统一备份和审计
  • 对不熟悉分支模型的团队更容易上手

缺点:

  • 强依赖中央服务器
  • 离线能力弱
  • 分支成本相对高
  • 本地没有完整历史,很多操作需要连接服务器
  • 中央服务器故障时协作会受影响
  • 大规模分支和合并不如 Git 灵活

适用场景

集中式版本控制适合:

  • 强权限管控的传统企业项目
  • 文件目录权限要求细的项目
  • 团队协作模式稳定、分支较少的项目
  • 历史项目维护

但对于现代敏捷开发、频繁分支、频繁合并、CI/CD 驱动的团队,Git 通常更合适。


2.3 分布式版本控制(Git)#

代表工具:

  • Git
  • Mercurial

分布式版本控制系统中,每个开发者本地都有完整仓库历史。

典型结构:

开发者 A 本地完整仓库 <-> 远程仓库
开发者 B 本地完整仓库 <-> 远程仓库
开发者 C 本地完整仓库 <-> 远程仓库

远程仓库不再是唯一的历史保存点,而是团队协作中的一个交换中心。每个开发者本地仓库都可以独立提交、分支、查看历史和回退。

工作流程

以 Git 为例,常见流程是:

clone 远程仓库
-> 本地创建分支
-> 本地多次 commit
-> push 到远程
-> 创建 PR/MR
-> review 后合并

命令示例:

git clone <repo-url>
git switch -c feature/login
git add .
git commit -m "feat(auth): add login validation"
git push -u origin feature/login

优点:

  • 本地提交、分支、查看历史都很快
  • 离线也能工作
  • 分支非常轻量
  • 容灾能力强
  • 适合多人并行开发
  • 适合开源协作
  • 支持多远程仓库协作
  • 非常适合 CI/CD 和代码评审流程

缺点:

分布式版本控制也有学习成本:

  • 概念更多,例如工作区、暂存区、本地仓库、远程仓库
  • 分支、merge、rebase、reset 等命令容易混淆
  • 提交历史可改写,需要团队规范约束
  • 大二进制文件管理需要额外工具,如 Git LFS
  • 权限控制一般在托管平台层实现,而不是 Git 本身细粒度控制

Git 是目前最主流的分布式版本控制系统。


2.4 对比#

三类版本控制系统对比

对比项本地版本控制集中式版本控制分布式版本控制
代表方式手工备份、本地历史库SVN、CVSGit、Mercurial
历史保存位置当前机器中央服务器每个本地仓库都有完整历史
离线提交不完整或不支持通常不支持支持
多人协作很弱支持
分支成本几乎没有正式分支相对较高很低
容灾能力依赖中央服务器备份
学习成本中高
现代软件开发适配度

Git 和 SVN 的核心区别

Git 和 SVN 是最常被拿来比较的两个工具。

对比项GitSVN
架构分布式集中式
本地是否有完整历史通常没有
本地提交支持不支持,提交到服务器
分支轻量、常用相对重
合并能力较弱
离线工作
权限控制依赖平台和仓库策略目录级权限控制较强
适用生态现代开源和企业研发传统企业和历史项目

简单理解:

  • Git 更适合频繁分支、频繁合并、快速迭代。
  • SVN 更适合强中心化、强目录权限、流程稳定的项目。

为什么现代项目大多选择 Git

Git 成为主流,不只是因为它速度快,还因为它适合现代软件研发模式。

现代项目通常需要:

  • 多人并行开发
  • 功能分支
  • Pull Request / Merge Request
  • 自动化测试
  • 自动化部署
  • 开源协作
  • 快速回滚
  • 版本发布
  • 多环境交付

Git 的分支模型、远程协作模型和生态工具链,正好适配这些需求。

GitHub、GitLab、Gitee、Bitbucket 等平台进一步把 Git 扩展成完整研发协作平台:

  • 代码托管
  • Issue 管理
  • 代码评审
  • CI/CD
  • 权限管理
  • Release 管理
  • 安全扫描

所以,现代项目里常说的“用 Git 管理代码”,通常包含两层意思:

  1. 用 Git 管理版本历史。
  2. 用 Git 托管平台管理团队协作流程。

** 如何选择版本控制系统**

大多数新项目直接选择 Git 即可。

可以按下面方式判断:

场景推荐
新的软件项目Git
开源项目Git + GitHub / GitLab
国内个人或团队项目Git + Gitee / GitLab
企业内部平台GitLab / GitHub Enterprise / Gitee 企业版
传统 SVN 历史项目维护继续 SVN 或逐步迁移 Git
大量大文件资产管理Git + Git LFS,或专门资产管理系统
极强目录级权限要求SVN 或平台级权限方案

对于学习者来说,建议优先掌握 Git。
理解 Git 以后,再看 SVN、Mercurial 或其他工具会容易很多。


3. 常见代码管理工具#

代码管理工具可以分成两类:

  1. 版本控制工具:负责记录代码历史,例如 Git、SVN、Mercurial。
  2. 代码托管与协作平台:基于版本控制工具提供远程仓库、评审、Issue、CI/CD 等能力,例如 GitHub、GitLab、Gitee、Bitbucket。

这两类经常被混在一起说,但它们不是一回事。

Git = 版本控制工具
GitHub = 基于 Git 的代码托管和协作平台
GitLab = 基于 Git 的代码托管和 DevOps 平台
Gitee = 基于 Git 的国内代码托管平台
Bitbucket = 基于 Git 的代码托管平台

工具总览

工具类型特点适用场景
Git分布式版本控制分支轻量、生态强、速度快绝大多数现代软件项目
SVN集中式版本控制权限集中、目录级控制强传统企业项目、强中心化流程
Mercurial分布式版本控制易用性较好少量历史项目
GitHubGit 托管平台PR、Issue、Actions、开源生态强开源、团队协作
GitLabGit 托管平台CI/CD、权限、私有化部署强企业研发平台
GiteeGit 托管平台国内访问友好国内团队和个人项目
BitbucketGit 托管平台和 Atlassian 生态结合Jira/Confluence 团队

注意:

Git 是版本控制工具。
GitHub、GitLab、Gitee 是基于 Git 的代码托管和协作平台。


3.1 版本控制工具#

Git

Git 是目前最主流的分布式版本控制工具。

它负责:

  • 初始化仓库
  • 记录提交历史
  • 管理分支
  • 合并代码
  • 回退版本
  • 比较差异
  • 管理标签
  • 与远程仓库同步

常见命令:

git init
git clone <url>
git status
git add .
git commit -m "feat: add feature"
git branch
git switch -c feature/demo
git merge feature/demo
git push
git pull

Git 的优势:

  • 本地操作快
  • 离线也能提交
  • 分支创建和切换成本低
  • 适合多人并行开发
  • 生态成熟
  • 与 CI/CD、代码评审、开源社区结合紧密

Git 的不足:

  • 初学概念较多
  • 命令体系较复杂
  • 历史改写容易误操作
  • 大文件管理不如专门资产系统

适合场景:

  • Web 项目
  • Android / iOS 项目
  • 后端服务
  • 开源项目
  • 文档项目
  • DevOps 和 CI/CD 项目
  • 绝大多数现代软件工程项目

SVN

SVN,全称 Subversion,是典型的集中式版本控制系统。

它负责:

  • 从中央服务器检出代码
  • 提交修改到中央服务器
  • 管理目录级权限
  • 记录集中式历史

SVN 的常见命令:

svn checkout <repo-url>
svn update
svn status
svn add file.txt
svn commit -m "update document"

SVN 的优势:

  • 权限控制集中
  • 目录级权限能力较强
  • 使用模型直观
  • 适合传统企业管理方式

SVN 的不足:

  • 本地没有完整仓库历史
  • 离线能力弱
  • 分支和合并体验不如 Git
  • 不适合高频分支开发
  • 现代开源生态不如 Git

适合场景:

  • 历史遗留项目
  • 强中心化企业项目
  • 对目录权限控制要求很细的项目
  • 团队暂时没有迁移 Git 的条件

Mercurial

Mercurial 也是分布式版本控制系统,和 Git 在定位上比较接近。

它的特点:

  • 分布式
  • 命令相对简洁
  • 学习曲线比 Git 平缓一些
  • 曾经在一些大型项目中使用

常见命令风格:

hg clone <url>
hg status
hg add
hg commit -m "update"
hg push
hg pull

Mercurial 的优势:

  • 使用体验较一致
  • 分布式模型清晰
  • 对新手相对友好

Mercurial 的不足:

  • 生态规模小于 Git
  • 平台支持和社区资源少于 Git
  • 新项目采用率较低

适合场景:

  • 已经使用 Mercurial 的历史项目
  • 团队已有 Mercurial 经验

对于大多数新项目,不建议优先选择 Mercurial,除非有明确历史原因。


3.2 代码托管平台#

GitHub

GitHub 是最流行的 Git 托管平台之一。

它提供的不只是远程仓库,还包括:

  • Pull Request
  • Issue
  • Actions
  • Projects
  • Wiki
  • Releases
  • Code Review
  • Dependabot
  • Security alerts

GitHub 的优势:

  • 开源生态最强
  • 文档和社区资源丰富
  • GitHub Actions 易用
  • 适合个人作品集和开源协作
  • 第三方集成多

GitHub 的不足:

  • 国内网络访问可能不稳定
  • 企业私有化成本和策略需要额外考虑
  • 对强本地化合规团队未必最方便

适合场景:

  • 开源项目
  • 个人项目展示
  • 国际化团队协作
  • 需要 GitHub Actions 的自动化流程

GitLab

GitLab 是一体化 DevOps 平台,既可以使用云服务,也可以私有化部署。

它常见能力:

  • Git 仓库托管
  • Merge Request
  • Issue
  • CI/CD
  • Container Registry
  • Package Registry
  • 权限管理
  • Runner
  • 安全扫描

GitLab 的优势:

  • CI/CD 集成强
  • 私有化部署成熟
  • 适合企业内部研发平台
  • 权限和流程管理能力强
  • 从代码到部署链路完整

GitLab 的不足:

  • 自建维护成本较高
  • 功能多,配置复杂度也更高
  • 小型个人项目可能显得重

适合场景:

  • 企业内部研发平台
  • 需要私有化部署的团队
  • 需要统一 CI/CD 的团队
  • 对权限和流程有较强要求的组织

Gitee

Gitee 是国内常见的 Git 托管平台。

它常见能力:

  • Git 仓库托管
  • Pull Request
  • Issue
  • Wiki
  • Pages
  • 企业版能力
  • 国内访问优化

Gitee 的优势:

  • 国内访问相对方便
  • 适合国内团队协作
  • 对中文用户友好
  • 可以作为 GitHub 镜像或备份平台

Gitee 的不足:

  • 国际开源生态不如 GitHub
  • 第三方集成生态相对有限
  • 跨国协作时影响力不如 GitHub

适合场景:

  • 国内个人项目
  • 国内团队项目
  • 需要中文平台体验
  • 需要国内访问稳定性

Bitbucket

Bitbucket 是 Atlassian 旗下的 Git 托管平台。

它经常和这些工具配合:

  • Jira
  • Confluence
  • Trello
  • Bamboo

Bitbucket 的优势:

  • 和 Atlassian 生态集成好
  • 适合 Jira 驱动的项目管理流程
  • 支持 Pull Request 和权限管理
  • 对企业团队友好

Bitbucket 的不足:

  • 开源社区影响力不如 GitHub
  • 国内使用率相对较低
  • 如果不用 Atlassian 生态,优势会减弱

适合场景:

  • 已经使用 Jira / Confluence 的团队
  • Atlassian 体系内的软件项目

3.3 Git 客户端工具#

除了命令行,还可以使用图形化 Git 客户端。

常见工具:

工具特点
Git CLI最基础、最完整、最推荐掌握
SourceTree图形化强,适合看分支图
GitKrakenUI 友好,适合可视化操作
GitHub Desktop简洁,适合 GitHub 用户
Fork轻量好用,适合日常 Git 操作
VS Code Git 面板和编辑器结合紧密
IntelliJ IDEA Git 工具JetBrains 系 IDE 内置,适合 Java/Kotlin 项目

建议:

  • 初学者可以用 GUI 理解分支和提交历史。
  • 但必须掌握 Git CLI 的基本命令。
  • 遇到复杂问题时,命令行更可靠,也更容易搜索解决方案。

工具选择建议

大多数情况下:

版本控制工具:Git
代码托管平台:GitHub / GitLab / Gitee
本地操作方式:Git CLI + IDE Git 工具

不同场景下的推荐:

场景推荐组合
个人学习项目Git + GitHub 或 Gitee
开源项目Git + GitHub
国内团队协作Git + Gitee 或 GitLab
企业私有化平台Git + GitLab
Android / Kotlin 项目Git + GitHub/GitLab + Android Studio/IDEA Git
强 Jira 流程团队Git + Bitbucket
历史 SVN 项目继续 SVN 或逐步迁移 Git

4. Git 是什么#

Git 是一个分布式版本控制系统,最初由 Linus Torvalds 为 Linux 内核开发。

它的主要任务是记录项目历史,并让开发者可以高效地进行分支开发、合并协作、版本回退和发布管理。

一句话理解:

Git = 用提交记录管理项目演进历史的分布式版本控制工具

Git 解决的核心问题

Git 主要解决软件开发中的这些问题:

  • 如何记录每次代码变更
  • 如何知道代码是谁改的
  • 如何比较两个版本的差异
  • 如何回退错误修改
  • 如何支持多人并行开发
  • 如何隔离不同功能开发
  • 如何把多个分支的代码合并起来
  • 如何标记发布版本
  • 如何让代码历史可审查、可追踪

它不是项目管理工具,也不是代码托管平台。
Git 本身只负责版本控制;GitHub、GitLab、Gitee 这类平台才负责远程协作、PR、Issue、CI/CD 等能力。


Git 的核心特点

Git 的核心特点:

  • 分布式
  • 分支轻量
  • 本地操作快
  • 数据完整性强
  • 支持非线性开发
  • 适合大型协作项目

这些特点决定了 Git 特别适合现代软件工程。

1. 分布式

每个开发者本地都有一份完整仓库历史。

这意味着:

  • 本地可以提交
  • 本地可以查看日志
  • 本地可以创建分支
  • 本地可以回退版本
  • 没有网络也能继续开发

远程仓库只是协作中心,不是唯一的历史保存点。

2. 分支轻量

Git 的分支本质上是指向某个提交的指针。

创建分支非常快:

git switch -c feature/login

这让 Git 很适合:

  • 一个需求一个分支
  • 一个 bugfix 一个分支
  • 一个实验一个分支
  • 一个发布版本一个分支

3. 本地操作快

大部分 Git 操作都在本地完成,例如:

git status
git log
git diff
git branch
git commit

这些命令通常不需要访问远程服务器。

4. 数据完整性强

Git 用哈希值标识对象和提交。

每一次提交都有一个类似这样的提交 ID:

a1b2c3d4e5f6...

这个 ID 和提交内容相关。内容变化后,哈希也会变化。
因此 Git 能较好地保证历史记录不被无声篡改。

5. 支持非线性开发

Git 允许多个分支并行演进:

main: A---B---C------F
\ /
feature: D---E---

这就是非线性开发。

它非常适合多人协作,因为每个人都可以在自己的分支上独立工作,最后再合并。


Git 记录的是快照,不只是差异

很多人以为 Git 只记录文件每次修改的差异。
实际上,Git 更像是在每次提交时保存项目的一份快照。

可以这样理解:

commit A = 项目在 A 时刻的完整状态
commit B = 项目在 B 时刻的完整状态
commit C = 项目在 C 时刻的完整状态

如果某个文件没有变化,Git 不会重复保存一份完整内容,而是复用已有对象。
所以 Git 既保留了快照模型的清晰性,又通过对象复用节省空间。

快照模型的好处:

  • 容易回到某个历史版本
  • 容易比较两个版本差异
  • 容易创建分支
  • 容易标记发布状态

Git 的基本工作模型

Git 的日常工作可以理解为四步:

修改 -> 暂存 -> 提交 -> 推送

对应命令:

git status
git add .
git commit -m "feat: add user profile"
git push

更完整的协作模型:

远程仓库
↓ clone / pull
本地仓库
↓ checkout / switch
工作区
↓ add
暂存区
↓ commit
本地仓库
↓ push
远程仓库

其中:

  • clone:复制远程仓库到本地
  • pull:拉取远程更新
  • add:把修改放入暂存区
  • commit:把暂存区内容记录为提交
  • push:把本地提交推送到远程仓库

Git 与远程仓库的关系

Git 可以完全在本地使用,不一定必须连接 GitHub 或 GitLab。

例如:

git init
git add .
git commit -m "initial commit"

这已经是一个完整的本地 Git 仓库。

但是团队协作通常需要远程仓库:

  • GitHub
  • GitLab
  • Gitee
  • Bitbucket
  • 自建 Git 服务

远程仓库的作用:

  • 统一代码同步位置
  • 支持多人协作
  • 作为备份
  • 支持 PR / MR
  • 触发 CI/CD
  • 管理权限

Git 适合管理什么

Git 适合管理:

  • 源代码
  • 配置文件
  • Markdown 文档
  • 测试脚本
  • 构建脚本
  • 小型文本资源

Git 不适合直接管理:

  • 频繁变化的大型二进制文件
  • 编译产物
  • 临时缓存
  • 日志文件
  • 依赖下载目录
  • 密钥和敏感配置

这些不适合 Git 管理的内容,通常应该放进:

  • .gitignore
  • Git LFS
  • 制品仓库
  • 对象存储
  • 密钥管理系统

Git 的优势和代价

优势

Git 的主要优势:

  • 速度快
  • 分支灵活
  • 离线可用
  • 生态强大
  • 支持复杂协作
  • 支持代码审查和 CI/CD 流程
  • 能精确追踪历史

代价

Git 的代价主要是学习曲线:

  • 概念多
  • 命令多
  • resetrebasecheckout 等命令容易误用
  • 冲突处理需要经验
  • 团队需要统一提交和分支规范

所以学 Git 时,不能只记命令,还要理解它背后的模型。

什么情况下不应该用 Git

Git 很强,但不是所有文件管理问题都应该用 Git 解决。

不适合 Git 的场景:

  • 管理大量图片、视频、模型等大文件
  • 管理每天自动生成的大量日志
  • 管理数据库运行时数据
  • 管理敏感密钥
  • 替代网盘做普通文件同步
  • 替代项目管理工具做需求排期

Git 的边界是:

适合管理可审查、可比较、可演进的项目文件。
不适合管理大量不可读、不可 diff、频繁变化的二进制数据。

初学需要理解

学习 Git 最重要的是先抓住这些核心关系:

工作区 -> 暂存区 -> 本地仓库 -> 远程仓库

然后理解:

  • add 是选择下次提交内容
  • commit 是保存一次历史
  • branch 是创建独立开发线
  • merge 是合并开发线
  • rebase 是整理提交基底
  • reset 是移动当前分支指针
  • revert 是用新提交撤销旧提交
  • push 是上传本地提交
  • pull 是拉取并整合远程提交

后面的章节会围绕这些关系展开。


5. Git 的四大核心区域#

理解 Git,必须理解它的几个核心区域。

很多 Git 命令看起来复杂,本质上都是在这些区域之间移动文件状态。

最常见的三大区域是:

工作区 Working Tree
暂存区 Staging Area / Index
本地仓库 Local Repository

如果考虑团队协作,还要加上远程仓库:

工作区 -> 暂存区 -> 本地仓库 -> 远程仓库

对应命令:

修改文件 -> git add -> git commit -> git push

5.1 工作区#

工作区是你实际编辑代码的目录。

例如你在 IDE 中看到的文件,就是工作区文件。

工作区里的文件可能处于这些状态:

  • 未跟踪
  • 已修改
  • 已删除
  • 已暂存
  • 与仓库一致

常用查看命令:

git status
git diff

2. 未跟踪文件

新建但还没有被 Git 管理的文件,叫未跟踪文件。

例如:

Untracked files:
notes.md

把它加入 Git 管理:

git add notes.md

如果不想提交,就写入 .gitignore

3. 已修改文件

已经被 Git 跟踪,但当前内容和上次提交不同。

查看修改:

git diff

放入暂存区:

git add file.txt

丢弃工作区修改:

git restore file.txt

4. 工作区干净

当工作区没有未提交修改时,git status 会提示:

nothing to commit, working tree clean

这表示当前工作区、暂存区和本地仓库是一致的。


5.2 暂存区#

暂存区是下一次提交的准备区。

执行:

git add <file>

文件修改会进入暂存区。

暂存区也叫 Index。它的作用是决定下一次 git commit 到底提交哪些内容。

可以理解为:

工作区:我当前改了什么
暂存区:我准备把哪些改动放进下一次提交

常用命令:

git add file.txt
git add .
git add -p
git diff --cached
git restore --staged file.txt

为什么暂存区很重要

暂存区让你可以把混在一起的修改拆成多个清晰提交。

例如你同时改了:

  • 登录逻辑
  • README
  • 按钮样式

可以分批暂存:

git add src/login.kt
git commit -m "fix(auth): validate login token"
git add README.md
git commit -m "docs: update setup guide"
git add app/src/main/res/layout/login.xml
git commit -m "style(ui): adjust login button spacing"

git add .git add -p

git add . 会把当前目录下的大部分变更都加入暂存区,适合小范围确认过的修改。

git add -p 会交互式选择部分修改,适合精细拆分提交。

建议:

  • 日常提交前先 git diff
  • 不确定时用 git add -p
  • 不要无脑 git add .

5.3 本地仓库#

执行:

git commit

暂存区内容会生成一次提交,进入本地仓库。

本地仓库保存在项目目录下的 .git 文件夹中。

.git 里面保存了:

  • 对象数据库
  • 分支引用
  • HEAD 指针
  • 配置
  • hooks
  • reflog
  • 暂存区信息

普通开发时不需要手动修改 .git 目录。

常用命令:

git commit
git log
git show <commit>
git reset
git revert
git reflog

commit 是什么

一次 commit 是一次项目快照。

它通常包含:

  • 提交 ID
  • 作者
  • 时间
  • 提交信息
  • 指向父提交的引用
  • 指向文件树的引用

可以用下面命令查看:

git show <commit>

本地提交不等于远程同步

执行 git commit 后,提交只存在于本地仓库。

如果要让远程仓库也拥有这次提交,需要:

git push

所以:

git commit = 写入本地历史
git push = 推送到远程仓库

5.4 远程仓库#

远程仓库是托管在服务器或平台上的 Git 仓库。

常见远程仓库平台:

  • GitHub
  • GitLab
  • Gitee
  • Bitbucket
  • 公司自建 Git 服务

远程仓库的作用:

  • 团队共享代码
  • 作为备份
  • 支持 PR / MR
  • 触发 CI/CD
  • 管理权限
  • 发布版本

常用命令:

git remote -v
git fetch origin
git pull
git push
git push -u origin feature/login

origin 是什么

origin 是默认远程仓库名称。

克隆仓库后,Git 通常会自动创建:

origin -> 远程仓库地址

查看:

git remote -v

注意:origin 只是名字,不是固定规则。你也可以把远程仓库命名为其他名称。


5.5 四个区域之间的流动关系#

基本流程:

修改文件 -> git add -> git commit
工作区 -> 暂存区 -> 本地仓库

完整协作流程:

远程仓库
↓ git clone / git pull
本地仓库
↓ git switch / git checkout
工作区
↓ 修改文件
工作区变更
↓ git add
暂存区
↓ git commit
本地仓库
↓ git push
远程仓库

1. 从工作区到暂存区

git add file.txt

含义:选择要进入下一次提交的修改。

2. 从暂存区到本地仓库

git commit -m "fix: update file"

含义:把暂存区内容保存为一次提交。

3. 从本地仓库到远程仓库

git push

含义:把本地提交同步到远程仓库。

4. 从远程仓库到本地仓库

git fetch

含义:拉取远程引用和对象,但不自动合并到当前工作分支。

5. 从远程仓库到当前工作分支

git pull

含义:拉取远程更新并整合到当前分支。


文件状态变化

Git 中文件状态大致可以这样流转:

未跟踪 untracked
-> git add
已暂存 staged
-> git commit
已提交 committed
已提交 committed
-> 修改文件
已修改 modified
-> git add
已暂存 staged

常见状态解释:

状态含义常见命令
untracked新文件,Git 尚未跟踪git add
modified已跟踪文件被修改git diff
staged已加入暂存区git diff --cached
committed已保存到本地仓库git log
pushed已推送到远程仓库git push

常用检查命令

检查命令含义解释
git status查看整体状态最常用的 Git 命令之一
git diff查看工作区改动查看还没有暂存的修改
git diff --cached查看暂存区改动查看已经暂存、下一次 commit 会提交的修改
git log --oneline --graph --decorate查看提交历史查看本地仓库提交历史

6. 为什么 Git 需要暂存区#

暂存区的价值是精确控制:

下一次提交到底包含哪些修改

如果没有暂存区,Git 只能把工作区里的所有修改一次性提交。
这会让提交很容易变得混乱:修 bug、改样式、写文档、调试代码全部混在一个 commit 里。

暂存区的存在,让 Git 提交从“保存当前全部文件”变成“选择一组相关修改并保存为一次清晰提交”。


暂存区的本质

暂存区,也叫 Index。

它位于工作区和本地仓库之间:

工作区 Working Tree
↓ git add
暂存区 Staging Area / Index
↓ git commit
本地仓库 Local Repository

可以这样理解:

区域含义
工作区你当前实际改了什么
暂存区你准备提交什么
本地仓库你已经正式记录了什么

暂存区不是备份区,也不是远程仓库。
它只是下一次 commit 的“候选清单”。


没有暂存区会有什么问题

假设你在一次开发中同时做了这些事情:

  • 修复登录 bug
  • 调整登录按钮样式
  • 更新 README
  • 临时添加一行调试日志

如果没有暂存区,你可能只能一次性提交:

fix login and update docs and style

问题是:

  • 提交目的不清楚
  • 代码评审很难看
  • 回滚时会把无关修改一起回滚
  • 后续查 bug 很难定位
  • 提交历史不可读

暂存区让你可以把这些修改拆成多个独立提交。


暂存区让提交更清晰

例如你同时做了三件事:

  • 修复登录 bug
  • 修改按钮样式
  • 更新 README

它们最好拆成三次提交:

git add src/login.kt
git commit -m "fix(auth): validate login token"
git add app/src/main/res/layout/login.xml
git commit -m "style(ui): adjust login button spacing"
git add README.md
git commit -m "docs: update setup guide"

这样历史更清晰,也更容易回滚。

提交历史会变成:

fix(auth): validate login token
style(ui): adjust login button spacing
docs: update setup guide

比下面这种好得多:

update login page

暂存区支持部分提交

有时候同一个文件里也可能包含多个不同目的的修改。

例如 UserService.kt 里同时改了:

  • 修复 token 校验
  • 重命名一个变量
  • 删除调试日志

如果直接:

git add UserService.kt

整个文件的修改都会进入暂存区。

如果想只选择其中一部分,可以使用:

git add -p UserService.kt

-p 表示 patch,Git 会把修改拆成小块,让你逐块选择是否暂存。

常见交互选项:

选项含义
y暂存当前修改块
n不暂存当前修改块
s尝试拆分当前修改块
e手动编辑当前修改块
q退出
?查看帮助

这对写出高质量提交非常重要。


暂存区支持提交前检查

暂存后,不要立刻提交。建议先看一下暂存区内容。

查看工作区未暂存差异:

git diff

查看已经暂存、即将提交的差异:

git diff --cached

也可以使用:

git diff --staged

--cached--staged 在这个场景下基本等价。

推荐提交流程:

git status
git diff
git add -p
git diff --cached
git commit -m "fix(auth): validate login token"

这个流程能明显减少误提交。


暂存区可以撤销

如果误把文件加入暂存区,可以取消暂存。

git restore --staged file.txt

这条命令只会把文件从暂存区移回工作区,不会删除你的修改。

也就是说:

暂存区 -> 工作区

取消暂存但保留修改

git restore --staged file.txt

结果:

staged -> modified

文件内容还在,只是不参与下一次提交。

丢弃工作区修改

git restore file.txt

结果:

modified -> clean

注意:这会丢弃未提交修改。

修改最近一次提交

git add missing-file.txt
git commit --amend

适合忘记把某个文件加入最近一次提交时使用。


暂存区在团队协作中的价值

暂存区不只是个人方便,它直接影响团队协作质量。

好的暂存习惯可以带来:

  • 更小的 commit
  • 更清楚的变更目的
  • 更容易 review
  • 更容易 revert
  • 更容易生成 changelog
  • 更容易定位 bug

代码评审时,审查者最怕看到这种提交:

update all files

因为它可能同时包含:

  • 业务逻辑修改
  • 格式化修改
  • 文档修改
  • 临时调试代码
  • 依赖升级

而这些内容应该被拆成独立提交。


暂存区和 Commit Message 的关系

Commit Message 写得好不好,前提是暂存区选得好不好。

如果暂存区里混入了多个无关修改,再好的 commit message 也很难准确描述。

合理关系应该是:

暂存区内容 = 一个明确变更目的
commit message = 对这个变更目的的准确描述

例如:

git add src/auth/TokenValidator.kt
git commit -m "fix(auth): reject expired token"

这个提交就很清楚。


7. Git 基础配置#

Git 安装完成后,第一件事不是立刻提交代码,而是先完成基础配置。

Git 配置会影响:

  • 提交作者信息
  • 默认分支名
  • 默认编辑器
  • 换行符处理
  • 代理
  • 命令别名
  • 凭据保存方式
  • 输出显示效果

配置正确后,后续协作会少很多问题。


1. 查看版本

git --version

如果能正常输出版本号,说明 Git 已经安装并加入系统 PATH。

示例:

git version 2.45.0

如果命令不存在,需要检查:

  • Git 是否安装
  • 终端是否重启
  • PATH 环境变量是否正确

2. 配置用户名和邮箱

git config --global user.name "Your Name"
git config --global user.email "you@example.com"

用户名和邮箱会写入每一次 commit。

查看提交时通常会看到:

Author: Your Name <you@example.com>

注意:

  • 这里的邮箱不一定必须是 GitHub 邮箱,但建议保持一致。
  • 公司项目建议使用公司邮箱。
  • 个人开源项目可以使用个人邮箱。
  • GitHub 用户如果想隐藏真实邮箱,可以使用 GitHub 提供的 noreply 邮箱。

查看当前配置:

git config --global user.name
git config --global user.email

如果某个仓库需要单独设置作者信息,可以去掉 --global

git config user.name "Work Name"
git config user.email "work@example.com"

3. 配置默认分支名

git config --global init.defaultBranch main

这个配置影响 git init 新仓库时默认创建的分支名。

常见默认分支名:

  • main
  • master
  • develop

现在很多新项目使用 main 作为默认分支名。

如果不配置,不同 Git 版本和模板环境可能使用不同默认名称。统一配置可以减少团队差异。


4. 查看配置

git config --list

查看全局配置:

git config --global --list

查看当前仓库配置:

git config --local --list

查看系统级配置:

git config --system --list

查看配置来源:

git config --list --show-origin

这在排查“为什么配置不生效”时很有用。


5. 配置编辑器

git config --global core.editor "code --wait"

编辑器用于这些场景:

  • 写多行 commit message
  • 处理 merge commit message
  • 执行交互式 rebase
  • 编辑 tag message

常见配置:

# VS Code
git config --global core.editor "code --wait"
# Vim
git config --global core.editor "vim"
# Notepad++
git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"

如果不配置,Git 可能默认打开 Vim。
如果你不熟悉 Vim,第一次看到编辑器界面可能会不知道如何退出。


6. 配置换行符

不同系统使用的换行符不同:

系统常见换行符
WindowsCRLF
macOS / LinuxLF

如果团队跨平台协作,换行符处理不当会导致大量无意义 diff。

Windows 常见配置:

git config --global core.autocrlf true

macOS / Linux 常见配置:

git config --global core.autocrlf input

也可以更推荐在项目中用 .gitattributes 统一:

* text=auto
*.sh text eol=lf
*.bat text eol=crlf

建议:

  • 个人全局配置只做基础处理。
  • 团队项目用 .gitattributes 明确规则。
  • 不要让换行符变化污染业务提交。

7. 配置大小写敏感

Windows 和 macOS 默认文件系统通常对大小写不敏感,Linux 通常大小写敏感。

这会导致类似问题:

UserService.kt
userservice.kt

在 Linux CI 上可能是两个文件,在 Windows 上可能冲突。

查看配置:

git config core.ignorecase

一般 Git 会根据文件系统自动设置。

实践建议:

  • 文件命名统一风格。
  • 不要只改文件名大小写后直接提交。
  • 如果必须修改大小写,使用 git mv

示例:

git mv userservice.kt UserService.kt

8. 配置凭据保存

使用 HTTPS 远程仓库时,Git 需要认证。

常见方式:

  • 用户名 + Token
  • SSH key
  • 凭据管理器

Windows 上 Git 通常会配合 Git Credential Manager。

查看凭据 helper:

git config --global credential.helper

常见配置:

git config --global credential.helper manager

macOS 常见:

git config --global credential.helper osxkeychain

Linux 可以使用 cache:

git config --global credential.helper cache

也可以设置缓存时间:

git config --global credential.helper "cache --timeout=3600"

安全建议:

  • 不要把 token 写进远程 URL。
  • 不要把 token 提交到仓库。
  • 公司项目优先使用组织推荐的认证方式。

9. 配置 SSH

如果使用 SSH 访问远程仓库,需要生成 SSH key。

生成:

ssh-keygen -t ed25519 -C "you@example.com"

查看公钥:

cat ~/.ssh/id_ed25519.pub

把公钥添加到 GitHub、GitLab 或 Gitee 后,可以测试:

ssh -T git@github.com

SSH 远程地址一般长这样:

git@github.com:user/repo.git

HTTPS 地址一般长这样:

https://github.com/user/repo.git

SSH 适合长期开发,HTTPS 适合快速克隆或简单使用。


10. 配置代理

如果访问 GitHub 慢,可能会配置代理。

HTTP 代理:

git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy http://127.0.0.1:7890

取消代理:

git config --global --unset http.proxy
git config --global --unset https.proxy

注意:

  • 代理地址要根据自己的环境调整。
  • 公司网络环境下不要随意配置未知代理。
  • 代理问题经常会影响 clone、fetch、push。

11. 配置常用别名

Git 命令较长,可以设置别名提高效率。

git config --global alias.st status
git config --global alias.co checkout
git config --global alias.sw switch
git config --global alias.br branch
git config --global alias.cm commit
git config --global alias.lg "log --oneline --graph --decorate --all"

使用:

git st
git lg

建议只给常用、安全、容易理解的命令设置别名。
不要给 reset --hardpush --force 这类危险命令设置太短的别名。


12. 配置默认 pull 行为

git pull 本质上是:

git fetch + merge/rebase

可以配置默认行为。

默认使用 merge:

git config --global pull.rebase false

默认使用 rebase:

git config --global pull.rebase true

只允许 fast-forward:

git config --global pull.ff only

团队应该统一策略,避免每个人 pull 后历史形态不一致。


13. 配置默认 push 行为

常见配置:

git config --global push.default simple

simple 表示只推送当前分支到它对应的 upstream 分支。
这是比较安全的默认行为。


14. 配置颜色输出

让 Git 输出更易读:

git config --global color.ui auto

通常新版本 Git 默认已经启用。


15. 配置提交模板

如果团队要求规范 commit message,可以配置提交模板。

创建模板文件,例如 ~/.gitmessage

type(scope): subject
Why:
What:
Impact:
Refs:

配置:

git config --global commit.template ~/.gitmessage

之后执行:

git commit

Git 会自动打开这个模板。


16. 配置层级

Git 配置有三个常见层级:

层级作用范围常见位置
system当前机器所有用户Git 安装目录配置
global当前系统用户~/.gitconfig
local当前仓库.git/config

优先级:

local > global > system

也就是说,当前仓库配置会覆盖全局配置。

示例:

git config --global user.email "personal@example.com"
git config user.email "work@example.com"

在当前仓库里,最终使用的是:

work@example.com

17. 推荐基础配置清单

个人开发环境可以先配置这些:

git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global core.editor "code --wait"
git config --global color.ui auto
git config --global push.default simple
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --decorate --all"

Windows 可以额外考虑:

git config --global core.autocrlf true

macOS / Linux 可以考虑:

git config --global core.autocrlf input

项目级换行规则更推荐交给 .gitattributes


8. 创建和克隆仓库#

Git 仓库的来源通常有两种:

  1. 本地已经有项目,现在要开始用 Git 管理。
  2. 远程已经有仓库,现在要克隆到本地开发。

对应命令分别是:

git init
git clone <repo-url>

这两个命令是进入 Git 项目的起点。


1. 初始化本地仓库

git init

git init 会在当前目录创建一个 .git 文件夹。
有了 .git 文件夹,这个目录就变成了 Git 仓库。

示例:

mkdir my-project
cd my-project
git init

初始化后可以查看状态:

git status

你会看到类似提示:

On branch main
No commits yet

这表示仓库已经创建,但还没有任何提交。


2. 初始化后的第一次提交

初始化仓库后,通常要添加项目文件并创建第一次提交。

echo "# My Project" > README.md
git add README.md
git commit -m "docs: add initial README"

第一次提交常见写法:

chore: initial commit
docs: add initial README
feat: initialize project structure

如果只是空项目初始化,可以用:

chore: initial commit

如果已经有明确项目结构,更推荐写清楚初始化内容。


3. 在已有项目中启用 Git

如果项目目录已经存在:

cd existing-project
git init
git status

然后添加必要文件:

git add .
git commit -m "chore: import existing project"

注意:

  • 提交前先写 .gitignore
  • 不要把构建产物、依赖目录、日志、密钥提交进去
  • 初次导入项目时,可以先用 git status 检查文件列表

常见初始 .gitignore

# build outputs
build/
dist/
target/
# dependencies
node_modules/
# logs
*.log
# env
.env
# IDE
../.idea/
.vscode/

4. 创建裸仓库

普通开发者很少需要裸仓库,但了解概念有帮助。

创建裸仓库:

git init --bare my-project.git

裸仓库没有工作区,通常用于服务器端远程仓库。

普通仓库:

项目文件 + .git

裸仓库:

只有 Git 仓库数据,没有可编辑工作区

现代团队通常直接使用 GitHub、GitLab、Gitee,不需要自己手动创建裸仓库。


5. 克隆远程仓库

git clone https://github.com/user/repo.git

git clone 会做几件事:

  1. 下载远程仓库数据。
  2. 创建本地工作目录。
  3. 自动设置远程仓库名为 origin
  4. 检出默认分支。

克隆后目录结构:

repo/
.git/
README.md
src/

进入项目:

cd repo
git status

6. 克隆到指定目录

默认情况下,Git 会用仓库名作为目录名。

git clone https://github.com/user/repo.git

会生成:

repo/

如果想指定目录名:

git clone https://github.com/user/repo.git my-local-name

会生成:

my-local-name/

7 使用 SSH 克隆

HTTPS 地址:

git clone https://github.com/user/repo.git

SSH 地址:

git clone git@github.com:user/repo.git

两者区别:

方式特点
HTTPS上手简单,常配合 token
SSH适合长期开发,不用每次输入账号密码

如果是自己的长期开发仓库,更推荐 SSH。


8. 浅克隆

如果仓库历史很大,只想拉最近历史,可以使用浅克隆:

git clone --depth 1 https://github.com/user/repo.git

适合:

  • CI 临时拉代码
  • 只需要最新版本
  • 仓库历史很大

不适合:

  • 需要完整历史分析
  • 需要 bisect
  • 需要查看很久以前的提交

9. 克隆指定分支

git clone -b develop https://github.com/user/repo.git

或者:

git clone --branch develop https://github.com/user/repo.git

适合只需要某个分支的情况。


10. 查看远程地址

git remote -v

输出示例:

origin git@github.com:user/repo.git (fetch)
origin git@github.com:user/repo.git (push)

其中:

  • fetch 表示拉取地址
  • push 表示推送地址

多数情况下,两者相同。


11. 添加远程仓库

git remote add origin git@github.com:user/repo.git

这个命令常用于:

  • 本地 git init 后,要关联 GitHub/GitLab/Gitee 上的新仓库
  • 本地已有项目,要推送到远程

完整流程:

git init
git add .
git commit -m "chore: initial commit"
git remote add origin git@github.com:user/repo.git
git push -u origin main

-u 表示设置 upstream。设置后,以后可以直接:

git push
git pull

12. 修改远程地址

如果远程地址错了,可以修改:

git remote set-url origin git@github.com:user/new-repo.git

查看确认:

git remote -v

13. 删除远程仓库引用

删除远程名:

git remote remove origin

注意:这只会删除本地对远程仓库的引用,不会删除 GitHub/GitLab 上的远程仓库。


14. 一个本地项目推送到新远程仓库的完整流程

假设你已经有一个本地项目,现在想推送到 GitHub。

步骤:

  1. 在 GitHub 创建空仓库。
  2. 本地初始化 Git。
  3. 创建第一次提交。
  4. 添加远程地址。
  5. 推送主分支。

命令:

cd my-project
git init
git add .
git commit -m "chore: initial commit"
git branch -M main
git remote add origin git@github.com:user/my-project.git
git push -u origin main

git branch -M main 的作用是把当前分支重命名为 main


15. 克隆后通常要做什么

克隆项目后,不是马上改代码,建议先做这些检查:

git status
git branch
git remote -v
git log --oneline -5

然后阅读:

  • README.md
  • CONTRIBUTING.md
  • 构建脚本
  • .gitignore
  • 项目文档

如果是团队项目,通常还要:

  • 安装依赖
  • 配置环境变量
  • 运行测试
  • 创建自己的功能分支

示例:

git switch -c feature/login

9. 查看状态与差异#

在 Git 日常开发中,statusdiff 是最常用、也最应该熟练掌握的命令。

它们分别回答两个问题:

git status: 当前仓库处于什么状态?
git diff: 具体改了什么?

一个好的提交习惯是:

git status
git diff
git add -p
git diff --cached
git commit

也就是说,提交前一定要先看状态和差异。


1. 查看状态

git status

git status 会告诉你:

  • 当前在哪个分支
  • 工作区是否有修改
  • 暂存区是否有内容
  • 是否有未跟踪文件
  • 当前分支是否领先或落后远程分支
  • 合并或 rebase 是否正在进行

典型输出:

On branch feature/login
Changes not staged for commit:
modified: src/LoginService.kt
Untracked files:
debug.log

含义:

  • 当前在 feature/login 分支
  • LoginService.kt 被修改但未暂存
  • debug.log 是未跟踪文件

2. 简洁状态

如果想看简洁输出:

git status -s

或:

git status --short

示例:

M src/LoginService.kt
A README.md
?? debug.log

常见标记:

标记含义
??未跟踪文件
M文件被修改
A新增文件
D删除文件
R重命名文件

短状态有两列:

XY file
  • X 表示暂存区状态
  • Y 表示工作区状态

例如:

M file.txt

表示工作区修改了,但还没暂存。

M file.txt

表示修改已经暂存。


3. 查看工作区差异

git diff

git diff 默认比较:

工作区 vs 暂存区

也就是查看还没有 git add 的修改。

适合在执行 git add 前检查自己改了什么。

示例输出结构:

diff --git a/src/LoginService.kt b/src/LoginService.kt
index 1111111..2222222 100644
--- a/src/LoginService.kt
+++ b/src/LoginService.kt
@@ -10,7 +10,7 @@
- return false
+ return token.isNotBlank()

含义:

  • - 开头表示旧内容
  • + 开头表示新内容
  • @@ 表示变更所在的上下文位置

4. 查看暂存区差异

git diff --cached

也可以写成:

git diff --staged

它比较的是:

暂存区 vs 最新提交 HEAD

也就是下一次 git commit 会提交的内容。

提交前非常建议执行:

git diff --cached

这样可以确认没有误提交。


5. 查看某个文件差异

git diff -- path/to/file

查看某个文件的暂存区差异:

git diff --cached -- path/to/file

查看某个目录的差异:

git diff -- src/main/

这个命令适合项目改动很多,但你只想看某个模块时使用。


6. 查看两个提交之间的差异

git diff <commit1> <commit2>

示例:

git diff HEAD~1 HEAD

含义:查看最近一次提交相对于上一次提交改了什么。

查看两个分支的差异:

git diff main feature/login

含义:比较 mainfeature/login 两个分支的文件内容差异。


7. 查看当前分支和远程分支差异

查看本地当前分支与远程分支差异:

git diff origin/main

如果你在功能分支上,想看自己相对 main 改了什么:

git fetch origin
git diff origin/main...HEAD

三个点 ... 常用于查看当前分支相对共同祖先的变更,适合 review 前检查。


8. 只看文件名

如果不想看具体内容,只想知道哪些文件变了:

git diff --name-only

查看暂存区变更文件:

git diff --cached --name-only

查看文件状态和文件名:

git diff --name-status

示例:

M src/LoginService.kt
A src/LoginValidator.kt
D old-login.md

9. 统计变更规模

查看每个文件增删行统计:

git diff --stat

查看暂存区统计:

git diff --cached --stat

示例:

src/LoginService.kt | 12 +++++++-----
README.md | 3 ++-
2 files changed, 9 insertions(+), 6 deletions(-)

适合提交前快速判断改动规模是否合理。


10. 忽略空白变化

有时 diff 里充满空格、缩进、换行变化,可以忽略空白差异。

忽略空白数量变化:

git diff -w

忽略行尾空白:

git diff --ignore-space-at-eol

注意:

  • 忽略空白只适合辅助阅读。
  • 不代表空白修改不存在。
  • 提交前仍要确认格式化是否应该单独提交。

11. 查看单词级差异

普通 diff 以行为单位。
如果一行很长,可以用单词级 diff:

git diff --word-diff

适合:

  • Markdown 文档
  • 长字符串
  • 一行较长的配置

12. 查看某次提交的差异

git show <commit>

例如:

git show HEAD

查看最近一次提交。

只看某次提交改了哪些文件:

git show --name-only <commit>

查看统计:

git show --stat <commit>

13. diff 输出怎么看

典型 diff:

diff --git a/file.txt b/file.txt
index 1111111..2222222 100644
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,4 @@
line 1
-old line
+new line
+another line
line 3

解释:

内容含义
diff --git正在比较的文件
--- a/file.txt旧文件
+++ b/file.txt新文件
@@ -1,3 +1,4 @@变更位置
-old line删除的旧内容
+new line新增的新内容
无符号行上下文

看 diff 时优先关注:

  1. 是否有无关文件。
  2. 是否有调试代码。
  3. 是否有敏感信息。
  4. 是否有大面积格式化。
  5. 是否和 commit message 对得上。

14. 查看冲突状态

发生冲突时:

git status

会提示哪些文件未合并。

查看冲突文件:

git diff

冲突标记一般长这样:

<<<<<<< HEAD
当前分支内容
=======
被合并分支内容
>>>>>>> feature/login

解决冲突后:

git add conflict-file
git commit

如果是 rebase:

git add conflict-file
git rebase --continue

15. 提交前推荐检查流程

日常提交前建议:

git status
git diff
git add -p
git diff --cached
git status
git commit

如果是较大的功能分支,提交或发 PR 前还可以看:

git diff --stat origin/main...HEAD
git diff --name-only origin/main...HEAD

确保:

  • 改动范围合理
  • 没有无关文件
  • 没有调试输出
  • 没有密钥和本地配置
  • 暂存区内容能被 commit message 准确描述

10. 添加与提交#

添加与提交是 Git 日常使用中最核心的操作。

基本流程:

修改文件 -> git add -> git commit
工作区 -> 暂存区 -> 本地仓库

这里要注意:

  • git add 不是提交,只是加入暂存区。
  • git commit 才会生成提交历史。
  • git push 才会把本地提交同步到远程。

1. 添加文件

git add file.txt
git add .
git add -p

其中 git add -p 可以交互式选择部分修改,非常适合拆分提交。


2. git add 的作用

git add 的作用是把工作区修改加入暂存区。

也就是:

工作区 -> 暂存区

常见命令:

git add file.txt
git add src/
git add .
git add -A
git add -p

3. 添加单个文件

git add README.md

适合只想提交某个文件时使用。

查看是否已经暂存:

git status
git diff --cached

4. 添加整个目录

git add src/

适合一个模块或目录下的改动属于同一个提交目的时使用。

例如:

git add src/auth/
git commit -m "fix(auth): validate expired token"

5. git add .git add -A

git add . 会添加当前目录及子目录下的修改。

git add .

git add -A 会添加整个仓库中的所有修改,包括新增、修改、删除。

git add -A

在仓库根目录下,两者很多时候效果接近。
但在子目录中执行时,git add . 只作用于当前目录范围,而 git add -A 作用于整个仓库。

建议:

  • 小改动可以用 git add file
  • 多文件但同一目的可以用 git add <dir>
  • 不确定时用 git add -p
  • 使用 git add .git add -A 后一定检查 git diff --cached

6. 交互式添加 git add -p

git add -p

-p 是 patch 的意思。Git 会把修改拆成一个个 hunk,让你选择是否加入暂存区。

常见选项:

选项含义
y暂存当前块
n不暂存当前块
s尝试拆分当前块
e手动编辑当前块
q退出
?查看帮助

适合场景:

  • 一个文件里混有多个修改目的
  • 想拆分多个 commit
  • 提交前精细挑选内容

7. 取消暂存

如果误 add,可以取消暂存:

git restore --staged file.txt

取消暂存不会删除文件内容,只是把它从暂存区移回工作区。

旧写法:

git reset HEAD file.txt

现代 Git 更推荐:

git restore --staged file.txt

8. 提交

git commit -m "fix(auth): handle empty password"

git commit 会把暂存区内容保存为一次提交。

一次提交通常包含:

  • 提交 ID
  • 作者
  • 时间
  • 提交信息
  • 文件快照
  • 父提交引用

查看最近提交:

git log --oneline -5

9. 使用 -m 写提交信息

最常见方式:

git commit -m "fix(auth): handle empty password"

适合简单提交。

如果提交比较复杂,不建议只写一行,可以直接执行:

git commit

Git 会打开编辑器,让你写多行提交信息。


10. 多行提交信息

多行 commit message 适合解释背景、方案和影响。

示例:

fix(auth): handle empty password
Reject empty password before sending login request.
This avoids unnecessary API calls and gives users immediate feedback.
Closes #123

结构:

标题
正文
尾注

标题说明做了什么,正文说明为什么和怎么做,尾注关联 issue 或 breaking change。


11. 跳过暂存直接提交已跟踪文件

git commit -am "fix: update tracked files"

这个命令相当于:

git add <所有已跟踪文件的修改和删除>
git commit -m "..."

注意:

  • 只对已经被 Git 跟踪的文件有效。
  • 不会添加新文件。
  • 容易误提交无关修改。

建议谨慎使用,尤其是在改动较多时。


12. 修改最近一次提交

git commit --amend

适合:

  • 修正最近一次提交信息
  • 补充忘记加入的文件

不建议对已经推送并被别人基于其开发的提交随意 amend。 如果只想直接改标题:

git commit --amend -m "fix(auth): handle empty password"

如果刚提交完发现漏了一个文件:

git add missing-file.txt
git commit --amend

这样会把文件补进最近一次提交。

amend 会重写最近一次提交,提交 ID 会改变。

所以:

  • 本地未推送提交可以放心 amend
  • 已推送但没人使用的分支可以谨慎 amend
  • 公共分支或别人已经基于其开发的提交不要随意 amend

13. 空提交

有时需要创建一个没有文件变化的提交,例如触发 CI。

git commit --allow-empty -m "chore: trigger ci"

适合:

  • 触发流水线
  • 标记某个流程节点
  • 测试 hook 或 CI 配置

不要滥用空提交,否则历史会变得嘈杂。


14. 好的提交粒度

一次提交应该只做一件事。

好的提交:

fix(auth): reject expired token
docs(readme): update setup guide
test(auth): add invalid login test

不好的提交:

update files
fix stuff
login changes

判断粒度是否合适:

  • 能否一句话说明
  • 是否可以独立回滚
  • 是否方便 review
  • 是否只对应一个目的
  • 是否和 commit message 对得上

15. 提交前检查流程

推荐流程:

git status
git diff
git add -p
git diff --cached
git commit

如果是简单修改:

git status
git add README.md
git diff --cached
git commit -m "docs: update README"

提交前检查:

  1. 是否有无关文件。
  2. 是否有调试日志。
  3. 是否有敏感信息。
  4. 是否有格式化噪声。
  5. 是否漏加测试。
  6. 提交信息是否清楚。

16. Commit Message 简要规范

更完整的 commit message 规范在后文会专门展开,这里先给出最常用格式:

type(scope): description

示例:

feat(auth): add email login
fix(api): handle timeout response
docs(git): update commit examples
refactor(ui): extract button component
test(auth): add login failure cases

常用 type:

type含义
feat新功能
fix修复 bug
docs文档
style格式,不影响逻辑
refactor重构
test测试
chore杂项维护

11. 查看历史#

Git 的提交历史是项目演进过程的记录。

查看历史可以帮助你回答这些问题:

  • 最近改了什么
  • 某个功能是谁加的
  • 某个 bug 可能是哪次提交引入的
  • 某个文件经历了哪些变化
  • 当前分支和主分支差了哪些提交
  • 某个版本发布时包含哪些修改

常用核心命令:

git log
git log --oneline
git show <commit>
git blame <file>

1. 普通日志

git log

git log 会按时间倒序显示提交历史,最新提交在最上面。

典型输出:

commit a1b2c3d4e5f6
Author: Your Name <you@example.com>
Date: Thu Jul 16 10:00:00 2026 +0800
fix(auth): handle empty password

包含信息:

  • commit hash
  • 作者
  • 时间
  • 提交信息

退出日志页面:

q

因为 git log 默认会进入分页器。


2. 单行日志

git log --oneline

输出示例:

a1b2c3d fix(auth): handle empty password
b2c3d4e docs(readme): update setup guide
c3d4e5f chore: initial commit

适合快速查看提交列表。

常用搭配:

git log --oneline -10

查看最近 10 条提交。


3. 图形化查看

git log --oneline --graph --decorate --all

这个命令适合查看分支结构。

参数含义:

参数含义
--oneline每个提交显示一行
--graph显示分支图
--decorate显示分支名、tag 等引用
--all显示所有分支

示例:

* a1b2c3d (HEAD -> feature/login) fix(auth): handle empty password
| * b2c3d4e (origin/main, main) docs: update README
|/
* c3d4e5f chore: initial commit

适合排查:

  • 当前在哪个分支
  • 分支从哪里分出来
  • 哪些提交还没合并
  • 是否产生了 merge commit

4. 查看某次提交详情

git show <commit>

示例:

git show a1b2c3d

查看最近一次提交:

git show HEAD

查看上一次提交:

git show HEAD~1

git show 会显示:

  • 提交信息
  • 作者
  • 时间
  • 具体 diff

只看文件列表:

git show --name-only <commit>

只看统计:

git show --stat <commit>

5. 搜索提交信息

git log --grep="login"

这个命令会在 commit message 中搜索关键词。

示例:

git log --grep="auth"
git log --grep="fix"
git log --grep="BREAKING CHANGE"

常用于:

  • 找某个需求相关提交
  • 找修复类提交
  • 找破坏性变更
  • 找某个模块相关记录

如果团队 commit message 规范清晰,这个命令非常有价值。


6. 搜索代码变化

git log -S "functionName"

-S 会搜索某段代码内容的增加或删除。

例如:

git log -S "validateToken"

适合回答:

  • 这个函数是什么时候加的
  • 这个常量什么时候被删除
  • 某行逻辑是哪次提交引入的

查看对应 diff:

git log -S "validateToken" -p

-p 会显示 patch。


7. 按作者筛选

git log --author="Alice"

示例:

git log --author="you@example.com"

适合:

  • 查看某个人的提交
  • 统计个人改动
  • 排查某个功能负责人相关历史

注意:作者信息来自 commit 的 user.nameuser.email


8. 按时间筛选

查看某日期之后的提交:

git log --since="2026-07-01"

查看某日期之前的提交:

git log --until="2026-07-16"

组合使用:

git log --since="2026-07-01" --until="2026-07-16"

也可以使用自然语言:

git log --since="2 weeks ago"
git log --since="yesterday"

适合生成阶段性变更记录。


9. 查看某个文件历史

git log -- path/to/file

示例:

git log -- README.md

查看某个文件每次变化的 diff:

git log -p -- README.md

查看简洁历史:

git log --oneline -- README.md

适合:

  • 查看文件何时被修改
  • 追踪配置文件历史
  • 排查某个文件的 bug 来源

10. 查看文件每一行是谁改的

git blame path/to/file

示例:

git blame src/LoginService.kt

输出会显示每一行对应的提交和作者。

适合:

  • 查某行代码来源
  • 找相关负责人
  • 理解历史上下文

注意:

git blame 不是“甩锅工具”,而是上下文追踪工具。
看到某行作者后,应该继续用 git show <commit> 查看当时为什么这样改。


11. 查看分支之间的提交差异

查看当前分支相比 main 多了哪些提交:

git log main..HEAD

简洁显示:

git log --oneline main..HEAD

查看 main 有而当前分支没有的提交:

git log HEAD..main

查看当前分支相对远程 main 的提交:

git fetch origin
git log --oneline origin/main..HEAD

适合在提交 PR 前检查自己到底提交了什么。


12. 查看两个分支的共同历史

查看分支分叉点:

git merge-base main feature/login

结合 git log

git log --oneline $(git merge-base main feature/login)..feature/login

这个命令更偏进阶,但理解它有助于掌握分支比较。


13. 自定义日志格式

可以用 --pretty=format: 自定义输出。

git log --pretty=format:"%h %an %ad %s" --date=short

常见占位符:

占位符含义
%H完整 commit hash
%h短 commit hash
%an作者名
%ae作者邮箱
%ad作者日期
%s提交标题

示例输出:

a1b2c3d Alice 2026-07-16 fix(auth): handle empty password

14. 常用日志别名

可以配置一个好用的日志别名:

git config --global alias.lg "log --oneline --graph --decorate --all"

之后使用:

git lg

也可以配置更详细的:

git config --global alias.hist "log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short"

15. 查看 tag 历史

查看所有 tag:

git tag

查看某个 tag 对应提交:

git show v1.0.0

查看两个版本之间的提交:

git log --oneline v1.0.0..v1.1.0

适合生成版本发布说明。


16. 结合 diff 查看历史

查看某个提交和上一个提交的差异:

git show <commit>

查看两个提交之间文件内容差异:

git diff <commit1> <commit2>

查看两个提交之间改了哪些文件:

git diff --name-only <commit1> <commit2>

查看统计:

git diff --stat <commit1> <commit2>

历史查询经常和 diff 配合使用。


17. 常见排查场景

查某个 bug 是什么时候引入的

可以先搜索相关代码:

git log -S "problematicFunction" -p

如果不确定具体代码,可以用后面会介绍的:

git bisect

查某个文件最近谁改过

git log --oneline -- path/to/file
git blame path/to/file

查当前分支准备合并哪些提交

git fetch origin
git log --oneline origin/main..HEAD

查某个版本之间的变更

git log --oneline v1.0.0..v1.1.0
git diff --stat v1.0.0..v1.1.0

18. reflog 查看本地操作历史

git log 查看提交历史。
git reflog 查看本地 HEAD 和分支指针移动历史。

git reflog

它适合找回:

  • 误删的分支
  • 误 reset 的提交
  • rebase 前的位置
  • checkout 过的历史位置

示例:

git reflog
git reset --hard HEAD@{1}

注意:

  • reflog 是本地记录。
  • 不同机器上的 reflog 不一样。
  • 它不是远程仓库历史。

19. 用历史生成发布说明

查看上一个版本到当前的提交:

git log v1.0.0..HEAD --oneline

如果团队使用 Conventional Commits,可以筛选功能和修复:

git log v1.0.0..HEAD --grep="^feat" --oneline
git log v1.0.0..HEAD --grep="^fix" --oneline

查看变更文件统计:

git diff --stat v1.0.0..HEAD

发布前常用组合:

git fetch --tags
git log v1.0.0..HEAD --oneline
git diff --stat v1.0.0..HEAD

12. Git 对象和引用#

Git 表面上是在管理文件、分支和提交,底层实际上是在管理两类东西:

对象 object
引用 reference

对象保存真实内容,引用给对象起名字。

理解对象和引用后,很多 Git 操作会变得清楚:

  • 分支为什么很轻量
  • HEAD 到底是什么
  • commit hash 为什么会变化
  • tag 和 branch 有什么区别
  • reset 为什么能回退
  • rebase 为什么会生成新提交
  • detached HEAD 是怎么回事

12.1 Git 对象#

1. Git 对象是什么

Git 对象是 Git 保存数据的基本单位。

Git 底层主要有四类对象:

对象含义
blob文件内容
tree目录结构
commit一次提交
tag标签

这些对象通常保存在:

.git/objects/

每个对象都有一个哈希值。
Git 通过哈希值定位对象。


2. blob:保存文件内容

blob 保存文件内容。

注意:blob 不保存文件名,只保存文件内容。

例如有两个文件:

a.txt
b.txt

如果它们内容完全一样,Git 可以让它们复用同一个 blob。

可以理解为:

blob = 文件内容

这也是 Git 节省存储空间的原因之一。


3. tree:保存目录结构

tree 保存目录结构。

它记录:

  • 文件名
  • 文件权限
  • 文件名对应哪个 blob
  • 子目录对应哪个 tree

可以理解为:

tree = 文件名 + 目录层级 + 对象引用

示意:

tree
README.md -> blob
src/ -> tree
Main.kt -> blob

blob 只知道内容,不知道自己叫什么名字。
文件名和目录关系由 tree 管理。


4. commit:保存一次提交

commit 对象表示一次提交。

它通常包含:

  • 指向 tree 的引用
  • 父提交 parent
  • 作者 author
  • 提交者 committer
  • 时间
  • commit message

可以理解为:

commit = 某个时间点的项目快照 + 元信息 + 父提交

结构示意:

commit
-> tree
-> blob
-> blob
-> parent commit
-> author
-> message

查看提交:

git show <commit>

查看更底层内容:

git cat-file -p <commit>

5. tag:标记重要版本

tag 用来标记某个重要提交。

常见用途:

  • 发布版本
  • 里程碑
  • 稳定版本快照

例如:

git tag v1.0.0

tag 通常固定不动,而 branch 会随着提交移动。

对比项branchtag
是否移动会移动通常固定
主要用途开发线发布版本
常见命名mainfeature/loginv1.0.0v2.1.3
是否频繁变化

6. 对象之间的关系

一次提交不是简单保存一个文件夹副本,而是通过对象引用组成一棵结构。

commit
|
v
tree
|
+-- README.md -> blob
+-- src/ -> tree
|
+-- Main.kt -> blob

当你提交时,Git 会记录:

  • 哪些文件内容变了
  • 目录结构是什么
  • 当前提交的父提交是谁
  • 提交者和提交信息是什么

未变化的文件内容可以复用已有 blob。


7. 哈希值和不可变性

Git 对象由内容计算哈希。

对象内容一变,哈希就会变。

因此:

  • 修改文件内容会生成新的 blob。
  • 修改目录结构会生成新的 tree。
  • 修改提交信息会生成新的 commit。
  • amend 和 rebase 会改变 commit hash。

例如:

git commit --amend

哪怕只是修改提交信息,也会生成新的提交 ID。

原因是 commit 对象内容变了。


12.2 引用#

1. 引用是什么

哈希值不方便人记,所以 Git 用引用给对象起名字。

常见引用:

引用含义
branch指向某个提交的可移动指针
HEAD当前所在位置
origin/main远程 main 分支的本地跟踪引用
tag指向重要版本的固定标记

引用通常指向 commit。


2. branch:可移动指针

分支本质上是指向某个提交的可移动指针。

例如:

main
|
v
A---B---C

当你在 main 上继续提交 D:

main
|
v
A---B---C---D

main 会自动移动到 D。

这就是 Git 分支轻量的根本原因:

创建分支不是复制一份代码,而是创建一个指针。

3. HEAD:当前所在位置

HEAD 表示当前工作区所在的位置。

通常 HEAD 指向当前分支:

HEAD -> main -> D

当你切换分支:

git switch feature/login

HEAD 会指向 feature/login

如果你直接切到某个提交:

git switch --detach <commit>

就会进入 detached HEAD:

HEAD -> <commit>

此时 HEAD 不再指向分支,而是直接指向某个提交。


4. origin/main:远程跟踪分支

origin/main 是本地记录的远程 main 分支状态。

它不是远程服务器上的分支本身,而是你本地对远程分支的快照。

更新它需要:

git fetch origin

关系:

main = 本地 main 分支
origin/main = 本地看到的远程 main 分支状态

查看本地落后远程多少:

git log --oneline main..origin/main

查看本地领先远程多少:

git log --oneline origin/main..main

5. 引用保存在哪里

分支引用通常在:

.git/refs/heads/

远程跟踪分支通常在:

.git/refs/remotes/

标签通常在:

.git/refs/tags/

但实际仓库中,Git 可能会把引用压缩到:

.git/packed-refs

普通开发时不要手动修改这些文件。


6. 常用底层查看命令

查看对象类型:

git cat-file -t <hash>

查看对象内容:

git cat-file -p <hash>

查看当前 HEAD 的完整哈希:

git rev-parse HEAD

查看当前分支:

git branch --show-current

查看所有引用:

git show-ref

这些命令不是每天都用,但有助于理解 Git 底层。


7. reset 和引用的关系

reset 的核心动作是移动当前分支指针。

假设当前历史:

main -> C
A---B---C

执行:

git reset --soft HEAD~1

结果:

main -> B
A---B---C

C 这个提交对象可能还在,但 main 不再指向它。

这也是为什么 reflog 能找回一些误操作:本地曾经记录过指针移动历史。


8. rebase 为什么会改变提交 ID

rebase 会把一组提交重新应用到新的基底上。

虽然代码内容可能类似,但新的提交有新的 parent,所以 commit 对象内容变了。

因此提交 ID 也会变。

示意:

rebase 前:
main: A---B---C
feature: \---D---E
rebase 后:
main: A---B---C
\---D'---E'

D'E' 是新提交,不是原来的 DE


9. 为什么不要随意改公共历史

如果某些提交已经推送并被别人拉取,别人本地也有这些提交。

你如果用:

git commit --amend
git rebase
git reset
git push --force

改写公共历史,可能导致:

  • 提交 ID 不一致
  • 队友分支难以合并
  • 远程和本地历史分叉
  • PR/MR 变得混乱

基本原则:

本地未共享历史可以整理。
公共共享历史谨慎改写。

13. 分支管理#

分支是 Git 最核心、最常用的能力之一。

它允许你在不影响主线代码的情况下开发新功能、修复 bug、实验方案或准备发布。

可以把分支理解成:

指向某个提交的可移动指针

分支不是复制一份完整代码,所以创建和切换都非常快。


1. 查看分支

git branch
git branch -a

常用命令:

git branch
git branch -a
git branch -r

含义:

命令说明
git branch查看本地分支
git branch -a查看本地和远程跟踪分支
git branch -r查看远程跟踪分支

当前分支前面会有 *

* main
feature/login

2. 创建分支

git branch feature/login

这条命令只创建分支,不会自动切换过去。

创建分支的本质是:

创建一个新的指针,指向当前提交

创建后可以查看:

git branch

3. 切换分支

git switch feature/login

git switch 是较新的分支切换命令,语义比旧的 git checkout 更清晰。

旧写法:

git checkout feature/login

推荐新项目优先使用:

git switch

4. 创建并切换

git switch -c feature/login

这等价于:

git branch feature/login
git switch feature/login

常见开发流程:

git switch main
git pull
git switch -c feature/user-profile

意思是:

  1. 切回主分支。
  2. 拉取最新代码。
  3. 从最新主分支创建功能分支。

5. 删除分支

git branch -d feature/login
git branch -D feature/login

区别:

命令含义
git branch -d安全删除,分支未合并时会阻止
git branch -D强制删除,不管是否合并

推荐优先使用:

git branch -d feature/login

只有确认不要这个分支时才用:

git branch -D feature/login

6. 重命名分支

重命名当前分支:

git branch -m new-name

重命名指定分支:

git branch -m old-name new-name

如果分支已经推送到远程,还需要处理远程分支:

git push origin --delete old-name
git push -u origin new-name

7. 从指定提交创建分支

从当前提交创建:

git switch -c feature/new

从指定提交创建:

git switch -c hotfix/old-version a1b2c3d

从 tag 创建:

git switch -c hotfix/v1.0.0 v1.0.0

适合:

  • 基于旧版本修复 bug
  • 从某个历史点做实验
  • 从发布 tag 拉 hotfix 分支

8. 查看分支合并情况

查看已经合并到当前分支的分支:

git branch --merged

查看尚未合并的分支:

git branch --no-merged

删除分支前可以先检查:

git branch --merged

这样可以避免误删未合并分支。


9. 本地分支和远程分支

本地分支:

main
feature/login

远程跟踪分支:

origin/main
origin/feature/login

注意:

origin/feature/login 是本地记录的远程状态,不是远程服务器上的真实分支本体。

更新远程分支信息:

git fetch origin

10. 推送本地分支到远程

第一次推送新分支:

git push -u origin feature/login

-u 的作用是设置 upstream。

设置后,以后在该分支上可以直接:

git push
git pull

11. 删除远程分支

删除远程分支:

git push origin --delete feature/login

删除后,本地可能还保留远程跟踪引用。可以清理:

git fetch --prune

或:

git remote prune origin

12. 分支命名规范

好的分支名应该表达用途。

常见格式:

feature/user-profile
fix/login-token
hotfix/payment-timeout
release/v1.2.0
docs/git-note
chore/update-deps

常见前缀:

前缀含义
feature/新功能
fix/普通 bug 修复
hotfix/紧急线上修复
release/发布准备
docs/文档
chore/杂项维护
experiment/实验性工作

建议:

  • 使用小写
  • 用短横线分隔单词
  • 名称不要太长
  • 最好带任务编号,如 feature/123-user-profile

13. 常见分支类型

分支作用
main稳定主线,通常对应可发布代码
develop集成开发分支,Git Flow 常见
feature/*功能开发
fix/*bug 修复
hotfix/*线上紧急修复
release/*发布准备

不是每个团队都需要所有分支。分支模型越复杂,管理成本越高。


14. 常见分支工作流程

一个常见功能开发流程:

git switch main
git pull
git switch -c feature/login
# 修改代码
git add .
git commit -m "feat(auth): add login validation"
git push -u origin feature/login

然后在 GitHub / GitLab / Gitee 上创建 PR 或 MR。


15. 分支切换前要注意什么

切换分支前,最好先看状态:

git status

如果工作区有未提交修改,切换分支可能失败:

Your local changes would be overwritten by checkout

处理方式:

  1. 提交当前修改。
  2. 暂存修改。
  3. 丢弃修改。

暂存修改:

git stash
git switch other-branch
git stash pop

14. 远程同步#

远程同步是多人协作的核心。

本地 Git 仓库可以独立提交和查看历史,但团队协作需要和远程仓库交换提交。

常见远程平台:

  • GitHub
  • GitLab
  • Gitee
  • Bitbucket
  • 公司自建 Git 服务

远程同步主要围绕这些命令:

git remote
git fetch
git pull
git push

它们分别负责:

remote: 管理远程仓库地址
fetch : 拉取远程信息,但不自动合并
pull : 拉取远程信息,并整合到当前分支
push : 推送本地提交到远程仓库

14.1 介绍#

1. remote 是什么

remote 是远程仓库的别名。

最常见的远程名是:

origin

当你执行:

git clone git@github.com:user/repo.git

Git 通常会自动创建一个远程引用:

origin -> git@github.com:user/repo.git

查看远程仓库:

git remote -v

输出示例:

origin git@github.com:user/repo.git (fetch)
origin git@github.com:user/repo.git (push)

其中:

  • fetch 是拉取地址
  • push 是推送地址

多数项目里这两个地址相同。


2. 添加、修改和删除远程仓库

添加远程仓库:

git remote add origin git@github.com:user/repo.git

修改远程地址:

git remote set-url origin git@github.com:user/new-repo.git

删除远程仓库引用:

git remote remove origin

注意:

git remote remove origin 只删除本地对远程仓库的引用,不会删除 GitHub/GitLab/Gitee 上的仓库。


3. fetch

git fetch origin

只拉取远程信息,不自动合并。

fetch 会更新本地的远程跟踪分支,例如:

origin/main
origin/feature/login

但它不会修改你当前工作分支的代码。

可以理解为:

git fetch = 先看看远程有什么新东西,但不动我当前代码

常见用法:

git fetch origin
git fetch --all
git fetch --prune

--prune 会清理本地已经不存在于远程的远程跟踪分支。


4. fetch 后查看差异

拉取远程信息后,可以比较本地和远程差异。

查看本地 main 落后远程多少:

git log --oneline main..origin/main

查看本地 main 领先远程多少:

git log --oneline origin/main..main

查看文件内容差异:

git diff main origin/main

这就是为什么很多人更喜欢先 fetch,确认后再 merge 或 rebase。


5. pull

git pull

等价于先 fetch,再 merge 或 rebase。

默认情况下,可以理解为:

git pull = git fetch + git merge

如果配置了 rebase,则可能是:

git pull = git fetch + git rebase

常见用法:

git pull
git pull origin main
git pull --rebase

6. pull 前为什么要先 status

执行 git pull 前建议先看:

git status

原因:

  • 本地有未提交修改时,pull 可能失败
  • 本地修改和远程修改可能冲突
  • 你可能不在预期分支

推荐流程:

git status
git fetch origin
git log --oneline HEAD..origin/main
git pull

如果你在功能分支上:

git switch feature/login
git fetch origin
git rebase origin/main

或者:

git merge origin/main

选择 merge 还是 rebase,要看团队规范。


7. pull 使用 merge 还是 rebase

两种常见策略:

策略命令特点
mergegit pullgit pull --no-rebase保留真实合并历史
rebasegit pull --rebase历史更线性

配置默认使用 merge:

git config --global pull.rebase false

配置默认使用 rebase:

git config --global pull.rebase true

只允许 fast-forward:

git config --global pull.ff only

建议团队统一配置,避免每个人生成不同形态的历史。


8. push

git push origin feature/login

push 用来把本地提交推送到远程仓库。

常见用法:

git push
git push origin main
git push origin feature/login

注意:

  • commit 只是提交到本地仓库。
  • push 才会同步到远程仓库。

关系:

工作区 -> git add -> 暂存区
暂存区 -> git commit -> 本地仓库
本地仓库 -> git push -> 远程仓库

9. 第一次推送新分支

第一次推送本地新分支时,常用:

git push -u origin feature/login

-u 表示设置 upstream。

设置 upstream 后,以后可以直接:

git push
git pull

不用每次都写:

git push origin feature/login

10. 设置 upstream

git push -u origin feature/login

之后可以直接:

git push
git pull

upstream 表示当前本地分支默认跟踪哪个远程分支。

查看分支 upstream:

git branch -vv

输出示例:

* feature/login a1b2c3d [origin/feature/login] fix(auth): handle login

这里 [origin/feature/login] 就是当前分支的 upstream。

手动设置 upstream:

git branch --set-upstream-to=origin/feature/login feature/login

11. 删除远程分支

删除远程分支:

git push origin --delete feature/login

删除后,本地可能还保留远程跟踪引用。

清理:

git fetch --prune

或:

git remote prune origin

建议:

  • PR/MR 合并后及时删除无用远程分支。
  • 删除前确认分支已经合并或不再需要。

12. 推送 tag

推送单个 tag:

git push origin v1.0.0

推送所有 tag:

git push origin --tags

删除远程 tag:

git push origin --delete v1.0.0

tag 常用于发布版本。发布流程中要谨慎删除或重建 tag。


13. 强制推送

普通强推:

git push --force

更安全的强推:

git push --force-with-lease

区别:

命令风险
--force直接覆盖远程分支,可能覆盖别人提交
--force-with-lease如果远程已有别人新提交,会拒绝覆盖

使用场景:

  • 整理个人功能分支历史后推送
  • rebase 后更新自己的 PR 分支

不要用于:

  • main
  • master
  • develop
  • release 分支
  • 多人共用分支

除非团队明确允许并已沟通。


14.2 常见远程同步流程#

1. 开始一天工作

git switch main
git pull
git switch feature/login
git rebase main

或:

git switch feature/login
git fetch origin
git rebase origin/main

2. 完成需求并推送

git status
git add .
git commit -m "feat(auth): add login validation"
git push -u origin feature/login

3. 主分支更新后同步到功能分支

方式一:merge

git fetch origin
git merge origin/main

方式二:rebase

git fetch origin
git rebase origin/main

团队要统一选择。


15. merge 与 rebase#

mergerebase 都用于整合分支修改。

它们解决的是同一个问题:

如何把一个分支上的修改整合到另一个分支?

但它们处理历史的方式不同:

  • merge 保留分支真实合并历史。
  • rebase 改写提交基底,让历史更线性。

理解二者差异,是 Git 协作的关键。


15.1 merge#

1. merge

把一个分支的修改合并到当前分支。

git switch main
git merge feature/login

意思是:

把 feature/login 分支合并到 main 分支

优点:

  • 保留真实历史
  • 操作安全
  • 适合公共分支

缺点:

  • 历史可能出现较多 merge commit

2. merge 的历史形态

假设当前历史如下:

main: A---B---C
\
feature: D---E

执行:

git switch main
git merge feature

如果不能 fast-forward,Git 会创建一个 merge commit:

main: A---B---C-------M
\ /
feature: D---E---

M 就是 merge commit,它有两个父提交:

  • 一个来自 main
  • 一个来自 feature

这种历史保留了真实分支结构。


3. fast-forward merge

如果 main 没有新的提交:

main: A---B
\
feature: C---D

执行:

git switch main
git merge feature

Git 可以直接把 main 指针移动到 D:

main: A---B---C---D

这叫 fast-forward merge。

特点:

  • 不产生 merge commit
  • 历史是线性的
  • 只是移动分支指针

4. 禁止 fast-forward

如果你希望保留“这个功能是通过分支合并进来的”这个信息,可以禁止 fast-forward:

git merge --no-ff feature/login

这样即使可以快进,Git 也会创建 merge commit。

适合:

  • 希望保留功能分支边界
  • Git Flow 类工作流
  • 发布分支合并

5. squash merge

squash merge 会把一个分支上的多个提交压成一次提交。

git switch main
git merge --squash feature/login
git commit -m "feat(auth): add login flow"

特点:

  • main 上只出现一个提交
  • 不保留 feature 分支的细节历史
  • 适合清理比较乱的功能分支提交

适合:

  • 功能分支里有很多临时提交
  • 团队希望主分支历史简洁
  • PR 合并时使用 squash 策略

不适合:

  • 需要保留完整提交过程的场景
  • 每个小提交都有独立价值的场景

15.2 rebase#

1. rebase

把当前分支的提交“移到”另一个基底之后。

git switch feature/login
git rebase main

意思是:

把 feature/login 上的提交重新放到 main 最新提交之后

优点:

  • 提交历史更线性
  • 方便阅读

缺点:

  • 会重写提交历史
  • 不适合随意对公共分支使用

2. rebase 的历史形态

rebase 前:

main: A---B---C
\
feature: D---E

执行:

git switch feature
git rebase main

rebase 后:

main: A---B---C
\
feature: D'---E'

注意:

  • D'E' 是新提交
  • 原来的 DE 被复制到了新的基底之后
  • 提交 ID 会改变

3. rebase 的本质

rebase 可以理解为:

找到当前分支和目标分支的共同祖先
取出当前分支独有的提交
把这些提交按顺序重新应用到目标分支之后

所以 rebase 会重写提交历史。

这也是为什么公共分支上不能随便 rebase。


4. rebase 黄金规则

不要 rebase 已经共享给别人并被别人基于开发的公共提交。

换句话说:

可以 rebase 自己本地还没推送的提交。
不要 rebase 别人可能已经拉取的提交。

适合 rebase:

  • 自己的本地 feature 分支
  • 还没推送的提交
  • PR 前整理个人提交
  • 同步 main 到自己的功能分支

不适合 rebase:

  • main 分支
  • release 分支
  • 多人共同开发的分支
  • 已经被别人基于开发的提交

merge 和 rebase 对比

对比项mergerebase
是否改写历史
历史形态保留分叉和合并更线性
是否产生 merge commit可能不产生
冲突处理一次合并中处理可能每个提交都处理
适合公共分支适合不适合随意使用
适合整理个人分支可以很适合
可追踪真实协作历史较弱
历史简洁度一般

推荐使用 merge 的场景:

  • 合并功能分支到主分支
  • 合并 release 分支
  • 保留完整协作历史
  • 多人共享分支
  • 不希望改写历史

示例:

git switch main
git merge --no-ff feature/login

团队中常见策略:

  • PR/MR 合并到 main 用 merge commit
  • 保留功能分支的完整上下文

推荐使用 rebase 的场景:

  • 本地功能分支同步 main
  • PR 前整理自己的提交
  • 保持个人分支历史线性
  • 清理临时提交

示例:

git switch feature/login
git fetch origin
git rebase origin/main

这样可以让 feature 分支基于最新 main。


** 交互式 rebase**

交互式 rebase 用于整理提交历史。

git rebase -i HEAD~3

常见操作:

操作含义
pick保留提交
reword修改提交信息
edit停下来修改提交
squash合并到上一个提交,并合并提交信息
fixup合并到上一个提交,丢弃当前提交信息
drop删除提交

适合:

  • PR 前整理提交
  • 合并临时提交
  • 修改提交信息
  • 删除误提交

不要对公共历史随意使用交互式 rebase。


pull 时的 merge/rebase

git pull 本质是:

git fetch + merge/rebase

默认 merge:

git pull

使用 rebase:

git pull --rebase

配置默认 rebase:

git config --global pull.rebase true

配置只允许 fast-forward:

git config --global pull.ff only

团队最好统一 pull 策略。


常见团队策略

策略一:功能分支 rebase,合并 main 用 merge

常见流程:

git switch feature/login
git fetch origin
git rebase origin/main
git push --force-with-lease

PR 合并时使用 merge commit。

优点:

  • feature 分支干净
  • main 保留合并历史

策略二:全部 squash merge

PR 合并时 squash 成一个提交。

优点:

  • main 历史非常简洁
  • 每个 PR 对应一个提交

缺点:

  • 丢失功能分支内部细节

策略三:只允许 fast-forward

要求所有分支先 rebase 到 main,再 fast-forward 合并。

优点:

  • 历史完全线性

缺点:

  • 对团队 Git 能力要求较高
  • 真实合并上下文较少

16. 冲突处理#

冲突是 Git 协作中很常见的情况。

它通常发生在 Git 无法自动判断应该保留哪一份修改时。

典型场景:

  • 两个人修改了同一文件的同一区域
  • 一个分支修改了文件,另一个分支删除了文件
  • 两个分支都重命名或移动了同一个文件
  • rebase 时旧提交和新基底修改了同一段代码

冲突不是错误,而是 Git 要求开发者人工确认最终内容。


冲突什么时候发生

常见会触发冲突的命令:

git merge
git rebase
git pull
git cherry-pick
git revert

其中:

  • git pull 可能触发冲突,因为它内部会执行 merge 或 rebase。
  • git rebase 可能多次触发冲突,因为它会逐个重放提交。
  • git cherry-pick 也可能冲突,因为它把某个提交应用到当前分支。

冲突标记

冲突标记:

<<<<<<< HEAD
当前分支内容
=======
被合并分支内容
>>>>>>> feature/login

含义:

标记含义
<<<<<<< HEAD当前分支的内容开始
=======两边内容的分隔线
>>>>>>> feature/login被合并分支的内容结束

示例:

<<<<<<< HEAD
return "login failed"
=======
return "invalid username or password"
>>>>>>> feature/login

你需要手动改成最终想要的内容,例如:

return "invalid username or password"

并删除所有冲突标记。


查看冲突状态

发生冲突后,先执行:

git status

Git 会列出未解决冲突的文件。

也可以查看冲突内容:

git diff

查看未合并文件:

git diff --name-only --diff-filter=U

merge 冲突处理流程

执行 merge:

git switch main
git merge feature/login

如果出现冲突,流程是:

git status
# 打开冲突文件,手动编辑
git add conflict-file
git commit

如果 Git 已经生成默认 merge commit message,执行 git commit 即可。

如果想放弃这次 merge:

git merge --abort

rebase 冲突处理流程

执行 rebase:

git switch feature/login
git rebase main

如果出现冲突,流程是:

git status
# 打开冲突文件,手动编辑
git add conflict-file
git rebase --continue

如果当前这个提交不想要了:

git rebase --skip

如果想放弃整个 rebase:

git rebase --abort

注意:

rebase 是逐个提交重放,所以可能解决完一个冲突后,后面又出现新的冲突。


cherry-pick 冲突处理

执行:

git cherry-pick <commit>

如果冲突:

git status
# 手动解决冲突
git add conflict-file
git cherry-pick --continue

放弃 cherry-pick:

git cherry-pick --abort

16.7 ours 和 theirs

解决冲突时,有时想直接选择一边。

保留当前分支版本:

git checkout --ours path/to/file

保留对方分支版本:

git checkout --theirs path/to/file

然后:

git add path/to/file

注意:

在 merge 和 rebase 中,ours / theirs 的语义容易让人混淆。
尤其是 rebase 时,当前重放提交和目标基底的视角会变复杂。

如果不确定,不要盲目使用 ours/theirs,应该打开文件手动确认。


使用 mergetool

Git 可以调用图形化工具处理冲突。

启动:

git mergetool

常见工具:

  • VS Code
  • IntelliJ IDEA
  • Android Studio
  • Beyond Compare
  • Meld
  • KDiff3

配置 VS Code:

git config --global merge.tool vscode
git config --global mergetool.vscode.cmd "code --wait $MERGED"

实际开发中,IDE 的冲突解决界面通常更直观。


IDE 中处理冲突

在 IntelliJ IDEA / Android Studio 中,冲突文件通常会显示为红色或提示冲突。

常见按钮:

  • Accept Yours
  • Accept Theirs
  • Merge
  • Apply

建议:

  • 不要只看按钮文字,先理解左右两边分别代表什么。
  • 冲突解决后运行测试。
  • 解决后检查 git diff --cached

删除/修改冲突

一种常见冲突是:

deleted by us
modified by them

或:

deleted by them
modified by us

意思是:

  • 一边删除了文件
  • 另一边修改了文件

你需要决定:

  1. 保留删除
  2. 保留修改
  3. 手动创建新的替代文件

保留删除:

git rm path/to/file

保留文件:

git add path/to/file

冲突解决后要做什么

冲突解决后不要立刻结束,建议检查:

git status
git diff --cached

然后运行:

  • 单元测试
  • 编译
  • 格式检查
  • 相关功能手动验证

原因:

冲突解决后的代码可能语法正确,但业务逻辑错误。


如何减少冲突

减少冲突的实践:

  • 小步提交
  • 经常同步主分支
  • 避免长期大分支
  • 同一模块多人改动前先沟通
  • 格式化和逻辑修改分开提交
  • 不随意大规模移动文件
  • 不在一个 PR 里混入无关修改
  • 公共配置文件改动提前同步团队

特别容易冲突的文件:

  • 大型配置文件
  • 路由表
  • 依赖版本文件
  • 自动生成文件
  • 大型单文件组件
  • 多人同时修改的核心类

17. 撤销、回退与恢复#

Git 的撤销和恢复命令很多,初学者最容易混淆。

先记住一个原则:

先判断要撤销的是哪一层,再选择命令。

Git 常见层级:

工作区 -> 暂存区 -> 本地提交 -> 远程提交

不同层级对应不同命令:

场景常用命令
丢弃工作区修改git restore
取消暂存git restore --staged
回退本地提交git reset
安全撤销公共提交git revert
找回误操作git reflog

丢弃工作区修改

git restore file.txt

这个命令会把工作区文件恢复到暂存区或最近提交中的状态。

适合:

  • 改错了某个文件
  • 想放弃还没暂存的修改
  • 临时调试后不想保留

查看修改:

git diff file.txt

丢弃修改:

git restore file.txt

注意:

git restore file.txt 会丢弃未提交修改,执行前要确认这些修改不再需要。


丢弃整个工作区修改

丢弃所有已跟踪文件的工作区修改:

git restore .

这不会删除未跟踪文件。

如果还存在未跟踪文件,可以查看:

git status

删除未跟踪文件要用 git clean,后面会专门说明。


取消暂存

git restore --staged file.txt

这个命令会把文件从暂存区移回工作区。

它不会丢弃文件内容。

状态变化:

staged -> modified

适合:

  • 误执行了 git add
  • 暂存区混入了不该提交的文件
  • 想重新拆分提交

取消所有暂存:

git restore --staged .

旧写法:

git reset HEAD file.txt

现代 Git 更推荐 restore --staged,语义更清楚。


恢复误删文件

如果删除了已跟踪文件,还没提交:

git restore deleted-file.txt

如果删除已经暂存:

git restore --staged deleted-file.txt
git restore deleted-file.txt

如果删除已经提交,则需要从历史中恢复:

git checkout <commit> -- deleted-file.txt

或新写法:

git restore --source=<commit> -- deleted-file.txt

回退提交但保留修改

git reset --soft HEAD~1

--soft 只移动分支指针,不动暂存区和工作区。

状态变化:

提交撤销,修改仍留在暂存区

适合:

  • 刚提交完发现 commit message 写错
  • 想把最近一次提交和下一次修改合并
  • 想重新提交但保留暂存状态

示例:

git reset --soft HEAD~1
git commit -m "fix(auth): handle expired token"

回退提交并保留到工作区

git reset --mixed HEAD~1

--mixedgit reset 的默认模式。

状态变化:

提交撤销,修改回到工作区,暂存区清空

等价于:

git reset HEAD~1

适合:

  • 想撤销提交并重新选择暂存内容
  • 想拆分最近一次提交
  • 误把多个改动提交在一起

回退并丢弃修改

git reset --hard HEAD~1

reset --hard 会丢弃修改,使用前必须确认。

状态变化:

提交撤销,暂存区和工作区也一起回退

危险点:

  • 会丢弃未保存修改
  • 会让文件回到指定提交状态
  • 如果目标提交写错,可能造成数据丢失

使用前建议:

git status
git log --oneline -5

如果不确定,先创建备份分支:

git branch backup/before-reset

reset 三种模式对比

命令移动 HEAD改暂存区改工作区适用场景
git reset --soft撤销提交,保留暂存
git reset --mixed撤销提交,重新选择暂存
git reset --hard彻底回退到指定版本

简单记忆:

soft = 只撤提交
mixed = 撤提交和暂存
hard = 提交、暂存、工作区都回退

安全回滚公共提交

git revert <commit>

revert 会生成一个反向提交,不重写历史,适合公共分支。

例如:

git revert a1b2c3d

它不是删除旧提交,而是新增一个提交来抵消旧提交的影响。

历史示意:

A---B---C---R

其中:

  • C 是有问题的提交
  • R 是 revert C 的新提交

适合:

  • main 分支
  • release 分支
  • 已经推送并被别人拉取的提交
  • 线上问题回滚

revert 多个提交

连续 revert 多个提交:

git revert <commit1> <commit2>

如果想先生成修改但不自动提交:

git revert -n <commit>

然后手动提交:

git commit -m "revert: rollback login change"

注意:

revert 也可能产生冲突,需要手动解决。


reset 和 revert 的区别

对比项resetrevert
是否新增提交
是否改写历史
是否适合公共分支通常不适合适合
是否会改变分支指针会向前新增提交
主要用途本地整理历史安全撤销已共享提交

选择建议:

本地还没 push:可以考虑 reset。
已经 push 到公共分支:优先 revert。

找回误操作

git reflog
git reset --hard <commit>

reflog 是 Git 本地操作记录,常用于找回误删分支或误 reset 的提交。

git reflog 记录 HEAD 和分支指针的移动历史。

示例:

git reflog

可能看到:

a1b2c3d HEAD@{0}: reset: moving to HEAD~1
b2c3d4e HEAD@{1}: commit: fix(auth): handle token
c3d4e5f HEAD@{2}: commit: docs: update README

如果误 reset 到旧版本,可以找回:

git reset --hard HEAD@{1}

或:

git reset --hard b2c3d4e

注意:

  • reflog 是本地记录。
  • 不是永久保存。
  • 不能依赖它替代备份。

找回误删分支

如果误删了分支:

git branch -D feature/login

可以先看 reflog:

git reflog

找到该分支最后的提交后,重新创建分支:

git branch feature/login <commit>

或直接切换:

git switch -c feature/login <commit>

从某个提交恢复单个文件

如果只想恢复某个文件,不想回退整个项目:

git restore --source=<commit> -- path/to/file

旧写法:

git checkout <commit> -- path/to/file

示例:

git restore --source=HEAD~1 -- README.md

这会把 README.md 恢复到上一个提交中的版本。


撤销 merge

如果 merge 过程中还没完成,可以中止:

git merge --abort

如果 merge commit 已经提交,但还没 push,可以 reset:

git reset --hard HEAD~1

如果 merge commit 已经推送到公共分支,应该 revert:

git revert -m 1 <merge-commit>

-m 1 表示选择第一个父提交作为主线。
revert merge commit 要谨慎,最好先在测试分支验证。


撤销 rebase

rebase 过程中想放弃:

git rebase --abort

rebase 已完成但想回到之前:

git reflog
git reset --hard HEAD@{n}

执行 rebase 前,建议先记下当前位置:

git branch backup/before-rebase

常见场景速查

场景命令
丢弃某文件工作区修改git restore file.txt
取消暂存git restore --staged file.txt
撤销最近提交,保留暂存git reset --soft HEAD~1
撤销最近提交,保留工作区git reset --mixed HEAD~1
彻底回退最近提交git reset --hard HEAD~1
安全撤销公共提交git revert <commit>
找回误 resetgit reflog
恢复某个历史文件git restore --source=<commit> -- file
中止 mergegit merge --abort
中止 rebasegit rebase --abort

危险操作前的保护流程

执行这些命令前要特别谨慎:

git reset --hard
git clean -fd
git push --force
git rebase

推荐保护流程:

git status
git log --oneline --graph --decorate -10
git branch backup/before-dangerous-operation

如果涉及远程分支,再确认:

git fetch origin
git branch -vv

这样即使操作失误,也可以通过备份分支或 reflog 找回。


公共分支回滚推荐流程

如果问题已经进入 mainrelease 等公共分支,不建议使用 reset 改写历史。

推荐流程:

git switch main
git pull
git log --oneline
git revert <bad-commit>
git push

如果要回滚多个提交,可以先在临时分支验证:

git switch -c revert/test
git revert <commit1> <commit2>
# 运行测试

确认没有问题后,再在目标分支执行正式回滚。

公共分支回滚要关注:

  • 是否会影响数据库迁移
  • 是否会影响配置文件
  • 是否会影响接口兼容性
  • 是否需要同步回滚发布版本
  • 是否需要通知团队和测试人员

18. stash 临时保存#

临时切换任务时,可以用 stash 保存当前工作区。

git stash push -m "work in progress"
git stash list
git stash pop

stash 的作用是:

把当前未提交修改临时收起来,让工作区恢复干净。

适合:

  • 临时切换分支
  • 拉取远程更新前保存本地修改
  • 暂时搁置未完成工作

stash 是什么

stash 是 Git 提供的临时保存机制。

它适合保存还不适合 commit 的修改。

常见场景:

正在写功能 A
突然要切到 hotfix 分支修线上 bug
当前修改还没完成,不想提交
此时可以 stash

流程:

git stash push -m "wip: login form"
git switch hotfix/payment
# 修复 bug
git switch feature/login
git stash pop

保存 stash

保存当前修改:

git stash

更推荐带说明:

git stash push -m "wip: update login form"

说明信息很重要。否则 stash 多了以后很难区分。


查看 stash 列表

git stash list

示例:

stash@{0}: On feature/login: wip: update login form
stash@{1}: On main: wip: before pull

最新的 stash 是 stash@{0}


查看 stash 内容

查看统计:

git stash show stash@{0}

查看具体 diff:

git stash show -p stash@{0}

如果不写编号,默认查看最新 stash:

git stash show -p

应用 stash

应用最新 stash,但保留 stash 记录:

git stash apply

应用指定 stash:

git stash apply stash@{1}

适合:

  • 想恢复修改
  • 但还想保留一份 stash 备份

pop stash

应用最新 stash,并删除该 stash 记录:

git stash pop

等价于:

apply + drop

如果应用时发生冲突,Git 可能不会删除 stash。
处理后可以手动检查 git stash list


删除 stash

删除指定 stash:

git stash drop stash@{0}

清空所有 stash:

git stash clear

clear 会删除所有 stash,谨慎使用。


stash 未跟踪文件

默认 git stash 只保存已跟踪文件的修改。

如果要保存未跟踪文件:

git stash push -u -m "wip: include untracked files"

-u 表示 include untracked。

如果要包括 ignored 文件:

git stash push -a -m "wip: include all files"

-a 表示 all,包括 ignored 文件,谨慎使用。


stash 暂存区状态

如果你希望恢复 stash 时保留原来的暂存状态:

git stash apply --index

或:

git stash pop --index

适合你 stash 前已经精心整理过暂存区的情况。


从 stash 创建分支

如果 stash 保存的是一组较大的修改,可以直接从 stash 创建分支:

git stash branch feature/from-stash stash@{0}

这个命令会:

  1. 基于 stash 创建时的提交创建新分支。
  2. 应用 stash。
  3. 如果成功,删除该 stash。

适合:

  • stash 很久以后再恢复
  • 当前分支变化太大,直接 apply 容易冲突
  • 想把临时修改变成正式分支

stash 和 commit 的区别

对比项stashcommit
目的临时保存正式记录历史
是否进入项目历史
是否适合共享
是否需要 message建议写必须认真写
保存时间临时长期

简单判断:

只是临时切换任务 -> stash
这是一个有意义的变更 -> commit

不要用 stash 长期保存重要工作。


stash 常见冲突

执行:

git stash pop

可能出现冲突。

原因:

  • stash 保存时的文件和当前分支文件差异较大
  • 同一区域被不同修改改过

处理流程:

git status
# 手动解决冲突
git add conflict-file

如果 pop 后 stash 没有自动删除,可以确认后手动删除:

git stash drop stash@{0}

临时切换分支

git stash push -m "wip: before switching branch"
git switch hotfix/payment

pull 前保存本地修改

git stash push -m "wip: before pull"
git pull
git stash pop

暂时搁置一个实验

git stash push -u -m "experiment: new layout"

之后恢复:

git stash apply stash@{0}

19. tag 与版本发布#

tag 是 Git 中用于标记某个特定提交的引用。

分支会随着新提交不断移动,而 tag 通常固定不动。

所以 tag 最常见的用途是:

  • 标记发布版本
  • 标记里程碑
  • 固定某个稳定提交
  • 生成 Release
  • 触发 CI/CD 发布流程

可以简单理解:

branch = 会移动的开发线
tag = 固定在某个提交上的版本标记

创建标签

git tag v1.0.0

这会创建一个轻量标签。

轻量标签本质上只是一个指向某个 commit 的引用。

查看标签:

git tag

查看某个标签指向的提交:

git show v1.0.0

轻量标签和附注标签

Git 标签主要分两类:

类型命令特点
轻量标签git tag v1.0.0只是一个简单引用
附注标签git tag -a v1.0.0 -m "release v1.0.0"有标签对象,包含作者、时间、说明

推荐:

  • 临时标记可以用轻量标签。
  • 正式发布版本推荐用附注标签。

创建附注标签

git tag -a v1.0.0 -m "release v1.0.0"

附注标签包含:

  • 标签名
  • 标签创建者
  • 标签创建时间
  • 标签说明
  • 指向的 commit

查看:

git show v1.0.0

正式发布更推荐附注标签,因为它能记录更多发布元信息。


给指定提交打标签

默认情况下,git tag 会给当前 HEAD 打标签。

也可以给历史提交打标签:

git tag -a v1.0.0 a1b2c3d -m "release v1.0.0"

适合:

  • 补打历史版本标签
  • 给某个稳定提交加版本号
  • 修复忘记打 tag 的发布流程

查看标签

查看所有标签:

git tag

按模式筛选:

git tag -l "v1.*"

查看标签详情:

git show v1.0.0

查看标签指向的提交:

git rev-list -n 1 v1.0.0

推送标签

git push origin v1.0.0
git push origin --tags

推送单个标签:

git push origin v1.0.0

推送所有本地标签:

git push origin --tags

注意:

普通 git push 默认不会推送所有 tag。
发布版本时要确认 tag 是否已经推送到远程。


删除标签

删除本地标签:

git tag -d v1.0.0

删除远程标签:

git push origin --delete v1.0.0

或旧写法:

git push origin :refs/tags/v1.0.0

注意:

已经发布出去的 tag 不要随意删除或重建。
如果 tag 已经被用户、CI、包管理平台使用,重写 tag 会带来很高风险。


检出标签

查看某个标签对应的代码:

git switch --detach v1.0.0

或旧写法:

git checkout v1.0.0

这会进入 detached HEAD 状态。

如果想基于某个 tag 修复 bug,应该创建分支:

git switch -c hotfix/v1.0.1 v1.0.0

这样后续提交会保存在 hotfix/v1.0.1 分支上,不会丢失。


版本号规范 SemVer

版本号常见格式遵循 SemVer:

MAJOR.MINOR.PATCH
主版本.次版本.修订版本

含义:

部分含义示例
MAJOR主版本,发生不兼容变更2.0.0
MINOR次版本,新增向后兼容功能1.3.0
PATCH修订版本,向后兼容 bugfix1.3.2

示例:

v1.0.0
v1.1.0
v1.1.1
v2.0.0

常见规则:

  • 修 bug:增加 PATCH
  • 新增兼容功能:增加 MINOR
  • 破坏兼容性:增加 MAJOR

预发布版本

SemVer 还支持预发布版本:

v1.0.0-alpha.1
v1.0.0-beta.1
v1.0.0-rc.1

常见含义:

标记含义
alpha早期测试版本
beta公开测试版本
rcrelease candidate,候选发布版本

示例流程:

v1.2.0-alpha.1
v1.2.0-beta.1
v1.2.0-rc.1
v1.2.0

常见发布流程

一个常见发布流程:

git switch main
git pull
git log --oneline v1.0.0..HEAD
# 确认测试通过后打标签
git tag -a v1.1.0 -m "release v1.1.0"
git push origin v1.1.0

如果 CI/CD 配置了 tag 触发发布,推送 tag 后会自动:

  • 构建
  • 测试
  • 打包
  • 发布 Release
  • 发布 Docker 镜像或制品

基于 tag 生成发布说明

查看两个版本之间的提交:

git log --oneline v1.0.0..v1.1.0

查看文件变化统计:

git diff --stat v1.0.0..v1.1.0

如果使用 Conventional Commits,可以按类型筛选:

git log --oneline v1.0.0..v1.1.0 --grep="^feat"
git log --oneline v1.0.0..v1.1.0 --grep="^fix"

这对编写 changelog 很有帮助。


release 分支与 tag

常见发布分支:

release/v1.1.0

常见流程:

git switch -c release/v1.1.0 main
# 只做版本号、文档、小修复
git commit -m "chore(release): prepare v1.1.0"
git tag -a v1.1.0 -m "release v1.1.0"
git push origin release/v1.1.0
git push origin v1.1.0

release 分支用于准备发布,tag 用于固定发布点。


hotfix 与 tag

线上版本 v1.0.0 出现 bug 时,可以从 tag 拉 hotfix 分支:

git switch -c hotfix/v1.0.1 v1.0.0

修复后:

git add .
git commit -m "fix(auth): handle token refresh failure"
git tag -a v1.0.1 -m "release v1.0.1"
git push origin hotfix/v1.0.1
git push origin v1.0.1

之后再把 hotfix 合并回主线,避免主线缺失修复。


tag 和 CI/CD

很多项目会用 tag 触发发布。

例如:

push tag v1.2.0
-> CI 构建
-> 运行测试
-> 生成制品
-> 发布 Release

GitHub Actions、GitLab CI、Jenkins 都可以监听 tag。

常见触发规则:

v*
v[0-9]+.[0-9]+.[0-9]+

建议:

  • 只有测试通过后再打 tag。
  • tag 发布流程要可重复。
  • 发布失败时优先修复后打新 tag,不要随意覆盖旧 tag。

20. .gitignore#

.gitignore 用于忽略不需要提交的文件。

它解决的问题是:

哪些文件不应该进入 Git 版本历史?

常见不应该提交的内容:

  • 依赖目录
  • 构建产物
  • 日志文件
  • 本地配置
  • IDE 配置
  • 操作系统临时文件
  • 密钥和环境变量
  • 缓存文件

.gitignore 只影响未被 Git 跟踪的文件。
如果文件已经被 Git 跟踪,后来再写入 .gitignore 不会自动生效。


基础示例

常见内容:

# dependencies
node_modules/
# build outputs
dist/
build/
target/
# logs
*.log
# IDE
.idea/
.vscode/
# OS
.DS_Store
Thumbs.db
# env
.env

这个文件通常放在项目根目录。

示例结构:

project/
.gitignore
README.md
src/

.gitignore 的匹配规则

常见规则:

写法含义
*.log忽略所有 .log 文件
build/忽略 build 目录
/build/只忽略项目根目录下的 build
temp*忽略 temp 开头的文件或目录
!keep.log不忽略 keep.log
**/cache/忽略任意层级下的 cache 目录

忽略文件和目录

忽略某类文件:

*.tmp
*.log

忽略目录:

build/
dist/
node_modules/

忽略根目录下的文件:

/local.properties

忽略任意目录下的文件:

local.properties

区别:

/local.properties 只匹配项目根目录
local.properties 匹配任意层级

反向规则

使用 ! 可以取消忽略。

例如忽略所有 .log,但保留一个示例文件:

*.log
!example.log

常见用途:

.env
!.env.example

意思是:

  • 忽略真实环境变量文件 .env
  • 保留示例文件 .env.example

这样既不会泄漏真实密钥,又能告诉别人需要哪些配置项。


已经被跟踪的文件不会自动忽略

注意:已经被 Git 跟踪的文件,不会因为后来加入 .gitignore 就自动取消跟踪。

取消跟踪但保留本地文件:

git rm --cached file.txt

如果是目录:

git rm -r --cached build/

然后提交:

git add .gitignore
git commit -m "chore: update gitignore"

注意:

--cached 表示只从 Git 索引中移除,不删除本地文件。


检查忽略规则是否生效

如果不确定某个文件为什么被忽略,可以使用:

git check-ignore -v path/to/file

示例:

git check-ignore -v debug.log

输出会告诉你是哪条规则让它被忽略。

查看未跟踪文件:

git status --ignored

全局 .gitignore

有些文件是个人环境产生的,不适合写进项目 .gitignore,例如:

  • 操作系统临时文件
  • 个人编辑器缓存
  • 本地工具生成文件

可以配置全局 ignore:

git config --global core.excludesfile ~/.gitignore_global

然后编辑:

.DS_Store
Thumbs.db
*.swp

建议:

  • 项目共享规则写进项目 .gitignore
  • 个人习惯规则写进全局 .gitignore_global

哪些文件不应该忽略

不要把这些重要文件随便忽略:

  • 源代码
  • 构建脚本
  • 依赖锁文件
  • 示例配置
  • 数据库迁移脚本
  • CI/CD 配置
  • .gitignore 本身
  • .gitattributes

依赖锁文件是否提交,取决于生态:

文件通常建议
package-lock.json应提交
yarn.lock应提交
pnpm-lock.yaml应提交
Gemfile.lock应提交,应用项目尤其需要
gradle.lockfile按团队依赖锁策略

锁文件有助于保证团队和 CI 使用一致依赖版本。


敏感信息处理

应该忽略:

.env
*.key
*.pem
secrets.*

但应该提交示例:

!.env.example

推荐做法:

.env 真实配置,不提交
.env.example 示例配置,提交

如果敏感信息已经提交到 Git 历史,仅仅加入 .gitignore 不够。
必须:

  1. 立即废弃泄漏密钥。
  2. 从历史中清理敏感信息。
  3. 通知团队重新处理本地仓库。

.gitignore 与 .gitattributes 的区别

文件作用
.gitignore控制哪些未跟踪文件不进入 Git
.gitattributes控制文件属性,如换行符、diff、merge 策略

例子:

build/
*.log

表示忽略文件。

*.sh text eol=lf
*.bat text eol=crlf

表示控制换行符。

两者不是替代关系。


21. Git LFS#

Git LFS,全称 Git Large File Storage,是 Git 的大文件管理扩展。

Git 本身非常适合管理文本文件和小型源代码文件,但不适合直接管理大型二进制文件,例如:

  • 视频
  • 音频
  • 设计源文件
  • 模型文件
  • 大型图片
  • PSD / AI / Sketch / Figma 导出资源
  • 大型压缩包
  • 数据集

这些文件通常有几个特点:

  • 文件体积大
  • diff 不可读
  • 每次修改都可能产生完整新版本
  • 克隆仓库会越来越慢
  • 仓库历史会快速膨胀

Git LFS 的作用是把大文件内容放到 LFS 存储中,而 Git 仓库里只保存一个很小的指针文件。


Git 不适合直接管理大文件

Git 的优势是管理文本差异。代码、配置、文档这类文件可以很好地 diff 和压缩。

但大二进制文件有明显问题:

问题说明
diff 不可读Git 无法像代码一样展示二进制文件差异
仓库膨胀每次修改都可能保存一份新对象
clone 变慢新成员需要下载越来越大的历史
存储浪费历史版本中的大文件难以清理
review 困难无法像代码一样审查变化内容

例如一个 200MB 的模型文件,如果更新 10 次,仓库历史可能膨胀到数 GB。


Git LFS 的基本原理

Git LFS 不会把大文件本体直接放进普通 Git 对象库。

它会在 Git 中保存一个指针文件,真实大文件放在 LFS 存储服务中。

示意:

普通 Git 仓库
└── model.bin pointer file
Git LFS storage
└── real model.bin content

指针文件大致包含:

version https://git-lfs.github.com/spec/v1
oid sha256:...
size 123456789

也就是说,Git 只跟踪这个小指针,真正的大文件由 LFS 管理。


安装和初始化

安装 Git LFS 后,需要初始化:

git lfs install

检查版本:

git lfs version

查看当前仓库 LFS 状态:

git lfs env

一般每台机器安装并执行一次 git lfs install 即可。


跟踪大文件类型

跟踪 PSD 文件:

git lfs track "*.psd"

跟踪模型文件:

git lfs track "*.bin"
git lfs track "*.onnx"
git lfs track "*.pt"

跟踪视频:

git lfs track "*.mp4"

执行 git lfs track 后,Git 会修改或创建 .gitattributes 文件。

例如:

*.psd filter=lfs diff=lfs merge=lfs -text
*.onnx filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text

.gitattributes 必须提交到仓库:

git add .gitattributes
git commit -m "chore: configure git lfs tracking"

添加 LFS 文件

配置跟踪规则后,再添加大文件:

git add model.onnx
git commit -m "chore(model): add initial onnx model"

推送:

git push

Git 会上传普通提交对象,也会把 LFS 文件上传到 LFS 服务器。


查看 LFS 文件

查看当前 LFS 跟踪规则:

git lfs track

查看仓库中的 LFS 文件:

git lfs ls-files

查看某个文件是否由 LFS 管理:

git check-attr filter -- model.onnx

如果输出包含:

model.onnx: filter: lfs

说明该文件使用 LFS。


克隆含 LFS 的仓库

正常克隆:

git clone <repo-url>

如果 Git LFS 已安装,通常会自动拉取 LFS 文件。

手动拉取 LFS 文件:

git lfs pull

只获取 Git 指针、不立即下载 LFS 内容的场景,可以使用:

GIT_LFS_SKIP_SMUDGE=1 git clone <repo-url>

之后需要文件时再执行:

git lfs pull

这对超大仓库或 CI 场景很有用。


已经提交过的大文件怎么办

如果大文件已经进入普通 Git 历史,仅仅后来执行:

git lfs track "*.bin"

并不会自动把历史中的大文件迁移到 LFS。

你需要迁移历史:

git lfs migrate import --include="*.bin,*.onnx,*.mp4"

注意:

  • 迁移历史会改写提交历史。
  • 已经推送并多人协作的仓库要谨慎。
  • 操作前必须备份。
  • 团队成员需要重新同步历史。

迁移后推送可能需要:

git push --force-with-lease

公共仓库或团队仓库执行前必须沟通。


Git LFS 和 .gitignore 的区别

工具作用
.gitignore不让文件进入 Git 管理
Git LFS让大文件进入版本管理,但大文件内容由 LFS 存储

区别:

不需要提交的文件 -> .gitignore
需要版本管理的大文件 -> Git LFS

例如:

  • build/node_modules/.env 应该进 .gitignore
  • model.onnxdemo.mp4design.psd 如果需要版本化,可以进 Git LFS

Git LFS 的适用场景

适合使用 Git LFS 的文件:

  • 设计源文件
  • 游戏资源
  • 模型文件
  • 数据样例
  • 大型图片
  • 二进制 SDK
  • 演示视频
  • 需要跟随代码版本变化的大型资产

不适合使用 Git LFS 的文件:

  • 构建产物
  • 日志文件
  • 缓存目录
  • 依赖下载目录
  • 临时文件
  • 敏感文件
  • 频繁生成的大量中间文件

这些通常应该忽略或放到制品仓库。


团队协作注意事项

团队使用 Git LFS 时要统一规则:

  1. .gitattributes 必须提交。
  2. 所有成员都要安装 Git LFS。
  3. 新增大文件前先确认跟踪规则。
  4. 不要把大文件先普通提交再迁移。
  5. 注意托管平台的 LFS 容量和流量限制。
  6. CI 环境也要安装并拉取 LFS 文件。
  7. 大文件更新频率要控制。

新成员拉仓库后,如果看到 LFS 文件是指针内容,通常说明 LFS 没有正确拉取。

执行:

git lfs install
git lfs pull

CI/CD 中的 Git LFS

CI 环境可能默认不拉 LFS 文件。

常见处理:

git lfs install
git lfs pull

在 GitHub Actions 中,actions/checkout 可以配置 LFS:

- uses: actions/checkout@v4
with:
lfs: true

如果构建依赖模型、资源、二进制文件,要确认 CI 真的拿到了 LFS 内容,而不是指针文件。


22. 常见协作工作流#

Git 协作工作流是团队围绕代码变更形成的一套规则。

它回答的不是“Git 能做什么”,而是:

  • 开发者应该从哪个分支开始工作
  • 新功能、缺陷修复、紧急修复应该提交到哪里
  • 代码什么时候合并
  • 谁来评审
  • 如何触发测试、构建、发布
  • 线上问题如何回滚或热修复

没有统一工作流时,团队常见问题包括:

  • 主分支经常不可用
  • 多人修改互相覆盖
  • 临近发布时大量冲突集中爆发
  • 不知道某个提交是否已经上线
  • 修复线上问题时找不到稳定基线
  • 分支长期不合并,最后变成“大爆炸式合并”

好的 Git 工作流不一定复杂,但必须让团队对以下事情形成共识:

分支怎么建,代码怎么审,变更怎么测,版本怎么发,问题怎么回滚。

22.1 集中式工作流#

集中式工作流是最简单的一种 Git 协作模式。

所有开发者都围绕同一个主分支工作,通常是:

  • main
  • master
  • trunk

基本结构:

developer A ----\
developer B ----- main
developer C ----/

典型流程:

git switch main
git pull origin main
# 修改代码
git status
git add .
git commit -m "fix: correct login validation"
git push origin main

适合:

  • 小团队
  • 简单项目
  • 个人项目
  • 原型验证项目
  • 代码变更频率较低的内部工具

缺点:

  • 容易互相影响
  • 对主分支稳定性要求高
  • 缺少天然的代码评审入口
  • 主分支一旦被提交错误代码,所有人都会受影响
  • 不适合多人并行开发复杂需求

集中式工作流的核心风险是:

所有风险都集中在主分支上。

如果团队使用集中式工作流,至少应该做到:

  • 提交前先 git pull
  • 推送前本地跑测试
  • 禁止直接提交明显不完整的代码
  • 使用小而清晰的 commit
  • 对重要项目开启分支保护,避免直接 push 到主分支

集中式工作流适合学习 Git 和小规模协作,但随着团队人数、功能复杂度、发布频率上升,通常需要过渡到 Feature Branch 或 Trunk Based Development。


22.2 Feature Branch 工作流#

Feature Branch 工作流是当前最常见、最容易落地的团队协作模式。

它的基本思想是:

每个需求、缺陷修复、重构任务都从主分支拉出独立分支,完成后通过 PR / MR 合并回主分支。

常见分支命名:

feature/user-profile
feature/payment-order
fix/login-timeout
bugfix/null-pointer-on-startup
refactor/order-service
docs/api-usage
test/add-login-tests
chore/update-dependencies

典型流程:

git switch main
git pull origin main
git switch -c feature/user-profile
# 修改代码
git status
git add .
git commit -m "feat(user): add profile edit page"
git push -u origin feature/user-profile

随后在平台上创建:

  • GitHub Pull Request
  • GitLab Merge Request
  • Gitee Pull Request
  • Bitbucket Pull Request

合并前通常会经过:

开发分支 -> 提交 PR/MR -> 自动化测试 -> 代码评审 -> 合并主分支

Feature Branch 的优势:

  • 每个任务独立开发,互不干扰
  • PR / MR 天然适合作为评审入口
  • 可以针对每个分支单独跑 CI
  • 便于追踪一个需求涉及的所有提交
  • 适合多数业务团队

Feature Branch 的风险:

  • 分支生命周期太长会导致冲突变多
  • 大分支合并时风险高
  • 如果评审排队严重,会阻塞交付
  • 如果主分支变化很快,功能分支容易落后

推荐做法:

  • 一个分支只做一件事
  • 分支尽量短生命周期
  • 每天同步主分支变化
  • PR 尽量小,便于评审
  • 不把多个无关需求塞进一个分支
  • 合并前确保 CI 通过

同步主分支的常用方式:

git switch feature/user-profile
git fetch origin
git rebase origin/main

或者:

git switch feature/user-profile
git fetch origin
git merge origin/main

两种方式的区别:

方式特点适合场景
merge origin/main保留真实合并记录团队重视完整历史
rebase origin/main历史更线性团队要求提交历史整洁

注意:

已经被多人共同使用的远程分支,不要随意 rebase 后强推。

Feature Branch 工作流适合大多数团队,尤其适合配合 PR / MR、CI、分支保护和代码评审一起使用。


22.3 Git Flow#

Git Flow 是一种较完整、较重的分支模型,最早常用于版本发布周期明确的软件项目。

它通常包含以下长期分支:

  • main
  • develop

以及以下临时分支:

  • feature/*
  • release/*
  • hotfix/*

各分支职责:

分支作用
main保存正式发布版本,通常每个发布点都打 tag
develop日常集成分支,保存下一版本开发内容
feature/*单个功能开发分支
release/*发布准备分支,用于测试、修复、版本号调整
hotfix/*线上紧急修复分支

Git Flow 的基本结构:

main: A ----------- M1 ----------- M2
\ / /
develop: D -- D -- R ---- D -- D --
\ \ /
feature: F1 F2 /
release: R ---
hotfix: H ---------/

功能开发流程:

git switch develop
git pull origin develop
git switch -c feature/report-export
# 修改代码并提交
git add .
git commit -m "feat(report): add export task"
git push -u origin feature/report-export

功能完成后合并回 develop

git switch develop
git pull origin develop
git merge --no-ff feature/report-export
git push origin develop

发布准备流程:

git switch develop
git pull origin develop
git switch -c release/1.4.0
# 修复发布前问题、更新版本号、补充文档
git add .
git commit -m "chore(release): prepare 1.4.0"

发布分支测试通过后,合并到 main 并打 tag:

git switch main
git pull origin main
git merge --no-ff release/1.4.0
git tag -a v1.4.0 -m "Release v1.4.0"
git push origin main --tags

同时要把发布分支的修复合并回 develop

git switch develop
git pull origin develop
git merge --no-ff release/1.4.0
git push origin develop

线上紧急修复流程:

git switch main
git pull origin main
git switch -c hotfix/1.4.1-login-error
# 修复线上问题
git add .
git commit -m "fix(auth): handle expired session"
git switch main
git merge --no-ff hotfix/1.4.1-login-error
git tag -a v1.4.1 -m "Release v1.4.1"
git push origin main --tags
git switch develop
git merge --no-ff hotfix/1.4.1-login-error
git push origin develop

Git Flow 适合:

  • 有明确版本号的软件
  • 发布周期比较固定
  • 生产环境和开发环境差异明显
  • 需要维护多个正式版本
  • 桌面软件、SDK、嵌入式软件、部分企业系统

Git Flow 的缺点:

  • 分支多,理解成本高
  • 流程重,容易降低交付速度
  • developmain 长期分离,可能导致集成成本上升
  • 不太适合高频部署和持续交付

判断是否应该使用 Git Flow,可以问:

项目是否真的需要长期 develop、release、hotfix 分支?

如果团队只是普通 Web 应用,且已经具备 CI/CD,通常 Feature Branch 或 GitHub Flow 更轻量。


22.4 GitHub Flow#

GitHub Flow 是比 Git Flow 更轻量的工作流。

它的核心原则是:

main 分支始终保持可部署状态,所有变更通过短生命周期分支和 Pull Request 合并。

流程如下:

main -> feature branch -> pull request -> review -> merge -> deploy

典型步骤:

git switch main
git pull origin main
git switch -c feature/search-filter
# 修改代码
git add .
git commit -m "feat(search): add filter options"
git push -u origin feature/search-filter

然后:

  1. 创建 Pull Request
  2. 触发 CI
  3. 进行代码评审
  4. CI 与评审通过后合并到 main
  5. 自动或手动部署

GitHub Flow 的关键要求:

  • main 必须稳定
  • 所有变更通过 PR
  • 自动化测试要足够可靠
  • 合并后可以快速部署
  • 发现问题可以快速回滚或修复

适合:

  • Web 应用
  • SaaS 系统
  • 移动端后端服务
  • 内部平台
  • 持续交付项目

不太适合:

  • 发布周期很长的传统软件
  • 同时维护多个历史版本的项目
  • 自动化测试薄弱的团队
  • 合并后不能快速验证和回滚的系统

GitHub Flow 的优势是轻量直接:

一个主分支 + 短分支 + PR + CI/CD。

这也是很多现代团队默认采用的协作模型。


22.5 GitLab Flow#

GitLab Flow 可以理解为在 Feature Branch / GitHub Flow 的基础上,引入环境分支或发布分支来对应真实部署流程。

常见环境分支:

main
pre-production
production

或者:

main
staging
production

一种常见流程:

feature/* -> main -> staging -> production

示例:

git switch main
git pull origin main
git switch -c feature/invoice-export
# 开发并提交
git add .
git commit -m "feat(invoice): support csv export"
git push -u origin feature/invoice-export

PR / MR 合并到 main 后,再根据部署节奏合并到环境分支:

git switch staging
git pull origin staging
git merge origin/main
git push origin staging
git switch production
git pull origin production
git merge origin/staging
git push origin production

GitLab Flow 适合:

  • 有多个部署环境
  • 需要区分测试环境、预发环境、生产环境
  • 企业内部系统
  • 发布需要审批
  • 不能做到每次合并 main 都立即上线的项目

注意:

环境分支不是越多越好。环境分支过多会带来:

  • 合并链路变长
  • 版本追踪变复杂
  • 修复需要在多个分支间传递
  • 环境之间容易产生差异

如果使用环境分支,应当明确:

  • 每个环境分支对应哪个环境
  • 谁有权限合并
  • 什么时候从上游分支同步
  • 线上问题从哪个分支修复
  • 是否需要打 tag 标记发布版本

22.6 Trunk Based Development#

Trunk Based Development,简称 TBD,中文通常称为主干开发。

它的核心思想是:

所有开发者围绕一个主干分支进行小步、高频、持续集成。

主干通常是:

  • main
  • master
  • trunk

TBD 不是简单地“大家都往 main 上提交”,它依赖一套工程能力:

  • 自动化测试
  • 持续集成
  • 代码评审
  • 小步提交
  • 快速回滚
  • Feature Flag
  • 主分支保护

典型流程:

git switch main
git pull origin main
git switch -c short/add-cache-key
# 小步修改
git add .
git commit -m "perf(cache): add user cache key"
git push -u origin short/add-cache-key

创建 PR,快速评审,尽快合并回 main

分支生命周期通常很短:

几个小时到一两天,而不是几周。

对于尚未完成的大功能,TBD 通常使用 Feature Flag:

代码可以合并,但功能默认关闭。
等功能完整并验证通过后,再通过配置打开。

示例伪代码:

if (featureFlags.isEnabled("new_checkout")) {
return newCheckoutService.submit(order);
}
return legacyCheckoutService.submit(order);

TBD 的优势:

  • 集成频率高,冲突更早暴露
  • 主分支持续可用
  • 交付链路短
  • 减少长期分支带来的合并风险
  • 非常适合持续交付和 DevOps 团队

TBD 的挑战:

  • 自动化测试必须可靠
  • 团队需要拆小任务的能力
  • 代码评审要快
  • 需要 Feature Flag 或灰度发布能力
  • 主分支失败要能快速修复或回滚

适合:

  • 自动化测试成熟
  • CI/CD 完整
  • 团队工程能力强
  • 高频交付
  • 服务端应用
  • 平台型项目

不适合:

  • 测试体系薄弱的团队
  • 大量手工测试的项目
  • 需求难以拆分的小团队初期项目
  • 发布流程非常严格但自动化不足的项目

TBD 的关键不是“少分支”,而是:

小批量变更 + 快速集成 + 自动验证 + 可控发布。

22.7 Forking 工作流#

Forking 工作流常见于开源项目和跨组织协作。

它的特点是:

贡献者没有主仓库写权限,只能 fork 一份自己的仓库,修改后向主仓库提交 PR。

基本结构:

upstream/main <---- Pull Request <---- origin/feature/*

其中:

  • upstream 表示原始主仓库
  • origin 表示自己 fork 后的仓库

常见初始化方式:

git clone git@github.com:your-name/project.git
cd project
git remote add upstream git@github.com:source-org/project.git
git remote -v

同步主仓库:

git fetch upstream
git switch main
git merge upstream/main
git push origin main

开发功能:

git switch -c fix/readme-typo
# 修改代码
git add README.md
git commit -m "docs: fix readme typo"
git push -u origin fix/readme-typo

然后从自己的 fork 仓库向主仓库提交 PR。

Forking 工作流适合:

  • 开源项目
  • 外部贡献者参与
  • 主仓库权限需要严格控制
  • 企业之间协作但不直接共享写权限

注意事项:

  • 经常同步 upstream/main
  • 每个 PR 一个独立分支
  • 不要直接在 fork 仓库的 main 上开发
  • 提交 PR 前阅读项目贡献指南
  • commit message 和代码风格应遵守项目规范

22.8 常见工作流对比#

工作流核心特点优点缺点适合场景
集中式工作流所有人围绕一个主分支提交简单直接主分支风险高小团队、个人项目
Feature Branch每个任务一个分支,通过 PR/MR 合并清晰、易评审长分支会产生冲突大多数业务团队
Git Flowmaindevelopreleasehotfix 分层管理发布控制强流程较重版本发布周期明确的项目
GitHub Flow短分支 + PR + 主分支可部署轻量、适合持续交付依赖 CI/CDWeb/SaaS 项目
GitLab Flow分支与环境或发布流程结合贴合企业部署环境分支容易复杂多环境部署项目
Trunk Based围绕主干高频集成冲突少、交付快工程要求高CI/CD 成熟团队
Forking贡献者 fork 后提交 PR权限隔离好同步成本较高开源项目

如何选择适合团队的工作流

选择工作流时,不应只看流行程度,而要看团队真实情况。

可以从以下维度判断:

判断维度推荐倾向
个人项目或学习项目集中式工作流
小团队业务项目Feature Branch
大多数 Web 应用GitHub Flow 或 Feature Branch
有预发、生产等多个环境GitLab Flow
版本发布周期固定Git Flow
高频部署且测试完善Trunk Based Development
开源项目Forking 工作流

更实际的选择建议:

  • 不确定时,优先选择 Feature Branch
  • 如果团队 CI/CD 很成熟,可以逐步靠近 Trunk Based
  • 如果项目需要明确版本发布和热修复,可以使用 Git Flow
  • 如果是开源项目,应使用 Forking 工作流
  • 如果工作流复杂到团队难以执行,说明需要简化

一个适合多数中小团队的默认方案:

main + feature/fix 分支 + PR/MR + CI + 分支保护 + 语义化提交

分支命名规范

分支命名应该让人一眼看出变更类型和大致内容。

推荐格式:

<type>/<short-description>

常见类型:

类型含义示例
feature新功能feature/user-profile
feat新功能简写feat/order-search
fix缺陷修复fix/login-timeout
bugfix缺陷修复bugfix/cart-price-error
hotfix线上紧急修复hotfix/payment-callback
release发布准备release/2.3.0
docs文档修改docs/api-guide
refactor重构refactor/user-service
test测试test/order-service
chore工程杂项chore/update-gradle

也可以加入任务编号:

feature/JIRA-128-user-profile
fix/BUG-203-login-timeout

不推荐的分支名:

dev
test
my-branch
new
final
temp
zhangsan
fixbug

原因是这些名字无法表达任务边界,时间久了很难判断是否还能删除。


分支保护与权限控制

无论使用哪种工作流,关键分支都应该受到保护。

通常需要保护的分支:

  • main
  • master
  • develop
  • release/*
  • production

常见保护规则:

  • 禁止直接 push
  • 必须通过 PR / MR 合并
  • 至少 1 到 2 人评审通过
  • CI 通过后才能合并
  • 不允许未解决评论就合并
  • 不允许强制推送
  • 不允许删除保护分支
  • 要求分支与目标分支保持最新

保护分支的目的不是增加流程负担,而是保证关键分支可追溯、可验证、可发布。

一个常见规则组合:

main 分支:
- 禁止直接 push
- PR 必须通过 CI
- 至少一名 reviewer approve
- squash merge 或 merge commit 由团队统一约定

合并策略选择

不同工作流通常会配合不同的合并策略。

常见合并策略:

策略命令或平台选项特点
Merge Commitgit merge --no-ff保留分支合并历史
Squash Merge平台 Squash and merge多个提交压成一个提交
Rebase Merge平台 Rebase and merge历史线性,但会重写提交基线
Fast-forwardgit merge --ff-only没有额外合并提交

推荐选择:

  • 团队重视完整分支上下文:使用 Merge Commit
  • 团队重视主分支整洁:使用 Squash Merge
  • 团队熟悉 rebase 且要求线性历史:使用 Rebase Merge
  • 发布分支、热修复分支:常用 Merge Commit 保留节点

多数业务团队可以使用:

功能分支使用 Squash Merge,发布分支和热修复分支使用 Merge Commit。

这样主分支既不会被大量零散提交污染,又能保留关键发布节点。


工作流中的 Commit Message 要求

协作工作流和 Commit Message 规范应该一起设计。

如果团队使用 Conventional Commits,可以把提交类型和分支类型对应起来:

分支类型Commit 类型示例
feature/*featfeat(user): add avatar upload
fix/*fixfix(auth): reject expired token
docs/*docsdocs(api): add auth examples
refactor/*refactorrefactor(order): split pricing service
test/*testtest(auth): add token expiry cases
chore/*chorechore(ci): update build workflow
hotfix/*fixfix(payment): handle duplicate callback

PR / MR 标题也建议遵守同样规则:

feat(user): add profile edit page
fix(order): correct discount calculation
docs(git): add branch workflow guide

如果使用 Squash Merge,PR 标题往往会成为最终进入主分支的 commit message,因此 PR 标题必须认真写。


23. Pull Request / Merge Request#

PR / MR 是团队协作中的核心入口。

不同平台叫法不同:

平台名称
GitHubPull Request,简称 PR
GitLabMerge Request,简称 MR
GiteePull Request
BitbucketPull Request

它们本质上解决的是同一个问题:

把一个分支上的修改,请求合并到另一个目标分支。

例如:

feature/login-page -> develop
hotfix/payment-bug -> main
release/1.4.0 -> main

PR / MR 不只是“合并代码”的按钮,它更像是一次完整的协作流程:

提出变更 -> 说明变更 -> 自动检查 -> 同伴评审 -> 修改完善 -> 合并发布

PR / MR 的作用

PR / MR 的主要作用包括:

  • 让代码变更在合并前被讨论
  • 让团队成员提前发现问题
  • 记录一次需求、缺陷或技术调整的上下文
  • 触发 CI 自动测试、构建、扫描
  • 形成可追溯的代码变更历史
  • 保护主干分支,避免未经检查的代码直接进入主分支

从工程实践角度看,PR / MR 是连接以下内容的中心节点:

内容说明
分支本次变更来自哪个分支
commit本次变更包含哪些提交
issue本次变更解决哪个任务或问题
CI本次变更是否通过自动检查
review谁评审了代码,提出了什么意见
merge最终如何合并到目标分支

一个成熟团队通常不会直接向 mainmaster 推送代码,而是通过 PR / MR 完成协作。


PR / MR 的基本生命周期

一次标准的 PR / MR 通常包含以下步骤:

创建分支
-> 编写代码
-> 提交 commit
-> 推送远程分支
-> 创建 PR / MR
-> 自动化检查
-> 代码评审
-> 根据反馈修改
-> 审批通过
-> 合并目标分支
-> 删除临时分支

示例命令:

git checkout -b feature/user-profile
# 修改代码后
git add .
git commit -m "feat(user): add profile page"
git push -u origin feature/user-profile

然后在代码托管平台上创建 PR / MR,目标分支通常是:

  • develop
  • main
  • release/*
  • 其他团队约定的集成分支

PR / MR 标题怎么写

标题应该让评审者一眼知道这次变更的目的。

推荐格式:

<type>(<scope>): <summary>

示例:

feat(auth): add password reset flow
fix(order): correct refund amount calculation
docs(git): expand pull request workflow notes
refactor(api): simplify user query service
test(payment): add retry scenario coverage

不推荐:

update
fix bug
修改代码
临时提交
优化一下

好的标题应该具备三个特点:

  • 明确变更类型
  • 明确影响范围
  • 明确做了什么

PR / MR 描述应该写什么

一个好的 PR 应该包含:

  • 清晰标题
  • 变更背景
  • 修改内容
  • 测试说明
  • 风险说明
  • 关联 issue
  • 截图或日志,若涉及 UI 或行为变化

推荐描述模板:

## 背景
说明为什么需要这次变更。
## 修改内容
- 修改点 1
- 修改点 2
- 修改点 3
## 测试说明
- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] 本地手动验证通过
## 风险影响
说明是否影响兼容性、数据结构、接口行为或发布流程。
## 关联任务
Closes #123

如果是 UI 变化,应该补充:

  • 截图
  • 录屏
  • 前后对比
  • 移动端和桌面端验证情况

如果是接口变化,应该补充:

  • 请求示例
  • 响应示例
  • 兼容性说明
  • 是否需要前后端同步发布

如果是数据库变化,应该补充:

  • migration 文件
  • 回滚方案
  • 数据兼容说明
  • 是否需要停机或灰度

小 PR 优于大 PR

PR / MR 越大,评审成本越高。

大 PR 常见问题:

  • 修改文件太多,评审者难以理解
  • 需求、重构、格式化混在一起
  • bug 更难定位
  • 合并冲突概率更高
  • 评审容易流于形式

推荐:

一个 PR 只解决一个明确问题。

例如把下面的大 PR 拆开:

新增登录功能 + 重构用户模块 + 修改格式化规则 + 修复支付 bug

拆成:

PR 1: refactor(user): split user service
PR 2: feat(auth): add login flow
PR 3: fix(payment): correct retry status
PR 4: chore(format): apply formatter rules

这样每个 PR 的目的更清晰,风险也更容易控制。


创建 PR / MR 前的自查清单

提交 PR / MR 前,作者应该先完成自查。

推荐清单:

  • 本地代码可以编译
  • 测试已经运行
  • 没有提交调试代码
  • 没有提交密钥、Token、密码
  • 没有无意义格式化大量文件
  • 没有把临时文件、日志文件、构建产物提交进去
  • commit message 可读
  • 变更范围和 PR 描述一致
  • 目标分支选择正确
  • 关联 issue 或任务单

常用检查命令:

git status
git diff
git diff --cached
git log --oneline --decorate -5

如果想查看本分支相对目标分支的变更:

git diff origin/main...HEAD

查看本分支新增的提交:

git log --oneline origin/main..HEAD

代码评审关注点

代码评审关注点:

  • 是否解决了正确的问题
  • 代码是否清晰
  • 是否有测试
  • 是否影响兼容性
  • 是否引入安全问题
  • 提交历史是否可读

更完整地说,评审者可以从以下角度看:

维度关注点
需求正确性是否真正解决了任务描述的问题
代码可读性命名、结构、抽象是否清晰
边界条件空值、异常、并发、失败场景是否处理
测试覆盖是否有必要的单测、集成测试或手动验证
性能影响是否引入明显性能退化
安全风险是否泄漏敏感信息,是否存在注入、越权等问题
兼容性是否破坏旧接口、旧数据、旧配置
可维护性后续修改是否容易
工程规范是否符合团队格式、提交、分支、发布规范

评审时不要只看代码风格,更重要的是看行为是否正确、设计是否合理、风险是否可控。


如何提出高质量 Review 意见

好的 review 意见应该具体、可执行、带理由。

不推荐:

这里不好。
改一下。
这写得不行。

推荐:

这里在 user 为空时会触发 NullPointerException,建议在进入分支前先判断空值。
这个方法同时做了参数校验、数据库查询和响应组装,后续会比较难测。是否可以把响应组装拆到单独函数?
这里修改了订单状态流转,建议补一个“已退款订单再次退款”的测试用例。

Review 意见可以分层:

标记含义
must必须修改,否则不建议合并
suggestion建议修改,但不阻塞合并
question询问设计原因或上下文
nit小问题,例如命名、格式、注释

示例:

must: 这里会导致未登录用户绕过权限检查,需要在 service 层补充校验。
suggestion: 这个变量可以命名为 retryCount,可读性会更好。
question: 这里为什么选择同步调用,而不是复用现有异步队列?

作者如何回应 Review

收到 review 后,作者应该:

  • 逐条确认意见
  • 能修改的直接修改
  • 不认同的地方解释原因
  • 修改后推送新 commit
  • 不要悄悄忽略关键评论

常见回应方式:

已修改,新增了空值判断和对应测试。
这里暂时保留当前实现,因为兼容旧接口需要这个字段。后续会在 #456 中移除。
同意,已将订单状态判断拆到 OrderStatusPolicy。

如果评审者指出的问题较多,不要急着合并。先把问题整理成几个类别:

  • 必须修复的问题
  • 可以本次顺手优化的问题
  • 适合拆到后续 issue 的问题

Draft PR / MR

Draft PR / MR 表示这个变更还没有准备好合并,但可以提前让团队看到。

适合使用 Draft 的场景:

  • 想提前同步方案
  • 需要别人先看整体方向
  • 功能还没写完
  • CI 还没完全通过
  • 需要跨团队协作

Draft 的好处是:

  • 提前暴露风险
  • 减少闭门造车
  • 方便持续沟通
  • 不会误合并未完成代码

当代码准备好后,再把 Draft 标记为 Ready for review。


合并前需要满足的条件

一个 PR / MR 合并前通常应该满足:

  • CI 通过
  • 至少一名或多名评审者批准
  • 没有未解决的 review 讨论
  • 目标分支没有严重冲突
  • 测试说明完整
  • 变更风险清楚
  • 分支保护规则允许合并

团队可以配置保护规则:

  • 禁止直接 push 到 main
  • 必须通过 PR / MR 合并
  • 必须通过指定 CI 检查
  • 必须至少 1 到 2 个 approval
  • 必须解决所有讨论
  • 必须保持分支与目标分支同步

这些规则可以减少人为疏漏。


常见合并策略

代码托管平台通常提供三种合并方式。

策略特点
Merge commit保留完整分支历史,并产生一个 merge commit
Squash merge把多个提交压缩成一个提交再合并
Rebase merge把提交线性追加到目标分支

Merge commit

特点:

  • 保留分支结构
  • 能看出 PR 合并节点
  • 历史更完整
  • 提交图可能更复杂

适合:

  • 需要保留完整上下文的功能分支
  • release 分支合并
  • 团队希望看到分支合并轨迹

命令类似:

git checkout main
git merge --no-ff feature/login

Squash merge

特点:

  • 多个 commit 合成一个
  • 主分支历史干净
  • PR 内部的临时提交不会污染主干
  • 可能丢失中间提交粒度

适合:

  • 小功能
  • bugfix
  • commit 历史比较碎的 PR
  • 希望主分支历史简洁的团队

例如 PR 内部提交:

wip
fix typo
adjust style
add test

合并后变成:

feat(auth): add login page

Rebase merge

特点:

  • 不产生 merge commit
  • 历史保持线性
  • 每个 commit 仍然保留
  • 对 commit 质量要求更高

适合:

  • 团队强调线性历史
  • 每个 commit 都有明确含义
  • PR 中提交粒度清晰

注意:如果分支已经多人共同使用,rebase 要谨慎。


处理 PR / MR 冲突

当目标分支发生变化,当前分支可能出现冲突。

常见处理方式一:merge 目标分支。

git checkout feature/login
git fetch origin
git merge origin/main

解决冲突后:

git add .
git commit
git push

常见处理方式二:rebase 到目标分支。

git checkout feature/login
git fetch origin
git rebase origin/main

解决冲突后:

git add .
git rebase --continue
git push --force-with-lease

区别:

方式特点
merge操作更安全,保留合并记录
rebase历史更线性,但会改写当前分支提交

如果分支只由自己使用,可以选择 rebase。
如果分支多人协作,优先使用 merge 或先和团队确认。


PR / MR 与 CI/CD

成熟项目通常会在 PR / MR 上运行自动检查。

常见检查:

  • 编译
  • 单元测试
  • 集成测试
  • 代码格式检查
  • lint
  • 类型检查
  • 安全扫描
  • 依赖漏洞扫描
  • 测试覆盖率检查
  • Docker 镜像构建

CI 的价值是:

  • 把重复检查自动化
  • 在合并前发现问题
  • 减少人工评审压力
  • 保护主分支稳定性

PR / MR 中常见状态:

状态含义
pending检查正在运行
passed检查通过
failed检查失败
skipped检查被跳过
required必须通过才能合并

如果 CI 失败,不要直接让评审者继续看代码。应先定位失败原因:

  • 是代码问题
  • 是测试不稳定
  • 是环境问题
  • 是依赖服务故障
  • 是配置问题

PR / MR 和 Issue 的关联

PR / MR 最好关联对应的 issue、需求单或缺陷单。

常见写法:

Closes #123
Fixes #123
Resolves #123
Refs #123

区别:

写法含义
Closes #123合并后关闭 issue
Fixes #123合并后关闭缺陷 issue
Resolves #123合并后解决 issue
Refs #123仅引用,不自动关闭

好处:

  • 需求和代码互相可追溯
  • 后续排查问题时能找到背景
  • 发布说明更容易整理
  • 项目管理状态更准确

PR / MR 模板

可以在仓库中配置 PR / MR 模板,减少遗漏。

GitHub 常见路径:

.github/pull_request_template.md

GitLab 常见路径:

.gitlab/merge_request_templates/default.md

模板示例:

## 背景
<!-- 为什么需要这个变更? -->
## 修改内容
-
## 测试
- [ ] 单元测试
- [ ] 集成测试
- [ ] 手动验证
## 风险
<!-- 是否影响接口、数据库、兼容性、性能或安全? -->
## 截图 / 日志
<!-- UI 变化、关键日志或验证截图 -->
## 关联 issue
Closes #

模板不是为了增加流程负担,而是为了让关键信息不被遗漏。


不同类型 PR / MR 的写法

功能型 PR

重点说明:

  • 新增了什么能力
  • 用户行为有什么变化
  • 是否有开关或灰度
  • 是否影响旧功能

示例标题:

feat(order): support partial refund

修复型 PR

重点说明:

  • bug 现象
  • 根因分析
  • 修复方案
  • 回归测试

示例标题:

fix(payment): prevent duplicate refund request

重构型 PR

重点说明:

  • 为什么需要重构
  • 行为是否保持不变
  • 如何验证没有改变外部行为
  • 是否为后续需求铺路

示例标题:

refactor(user): extract profile query service

文档型 PR

重点说明:

  • 补充了哪部分文档
  • 是否修正错误说明
  • 是否同步了示例代码

示例标题:

docs(git): add pull request workflow guide

推荐团队规范

团队可以约定:

  • 所有代码进入主分支必须经过 PR / MR
  • 每个 PR / MR 只解决一个明确问题
  • PR / MR 必须关联 issue 或任务
  • PR / MR 必须填写模板
  • CI 必须通过才能合并
  • 至少一名同伴 review
  • 高风险模块需要 owner review
  • 不允许提交密钥、构建产物和无关格式化
  • 合并后删除临时分支
  • 使用统一的 commit message 规范

示例规则:

main:
- 禁止直接 push
- 必须 PR 合并
- 必须 1 个 approval
- 必须通过 test、lint、build
release/*:
- 必须 2 个 approval
- 必须 QA 验证
- 必须填写风险说明

PR / MR 最佳实践

推荐做法:

  • 尽早创建 Draft PR,同步方向
  • 保持 PR 小而清晰
  • 标题使用规范格式
  • 描述写清背景、内容、测试和风险
  • 提交前自己先 review 一遍 diff
  • 把格式化和功能修改拆开
  • 不把多个需求塞进一个 PR
  • 对 review 意见逐条回应
  • 合并前确认 CI 和分支状态
  • 合并后删除已完成分支

一个高质量 PR / MR 应该让评审者做到:

看标题知道目的。
看描述知道背景。
看 diff 知道实现。
看测试说明知道如何验证。
看风险说明知道能不能合并。

24. Commit Message#

24.1 Commit Message是什么#

Commit message 是一次 Git 提交的说明文字,也就是开发者给这次代码变更写下的“历史说明”。

一次提交不仅包含代码快照,还应该包含一段能够解释这次变更的文字。代码告诉别人“改了哪里”,commit message 则告诉别人“为什么这样改、这次改动意味着什么、以后排查问题时应该如何理解这段历史”。

简单理解:

commit = 代码变更 + 变更说明 + 作者信息 + 时间信息 + 父提交关系

其中 commit message 是最容易被忽视、但对长期维护非常重要的一部分。

Commit Message 的基本作用

它应该回答:

  • 这次提交做了什么
  • 为什么要这样改
  • 是否有影响范围
  • 是否关联任务或 issue
  • 是否存在兼容性影响
  • 是否需要额外迁移、配置或测试

例如下面这个提交信息:

fix(auth): handle expired refresh token

它比下面这种写法更有价值:

fix bug

前者能看出:

  • 变更类型是 bug 修复
  • 影响范围是认证模块
  • 具体修复的是 refresh token 过期处理问题

后者只说明“修了一个 bug”,但没有告诉维护者 bug 在哪里、影响什么模块、以后如何定位。

Commit Message 服务的对象

Commit message 不是只写给 Git 的,它主要写给未来读代码的人。

它服务于:

  • 当前开发者:帮助自己整理变更思路
  • 同事:帮助 reviewer 快速理解提交意图
  • 维护者:帮助后续排查问题和回滚变更
  • 测试人员:帮助判断本次变更影响范围
  • 发布人员:帮助整理 release note 和 changelog
  • 自动化工具:帮助生成版本日志、识别语义化版本升级类型

很多时候,几个月之后再看一段代码,开发者已经忘记当时的上下文。清晰的 commit message 可以把当时的业务背景、技术决策和影响范围保留下来。

Commit Message 与代码 diff 的关系

代码 diff 只能说明“文件发生了什么变化”,但不一定能说明“为什么要这样变化”。

例如 diff 可能显示:

timeout = 3000
timeout = 10000

仅看代码,无法确定这是:

  • 修复接口超时问题
  • 适配弱网环境
  • 临时绕过后端性能问题
  • 为了兼容某个第三方服务

如果 commit message 写成:

fix(api): increase payment callback timeout
The payment provider occasionally responds after 3 seconds under high load.
Increase callback timeout to reduce false failure records.

维护者就能明确知道这次修改的背景和目的。

所以:

  • diff 负责说明“怎么改”
  • commit message 负责说明“为什么改”
  • issue / PR 负责说明“更完整的讨论和决策过程”

三者相互补充,不能完全互相替代。

好的 Commit Message 有什么特征

好的 commit message 能让团队:

  • 快速理解历史
  • 自动生成 changelog
  • 快速定位问题
  • 判断是否可以回滚
  • 支撑版本发布流程

更具体地说,它通常具备以下特征:

特征说明
清晰能一眼看出本次提交的主要目的
具体不使用 updatechangefix bug 这类过于模糊的描述
有边界能看出影响的模块、功能或文件范围
可追溯能关联 issue、需求、缺陷单或 PR
可维护几个月后仍然能帮助别人理解历史
可自动化能被工具解析,用于 changelog、版本发布和 CI 流程

Commit Message 与项目质量的关系

Commit message 不是形式主义,它直接影响项目的可维护性。

清晰的提交历史可以帮助团队:

  • 快速定位某个功能从哪次提交引入
  • 快速定位 bug 可能由哪次变更导致
  • 判断某个提交是否可以安全回滚
  • 在代码评审时按提交粒度阅读变更
  • 在版本发布时整理清晰的变更记录
  • 在新人接手项目时理解演进过程

如果一个项目的提交历史混乱,即使代码当前能运行,长期维护成本也会明显增加。

Commit Message 与代码评审的关系

在 Pull Request / Merge Request 中,reviewer 通常会同时看:

  • PR / MR 标题
  • PR / MR 描述
  • 单个 commit message
  • 代码 diff
  • 测试结果

如果每个 commit 都有清晰说明,reviewer 可以更容易按逻辑顺序阅读变更。

例如一个功能可以拆成:

feat(order): add order status enum
feat(order): implement order status transition
test(order): add status transition tests
docs(order): document order status lifecycle

这样的提交历史比一个巨大的 update order 更容易评审,也更容易在出问题时定位。

Commit Message 与 Changelog 的关系

很多团队会根据 commit message 自动生成 changelog。

例如:

feat(payment): support Apple Pay
fix(auth): reject expired token
perf(search): reduce query latency
docs(api): update authentication guide

可以被整理成:

Features
- payment: support Apple Pay
Bug Fixes
- auth: reject expired token
Performance
- search: reduce query latency
Documentation
- api: update authentication guide

如果提交信息没有规范,例如全部写成 updatefix bug,自动化工具就无法准确识别变更类型。

Commit Message 与回滚的关系

当线上出现问题时,团队经常需要判断:

  • 最近哪些提交可能相关
  • 哪个提交引入了风险
  • 哪个提交可以回滚
  • 回滚是否会影响其它功能

清晰的 commit message 能减少判断成本。

例如:

feat(cache): cache product detail response

如果上线后出现商品详情数据不刷新,就很容易联想到该提交。

而下面这种提交:

update product

就不容易判断它是否和缓存问题有关。

Commit Message 与 Issue 的关系

Commit message 可以通过 footer 或正文关联 issue。

常见写法:

fix(login): show error for locked account
Closes #128

或者:

feat(report): export monthly sales report
Refs #245

常见关键词:

关键词含义
Closes #123关闭对应 issue
Fixes #123修复并关闭对应 issue
Resolves #123解决并关闭对应 issue
Refs #123引用 issue,但不一定关闭
Related to #123与 issue 相关

是否自动关闭 issue 取决于代码托管平台,例如 GitHub、GitLab、Gitee 的具体规则可能略有差异。

Commit Message 与团队协作规范

团队项目中,commit message 最好形成统一规范,而不是每个人自由发挥。

团队可以约定:

  • 使用中文还是英文
  • 是否采用 Conventional Commits
  • type 可以有哪些
  • scope 如何命名
  • 标题最长多少字符
  • 是否必须关联 issue
  • 是否允许 WIP 提交进入主分支
  • squash merge 时如何整理提交信息
  • breaking change 如何标识

如果项目需要自动生成 changelog 或触发语义化版本发布,建议使用结构化规范,例如 Conventional Commits。

如果项目规模较小,也可以使用简化规范,但至少要保证标题清晰、范围明确、目的可读。

中文项目如何写 Commit Message

中文项目可以使用中文 commit message,关键是保持一致。

示例:

feat(订单): 增加订单状态流转校验
fix(登录): 修复锁定账号仍可登录的问题
docs(Git): 补充 commit message 规范说明

也可以使用英文 type + 中文描述:

feat(order): 增加订单状态流转校验
fix(login): 修复锁定账号仍可登录的问题
docs(git): 补充 commit message 规范说明

建议:

  • type 使用英文,方便工具识别
  • scope 可以使用英文模块名,方便跨语言协作
  • subject 可以根据团队习惯使用中文或英文
  • 同一个仓库中尽量保持风格一致

Commit Message 的常见误区

误区一:代码很简单,不需要写清楚

越是简单修改,越容易被忽略背景。

例如:

chore(config): increase upload size limit

这类配置修改看似简单,但上线后如果出现性能或安全问题,commit message 可以帮助快速定位。

误区二:PR 描述写了,commit message 就可以随便写

PR 描述很重要,但它不一定会随着 Git 历史长期保留在本地仓库中。

commit message 是 Git 历史的一部分,可以通过 git loggit showgit blame 等命令直接查看,因此仍然需要认真编写。

误区三:提交信息越长越好

好的 commit message 不是越长越好,而是信息足够。

简单改动可以只写一行:

docs(readme): fix setup command typo

复杂改动才需要补充正文说明背景、取舍和影响。

误区四:只要最终 squash,单个提交就无所谓

即使最终会 squash,开发过程中的提交历史仍然会影响 review 体验。

如果团队要求 squash merge,至少最终合并提交的信息需要认真整理,不能保留默认的 updatefixwip

Commit Message 的最小可用标准

如果暂时不引入复杂规范,至少做到以下几点:

<type>(<scope>): <简短说明>

例如:

fix(auth): handle expired token
feat(order): add cancel order API
docs(git): add commit message guide

最小标准:

  • 有明确 type
  • 有清楚的影响范围
  • subject 能说明具体变更
  • 不使用空泛词
  • 复杂变更补充正文
  • 重大变更说明兼容性影响

24.2 Commit Message 基本结构#

Commit message 的基本结构可以理解为:

标题:一句话说明这次提交做了什么
正文:解释为什么这样改、怎么改、有什么影响
页脚:补充 issue、破坏性变更、关联任务等元信息

推荐使用下面的结构:

<type>(<scope>): <subject>
<body>
<footer>

其中:

  • type:变更类型
  • scope:影响范围,可选
  • subject:一句话说明
  • body:详细说明,可选
  • footer:关联 issue 或 breaking change,可选

完整示例:

feat(auth): add email password login
Add email and password login for users who do not use third-party OAuth.
The login API now validates email format before password verification.
Closes #123

标题行

标题行是 commit message 中最重要的一行。

格式:

<type>(<scope>): <subject>

示例:

fix(login): handle empty password
feat(profile): add avatar upload
docs(git): add commit message guide
refactor(api): extract request client

标题行应该做到:

  • 一眼看出变更类型
  • 一眼看出影响范围
  • 一句话说明做了什么
  • 方便 git log --oneline 阅读
  • 方便自动生成 changelog

查看效果:

git log --oneline

好的输出应该像这样:

a1b2c3d feat(auth): add email password login
b2c3d4e fix(api): handle timeout error
c3d4e5f docs(git): add commit message examples

type:说明变更类型

type 表示这次提交属于哪类变更。

常见类型:

type说明
feat新功能
fixBug 修复
docs文档变更
style代码格式调整,不影响逻辑
refactor重构,不新增功能也不修复 bug
perf性能优化
test测试相关
build构建系统、依赖管理
ciCI/CD 配置
chore杂项维护
revert回滚提交

示例:

feat(search): add fuzzy search
fix(order): prevent duplicate payment
docs(readme): update installation guide
test(user): add registration unit tests
ci: add release workflow

type 的价值是让人和工具都能快速判断提交性质。

例如:

  • feat 通常进入版本发布说明。
  • fix 通常进入修复列表。
  • docs 通常不触发产品版本号变化。
  • cibuild 通常影响工程流程。

scope:说明影响范围

scope 表示这次提交影响哪个模块、目录、功能或系统边界。

格式:

type(scope): subject

示例:

fix(auth): reject expired token
feat(android): add offline cache
build(gradle): update kotlin plugin
docs(git): add branch workflow notes

常见 scope:

scope含义
auth登录、鉴权、权限
api接口、请求、响应处理
ui页面或组件
db数据库、迁移脚本
config配置文件
deps依赖
ci持续集成
docs文档
androidAndroid 端
server服务端

scope 不一定必须写,但在中大型项目中非常有用。

建议:

  • 不要太宽泛,例如 appcode
  • 不要太细碎,例如 login-button-left-icon-color
  • 优先使用团队已有模块名。
  • 同一仓库中保持命名一致。

如果变更影响多个模块,可以:

feat: add unified error handling

或者使用较高层级 scope:

refactor(core): unify error handling

subject:一句话说明做了什么

subject 是标题中的描述部分。

示例:

fix(login): handle empty password

其中 handle empty password 就是 subject。

写 subject 的原则:

  • 简短直接
  • 说明结果,而不是过程
  • 不写句号结尾
  • 不写模糊词
  • 不重复 type 和 scope 中已有的信息

推荐:

fix(login): handle empty password
feat(profile): add avatar upload
refactor(api): extract request client

不推荐:

fix login bug
update code
change files
fix issue
modify auth

原因:

  • fix login bug 没说修了什么。
  • update code 信息量太低。
  • change files 对阅读历史没有帮助。
  • fix issue 没说明问题本身。

更好的写法:

fix(login): reject empty password
fix(auth): refresh token before expiration
fix(order): prevent duplicate checkout

body:解释为什么和怎么做

body 是提交正文,用来解释标题说不清的内容。

适合写 body 的情况:

  • 变更原因比较复杂
  • 涉及取舍或兼容性
  • 修复的问题不容易从代码看出来
  • 变更影响多个模块
  • 需要说明迁移方式
  • 需要提醒后续维护者

示例:

fix(cache): avoid stale user profile
The profile page reused cached user data after logout and login.
This change clears profile cache when the active user id changes.

正文通常回答三个问题:

为什么改?
具体改了什么?
有什么影响?

示例:

refactor(api): split request retry logic
Move retry decision logic out of HttpClient to make timeout and
authorization failures easier to test separately.
No public API behavior is changed.

body 不应该只是重复标题。

不推荐:

fix(login): fix login bug
Fix login bug.

推荐:

fix(login): reject expired verification code
The previous login flow only checked whether the code existed.
It did not verify expiration time, so old codes could still pass.

footer:补充元信息

footer 用来放和提交相关的元信息,例如:

  • 关闭 issue
  • 关联任务
  • 标记破坏性变更
  • 标记审核信息
  • 标记迁移说明

常见写法:

Closes #123
Fixes #456
Refs #789
BREAKING CHANGE: remove legacy login endpoint

完整示例:

fix(payment): prevent duplicate charge
Add idempotency key validation before creating a payment order.
Fixes #342

多个 footer:

feat(api): add v2 user endpoint
Add a new user endpoint with cursor-based pagination.
Refs #120
BREAKING CHANGE: remove page-based pagination from user list API

Breaking Change 的写法

破坏性变更是指会让旧用法失效的变更。

例如:

  • 删除公开 API
  • 修改接口字段含义
  • 移除配置项
  • 修改数据库结构且不可兼容
  • 改变命令行参数
  • 修改 SDK 的公开方法签名

写法一:在 type 后加 !

feat(api)!: remove legacy user endpoint

写法二:在 footer 写 BREAKING CHANGE

feat(api): remove legacy user endpoint
BREAKING CHANGE: `/api/v1/users` is removed. Use `/api/v2/users` instead.

推荐在破坏性变更中写清楚:

  • 旧行为是什么
  • 新行为是什么
  • 用户或调用方需要怎么迁移

示例:

feat(config)!: rename auth token option
BREAKING CHANGE: `auth.token` is renamed to `auth.accessToken`.
Update application config before deploying this version.

和 issue / 任务系统关联

Commit message 可以关联 issue、需求或缺陷单。

常见格式:

Closes #123
Fixes #123
Refs #123
Related to #123

区别:

写法含义
Closes #123合并后关闭 issue
Fixes #123表示修复并关闭 issue
Refs #123仅引用,不关闭
Related to #123表示有关联

示例:

fix(upload): reject unsupported file type
Return a clear validation error for unsupported image formats.
Fixes #88

如果团队使用 Jira、禅道、Tapd 等任务系统,也可以把任务号写入 scope、subject 或 footer。

示例:

feat(order): add invoice export
Refs PROJ-1024

单行提交和多行提交

不是所有提交都需要完整三段式。

简单提交可以只写一行:

docs(readme): fix typo in install command

中等复杂提交建议写标题加正文:

fix(auth): refresh token before expiration
Refresh access token one minute before expiration to avoid request failure
caused by client and server clock drift.

涉及任务、风险或破坏性变更时,建议写完整结构:

feat(api)!: replace page pagination with cursor pagination
Cursor pagination avoids missing records when new data is inserted during
list traversal.
BREAKING CHANGE: `page` and `pageSize` are removed from the user list API.
Use `cursor` and `limit` instead.
Refs #901

判断标准:

别人半年后看这个提交,还能不能理解为什么这样改?

如果不能,就应该补充 body 或 footer。


好的结构示例

修复类:

fix(login): reject expired verification code
The previous flow only checked whether the code existed.
This allowed expired codes to pass validation.
Fixes #231

功能类:

feat(profile): add avatar upload
Add image validation, upload progress, and avatar preview before saving.

重构类:

refactor(api): extract retry policy
Move retry condition checks into RetryPolicy so timeout, network,
and authorization failures can be tested independently.

构建类:

build(gradle): update kotlin plugin to 2.0.0
Update Kotlin Gradle plugin and align stdlib versions across modules.

文档类:

docs(git): add commit message examples
Add examples for feat, fix, refactor, breaking change, and issue links.

编写步骤

写 commit message 时可以按下面顺序思考:

  1. 这次提交属于什么类型?确定 type
  2. 影响哪个模块?确定 scope
  3. 一句话说明做了什么?写 subject
  4. 这次变更为什么必要?必要时写 body
  5. 是否关联 issue、任务或破坏性变更?必要时写 footer

模板:

<type>(<scope>): <subject>
<why>
<what changed>
<impact>
<footer>

24.3 Conventional Commits 规范#

Conventional Commits 是目前非常常用的一套提交信息规范,中文通常称为“约定式提交”。

它的目标不是让提交信息看起来更复杂,而是让提交历史具备稳定结构,使人和工具都能理解每次提交的含义。

简单说:

Conventional Commits = 规范化的 commit message 写法

它把提交信息拆成固定结构:

  • 变更类型
  • 影响范围
  • 简短描述
  • 详细说明
  • 关联 issue
  • 破坏性变更说明

这样做可以让提交历史不仅能读,还能被自动化工具解析。


Conventional Commits 解决什么问题

没有规范时,提交历史可能长这样:

update
fix
modify
调整
bug fixed
wip

这些提交信息的问题是:

  • 看不出修改类型
  • 看不出影响模块
  • 看不出是否是新功能
  • 看不出是否修复了 bug
  • 无法自动生成 changelog
  • 无法判断版本号应该如何升级
  • 后期排查问题成本高

使用 Conventional Commits 后,历史会变成:

feat(auth): add email login
fix(order): prevent duplicate payment
docs(git): add commit message guide
refactor(api): extract request client
test(user): add profile update tests

阅读者可以快速看出:

  • feat 是新功能
  • fix 是 bug 修复
  • docs 是文档变更
  • authorderapi 是影响范围
  • 冒号后面是本次提交的简要说明

26.2 基本格式

基本格式:

<type>[optional scope]: <description>
[optional body]
[optional footer(s)]

更接近实际使用的写法是:

<type>(<scope>): <subject>
<body>
<footer>

其中:

部分是否必需作用
type必需表示变更类型
scope可选表示影响范围
description / subject必需一句话说明变更内容
body可选说明背景、原因、实现思路、影响
footer可选关联 issue、标记 breaking change

示例:

feat(auth): add email login
Add email and password login flow for users who do not use OAuth.
Closes #123

这个示例表示:

  • feat:新增功能
  • auth:影响认证模块
  • add email login:增加邮箱登录
  • 正文说明为什么增加这个能力
  • Closes #123:关闭对应 issue

type 的作用

type 是提交类型,用于说明这次提交属于哪类变更。

常见类型包括:

feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert

示例:

feat(search): add fuzzy matching
fix(login): reject expired token
docs(readme): update installation guide
test(order): add refund tests

type 的价值在于:

  • 让人快速理解变更性质
  • 让 changelog 工具按类型分组
  • 让发布工具推断版本号变化
  • 让团队形成统一提交语言

更详细的常用 type 会在下一章单独说明。


scope 的作用

scope 表示影响范围,通常写模块名、包名、功能名或子系统名。

格式:

type(scope): description

示例:

fix(auth): handle locked account login
feat(payment): support refund callback
docs(git): add lfs usage guide

scope 可以帮助阅读者快速定位这次提交影响哪里。

常见 scope:

auth
user
order
payment
api
ui
db
config
deps
ci
docs
android
backend

建议:

  • 使用项目中真实存在的模块名
  • 不要过度细分
  • 不要全部写成 common
  • 同一个模块保持同一种写法
  • 团队可以维护一份 scope 列表

description / subject 的写法

description 是提交标题中的简短说明。

示例:

fix(cache): clear user cache after logout

其中:

clear user cache after logout

就是 description。

它应该做到:

  • 简短
  • 具体
  • 说明本次提交做了什么
  • 不以句号结尾
  • 避免空泛词

推荐:

fix(order): prevent duplicate payment
feat(profile): add avatar upload
docs(api): document token refresh flow

不推荐:

fix: fix bug
feat: add feature
update: update code
chore: modify files

26.6 body 的写法

body 是提交正文,用于说明标题无法表达完整的信息。

适合写在 body 中的内容:

  • 为什么要这样改
  • 问题产生的原因
  • 采用了什么实现方案
  • 有没有替代方案
  • 是否有兼容性影响
  • 是否需要迁移数据或配置
  • 测试方式或验证结果

示例:

fix(payment): retry callback verification
The payment provider may return a temporary network error during callback
verification. Add a retry with exponential backoff to reduce false failures.
Closes #412

如果提交很简单,可以没有 body:

docs(readme): fix setup command typo

判断是否需要 body 的标准:

如果只看标题无法理解背景,就应该写 body。

footer 的写法

footer 通常用于写关联 issue、PR、任务号或 breaking change。

常见写法:

Closes #123
Refs #456
Related to #789

示例:

fix(login): show error for locked account
Locked users were redirected to the home page without a clear error message.
Closes #128

footer 的作用:

  • 建立提交与需求、缺陷、任务之间的关联
  • 帮助平台自动关闭 issue
  • 帮助维护者追溯上下文
  • 帮助自动化工具生成发布记录

Breaking Change 的写法

Breaking Change 表示破坏性变更,也就是可能导致旧版本调用方式、配置、数据结构或行为不兼容的改动。

Conventional Commits 中有两种常见标记方式。

第一种:在 type 或 scope 后加 !

feat(api)!: remove legacy user endpoint

第二种:在 footer 中写 BREAKING CHANGE:

feat(api): remove legacy user endpoint
BREAKING CHANGE: The /v1/users endpoint has been removed. Use /v2/users instead.

也可以两者同时使用:

feat(auth)!: require two-factor authentication
BREAKING CHANGE: Password-only login is no longer supported for admin users.

常见 breaking change:

  • 删除公开 API
  • 修改接口入参或返回结构
  • 修改数据库字段含义
  • 删除配置项
  • 修改默认行为
  • 升级依赖导致不兼容
  • 修改鉴权规则

只要可能影响调用方、用户、部署环境或下游系统,就应该明确标记。


Conventional Commits 与语义化版本

Conventional Commits 常与 Semantic Versioning 一起使用。

语义化版本格式:

MAJOR.MINOR.PATCH

常见对应关系:

提交类型版本影响示例
fixPATCH1.2.3 -> 1.2.4
featMINOR1.2.3 -> 1.3.0
BREAKING CHANGE!MAJOR1.2.3 -> 2.0.0

示例:

fix(auth): reject expired token

通常表示补丁版本升级。

feat(payment): support Apple Pay

通常表示次版本升级。

feat(api)!: remove legacy endpoint

通常表示主版本升级。

这也是 Conventional Commits 能用于自动发布的核心原因。


完整示例

简单 bug 修复

fix(login): handle empty password input

适合非常简单、无需额外解释的修复。

带正文的 bug 修复

fix(order): prevent duplicate payment submission
Disable the submit button while the payment request is pending.
This prevents users from creating duplicate payment records by double clicking.
Closes #236

适合需要说明原因和验证方式的修复。

新功能

feat(report): export monthly sales report
Add CSV export for monthly sales data and include order count, total amount,
refund amount, and net revenue columns.
Refs #310

适合完整功能提交。

破坏性变更

feat(config)!: rename database connection settings
BREAKING CHANGE: DB_HOST and DB_PORT are replaced by DATABASE_URL.
Update deployment configuration before upgrading.

适合会影响部署、调用或升级的提交。

回滚提交

revert: feat(cache): cache product detail response
This reverts commit 4f3a2b1 because cached product data may remain stale after inventory updates.

适合明确说明为什么回滚。


中文项目中的写法

中文项目可以采用“英文 type + 中文描述”的方式。

示例:

feat(order): 增加订单取消接口
fix(login): 修复锁定账号仍可登录的问题
docs(git): 补充 Conventional Commits 说明

也可以使用英文:

feat(order): add cancel order API
fix(login): reject locked account login
docs(git): add conventional commits guide

建议:

  • type 使用英文,便于工具识别
  • scope 尽量使用英文模块名
  • description 可按团队习惯使用中文或英文
  • 一个仓库中保持统一风格

与普通 Commit Message 结构的关系

普通 commit message 可以写成:

标题
正文
footer

Conventional Commits 在这个基础上进一步规定了标题格式:

type(scope): description

也就是说,Conventional Commits 不是另一种 Git 功能,而是对 commit message 的一种结构化约定。

Git 本身并不强制你使用它,但团队、CI、commitlint、semantic-release 等工具可以强制检查。


常用工具

团队落地 Conventional Commits 时,常见工具包括:

工具作用
commitlint检查 commit message 是否符合规范
husky在 Git hooks 中运行检查脚本
lint-staged对暂存文件执行 lint 或格式化
Commitizen提供交互式提交信息生成
semantic-release根据 commit 自动计算版本并发布
standard-version根据 commit 生成 changelog 和版本号

典型流程:

开发者提交代码
-> commit-msg hook 检查提交信息
-> PR / MR 检查提交历史
-> 合并到主分支
-> 发布工具读取 commit
-> 自动生成 changelog 和版本号

commitlint 简单示例

在前端或 Node 项目中,可以使用 commitlint 检查提交信息。

安装:

npm install --save-dev @commitlint/cli @commitlint/config-conventional

配置 commitlint.config.js

module.exports = {
extends: ['@commitlint/config-conventional'],
};

结合 Git hook 后,可以在提交时自动检查。

如果提交信息不符合规范:

update

可能会被拒绝。

正确写法:

fix(auth): handle expired token

团队落地建议

落地 Conventional Commits 时,不建议一开始就把规则定得过于复杂。

推荐步骤:

  1. 先统一基本格式:type(scope): description
  2. 再统一常用 type 列表
  3. 再约定 scope 命名方式
  4. 再接入 commitlint
  5. 最后接入 changelog 或自动发布

团队可以先使用最小规则:

feat: 新功能
fix: 修复问题
docs: 文档修改
refactor: 重构
test: 测试
chore: 维护任务

等团队习惯后,再增加 perfbuildcistylerevert 等类型。


24.4 常用 type#

type 是 Conventional Commits 中最重要的字段之一,用来说明一次提交的变更类型。

它回答的问题是:

这次提交主要属于哪一类改动?

常见的 type 包括:

type含义示例
feat新功能feat(search): add fuzzy search
fix修复 bugfix(login): handle empty password
docs文档修改docs(readme): update setup guide
style格式调整,不影响逻辑style: format kotlin files
refactor重构,不新增功能不修 bugrefactor(api): extract request client
perf性能优化perf(cache): reduce disk reads
test测试相关test(auth): add login tests
build构建系统或依赖build(gradle): update kotlin plugin
ciCI 配置ci: add release workflow
chore杂项维护chore: update gitignore
revert回滚提交revert: remove unstable login change

type 看起来只是一个短词,但它会影响:

  • 提交历史的可读性
  • changelog 的分类
  • 版本号的自动升级
  • PR / MR 的审查效率
  • 团队对变更性质的理解

feat:新增功能

feat 表示新增功能。

这里的“功能”通常指用户、调用方或业务能够感知到的新能力。

示例:

feat(auth): add email login
feat(order): support order cancellation
feat(report): export monthly sales report
feat(search): add fuzzy search

适合使用 feat 的场景:

  • 新增页面
  • 新增接口
  • 新增命令
  • 新增配置项
  • 新增业务流程
  • 新增用户可见能力
  • 新增第三方集成

不适合使用 feat 的场景:

  • 修复 bug
  • 只修改文档
  • 只调整格式
  • 只重构内部实现
  • 只补充测试

判断标准:

这次提交是否让系统增加了一个新的可用能力?

如果答案是肯定的,通常使用 feat


fix:修复问题

fix 表示修复 bug 或错误行为。

示例:

fix(login): handle empty password
fix(auth): reject expired token
fix(order): prevent duplicate payment
fix(ui): correct button alignment on mobile

适合使用 fix 的场景:

  • 修复接口异常
  • 修复页面展示错误
  • 修复边界条件问题
  • 修复数据不一致
  • 修复鉴权漏洞
  • 修复空指针、崩溃、异常
  • 修复线上缺陷

判断标准:

这次提交是否把错误行为改成了正确行为?

如果是,通常使用 fix

注意:如果是安全漏洞修复,有些团队会使用自定义 security,但如果团队没有定义该类型,使用 fix 更稳妥。


docs:文档修改

docs 表示文档相关变更。

示例:

docs(readme): update setup guide
docs(api): document token refresh flow
docs(git): add commit message examples
docs(android): add release build notes

适合使用 docs 的场景:

  • README 修改
  • API 文档修改
  • 学习笔记修改
  • 使用说明修改
  • 注释文档补充
  • 架构说明更新
  • changelog 手动维护

如果一次变更既包含代码又包含文档,建议拆成两个提交:

feat(api): add pagination support
docs(api): document pagination parameters

这样历史更清楚,也方便 changelog 分类。


style:格式调整

style 表示不影响代码逻辑的格式调整。

示例:

style: format kotlin files
style(api): remove trailing spaces
style(ui): normalize css indentation
style(java): apply spotless formatting

适合使用 style 的场景:

  • 代码格式化
  • 调整缩进
  • 删除多余空格
  • 调整换行
  • 统一引号风格
  • 统一分号风格
  • 自动格式化工具输出

重要区别:

style 不是“界面样式变更”的默认 type。

如果改 CSS 只是格式化文件,可以用:

style(ui): format stylesheet

如果改 CSS 影响了实际页面显示,应该根据目的选择:

fix(ui): correct button alignment
feat(theme): add dark mode

refactor:代码重构

refactor 表示重构代码。

重构的核心是:

改善内部结构,但不改变外部行为。

示例:

refactor(auth): extract token validator
refactor(api): split request client
refactor(order): simplify status transition logic
refactor(db): move query builder to repository layer

适合使用 refactor 的场景:

  • 抽取函数
  • 拆分类
  • 合并重复逻辑
  • 调整模块结构
  • 改善命名
  • 降低复杂度
  • 改善可测试性

不适合使用 refactor 的场景:

  • 新增业务功能
  • 修复明显 bug
  • 性能优化是主要目标
  • 修改对外接口行为

如果重构过程中发现并修复 bug,最好拆开:

refactor(auth): extract token validator
fix(auth): reject expired token

这样后续排查问题时更容易定位。


27.6 perf:性能优化

perf 表示性能优化。

示例:

perf(search): reduce query latency
perf(cache): reduce memory usage
perf(db): batch insert audit logs
perf(image): cache decoded thumbnails

适合使用 perf 的场景:

  • 降低接口耗时
  • 减少内存占用
  • 减少数据库查询
  • 减少网络请求
  • 优化缓存策略
  • 优化渲染性能
  • 优化算法复杂度

性能优化最好在正文里说明依据。

示例:

perf(search): cache normalized query tokens
Avoid repeated token normalization during ranking.
Average query time drops from 120ms to 75ms in local benchmark.

如果只是整理代码结构,但性能不是目标,应该使用 refactor


test:测试相关

test 表示测试相关变更。

示例:

test(auth): add expired token tests
test(order): cover cancellation flow
test(api): add contract tests
test(ui): add login form snapshot tests

适合使用 test 的场景:

  • 新增单元测试
  • 新增集成测试
  • 新增端到端测试
  • 修改测试断言
  • 修复测试数据
  • 调整测试工具
  • 增加测试覆盖率

如果功能和测试一起提交也可以,但在较严格团队中,更推荐拆成:

feat(order): add cancellation API
test(order): add cancellation API tests

这样 reviewer 可以先看功能实现,再看测试覆盖。


build:构建系统或依赖

build 表示影响构建系统、依赖管理、打包流程的变更。

示例:

build(gradle): update kotlin plugin
build(maven): add surefire plugin
build(npm): update vite dependency
build(docker): add production image

适合使用 build 的场景:

  • 修改 Gradle 配置
  • 修改 Maven 配置
  • 修改 npm / pnpm / yarn 配置
  • 修改 Dockerfile
  • 修改依赖锁文件
  • 升级构建插件
  • 调整打包脚本
  • 修改编译目标版本

常见 scope:

gradle
maven
npm
deps
docker
android

依赖升级可以用:

build(deps): update okhttp to 4.12.0

如果团队更喜欢单独使用 chore(deps),也可以,但必须保持一致。


ci:持续集成配置

ci 表示 CI/CD 配置或流水线相关变更。

示例:

ci: add release workflow
ci(github): run tests on pull request
ci(gitlab): cache gradle dependencies
ci(jenkins): add deploy stage

适合使用 ci 的场景:

  • GitHub Actions
  • GitLab CI
  • Jenkins Pipeline
  • Gitee Go
  • CircleCI
  • Travis CI
  • 自动测试流程
  • 自动部署流程
  • CI 缓存策略
  • 分支保护检查

buildci 的区别:

类型关注点
build项目如何构建、打包、管理依赖
ci自动化流水线如何运行

示例:

build(gradle): enable configuration cache
ci(github): cache gradle wrapper

chore:杂项维护

chore 表示维护类杂项变更。

示例:

chore: update gitignore
chore(repo): add editorconfig
chore(cleanup): remove unused files
chore(config): update local development example

适合使用 chore 的场景:

  • 更新 .gitignore
  • 添加 .editorconfig
  • 清理无用文件
  • 调整仓库元信息
  • 修改脚手架配置
  • 更新非业务性项目文件

注意:chore 是兜底类型,但不能滥用。

不推荐:

chore: fix login bug
chore: add payment page
chore: update readme
chore: add tests

推荐:

fix(login): reject expired token
feat(payment): add payment page
docs(readme): update setup guide
test(auth): add login tests

如果能归类到更明确的 type,就不要使用 chore


revert:回滚提交

revert 表示回滚之前的提交。

示例:

revert: feat(auth): add email login

Git 自动生成的 revert message 通常类似:

Revert "feat(auth): add email login"
This reverts commit abc1234.

建议保留:

  • 被回滚提交的标题
  • 被回滚提交的 hash
  • 回滚原因
  • 是否有后续修复计划

更清晰的示例:

revert: feat(cache): cache product detail response
This reverts commit 4f3a2b1 because cached product data may remain stale
after inventory updates.

适合使用 revert 的场景:

  • 回滚线上故障提交
  • 回滚误合并内容
  • 回滚不兼容功能
  • 回滚有风险的实验性变更

常见 type 选择速查

场景推荐 type
新增用户登录方式feat
修复 token 过期仍可访问fix
修改 README 安装步骤docs
只格式化代码style
抽取公共方法但行为不变refactor
减少接口响应时间perf
新增单元测试test
修改 Gradle 配置build
修改 GitHub Actionsci
更新 .gitignorechore
回滚某个提交revert

团队自定义 type

Conventional Commits 允许团队自定义 type。

常见自定义类型:

type含义
deps依赖升级
security安全修复
i18n国际化
a11y可访问性
release发布相关
config配置调整

但是自定义 type 要谨慎。

建议:

  • 写入团队规范
  • 配置 commitlint
  • 避免和已有 type 重叠
  • 不要定义太多
  • 保证所有成员理解含义

例如,如果团队已经用:

build(deps): update kotlin plugin

就不一定需要再定义:

deps: update kotlin plugin

两种方式都可以,但同一个仓库不要混用。


type 与版本发布的关系

如果项目使用自动化发布工具,type 可能影响版本号。

常见规则:

提交版本影响
fixPATCH
featMINOR
type!MAJOR
BREAKING CHANGEMAJOR
docsteststylechore通常不触发正式版本升级

示例:

fix(auth): reject expired token

可能生成:

1.2.3 -> 1.2.4
feat(order): add cancellation API

可能生成:

1.2.3 -> 1.3.0
feat(api)!: remove legacy endpoint

可能生成:

1.2.3 -> 2.0.0

这不是 Git 的内置规则,而是语义化版本工具和团队约定。


一个提交只选择一个主要 type

一次提交最好只表达一个主要目的。

不推荐:

feat(order): add cancellation API and update docs and fix login bug

推荐拆分:

feat(order): add cancellation API
docs(order): document cancellation API
fix(login): reject expired token

这样做的好处:

  • 容易 review
  • 容易回滚
  • 容易生成 changelog
  • 容易定位问题
  • 提交历史更清晰

如果一个提交很难选择 type,通常说明它做了太多事情。


type 的最佳实践

推荐做法:

  • 使用小写英文
  • 使用团队约定的固定列表
  • 优先选择更具体的 type
  • 不随意发明 type
  • 不滥用 chore
  • 不把多个不相关变更放进一个提交
  • 使用 commitlint 固化规则
  • 在贡献指南中写清楚 type 含义

提交前可以用这个问题自查:

这次提交的主要目的是什么?

如果主要目的是新增能力,用 feat
如果主要目的是修复问题,用 fix
如果主要目的是补充测试,用 test
如果主要目的是调整构建,用 build
如果主要目的是调整 CI,用 ci


24.5 scope#

scope 怎么写

scope 表示一次提交的影响范围,通常写在 type 后面的括号中。

基本格式:

<type>(<scope>): <subject>

例如:

fix(auth): reject expired token
feat(order): add cancel order API
docs(git): add commit message examples

其中:

  • fixfeatdocstype
  • authordergitscope
  • 冒号后面是本次提交的简短说明

简单理解:

type 说明改动类型。
scope 说明改动范围。
subject 说明具体做了什么。

scope 的作用

scope 的核心作用是帮助读者快速判断这次提交影响哪里。

没有 scope 的提交:

fix: reject expired token

有 scope 的提交:

fix(auth): reject expired token

第二种写法更清楚,因为它直接告诉读者:

这是 auth 模块的修复。

scope 的价值主要体现在:

  • 阅读 git log 时快速定位模块
  • 生成 changelog 时按模块归类
  • 代码评审时判断影响范围
  • 排查线上问题时缩小搜索范围
  • 判断提交是否适合回滚
  • 判断是否需要通知相关模块负责人

例如:

feat(payment): support Apple Pay
fix(search): handle empty keyword
perf(cache): reduce product detail reads

这些提交即使不看代码,也能大致知道改动发生在哪个区域。


scope 是可选的,但建议使用

在 Conventional Commits 中,scope 是可选项。

下面两种都合法:

fix: correct typo
fix(docs): correct typo

是否使用 scope 取决于项目规模。

适合省略 scope 的场景:

  • 个人项目
  • 很小的脚本项目
  • 影响范围非常明显
  • 修改的是整个仓库级别的配置

建议使用 scope 的场景:

  • 多模块项目
  • 前后端混合仓库
  • Android / iOS / Backend 共用仓库
  • Monorepo
  • 有自动 changelog 需求
  • 团队多人协作

对于团队项目,建议默认写 scope。即使规范允许省略,也应该尽量保持提交历史可读。


scope 的常见来源

常见 scope:

  • auth
  • api
  • ui
  • db
  • docs
  • config
  • deps
  • ci
  • android
  • backend

也可以按实际项目拆分为更多类型。

按业务模块划分

适合业务系统。

auth
user
order
payment
product
cart
search
report
message
notification

示例:

feat(order): add order cancel reason
fix(payment): handle duplicate callback
perf(search): cache hot keyword result

按技术层划分

适合分层清晰的后端或基础设施项目。

api
service
repository
db
cache
config
security
logging
metrics

示例:

refactor(service): split order validation logic
fix(db): add missing index for order query
chore(config): update default timeout

按端或平台划分

适合多端项目。

web
android
ios
backend
admin
desktop
miniapp

示例:

feat(android): add offline cache
fix(web): prevent duplicate form submit
build(ios): update signing configuration

按包或子项目划分

适合 Monorepo。

app
server
admin
shared
ui
cli
sdk
docs

示例:

feat(cli): add init command
fix(ui): correct button loading state
build(shared): publish common package

按工程系统划分

适合构建、部署、CI、依赖更新。

ci
deps
docker
gradle
vite
webpack
release
lint
test

示例:

ci(github): add release workflow
build(gradle): upgrade kotlin plugin
chore(deps): update okhttp version

scope 的命名原则

scope 不要太细,也不要太泛。
能帮助阅读者快速定位模块即可。

好的 scope 应该满足:

  • 简短
  • 稳定
  • 可理解
  • 和项目结构或业务模块一致
  • 不频繁变化
  • 团队成员能达成共识

推荐:

auth
order
payment
search
android
backend
docs
ci
deps

不推荐:

some-code
misc
stuff
temp
new
fix
common-change
zhangsan
today

原因:

  • miscstuff 太泛
  • temptoday 没有长期意义
  • zhangsan 按人命名,不表达影响范围
  • fix 是 type,不应该当 scope

scope 粒度怎么控制

scope 最难的是粒度。

太粗:

fix(app): handle expired token
feat(project): add refund API

问题是 appproject 过于宽泛,不能帮助定位影响范围。

太细:

fix(auth-token-refresh-handler-service): handle expired token
feat(order-refund-controller-method): add refund API

问题是太长、太依赖具体代码结构,后续重构后容易失效。

更合适:

fix(auth): handle expired token
feat(order): add refund API

判断标准:

scope 应该让人知道影响哪个模块,但不需要精确到类名、函数名或文件名。

一般建议:

  • 业务系统:按业务模块划分
  • 前端项目:按页面、组件域或功能模块划分
  • 后端项目:按业务域或服务模块划分
  • Monorepo:按 package、app、service 划分
  • 工程配置:按工具或系统划分

多个模块同时修改时 scope 怎么写

如果一次提交影响多个模块,有几种处理方式。

1. 优先拆分提交

如果多个模块的修改可以独立理解,优先拆成多个 commit。

例如:

fix(auth): reject expired token
fix(user): refresh profile after login

这通常比下面这种更清楚:

fix(auth,user): update login behavior

2 使用更高层级 scope

如果多个模块属于同一个更大的业务域,可以使用上层 scope。

例如同时修改订单创建、订单支付、订单查询:

feat(order): support order split shipment

3 使用 cross-cutting scope

如果是横切改动,可以使用工程类 scope。

例如:

chore(deps): update spring boot version
style(format): apply ktlint rules
refactor(error-handling): unify API error response

4 谨慎使用多个 scope

有些团队允许:

fix(auth,user): sync login profile state

但多个 scope 会降低一致性,也不一定被工具很好识别。

建议:

能拆就拆;不能拆时使用更合适的上层 scope。

scope 和 type 的区别

很多初学者会混淆 typescope

字段作用示例
type说明变更类型featfixdocs
scope说明影响范围authorderci
subject说明具体内容add email login

示例:

fix(auth): reject expired token

拆解:

fix -> 这是 bug 修复
auth -> 影响认证模块
reject expired token -> 具体修复内容

错误示例:

auth(login): fix expired token

这里 auth 被误放到了 type 的位置。

正确写法:

fix(auth): handle expired token

scope 和分支名的关系

scope 可以和分支名保持一致,但不要求完全相同。

例如分支:

feature/order-cancel

提交:

feat(order): add cancel order API
test(order): add cancel order service tests
docs(order): document cancel order flow

这样分支名、commit message、PR 标题之间形成统一语义。

如果分支是:

fix/payment-callback-timeout

提交可以是:

fix(payment): increase callback timeout
test(payment): cover delayed callback scenario

这比所有提交都写成下面这样更清楚:

fix: update callback
test: add test

scope 的最佳实践

推荐做法:

  • 默认使用 scope,除非变更确实是全局性的
  • scope 使用小写英文
  • scope 尽量与模块、业务域或 package 对齐
  • 不使用人名、日期、临时词
  • 不把 type 写成 scope
  • 不要过度细化到类名或函数名
  • 多模块改动优先拆分 commit
  • 团队维护一份允许的 scope 列表
  • PR 标题和 squash commit 也使用相同 scope 规范

一个较好的 scope 列表示例:

auth
user
order
payment
product
search
cart
api
db
cache
web
android
ios
docs
ci
deps
build
release

s

24.6. subject#

subject 是 commit message 标题里的描述部分,也就是冒号后面的那句话。

在下面这个提交信息中:

fix(parser): handle empty input

各部分含义是:

部分内容说明
fixtype表示这是一次 bug 修复
parserscope表示影响解析器模块
handle empty inputsubject表示具体做了什么

subject 的作用是用一句简短、明确的话说明本次提交的核心变更。

可以理解为:

type 说明变更类型。
scope 说明影响范围。
subject 说明具体动作。

subject 的核心目标

subject 应该让读者在 git log --oneline 中快速理解这次提交。

例如:

git log --oneline

如果输出是:

a1b2c3d fix(parser): handle empty input
b2c3d4e feat(auth): add email login
c3d4e5f docs(git): add commit message examples

读者不需要打开 diff,就能大致知道每次提交的目的。

好的 subject 应该回答:

  • 这次提交具体做了什么
  • 影响哪个行为或能力
  • 和其它提交相比有什么区别

不应该只写:

  • 改了代码
  • 修了 bug
  • 做了调整
  • 更新了一下

这些信息对后续维护几乎没有帮助。


subject 的基本规则

建议:

  • 简短明确
  • 使用动词开头
  • 只描述一件事
  • 不以句号结尾
  • 不写模糊词
  • 不重复 type 和 scope
  • 不写实现细节堆砌
  • 不写无意义的情绪化描述

推荐格式:

<动词> <对象>

或者:

<动词> <对象> <条件/结果>

示例:

add email login
handle empty input
reject expired token
remove unused config
update setup guide
extract request serializer

完整 commit 示例:

feat(auth): add email login
fix(parser): handle empty input
fix(auth): reject expired token
chore(config): remove unused option
docs(readme): update setup guide
refactor(api): extract request serializer

subject 应该写“结果”,不是写“过程”

subject 最好描述提交带来的结果,而不是描述自己编辑代码的过程。

不推荐:

change login file
modify parser code
update several classes
adjust user service

这些写法只说明“你改了文件”,没有说明“系统行为发生了什么变化”。

推荐:

fix(login): reject empty password
fix(parser): handle empty input
refactor(user): split profile service
feat(order): add cancellation reason

对比:

不推荐推荐
update auth codefix(auth): reject expired token
change order logicfix(order): prevent duplicate payment
modify configchore(config): remove unused timeout option
update docsdocs(api): add token refresh example

subject 要具体,不要空泛

subject 最常见的问题是过于空泛。

差的例子:

update code
fix bug
misc changes
change logic
optimize
adjust
modify

这些写法的问题:

  • 不知道修改了哪里
  • 不知道解决了什么问题
  • 不知道是否影响功能
  • 不知道是否可以回滚
  • 很难生成有意义的 changelog

好的例子:

fix(parser): handle empty input
fix(order): prevent duplicate checkout
feat(search): add fuzzy matching
perf(cache): reduce repeated database reads
docs(readme): add local setup steps

如果 subject 中出现 updatechangemodify,要问自己:

到底更新了什么?
到底改变了什么?
到底修改后的行为是什么?

常用动词选择

subject 常用动词如下:

动词适合场景示例
add新增功能、文件、配置add email login
remove删除功能、文件、配置remove unused cache config
update更新文档、依赖、配置update setup guide
fix修复问题fix token refresh failure
handle处理异常、边界情况handle empty input
reject拒绝非法输入或状态reject expired token
prevent防止错误行为prevent duplicate payment
support支持新能力或兼容场景support dark mode
extract抽取函数、类、模块extract retry policy
rename重命名rename userId to uid
replace替换实现或依赖replace legacy parser
align对齐版本、配置、行为align dependency versions
simplify简化逻辑simplify checkout validation
improve改善体验或实现improve error message
document补充文档说明document release workflow

注意:

fix 已经是 type 时,subject 中通常不需要再写 fix

不推荐:

fix(auth): fix token refresh

推荐:

fix(auth): refresh token before expiration

subject 的长度控制

subject 应该短,但不能短到没有信息。

常见建议:

  • 尽量控制在 50 字符左右
  • 不要写成一整段话
  • 超过一行的信息放到 body
  • 标题只保留核心结论

过长示例:

fix(auth): fix the problem where users cannot login when the refresh token is expired and the server returns 401

更好的写法:

fix(auth): refresh token before expiration
Refresh access token one minute before expiration to avoid failed requests
caused by client and server clock drift.

标题负责概括,正文负责解释。


subject 不要以句号结尾

Commit message 标题通常不以句号结尾。

不推荐:

fix(auth): reject expired token.
docs(readme): update setup guide.

推荐:

fix(auth): reject expired token
docs(readme): update setup guide

原因:

  • 标题不是完整段落
  • git log --oneline 中句号没有必要
  • 与 Conventional Commits 的常见风格保持一致

subject 与 type、scope 不要重复

subject 应该补充 type 和 scope 没有表达的信息。

不推荐:

feat(auth): auth feature
fix(order): fix order bug
docs(readme): docs update

推荐:

feat(auth): add password reset flow
fix(order): correct refund amount calculation
docs(readme): add Docker setup steps

分析:

feat(auth): auth feature

这个标题中,feat 已经表示功能,auth 已经表示认证模块,subject 再写 auth feature 没有新增信息。


subject 只描述一件事

一个提交最好只做一件事,subject 也应该只描述一件事。

不推荐:

feat(user): add avatar upload and fix login error and update docs

这说明提交本身可能已经混入多个无关变更。

建议拆成:

feat(user): add avatar upload
fix(login): show error for locked account
docs(user): add avatar upload guide

如果多个变更确实属于同一件事,可以用更高层级的描述。

例如:

feat(profile): add editable user profile

它可以包含:

  • 表单字段
  • 保存接口
  • 前端校验
  • 基础测试

因为这些都服务于同一个功能目标。


中文 subject 怎么写

中文团队可以使用中文 subject,但建议保留英文 type 和英文 scope,方便工具识别。

示例:

fix(auth): 修复 token 过期后仍可访问的问题
feat(order): 增加订单取消原因
docs(git): 补充 commit message 示例
refactor(api): 拆分请求重试逻辑

中文 subject 的建议:

  • 直接说明行为变化
  • 不写“代码”“文件”等无意义对象
  • 不写“优化一下”“调整一下”
  • 避免太口语化
  • 同一项目中保持中文或英文风格一致

不推荐:

fix(auth): 修改登录代码
feat(order): 做一下订单功能
docs(git): 更新文档

推荐:

fix(auth): 修复空密码仍能提交的问题
feat(order): 增加订单取消原因
docs(git): 补充 rebase 冲突处理示例

不同 type 的 subject 示例

1. feat

feat(auth): add password reset flow
feat(search): support fuzzy matching
feat(order): add cancellation reason

feat 的 subject 应该说明新增了什么用户可感知或系统可使用的能力。

2. fix

fix(login): reject empty password
fix(payment): prevent duplicate charge
fix(parser): handle empty input

fix 的 subject 应该说明修复后的正确行为,而不只是写 fix bug

3. docs

docs(readme): add local setup steps
docs(api): document token refresh flow
docs(git): add commit message examples

docs 的 subject 应说明补充、修正或更新了哪部分说明。

4. refactor

refactor(api): extract request serializer
refactor(user): split profile service
refactor(cache): simplify key generation

refactor 的 subject 应说明结构如何变化,同时避免暗示新增功能或修复 bug。

5. perf

perf(search): reduce repeated database reads
perf(cache): avoid duplicate serialization
perf(image): lazy load gallery thumbnails

perf 的 subject 应说明性能改善点。

6. test

test(auth): add expired token cases
test(order): cover refund failure path
test(api): add timeout retry tests

test 的 subject 应说明覆盖了什么场景。

7. build / ci / chore

build(gradle): update kotlin plugin
ci(github): add release workflow
chore(deps): update dependency lockfile

这类 subject 应说明工程配置、依赖或自动化流程的变化。


subject 与 body 的分工

subject 不需要解释所有细节。

如果标题太长,应该把原因、背景、影响放到 body。

不推荐:

fix(payment): prevent duplicate charge by adding idempotency key validation before creating payment order

推荐:

fix(payment): prevent duplicate charge
Add idempotency key validation before creating a payment order.
This prevents repeated callbacks from creating multiple charges.

分工原则:

部分负责内容
subject一句话概括提交结果
body解释背景、原因、实现方式、影响
footer关联 issue、Breaking Change 等元信息

24.7. body#

body 是 commit message 的正文部分,用来解释标题说不清的内容。

标题行适合回答:

这次提交做了什么?

body 适合进一步回答:

为什么要这样改?
具体怎么改?
有什么影响?
有哪些风险或取舍?
是否需要迁移或额外验证?

简单提交可以不写 body。复杂提交、风险较高的提交、影响范围较大的提交,应该认真写 body。


body 的作用

代码 diff 能告诉读者“代码怎么变了”,但不一定能说明“为什么这样变”。

例如 diff 显示:

timeout = 3000
timeout = 10000

仅看代码,很难判断这是:

  • 适配弱网环境
  • 修复第三方接口响应慢
  • 临时绕过性能问题
  • 业务要求等待更长时间
  • 还是无意中改错了配置

如果 body 写清楚:

fix(payment): increase callback timeout
The payment provider may respond after 3 seconds during peak traffic.
Increase callback timeout to avoid marking valid payments as failed.

未来维护者就能理解这次修改的背景和目的。

body 的核心价值是:

保存上下文,减少未来读代码时的猜测成本。

什么时候需要写 body

适合写:

  • 为什么要改
  • 怎么改的
  • 有什么影响
  • 有什么取舍
  • 是否存在兼容风险
  • 是否涉及数据迁移
  • 是否改变了外部行为
  • 是否有安全、性能、稳定性风险
  • 是否修复了线上问题
  • 是否有重要测试或验证说明

推荐写 body 的场景:

场景原因
修复线上 bug需要保留故障背景和根因
修改公共 API需要说明兼容性影响
修改数据库结构需要说明迁移和回滚
性能优化需要说明优化目标和取舍
重构需要说明行为是否保持不变
安全修复需要说明风险范围,但避免泄露攻击细节
配置调整需要说明为什么改这个值
复杂业务逻辑需要说明规则来源和影响范围

可以不写 body 的场景:

docs(readme): fix typo
style: format kotlin files
chore(gitignore): ignore idea files
test(auth): rename test case

判断标准:

半年后只看标题和 diff,还能不能理解这次提交为什么存在?

如果不能,就应该写 body。


body 应该写什么

body 不需要每次都写得很长,但应该补充真正有价值的信息。

常见内容:

内容说明
Why为什么要修改
What改了哪些关键点
How采用了什么实现方案
Impact影响哪些行为、模块或用户
Risk有哪些风险或取舍
Migration是否需要迁移配置、数据、接口
Test如何验证

可以使用自然段:

fix(cache): avoid stale user profile
The profile page reused cached user data after logout and login.
This change clears profile cache when the active user id changes.

也可以使用分段结构:

refactor(order): split pricing calculation
Why:
The previous pricing service mixed discount, tax, and shipping logic.
What:
Move each pricing rule into a separate calculator.
Impact:
Public order creation behavior is unchanged.

团队可以选择一种风格,关键是保持清晰一致。


body 的格式规范

推荐格式:

<type>(<scope>): <subject>
<body>
<footer>

注意:

  • 标题行和 body 之间空一行
  • body 和 footer 之间空一行
  • body 可以写多段
  • 每行不要过长
  • 使用清晰自然语言
  • 不要复制整段代码 diff
  • 不要只重复 subject

示例:

refactor(api): split request serialization
Move request serialization from ApiClient to RequestSerializer so that
the retry layer can reuse the same payload generation logic.
This keeps behavior unchanged but makes future timeout handling easier.

这段 body 说明了:

  • 为什么拆分:让 retry 层复用 payload 生成逻辑
  • 行为影响:保持行为不变
  • 后续收益:让 timeout 处理更容易

写 Why:说明为什么改

Why 是 body 中最重要的部分。

很多代码变化从 diff 可以看出“改了什么”,但看不出“为什么需要改”。

示例:

fix(auth): refresh token before expiration
Requests may fail when the client clock is slightly behind the server.
Refresh the token one minute before expiration to reduce false failures.

这比下面这种更有价值:

fix(auth): refresh token before expiration
Refresh token before expiration.

后者只是重复标题,没有增加上下文。


写 What:说明关键修改点

如果一次提交包含多个关键改动,可以在 body 中概括。

示例:

feat(report): add monthly sales export
- Add export task creation API
- Add CSV writer for monthly sales rows
- Add permission check before creating export tasks
- Add tests for empty report results

注意:body 不是文件清单,不要写成:

Changed ReportController, ReportService, ReportRepository, CsvWriter...

文件列表可以从 diff 中看到,body 应该解释关键逻辑和意图。


写 Impact:说明影响范围

如果变更会影响用户行为、接口行为、数据、配置或部署流程,body 应该说明。

示例:

fix(upload): reject oversized image files
Images larger than 5MB are now rejected before upload.
Existing uploaded images are not affected.

这里说明了:

  • 新行为:超过 5MB 的图片会被拒绝
  • 旧数据:已有图片不受影响

配置变更示例:

chore(config): increase worker concurrency
Increase default worker concurrency from 4 to 8.
This may increase database connection usage during peak traffic.

这种说明能帮助运维或后续维护者提前判断风险。


写 Risk:说明风险和取舍

有些提交不是纯收益,会带来取舍。

示例:

perf(search): cache product query results
Cache search results for 30 seconds to reduce database load.
The tradeoff is that newly updated products may take up to 30 seconds
to appear in search results.

这段 body 清楚说明:

  • 为什么加缓存
  • 缓存多久
  • 带来的代价是什么

如果只写:

perf(search): add cache

未来排查“商品更新后搜索结果延迟”时就不容易联想到这次改动。


写 Migration:说明迁移事项

如果提交涉及配置、数据库、接口或 SDK 的迁移,要在 body 或 footer 中写清楚。

示例:

feat(config): rename auth token option
Rename `auth.token` to `auth.accessToken` to match OAuth terminology.
Existing deployments must update configuration before upgrading.

如果是破坏性变更,还应该使用 !BREAKING CHANGE

feat(config)!: rename auth token option
Rename `auth.token` to `auth.accessToken` to match OAuth terminology.
BREAKING CHANGE: `auth.token` is no longer supported.

迁移说明应尽量包含:

  • 旧用法是什么
  • 新用法是什么
  • 是否兼容旧版本
  • 用户或调用方需要做什么

写 Test:说明验证方式

测试说明可以写在 PR / MR 描述中,也可以写在重要 commit 的 body 中。

示例:

fix(order): prevent duplicate checkout
Add idempotency check before creating payment orders.
Tested with:
- duplicate checkout request
- retry after network timeout
- normal single checkout request

适合在 body 中写测试说明的场景:

  • 线上问题修复
  • 边界条件复杂
  • 手动验证步骤重要
  • 自动化测试暂时无法覆盖全部场景

如果团队规定测试说明统一写在 PR 模板中,body 可以只保留关键验证信息。


body 和 footer 的区别

body 和 footer 都在标题下面,但职责不同。

部分作用
body用自然语言解释背景、原因、实现、影响
footer放结构化元信息,如 issue、BREAKING CHANGE

示例:

fix(payment): prevent duplicate refund
Refund callbacks may be retried when the provider does not receive a
successful response. Add idempotency check before creating refund records.
Fixes #342

其中:

  • body 解释问题和修复思路
  • footer 关联 issue

body 和 PR 描述的区别

PR / MR 描述通常解释整个变更,commit body 解释单个提交。

内容commit bodyPR / MR 描述
粒度单个 commit一个完整需求或一组提交
保存位置Git 历史平台页面
查看方式git showgit logPR / MR 页面
重点这次提交为什么这样改整个变更背景、测试、风险

如果团队使用 squash merge,PR 标题和描述可能会成为最终合并提交的一部分,因此 PR 描述也要认真写。


body 写作模板

简洁模板:

<type>(<scope>): <subject>
<why this change is needed>
<what changed>
<impact or risk if any>

结构化模板:

<type>(<scope>): <subject>
Why:
<why this change is needed>
What:
<what changed>
Impact:
<compatibility, risk, migration, or test notes>

中文模板:

<type>(<scope>): <subject>
原因:
说明为什么需要这次修改。
修改:
说明关键修改点。
影响:
说明影响范围、兼容性、风险或验证方式。

footer 是 commit message 的页脚部分,通常放在提交信息的最后,用来补充结构化元信息。

它不是每次提交都必须写,但在以下场景中非常重要:

  • 关联 issue、需求单、缺陷单
  • 标记 Breaking Change
  • 说明迁移方式
  • 记录回滚、审核、协作者等信息
  • 给自动化工具提供可解析的信息

基本结构:

<type>(<scope>): <subject>
<body>
<footer>

示例:

fix(payment): prevent duplicate charge
Add idempotency key validation before creating a payment order.
Fixes #342

其中 Fixes #342 就是 footer。


footer 的作用

footer 主要解决的问题是:

这次提交还需要补充哪些可追踪、可解析、可自动化处理的信息?

它和 subject、body 的分工不同:

部分作用
subject一句话说明做了什么
body解释为什么做、怎么做、有什么影响
footer补充 issue、Breaking Change、协作者、任务号等元信息

例如:

feat(api): add cursor pagination
Cursor pagination avoids missing records when new data is inserted during
list traversal.
Refs #901

这里:

  • subject 说明新增游标分页
  • body 说明为什么要改
  • footer 关联对应任务

关联 issue

Closes #123
Fixes #456
Refs #789

关联 issue 是 footer 最常见的用途。

常见关键词:

写法含义
Closes #123合并后关闭 issue
Fixes #123表示修复并关闭 issue
Resolves #123表示解决并关闭 issue
Refs #123仅引用 issue,不一定关闭
Related to #123表示有关联

示例:

fix(login): show error for locked account
Locked users were previously redirected without a clear message.
Fixes #128

如果只是相关,但不希望自动关闭 issue,可以使用:

Refs #128

如果一个提交关联多个 issue:

Fixes #128
Refs #135

注意:不同代码托管平台对自动关闭关键词的支持可能略有差异,团队应以 GitHub、GitLab、Gitee 或公司内部平台的实际规则为准。


Breaking Change

Breaking Change 表示破坏性变更,也就是旧版本的使用方式可能失效。

如果提交破坏了旧接口,必须明确说明。

feat(api)!: rename userId to uid
BREAKING CHANGE: response field userId is renamed to uid.
Clients must update their deserialization logic.

!BREAKING CHANGE: 都可以表示破坏性变更。

推荐写完整一些:

feat(api)!: replace page pagination with cursor pagination
BREAKING CHANGE: `page` and `pageSize` are removed from the user list API.
Use `cursor` and `limit` instead.

Breaking Change 应该说明:

  • 旧行为是什么
  • 新行为是什么
  • 哪些调用方会受影响
  • 应该如何迁移
  • 是否需要数据或配置变更

常见 Breaking Change:

  • 删除公开 API
  • 修改接口字段名
  • 修改字段含义
  • 删除配置项
  • 修改默认行为
  • 修改命令行参数
  • 修改数据库结构且不兼容旧版本
  • 升级运行环境要求

错误示例:

feat(api): update response

问题是没有说明这是破坏性变更。

更好的写法:

feat(api)!: rename userId response field to uid
BREAKING CHANGE: response field `userId` is renamed to `uid`.
Clients must update their deserialization logic before upgrading.

footer 的格式要求

footer 通常采用类似键值对的形式。

常见格式:

Token: value

或者:

Token #123

示例:

Closes #123
Refs #456
BREAKING CHANGE: remove legacy login endpoint
Reviewed-by: Alice
Co-authored-by: Bob <bob@example.com>

footer 与 body 之间通常空一行:

fix(auth): refresh token before expiration
Refresh access token one minute before expiration to avoid failed requests.
Fixes #231

如果没有 body,也可以直接写 footer,但仍建议空一行:

fix(auth): reject expired token
Fixes #231

多个 footer 怎么写

一个提交可以有多个 footer。

示例:

feat(report): add monthly sales export
Add CSV export for monthly sales reports.
Refs #245
Reviewed-by: Alice
Co-authored-by: Bob <bob@example.com>

Breaking Change 与 issue 关联同时存在:

feat(config)!: rename database connection option
Use a single DATABASE_URL value instead of separate DB_HOST and DB_PORT.
BREAKING CHANGE: DB_HOST and DB_PORT are no longer supported.
Refs #512

建议:

  • 每个 footer 单独一行
  • 不要把多个信息挤在一行
  • BREAKING CHANGE 写清楚迁移说明
  • issue 引用和审核信息分开写

常见 footer 类型

footer用途示例
Closes关闭 issueCloses #123
Fixes修复并关闭 issueFixes #456
Refs引用 issueRefs #789
BREAKING CHANGE标记破坏性变更BREAKING CHANGE: remove v1 API
Co-authored-by标记共同作者Co-authored-by: Bob <bob@example.com>
Reviewed-by标记评审者Reviewed-by: Alice
Signed-off-by签署提交Signed-off-by: Alice <alice@example.com>

并不是所有团队都会使用全部 footer。

最常用的是:

Closes #123
Fixes #123
Refs #123
BREAKING CHANGE: ...

Co-authored-by

Co-authored-by 用于标记共同作者。

示例:

feat(editor): add markdown preview
Co-authored-by: Alice <alice@example.com>
Co-authored-by: Bob <bob@example.com>

适合:

  • 结对编程
  • 多人共同完成一个提交
  • 协作者提供核心实现
  • 合并他人贡献时保留署名

注意:

  • 邮箱应与平台账号关联
  • 每个共同作者单独一行
  • 不要随意添加未参与贡献的人

Signed-off-by

Signed-off-by 常用于开源项目或有 DCO 要求的项目。

示例:

fix(kernel): handle null device pointer
Signed-off-by: Alice <alice@example.com>

它通常表示提交者确认自己有权贡献这段代码,并同意项目的贡献规则。

可以用 Git 自动添加:

git commit -s -m "fix(auth): reject expired token"

生成的提交信息会包含:

Signed-off-by: Your Name <your.email@example.com>

是否需要使用 Signed-off-by 取决于项目贡献规范。


footer 与 issue 平台

不同平台支持的自动关闭关键词可能略有不同。

常见平台:

  • GitHub
  • GitLab
  • Gitee
  • Bitbucket
  • Jira
  • Tapd
  • 禅道

GitHub / GitLab 常见写法:

Closes #123
Fixes #123
Resolves #123

如果是 Jira:

Refs PROJ-123

或者:

Closes PROJ-123

具体是否自动改变任务状态,要看项目平台和集成配置。

建议团队统一约定:

  • 使用哪种关键词
  • issue 编号写在哪里
  • 是否必须关联任务
  • 合并后是否自动关闭
  • 跨仓库 issue 如何引用

footer 与 changelog

规范 footer 可以提升 changelog 的质量。

例如:

feat(api)!: remove legacy user endpoint
BREAKING CHANGE: `/api/v1/users` is removed. Use `/api/v2/users` instead.
Refs #901

自动 changelog 可以识别:

  • 这是一个新功能类变更
  • 它包含破坏性变更
  • 它关联了 #901
  • 发布说明中应提示迁移方式

如果只写:

update api

工具和维护者都无法知道这是否是破坏性变更。


footer 与 body 的区别

body 和 footer 都在标题下面,但作用不同。

部分内容特点
body自然语言说明背景、原因、实现和影响
footer结构化元信息,便于工具解析

示例:

fix(upload): reject unsupported image format
The previous validation only checked file extension and allowed invalid MIME
types to pass.
Fixes #87

其中:

  • body 解释问题原因
  • footer 关联 issue

不要把 issue 信息混在正文段落中:

不推荐:

This fixes issue #87 and updates upload validation.

推荐:

Fixes #87

结构化 footer 更利于平台和工具识别。


中文项目中的 footer

中文项目中,footer 仍建议保留英文关键词。

推荐:

fix(auth): 修复 token 过期后仍可访问的问题
Fixes #128

Breaking Change 也建议保留标准英文标记:

feat(api)!: 重命名用户接口返回字段
BREAKING CHANGE: response field `userId` is renamed to `uid`.
客户端需要同步更新反序列化逻辑。

原因:

  • 英文关键词更容易被工具识别
  • GitHub、GitLab 等平台对固定关键词有自动处理
  • 团队后续接入 changelog、release 工具更方便

正文说明可以使用中文,但结构化关键词建议保持标准格式。


推荐模板

普通 issue 关联:

fix(scope): short subject
Explain why this fix is needed and what changed.
Fixes #123

仅引用任务:

feat(scope): short subject
Explain the feature background and behavior.
Refs #123

Breaking Change:

feat(scope)!: short subject
Explain the new behavior and migration reason.
BREAKING CHANGE: describe what is incompatible and how to migrate.
Refs #123

多人协作:

feat(scope): short subject
Explain the change.
Co-authored-by: Alice <alice@example.com>
Co-authored-by: Bob <bob@example.com>

25. 好的提交粒度#

提交粒度指的是:

一次 commit 应该包含多少改动。

粒度太大,提交难以 review、难以回滚、难以排查问题。
粒度太小,历史会变得零碎,阅读成本也会上升。

好的提交粒度不是“越小越好”,而是:

一次提交只解决一个明确、完整、可理解的问题。

为什么提交粒度重要

提交粒度直接影响 Git 历史的质量。

好的粒度可以让团队:

  • 更容易 review
  • 更容易定位 bug
  • 更容易回滚问题提交
  • 更容易理解功能演进
  • 更容易生成 changelog
  • 更容易用 git bisect 定位问题
  • 更容易把不同类型变更拆开管理

差的粒度会导致:

  • 一个提交里混入多个无关修改
  • reviewer 不知道重点在哪里
  • 回滚时误删无关代码
  • bug 排查时无法判断是哪部分引入问题
  • 提交信息很难写清楚

一个重要判断:

如果 commit message 很难一句话写清楚,通常说明提交粒度过大。

好提交的基本特征

一次好的提交通常满足:

  • 只做一件事
  • 有明确目的
  • 可以独立理解
  • 可以独立 review
  • 可以独立回滚
  • 提交后项目仍尽量保持可编译、可测试
  • commit message 能准确描述本次变更

合理提交:

fix(auth): validate empty password
docs(readme): update local setup guide
test(auth): add invalid login tests

不合理提交:

update login, docs, style and tests

不合理的原因:

  • 登录逻辑、文档、格式、测试混在一起
  • 无法判断这次提交的主要目的
  • 如果登录修复有问题,回滚时会连文档和测试一起回滚
  • review 时不同关注点混杂,容易漏看关键逻辑

判断提交粒度的标准

判断提交粒度可以看以下问题:

  • 能否一句话说清楚
  • 是否可以独立回滚
  • 是否方便 review
  • 是否和一个任务或问题对应
  • 是否只包含一种类型的变更
  • 是否混入无关文件
  • 是否能独立通过测试
  • 是否能用一个准确的 commit message 描述

如果答案是否定的,通常应该拆分。

示例:

feat(order): add cancellation API

这是一个相对清晰的提交。

如果标题变成:

feat(order): add cancellation API and refactor payment and update docs

说明它可能应该拆成多个提交。


粒度太大的表现

粒度太大的提交常见表现:

  • 一次提交修改几十个无关文件
  • 同时包含功能、修复、重构、格式化
  • commit message 写成 updatemisc changes
  • reviewer 需要长时间才能理解整体变更
  • 回滚时无法只回滚有问题的部分
  • 本次提交很难对应一个 issue 或任务

示例:

feat: add user profile, fix login, update docs, format code

更好的拆分:

feat(user): add profile edit page
fix(auth): show error for locked account
docs(user): add profile edit guide
style: format kotlin files

这样每个提交都有独立目的。


粒度太小的表现

提交也不是越小越好。

粒度太小的提交可能是:

wip
add file
fix typo
fix typo again
change variable
try another way
debug

这些提交在开发过程中可以临时存在,但不适合直接进入主分支。

问题:

  • 历史噪音太多
  • 难以看出完整意图
  • reviewer 需要在大量碎片提交中拼上下文
  • changelog 无法生成有意义内容

开发过程中可以先小步提交,合并前再整理。

例如开发时:

wip login page
fix validation
add test
fix typo

合并前整理成:

feat(auth): add password login page
test(auth): add login validation tests

按变更类型拆分

最常见的拆分方式是按变更类型拆分。

不要把这些内容混在一个提交里:

  • 功能开发
  • bug 修复
  • 重构
  • 文档
  • 测试
  • 格式化
  • 依赖升级
  • CI 配置

不推荐:

feat(login): add login page and format project

推荐:

style: format source files
feat(login): add password login page
test(login): add password validation tests

这样做的好处:

  • 格式化不会干扰功能 review
  • 功能实现和测试关系清楚
  • 如果功能有问题,可以单独回滚

按业务步骤拆分

一个较大的功能可以按业务步骤拆分。

例如“订单取消”功能可以拆成:

feat(order): add cancellation status
feat(order): add cancellation API
feat(order): add cancellation permission check
test(order): add cancellation flow tests
docs(order): document cancellation API

每个提交都是完整的一步。

不推荐拆成这种过细粒度:

add enum
add field
add method
add controller
add test file

这些提交太偏代码操作,不利于理解业务演进。

更好的拆分原则是:

按业务能力拆,而不是按敲代码的顺序拆。

按风险拆分

高风险改动最好单独提交。

例如:

  • 数据库迁移
  • 鉴权规则修改
  • 支付逻辑修改
  • 缓存策略修改
  • 公共 API 变更
  • 依赖大版本升级
  • 大规模格式化

不推荐:

feat(payment): add refund flow and update database schema

更推荐:

feat(payment): add refund status field
feat(payment): implement refund request flow
test(payment): add refund failure cases

如果数据库迁移风险较高,还可以单独提交:

feat(db): add refund status column

这样发布、回滚、审查都会更清晰。


按 review 视角拆分

好的提交应该方便 reviewer 阅读。

从 review 角度看,下面这种提交很难评审:

feat(user): add profile page and refactor api client and update formatter

因为 reviewer 同时要判断:

  • 新功能是否正确
  • API client 重构是否安全
  • 格式化是否影响逻辑

更好的拆分:

refactor(api): extract request client
feat(user): add profile page
test(user): add profile update tests

reviewer 可以按顺序看:

  1. 先看重构是否保持行为不变。
  2. 再看功能实现是否正确。
  3. 最后看测试是否覆盖关键场景。

按回滚视角拆分

提交粒度要考虑回滚。

一个好的提交应该尽量可以独立回滚。

例如:

feat(cache): cache product detail response

如果上线后发现商品详情缓存导致数据不刷新,可以单独回滚这个提交。

如果提交是:

feat(product): add detail cache and update product page and fix search

回滚缓存时会连产品页面和搜索修复一起回滚,风险变大。

判断标准:

如果这部分出问题,能不能只回滚它?

如果不能,就考虑拆分。


功能提交的粒度

功能提交应该围绕“可理解的功能单元”。

合理:

feat(auth): add email login
feat(auth): add password reset flow
feat(order): add csv export

不合理:

feat: add many features

也不建议太机械:

add login html
add login css
add login js
add login api call

如果这些文件共同构成一个完整登录页面,可以合成:

feat(auth): add password login page

如果功能很大,可以按可验证阶段拆分:

feat(auth): add login form validation
feat(auth): submit login request
feat(auth): persist login session
test(auth): add login flow tests

修复提交的粒度

修复提交应该围绕一个明确 bug。

合理:

fix(auth): validate empty password
fix(payment): prevent duplicate refund
fix(search): handle empty keyword

不合理提交:

fix bugs
fix many issues
fix login and payment and search

如果一个修复需要补测试,可以选择:

fix(auth): validate empty password
test(auth): add invalid login tests

也可以把测试和修复放在同一个提交中:

fix(auth): validate empty password

是否拆分取决于团队习惯。关键是不要混入无关修复。


重构提交的粒度

重构提交尤其需要控制粒度。

推荐:

refactor(api): extract request serializer
refactor(order): split pricing service
refactor(auth): isolate token validation

不推荐:

refactor: rewrite project
refactor: clean code
refactor: update architecture

重构最好满足:

  • 行为不变
  • 每次只调整一个结构目标
  • 不和功能开发混在一起
  • 不和大规模格式化混在一起

如果确实需要大规模重构,最好拆成多个可验证步骤。

例如:

refactor(order): extract order status policy
refactor(order): move pricing logic to pricing service
refactor(order): split order query repository

格式化提交要单独拆

格式化会制造大量 diff。

如果把格式化和功能修改混在一起,reviewer 很难看清真正的逻辑变更。

不推荐:

feat(auth): add login flow and format files

推荐:

style: format kotlin files
feat(auth): add login flow

或者先功能后格式化:

feat(auth): add login flow
style(auth): format login files

一般建议:

格式化提交单独做,避免污染功能 diff。

依赖升级提交要单独拆

依赖升级可能带来隐性风险。

不推荐:

feat(report): add export and update spring boot

推荐:

build(deps): update spring boot
feat(report): add export API

原因:

  • 依赖升级可能导致运行时行为变化
  • 回滚功能时不一定要回滚依赖
  • 回滚依赖时不一定要回滚功能
  • CI 失败时更容易判断原因

如果是安全补丁,可以写清楚:

build(deps): update log4j for security fix

测试提交是否单独拆

测试可以和功能在同一个提交,也可以单独提交。

适合放在同一提交:

fix(auth): validate empty password

这个提交同时包含修复和对应测试,整体仍然只解决一个问题。

适合单独提交:

feat(order): add cancellation API
test(order): add cancellation flow tests

当测试较多、需要单独 review,或者补历史测试时,单独提交更清晰。

补历史测试示例:

test(payment): add duplicate callback regression tests

临时提交如何处理

开发过程中出现临时提交很正常。

例如:

wip
try cache
fix again
debug payment

这些提交可以用于保存进度,但合并前应该整理。

常用方式:

git rebase -i HEAD~4

可以把临时提交整理成:

feat(payment): add callback retry
test(payment): add callback retry tests

如果团队使用 squash merge,也要在合并时认真整理最终提交信息,不要让 wip 进入主分支。


使用 git add -p 控制粒度

如果一个文件里同时包含多个改动,可以用交互式暂存控制提交粒度。

命令:

git add -p

它可以让你选择只暂存部分 diff。

常见场景:

  • 同一个文件里既有功能修改,也有格式调整
  • 同一个文件里修了两个无关 bug
  • 同一个文件里同时有重构和行为变化

查看暂存内容:

git diff --cached

确认无误后再提交:

git commit -m "fix(auth): reject expired token"

26. 整理提交历史#

整理提交历史,是指在合并到主分支前,把开发过程中的临时提交、零散提交、错误提交整理成清晰、可读、可维护的提交历史。

开发过程中产生临时提交很正常:

wip
fix
fix again
try something

这些提交适合保存开发进度,但不适合直接进入主分支。

整理后的历史应该像这样:

feat(auth): add password login page
fix(auth): reject expired token
test(auth): add login validation tests

为什么要整理提交历史

整理提交历史的目的不是追求表面整洁,而是提高维护效率。

清晰的提交历史可以帮助团队:

  • 按逻辑顺序 review 代码
  • 快速理解一个需求的演进过程
  • 使用 git bisect 定位问题
  • 出问题时精准回滚
  • 自动生成更清晰的 changelog
  • 避免主分支出现大量 wipfix again

不整理历史的常见结果:

wip
fix
fix again
try
debug
final
final final

几周后再看,很难判断这些提交分别做了什么。


哪些历史适合整理

适合整理:

  • 自己本地未推送的功能分支
  • 自己远程个人分支,且没人基于它继续开发
  • PR / MR 合并前的临时提交
  • commit message 写错的提交
  • 多个临时提交需要合并成逻辑提交
  • 提交顺序不合理的分支
  • 有调试提交、试验提交、无意义提交的分支

不建议随意整理:

  • main
  • master
  • develop
  • release/*
  • 多人共用的远程分支
  • 已经被别人拉取并继续开发的提交
  • 已经发布到生产环境的历史

核心原则:

只改写自己拥有的历史,不改写别人依赖的历史。

整理前先查看历史

查看最近提交:

git log --oneline --decorate -10

查看当前分支相对主分支新增了哪些提交:

git fetch origin
git log --oneline origin/main..HEAD

示例:

a1b2c3d fix again
b2c3d4e fix
c3d4e5f wip
d4e5f6a feat(auth): add login page

看到这种历史,就适合在合并前整理。


交互式 rebase 基本用法

合并前可以用交互式 rebase 整理:

git rebase -i HEAD~4

表示整理最近 4 个提交。

如果想整理当前分支相对 main 的所有提交:

git fetch origin
git rebase -i origin/main

执行后会打开编辑器,内容类似:

pick d4e5f6a feat(auth): add login page
pick c3d4e5f wip
pick b2c3d4e fix
pick a1b2c3d fix again

你可以修改每行前面的操作。

常见操作:

操作含义
pick保留
reword修改提交信息
squash合并到上一个提交,并合并信息
fixup合并到上一个提交,丢弃信息
drop删除提交

reword:修改提交信息

如果提交内容没问题,只是提交信息写得不好,可以使用 reword

原始内容:

pick a1b2c3d update login

改成:

reword a1b2c3d update login

保存后 Git 会让你重新编辑提交信息。

可以改成:

feat(auth): add password login page

适合:

  • type 写错
  • scope 写错
  • subject 太模糊
  • 忘记关联 issue
  • 需要补充 body 或 footer

squash:合并提交并合并信息

squash 会把当前提交合并到上一个提交,并让你编辑合并后的提交信息。

整理前:

pick a1b2c3d feat(auth): add login page
pick b2c3d4e fix typo
pick c3d4e5f add validation

整理时:

pick a1b2c3d feat(auth): add login page
squash b2c3d4e fix typo
squash c3d4e5f add validation

最终可以整理成:

feat(auth): add password login page

适合:

  • 多个提交共同组成一个功能
  • 中间有修修补补的提交
  • 想保留多个提交信息作为参考再整理

fixup:合并提交并丢弃信息

fixupsquash 类似,但会丢弃当前提交的提交信息。

整理前:

pick a1b2c3d feat(auth): add login page
pick b2c3d4e fix lint
pick c3d4e5f fix typo

整理时:

pick a1b2c3d feat(auth): add login page
fixup b2c3d4e fix lint
fixup c3d4e5f fix typo

最终只保留第一个提交的信息。

适合:

  • fix typo
  • fix lint
  • adjust format
  • small fix
  • 不值得保留 message 的临时提交

drop:删除提交

drop 用于删除某个提交。

示例:

pick a1b2c3d feat(auth): add login page
drop b2c3d4e debug login state
pick c3d4e5f test(auth): add login tests

适合:

  • 删除调试提交
  • 删除误提交
  • 删除不再需要的实验提交
  • 删除临时验证代码

也可以直接删除那一行,但显式写 drop 更清楚。

注意:

drop 会让该提交从当前历史中消失。

执行前要确认这个提交确实不需要。


调整提交顺序

交互式 rebase 中可以调整提交顺序。

整理前:

pick a1b2c3d test(auth): add login tests
pick b2c3d4e feat(auth): add login page

更合理:

pick b2c3d4e feat(auth): add login page
pick a1b2c3d test(auth): add login tests

这样历史更符合逻辑:

先实现功能,再补测试。

注意:调整顺序可能产生冲突,因为后面的提交可能依赖前面的代码。


拆分一个过大的提交

如果一个提交太大,可以用 edit 拆分。

流程:

git rebase -i HEAD~3

把要拆分的提交前面的 pick 改成:

edit

Git 停在该提交时,执行:

git reset HEAD^

然后分批暂存并提交:

git add -p
git commit -m "feat(auth): add login form"
git add -p
git commit -m "test(auth): add login validation tests"

继续 rebase:

git rebase --continue

适合:

  • 一个提交混入多个无关改动
  • 功能和测试需要拆开
  • 格式化和逻辑修改混在一起
  • 依赖升级和业务代码混在一起

修改最近一次提交:amend

如果只需要修改最近一次提交,可以用:

git commit --amend

常见用途:

  • 修改最近一次 commit message
  • 补充漏提交的文件
  • 删除最近提交中的小错误

补文件示例:

git add missing-test.kt
git commit --amend

只修改 message:

git commit --amend

注意:如果最近提交已经推送,amend 会改写历史,推送时通常需要 --force-with-lease


处理 rebase 冲突

整理历史时可能遇到冲突。

查看状态:

git status

解决冲突后:

git add <file>
git rebase --continue

放弃本次 rebase:

git rebase --abort

跳过当前提交:

git rebase --skip

注意:--skip 会丢弃当前正在应用的提交,使用前要确认。


force-with-lease

如果整理的是已经推送过的个人分支,普通 push 可能失败。

不要优先使用:

git push --force

更推荐:

git push --force-with-lease

区别:

命令风险
--force直接覆盖远程分支,可能覆盖别人提交
--force-with-lease只有远程分支仍是你本地认知的状态时才覆盖

--force-with-lease 更安全,但仍然属于改写远程历史。公共分支不要随意使用。


整理前创建备份

如果不熟悉 rebase,可以先创建备份分支:

git branch backup/my-feature-before-rebase

整理出问题时,可以切回备份:

git switch backup/my-feature-before-rebase

也可以用 reflog 找回历史:

git reflog

reflog 会记录 HEAD 移动历史,是找回误操作提交的重要工具。


PR / MR 前推荐整理流程

推荐流程:

git fetch origin
git log --oneline origin/main..HEAD
git rebase -i origin/main
git log --oneline origin/main..HEAD
git status

如果分支已推送到个人远程分支:

git push --force-with-lease

整理后检查:

  • commit message 是否清楚
  • 临时提交是否已合并或删除
  • 提交顺序是否合理
  • 每个提交是否粒度合适
  • 测试是否仍然通过
  • PR / MR 中提交历史是否可读

整理前后示例

整理前:

wip
add login
fix
fix again
add test
update docs

整理后:

feat(auth): add password login
test(auth): add login validation tests
docs(auth): document login flow

整理前:

try cache
fix cache
fix lint
final

整理后:

perf(cache): cache product detail response
test(cache): add stale product cache tests

27. 分支命名规范#

分支命名规范用于统一团队创建、识别、协作和清理分支的方式。

一个好的分支名应该让人一眼看出:

  • 这个分支属于什么类型
  • 这个分支要解决什么问题
  • 是否与某个任务或缺陷关联
  • 是否是临时分支还是长期分支
  • 合并后是否可以删除

分支名不是随便起的标签,它是团队协作中的重要信息。


基本命名格式

推荐格式:

<type>/<short-description>

常见命名:

feature/user-profile
fix/login-null-pointer
hotfix/payment-timeout
release/v1.2.0
docs/git-note
chore/update-deps

其中:

  • type 表示分支类型
  • / 用于分隔类型和描述
  • short-description 用短语说明分支目的

基本命名建议

建议:

  • 小写
  • 用短横线分隔单词
  • 前缀表达类型
  • 名称表达目的
  • 不使用空格
  • 不使用中文标点
  • 不使用无意义缩写
  • 不使用人名作为主要名称
  • 不使用过长描述

推荐:

feature/order-export
fix/payment-duplicate-callback
docs/commit-message-guide

不推荐:

dev
test
new
temp
final
zhangsan
fixbug
my-branch

原因:

  • 看不出用途
  • 看不出类型
  • 不知道是否可以删除
  • 多人协作时容易混乱

常见分支前缀

前缀含义示例
feature/新功能feature/user-profile
feat/新功能简写feat/order-export
fix/普通缺陷修复fix/login-null-pointer
bugfix/缺陷修复bugfix/cart-total-error
hotfix/线上紧急修复hotfix/payment-timeout
release/发布准备release/v1.2.0
docs/文档修改docs/git-note
refactor/重构refactor/order-service
test/测试相关test/login-validation
chore/杂项维护chore/update-deps
ci/CI/CD 配置ci/add-release-workflow
build/构建相关build/update-gradle

团队可以使用 feature/,也可以使用 feat/,但应统一一种风格。


功能分支命名

功能分支用于开发新功能。

示例:

feature/user-profile
feature/order-export
feature/password-reset
feature/report-dashboard

或者团队统一使用简写:

feat/user-profile
feat/order-export

分支名应该表达功能目标,而不是代码实现细节。

不推荐:

feature/add-controller
feature/write-code
feature/new-page

推荐:

feature/order-export
feature/password-reset
feature/user-avatar-upload

修复分支命名

普通 bug 修复使用 fix/bugfix/

示例:

fix/login-null-pointer
fix/order-total-error
fix/upload-empty-file
bugfix/cart-price-rounding

分支名应尽量说明错误现象或影响点。

不推荐:

fix/bug
fix/error
fix/problem
fix/test

推荐:

fix/login-empty-password
fix/payment-duplicate-callback
fix/search-empty-keyword

hotfix 分支命名

hotfix/ 用于线上紧急修复。

示例:

hotfix/payment-timeout
hotfix/login-500-error
hotfix/order-callback-duplicate
hotfix/v1.2.1-auth-token-expiry

如果团队按版本维护,可以带版本号:

hotfix/v1.2.1-payment-timeout

hotfix 分支通常从生产版本、main 或发布 tag 拉出,修复后要合并回相关长期分支。

常见流向:

main -> hotfix/* -> main
-> develop

如果使用 release 分支,也可能需要合并回对应 release/*


release 分支命名

发布分支通常使用版本号命名。

示例:

release/v1.2.0
release/1.2.0
release/2026.07
release/android-2.3.0

建议团队统一是否带 v

推荐统一:

release/v1.2.0
release/v1.2.1
release/v1.3.0

不推荐混用:

release/1.2.0
release/v1.2.1
release/ver-1.2.2

release 分支适合:

  • 发布前测试
  • 修复发布阻塞问题
  • 更新版本号
  • 准备 changelog
  • 打 tag 前最终验证

文档、重构、测试、维护分支

文档分支:

docs/git-note
docs/api-auth-guide
docs/readme-setup

重构分支:

refactor/order-service
refactor/api-client
refactor/auth-token-validator

测试分支:

test/login-validation
test/payment-callback
test/order-refund

维护分支:

chore/update-deps
chore/cleanup-assets
chore/update-gitignore

CI / 构建分支:

ci/add-release-workflow
build/update-gradle
build/docker-production-image

带任务编号的分支名

如果团队使用 Jira、Tapd、禅道、GitHub Issue,可以把任务号放进分支名。

格式:

<type>/<ticket-id>-<short-description>

示例:

feature/PROJ-123-user-profile
fix/BUG-456-login-null-pointer
hotfix/INC-789-payment-timeout
docs/GIT-12-commit-message-note

优点:

  • 分支和任务容易关联
  • CI / 平台可以自动识别任务号
  • 项目管理工具更容易追踪
  • PR / MR 更容易自动关联需求

注意:

任务号不要替代描述。

不推荐:

feature/PROJ-123
fix/BUG-456

推荐:

feature/PROJ-123-user-profile
fix/BUG-456-login-null-pointer

分支名与 Commit Message 的关系

分支名前缀和 commit type 最好保持语义一致。

示例:

分支:feature/order-export
提交:feat(order): add csv export
分支:fix/login-empty-password
提交:fix(auth): reject empty password
分支:docs/git-note
提交:docs(git): add branch naming guide

这样分支、commit、PR / MR 标题可以形成统一语义。


分支名与 PR / MR 标题

PR / MR 标题应该比分支名更正式。

分支名:

feature/order-export

PR 标题:

feat(order): add csv export

分支名:

fix/payment-timeout

PR 标题:

fix(payment): increase callback timeout

分支名负责快速识别任务,PR 标题负责形成可读的合并记录。


分支名字符规范

建议:

  • 使用小写英文
  • 单词之间用短横线 -
  • 类型和描述之间用斜杠 /
  • 可包含任务编号
  • 不使用空格
  • 不使用中文标点
  • 不使用特殊符号

推荐:

feature/user-profile
fix/order-total-error
release/v1.2.0

不推荐:

Feature/UserProfile
fix/order total error
release\v1.2.0
需求/用户资料
fix#123

分支名长度控制

分支名应简短但具体。

太短:

fix/bug
feature/new

太长:

feature/add-the-new-user-profile-page-with-avatar-upload-and-basic-information-form

更合适:

feature/user-profile
feature/avatar-upload
fix/login-empty-password

原则:

能表达目的即可,不要把需求描述全文写进分支名。

详细背景应该写在 issue、PR 描述或 commit body 中。


长期分支与短期分支

分支可以分为长期分支和短期分支。

长期分支:

main
master
develop
release/*
production

短期分支:

feature/*
fix/*
docs/*
refactor/*
chore/*

长期分支需要保护,短期分支完成后应删除。

常见规则:

  • main 保存稳定版本
  • develop 保存开发集成版本
  • release/* 用于发布准备
  • feature/* 合并后删除
  • fix/* 合并后删除
  • hotfix/* 合并并发布后删除

分支删除规范

功能完成并合并后,应删除临时分支。

删除本地分支:

git branch -d feature/user-profile

强制删除本地分支:

git branch -D feature/user-profile

删除远程分支:

git push origin --delete feature/user-profile

清理远程已删除分支引用:

git remote prune origin

注意:

  • 不要删除仍在使用的分支
  • 不要删除受保护分支
  • 删除前确认 PR / MR 已合并
  • 删除前确认没有未同步的重要提交

团队分支命名规范示例

团队可以制定如下规范:

长期分支:
- main:稳定主分支
- develop:开发集成分支
- release/vX.Y.Z:发布准备分支
短期分支:
- feature/<description>
- fix/<description>
- hotfix/<description>
- docs/<description>
- refactor/<description>
- test/<description>
- chore/<description>
- ci/<description>
- build/<description>
命名要求:
- 全部小写
- 单词用短横线
- 合并后删除短期分支
- 如有任务号,放在 type 后

带任务号版本:

feature/PROJ-123-user-profile
fix/BUG-456-login-null-pointer
hotfix/INC-789-payment-timeout

28. 团队代码管理规范建议#

团队可以制定如下规则:

  1. 主分支必须受保护。
  2. 需求必须从主分支拉新分支。
  3. 合并必须通过 PR / MR。
  4. PR 至少一人 review。
  5. CI 通过后才能合并。
  6. commit message 必须符合 Conventional Commits。
  7. 禁止向主分支直接 force push。
  8. 线上修复走 hotfix 分支。
  9. 发布版本必须打 tag。
  10. 敏感信息禁止提交到仓库。

29. 安全实践#

不要提交:

  • 密码
  • token
  • 私钥
  • .env
  • 证书
  • 生产数据库地址

如果误提交敏感信息:

  1. 立即废弃泄漏的密钥。
  2. 从历史中清理敏感文件。
  3. 通知团队重新拉取或处理历史。
  4. 检查访问日志。

仅仅删除文件并再次提交,不等于从 Git 历史中删除。


30. Git 日常命令速查#

仓库

git init
git clone <url>
git remote -v

状态

git status
git diff
git diff --cached

提交

git add .
git add -p
git commit
git commit --amend

分支

git branch
git switch -c feature/name
git switch main
git branch -d feature/name

同步

git fetch
git pull
git push
git push -u origin feature/name

合并与变基

git merge feature/name
git rebase main
git rebase --continue
git rebase --abort

撤销恢复

git restore file
git restore --staged file
git reset --soft HEAD~1
git revert <commit>
git reflog

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

代码版本管理与Git学习笔记(AI整理)
https://niushaoxiong.top/posts/代码版本管理与git学习笔记/
作者
一只捡星星的熊
发布于
2026-04-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录