代码版本管理与 Git 学习笔记
1. 什么是代码版本管理
代码版本管理,也叫版本控制,是对项目文件变化进行记录、比较、回退、审查和协作管理的过程。
它不是简单地“备份一份代码”,而是把软件开发中的每一次有效变更都整理成可追踪的历史记录。通过版本管理,团队可以知道代码从哪里来、为什么变成现在这样、某个问题是从哪次修改开始出现的,以及需要时如何恢复到某个稳定状态。
可以把代码版本管理理解为软件项目的“时间线系统”:
初始代码 -> 第一次提交 -> 第二次提交 -> 修复 bug -> 新增功能 -> 发布版本每一个节点都记录了项目在某个时刻的状态。
1. 版本管理到底管理什么
版本管理工具通常管理的不只是代码文件,还包括和项目交付有关的文本型工程资产。
常见管理对象:
- 源代码:如
.kt、.java、.py、.js、.cpp - 配置文件:如
yml、json、xml、properties - 构建脚本:如
Gradle、Maven、Makefile - 文档:如
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. 学习版本管理时要建立的思维
学习版本管理,不要只背命令,而要建立几个思维:
- 每次提交都应该表达一个明确目的。
- 分支是隔离工作内容的工具。
- 提交历史是项目的工程档案。
- 公共历史要谨慎修改。
- 主分支应该尽量保持稳定。
- 版本发布要有明确标记。
- 提交信息应该能解释变更原因。
后面学习 Git 命令、分支策略和 Commit Message 规范,本质上都是围绕这些思维展开。
2. 版本控制系统分类
版本控制系统可以按架构和协作方式分为三类:
- 本地版本控制
- 集中式版本控制
- 分布式版本控制
这三类不是简单的新旧替代关系,而是代表了不同阶段的软件协作方式。
2.1 本地版本控制
最早的方式是在本地保存多个版本,例如:
project_v1.zipproject_v2.zipproject_final.zipproject_final_final.zip这严格来说还不是现代意义上的版本控制系统,更像是人工备份。后来也出现过一些本地版本数据库工具,用来在单台机器上记录文件变化。
本地版本控制的核心特点是:
- 版本历史只保存在当前机器
- 不依赖服务器
- 主要面向个人使用
- 协作能力很弱
工作方式可以简单理解为:
本地文件 -> 本地版本记录 -> 本地恢复这种方式简单,但问题很多:
- 不适合多人协作
- 难比较差异
- 难追踪修改原因
- 容易丢失文件
- 机器损坏时历史可能一起丢失
- 无法自然支持代码审查和远程发布
适用场景:
本地版本控制只适合:
- 个人临时草稿
- 不需要协作的小脚本
- 简单文档备份
- 学习版本概念的早期阶段
对正式软件项目来说,本地版本控制远远不够。
2.2 集中式版本控制(SVN、CVS)
代表工具:
- SVN
- CVS
集中式版本控制系统有一个中央服务器。开发者从服务器拉代码,再把修改提交回服务器。
典型结构:
开发者 A \开发者 B -> 中央版本库 -> 统一保存历史开发者 C /所有人的提交都进入中央服务器。中央服务器保存完整版本历史,本地工作副本通常只保存当前版本和少量元信息。
工作流程:
集中式版本控制的常见流程:
从服务器 checkout/update -> 本地修改 -> 解决可能的冲突 -> commit 到中央服务器以 SVN 为例:
svn checkout <repo-url>svn updatesvn 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/logingit 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、CVS | Git、Mercurial |
| 历史保存位置 | 当前机器 | 中央服务器 | 每个本地仓库都有完整历史 |
| 离线提交 | 不完整或不支持 | 通常不支持 | 支持 |
| 多人协作 | 很弱 | 支持 | 强 |
| 分支成本 | 几乎没有正式分支 | 相对较高 | 很低 |
| 容灾能力 | 弱 | 依赖中央服务器备份 | 强 |
| 学习成本 | 低 | 中 | 中高 |
| 现代软件开发适配度 | 低 | 中 | 高 |
Git 和 SVN 的核心区别
Git 和 SVN 是最常被拿来比较的两个工具。
| 对比项 | Git | SVN |
|---|---|---|
| 架构 | 分布式 | 集中式 |
| 本地是否有完整历史 | 有 | 通常没有 |
| 本地提交 | 支持 | 不支持,提交到服务器 |
| 分支 | 轻量、常用 | 相对重 |
| 合并能力 | 强 | 较弱 |
| 离线工作 | 强 | 弱 |
| 权限控制 | 依赖平台和仓库策略 | 目录级权限控制较强 |
| 适用生态 | 现代开源和企业研发 | 传统企业和历史项目 |
简单理解:
- Git 更适合频繁分支、频繁合并、快速迭代。
- SVN 更适合强中心化、强目录权限、流程稳定的项目。
为什么现代项目大多选择 Git
Git 成为主流,不只是因为它速度快,还因为它适合现代软件研发模式。
现代项目通常需要:
- 多人并行开发
- 功能分支
- Pull Request / Merge Request
- 自动化测试
- 自动化部署
- 开源协作
- 快速回滚
- 版本发布
- 多环境交付
Git 的分支模型、远程协作模型和生态工具链,正好适配这些需求。
GitHub、GitLab、Gitee、Bitbucket 等平台进一步把 Git 扩展成完整研发协作平台:
- 代码托管
- Issue 管理
- 代码评审
- CI/CD
- 权限管理
- Release 管理
- 安全扫描
所以,现代项目里常说的“用 Git 管理代码”,通常包含两层意思:
- 用 Git 管理版本历史。
- 用 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. 常见代码管理工具
代码管理工具可以分成两类:
- 版本控制工具:负责记录代码历史,例如 Git、SVN、Mercurial。
- 代码托管与协作平台:基于版本控制工具提供远程仓库、评审、Issue、CI/CD 等能力,例如 GitHub、GitLab、Gitee、Bitbucket。
这两类经常被混在一起说,但它们不是一回事。
Git = 版本控制工具GitHub = 基于 Git 的代码托管和协作平台GitLab = 基于 Git 的代码托管和 DevOps 平台Gitee = 基于 Git 的国内代码托管平台Bitbucket = 基于 Git 的代码托管平台工具总览
| 工具 | 类型 | 特点 | 适用场景 |
|---|---|---|---|
| Git | 分布式版本控制 | 分支轻量、生态强、速度快 | 绝大多数现代软件项目 |
| SVN | 集中式版本控制 | 权限集中、目录级控制强 | 传统企业项目、强中心化流程 |
| Mercurial | 分布式版本控制 | 易用性较好 | 少量历史项目 |
| GitHub | Git 托管平台 | PR、Issue、Actions、开源生态强 | 开源、团队协作 |
| GitLab | Git 托管平台 | CI/CD、权限、私有化部署强 | 企业研发平台 |
| Gitee | Git 托管平台 | 国内访问友好 | 国内团队和个人项目 |
| Bitbucket | Git 托管平台 | 和 Atlassian 生态结合 | Jira/Confluence 团队 |
注意:
Git 是版本控制工具。
GitHub、GitLab、Gitee 是基于 Git 的代码托管和协作平台。
3.1 版本控制工具
Git
Git 是目前最主流的分布式版本控制工具。
它负责:
- 初始化仓库
- 记录提交历史
- 管理分支
- 合并代码
- 回退版本
- 比较差异
- 管理标签
- 与远程仓库同步
常见命令:
git initgit clone <url>git statusgit add .git commit -m "feat: add feature"git branchgit switch -c feature/demogit merge feature/demogit pushgit pullGit 的优势:
- 本地操作快
- 离线也能提交
- 分支创建和切换成本低
- 适合多人并行开发
- 生态成熟
- 与 CI/CD、代码评审、开源社区结合紧密
Git 的不足:
- 初学概念较多
- 命令体系较复杂
- 历史改写容易误操作
- 大文件管理不如专门资产系统
适合场景:
- Web 项目
- Android / iOS 项目
- 后端服务
- 开源项目
- 文档项目
- DevOps 和 CI/CD 项目
- 绝大多数现代软件工程项目
SVN
SVN,全称 Subversion,是典型的集中式版本控制系统。
它负责:
- 从中央服务器检出代码
- 提交修改到中央服务器
- 管理目录级权限
- 记录集中式历史
SVN 的常见命令:
svn checkout <repo-url>svn updatesvn statussvn add file.txtsvn commit -m "update document"SVN 的优势:
- 权限控制集中
- 目录级权限能力较强
- 使用模型直观
- 适合传统企业管理方式
SVN 的不足:
- 本地没有完整仓库历史
- 离线能力弱
- 分支和合并体验不如 Git
- 不适合高频分支开发
- 现代开源生态不如 Git
适合场景:
- 历史遗留项目
- 强中心化企业项目
- 对目录权限控制要求很细的项目
- 团队暂时没有迁移 Git 的条件
Mercurial
Mercurial 也是分布式版本控制系统,和 Git 在定位上比较接近。
它的特点:
- 分布式
- 命令相对简洁
- 学习曲线比 Git 平缓一些
- 曾经在一些大型项目中使用
常见命令风格:
hg clone <url>hg statushg addhg commit -m "update"hg pushhg pullMercurial 的优势:
- 使用体验较一致
- 分布式模型清晰
- 对新手相对友好
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 | 图形化强,适合看分支图 |
| GitKraken | UI 友好,适合可视化操作 |
| 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 statusgit loggit diffgit branchgit 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 statusgit 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 initgit 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 的代价主要是学习曲线:
- 概念多
- 命令多
reset、rebase、checkout等命令容易误用- 冲突处理需要经验
- 团队需要统一提交和分支规范
所以学 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 push5.1 工作区
工作区是你实际编辑代码的目录。
例如你在 IDE 中看到的文件,就是工作区文件。
工作区里的文件可能处于这些状态:
- 未跟踪
- 已修改
- 已删除
- 已暂存
- 与仓库一致
常用查看命令:
git statusgit diff2. 未跟踪文件
新建但还没有被 Git 管理的文件,叫未跟踪文件。
例如:
Untracked files: notes.md把它加入 Git 管理:
git add notes.md如果不想提交,就写入 .gitignore。
3. 已修改文件
已经被 Git 跟踪,但当前内容和上次提交不同。
查看修改:
git diff放入暂存区:
git add file.txt丢弃工作区修改:
git restore file.txt4. 工作区干净
当工作区没有未提交修改时,git status 会提示:
nothing to commit, working tree clean这表示当前工作区、暂存区和本地仓库是一致的。
5.2 暂存区
暂存区是下一次提交的准备区。
执行:
git add <file>文件修改会进入暂存区。
暂存区也叫 Index。它的作用是决定下一次 git commit 到底提交哪些内容。
可以理解为:
工作区:我当前改了什么暂存区:我准备把哪些改动放进下一次提交常用命令:
git add file.txtgit add .git add -pgit diff --cachedgit restore --staged file.txt为什么暂存区很重要
暂存区让你可以把混在一起的修改拆成多个清晰提交。
例如你同时改了:
- 登录逻辑
- README
- 按钮样式
可以分批暂存:
git add src/login.ktgit commit -m "fix(auth): validate login token"
git add README.mdgit commit -m "docs: update setup guide"
git add app/src/main/res/layout/login.xmlgit 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 commitgit loggit show <commit>git resetgit revertgit reflogcommit 是什么
一次 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 -vgit fetch origingit pullgit pushgit push -u origin feature/loginorigin 是什么
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.ktgit commit -m "fix(auth): validate login token"
git add app/src/main/res/layout/login.xmlgit commit -m "style(ui): adjust login button spacing"
git add README.mdgit commit -m "docs: update setup guide"这样历史更清晰,也更容易回滚。
提交历史会变成:
fix(auth): validate login tokenstyle(ui): adjust login button spacingdocs: 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 statusgit diffgit add -pgit diff --cachedgit 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.txtgit commit --amend适合忘记把某个文件加入最近一次提交时使用。
暂存区在团队协作中的价值
暂存区不只是个人方便,它直接影响团队协作质量。
好的暂存习惯可以带来:
- 更小的 commit
- 更清楚的变更目的
- 更容易 review
- 更容易 revert
- 更容易生成 changelog
- 更容易定位 bug
代码评审时,审查者最怕看到这种提交:
update all files因为它可能同时包含:
- 业务逻辑修改
- 格式化修改
- 文档修改
- 临时调试代码
- 依赖升级
而这些内容应该被拆成独立提交。
暂存区和 Commit Message 的关系
Commit Message 写得好不好,前提是暂存区选得好不好。
如果暂存区里混入了多个无关修改,再好的 commit message 也很难准确描述。
合理关系应该是:
暂存区内容 = 一个明确变更目的commit message = 对这个变更目的的准确描述例如:
git add src/auth/TokenValidator.ktgit 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.namegit 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 新仓库时默认创建的分支名。
常见默认分支名:
mainmasterdevelop
现在很多新项目使用 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 Codegit config --global core.editor "code --wait"
# Vimgit 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. 配置换行符
不同系统使用的换行符不同:
| 系统 | 常见换行符 |
|---|---|
| Windows | CRLF |
| macOS / Linux | LF |
如果团队跨平台协作,换行符处理不当会导致大量无意义 diff。
Windows 常见配置:
git config --global core.autocrlf truemacOS / Linux 常见配置:
git config --global core.autocrlf input也可以更推荐在项目中用 .gitattributes 统一:
* text=auto*.sh text eol=lf*.bat text eol=crlf建议:
- 个人全局配置只做基础处理。
- 团队项目用
.gitattributes明确规则。 - 不要让换行符变化污染业务提交。
7. 配置大小写敏感
Windows 和 macOS 默认文件系统通常对大小写不敏感,Linux 通常大小写敏感。
这会导致类似问题:
UserService.ktuserservice.kt在 Linux CI 上可能是两个文件,在 Windows 上可能冲突。
查看配置:
git config core.ignorecase一般 Git 会根据文件系统自动设置。
实践建议:
- 文件命名统一风格。
- 不要只改文件名大小写后直接提交。
- 如果必须修改大小写,使用
git mv。
示例:
git mv userservice.kt UserService.kt8. 配置凭据保存
使用 HTTPS 远程仓库时,Git 需要认证。
常见方式:
- 用户名 + Token
- SSH key
- 凭据管理器
Windows 上 Git 通常会配合 Git Credential Manager。
查看凭据 helper:
git config --global credential.helper常见配置:
git config --global credential.helper managermacOS 常见:
git config --global credential.helper osxkeychainLinux 可以使用 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.comSSH 远程地址一般长这样:
git@github.com:user/repo.gitHTTPS 地址一般长这样:
https://github.com/user/repo.gitSSH 适合长期开发,HTTPS 适合快速克隆或简单使用。
10. 配置代理
如果访问 GitHub 慢,可能会配置代理。
HTTP 代理:
git config --global http.proxy http://127.0.0.1:7890git config --global https.proxy http://127.0.0.1:7890取消代理:
git config --global --unset http.proxygit config --global --unset https.proxy注意:
- 代理地址要根据自己的环境调整。
- 公司网络环境下不要随意配置未知代理。
- 代理问题经常会影响 clone、fetch、push。
11. 配置常用别名
Git 命令较长,可以设置别名提高效率。
git config --global alias.st statusgit config --global alias.co checkoutgit config --global alias.sw switchgit config --global alias.br branchgit config --global alias.cm commitgit config --global alias.lg "log --oneline --graph --decorate --all"使用:
git stgit lg建议只给常用、安全、容易理解的命令设置别名。
不要给 reset --hard、push --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 simplesimple 表示只推送当前分支到它对应的 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 commitGit 会自动打开这个模板。
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.com17. 推荐基础配置清单
个人开发环境可以先配置这些:
git config --global user.name "Your Name"git config --global user.email "you@example.com"git config --global init.defaultBranch maingit config --global core.editor "code --wait"git config --global color.ui autogit config --global push.default simplegit config --global alias.st statusgit config --global alias.lg "log --oneline --graph --decorate --all"Windows 可以额外考虑:
git config --global core.autocrlf truemacOS / Linux 可以考虑:
git config --global core.autocrlf input项目级换行规则更推荐交给 .gitattributes。
8. 创建和克隆仓库
Git 仓库的来源通常有两种:
- 本地已经有项目,现在要开始用 Git 管理。
- 远程已经有仓库,现在要克隆到本地开发。
对应命令分别是:
git initgit clone <repo-url>这两个命令是进入 Git 项目的起点。
1. 初始化本地仓库
git initgit init 会在当前目录创建一个 .git 文件夹。
有了 .git 文件夹,这个目录就变成了 Git 仓库。
示例:
mkdir my-projectcd my-projectgit init初始化后可以查看状态:
git status你会看到类似提示:
On branch mainNo commits yet这表示仓库已经创建,但还没有任何提交。
2. 初始化后的第一次提交
初始化仓库后,通常要添加项目文件并创建第一次提交。
echo "# My Project" > README.mdgit add README.mdgit commit -m "docs: add initial README"第一次提交常见写法:
chore: initial commitdocs: add initial READMEfeat: initialize project structure如果只是空项目初始化,可以用:
chore: initial commit如果已经有明确项目结构,更推荐写清楚初始化内容。
3. 在已有项目中启用 Git
如果项目目录已经存在:
cd existing-projectgit initgit status然后添加必要文件:
git add .git commit -m "chore: import existing project"注意:
- 提交前先写
.gitignore - 不要把构建产物、依赖目录、日志、密钥提交进去
- 初次导入项目时,可以先用
git status检查文件列表
常见初始 .gitignore:
# build outputsbuild/dist/target/
# dependenciesnode_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.gitgit clone 会做几件事:
- 下载远程仓库数据。
- 创建本地工作目录。
- 自动设置远程仓库名为
origin。 - 检出默认分支。
克隆后目录结构:
repo/ .git/ README.md src/进入项目:
cd repogit status6. 克隆到指定目录
默认情况下,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.gitSSH 地址:
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 initgit add .git commit -m "chore: initial commit"git remote add origin git@github.com:user/repo.gitgit push -u origin main-u 表示设置 upstream。设置后,以后可以直接:
git pushgit pull12. 修改远程地址
如果远程地址错了,可以修改:
git remote set-url origin git@github.com:user/new-repo.git查看确认:
git remote -v13. 删除远程仓库引用
删除远程名:
git remote remove origin注意:这只会删除本地对远程仓库的引用,不会删除 GitHub/GitLab 上的远程仓库。
14. 一个本地项目推送到新远程仓库的完整流程
假设你已经有一个本地项目,现在想推送到 GitHub。
步骤:
- 在 GitHub 创建空仓库。
- 本地初始化 Git。
- 创建第一次提交。
- 添加远程地址。
- 推送主分支。
命令:
cd my-projectgit initgit add .git commit -m "chore: initial commit"git branch -M maingit remote add origin git@github.com:user/my-project.gitgit push -u origin maingit branch -M main 的作用是把当前分支重命名为 main。
15. 克隆后通常要做什么
克隆项目后,不是马上改代码,建议先做这些检查:
git statusgit branchgit remote -vgit log --oneline -5然后阅读:
README.mdCONTRIBUTING.md- 构建脚本
.gitignore- 项目文档
如果是团队项目,通常还要:
- 安装依赖
- 配置环境变量
- 运行测试
- 创建自己的功能分支
示例:
git switch -c feature/login9. 查看状态与差异
在 Git 日常开发中,status 和 diff 是最常用、也最应该熟练掌握的命令。
它们分别回答两个问题:
git status: 当前仓库处于什么状态?git diff: 具体改了什么?一个好的提交习惯是:
git statusgit diffgit add -pgit diff --cachedgit commit也就是说,提交前一定要先看状态和差异。
1. 查看状态
git statusgit status 会告诉你:
- 当前在哪个分支
- 工作区是否有修改
- 暂存区是否有内容
- 是否有未跟踪文件
- 当前分支是否领先或落后远程分支
- 合并或 rebase 是否正在进行
典型输出:
On branch feature/loginChanges 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.ktA README.md?? debug.log常见标记:
| 标记 | 含义 |
|---|---|
?? | 未跟踪文件 |
M | 文件被修改 |
A | 新增文件 |
D | 删除文件 |
R | 重命名文件 |
短状态有两列:
XY file- X 表示暂存区状态
- Y 表示工作区状态
例如:
M file.txt表示工作区修改了,但还没暂存。
M file.txt表示修改已经暂存。
3. 查看工作区差异
git diffgit diff 默认比较:
工作区 vs 暂存区也就是查看还没有 git add 的修改。
适合在执行 git add 前检查自己改了什么。
示例输出结构:
diff --git a/src/LoginService.kt b/src/LoginService.ktindex 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含义:比较 main 和 feature/login 两个分支的文件内容差异。
7. 查看当前分支和远程分支差异
查看本地当前分支与远程分支差异:
git diff origin/main如果你在功能分支上,想看自己相对 main 改了什么:
git fetch origingit diff origin/main...HEAD三个点 ... 常用于查看当前分支相对共同祖先的变更,适合 review 前检查。
8. 只看文件名
如果不想看具体内容,只想知道哪些文件变了:
git diff --name-only查看暂存区变更文件:
git diff --cached --name-only查看文件状态和文件名:
git diff --name-status示例:
M src/LoginService.ktA src/LoginValidator.ktD old-login.md9. 统计变更规模
查看每个文件增删行统计:
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.txtindex 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 时优先关注:
- 是否有无关文件。
- 是否有调试代码。
- 是否有敏感信息。
- 是否有大面积格式化。
- 是否和 commit message 对得上。
14. 查看冲突状态
发生冲突时:
git status会提示哪些文件未合并。
查看冲突文件:
git diff冲突标记一般长这样:
<<<<<<< HEAD当前分支内容=======被合并分支内容>>>>>>> feature/login解决冲突后:
git add conflict-filegit commit如果是 rebase:
git add conflict-filegit rebase --continue15. 提交前推荐检查流程
日常提交前建议:
git statusgit diffgit add -pgit diff --cachedgit statusgit commit如果是较大的功能分支,提交或发 PR 前还可以看:
git diff --stat origin/main...HEADgit diff --name-only origin/main...HEAD确保:
- 改动范围合理
- 没有无关文件
- 没有调试输出
- 没有密钥和本地配置
- 暂存区内容能被 commit message 准确描述
10. 添加与提交
添加与提交是 Git 日常使用中最核心的操作。
基本流程:
修改文件 -> git add -> git commit工作区 -> 暂存区 -> 本地仓库这里要注意:
git add不是提交,只是加入暂存区。git commit才会生成提交历史。git push才会把本地提交同步到远程。
1. 添加文件
git add file.txtgit add .git add -p其中 git add -p 可以交互式选择部分修改,非常适合拆分提交。
2. git add 的作用
git add 的作用是把工作区修改加入暂存区。
也就是:
工作区 -> 暂存区常见命令:
git add file.txtgit add src/git add .git add -Agit add -p3. 添加单个文件
git add README.md适合只想提交某个文件时使用。
查看是否已经暂存:
git statusgit diff --cached4. 添加整个目录
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.txt8. 提交
git commit -m "fix(auth): handle empty password"git commit 会把暂存区内容保存为一次提交。
一次提交通常包含:
- 提交 ID
- 作者
- 时间
- 提交信息
- 文件快照
- 父提交引用
查看最近提交:
git log --oneline -59. 使用 -m 写提交信息
最常见方式:
git commit -m "fix(auth): handle empty password"适合简单提交。
如果提交比较复杂,不建议只写一行,可以直接执行:
git commitGit 会打开编辑器,让你写多行提交信息。
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.txtgit commit --amend这样会把文件补进最近一次提交。
amend 会重写最近一次提交,提交 ID 会改变。
所以:
- 本地未推送提交可以放心 amend
- 已推送但没人使用的分支可以谨慎 amend
- 公共分支或别人已经基于其开发的提交不要随意 amend
13. 空提交
有时需要创建一个没有文件变化的提交,例如触发 CI。
git commit --allow-empty -m "chore: trigger ci"适合:
- 触发流水线
- 标记某个流程节点
- 测试 hook 或 CI 配置
不要滥用空提交,否则历史会变得嘈杂。
14. 好的提交粒度
一次提交应该只做一件事。
好的提交:
fix(auth): reject expired tokendocs(readme): update setup guidetest(auth): add invalid login test不好的提交:
update filesfix stufflogin changes判断粒度是否合适:
- 能否一句话说明
- 是否可以独立回滚
- 是否方便 review
- 是否只对应一个目的
- 是否和 commit message 对得上
15. 提交前检查流程
推荐流程:
git statusgit diffgit add -pgit diff --cachedgit commit如果是简单修改:
git statusgit add README.mdgit diff --cachedgit commit -m "docs: update README"提交前检查:
- 是否有无关文件。
- 是否有调试日志。
- 是否有敏感信息。
- 是否有格式化噪声。
- 是否漏加测试。
- 提交信息是否清楚。
16. Commit Message 简要规范
更完整的 commit message 规范在后文会专门展开,这里先给出最常用格式:
type(scope): description示例:
feat(auth): add email loginfix(api): handle timeout responsedocs(git): update commit examplesrefactor(ui): extract button componenttest(auth): add login failure cases常用 type:
| type | 含义 |
|---|---|
feat | 新功能 |
fix | 修复 bug |
docs | 文档 |
style | 格式,不影响逻辑 |
refactor | 重构 |
test | 测试 |
chore | 杂项维护 |
11. 查看历史
Git 的提交历史是项目演进过程的记录。
查看历史可以帮助你回答这些问题:
- 最近改了什么
- 某个功能是谁加的
- 某个 bug 可能是哪次提交引入的
- 某个文件经历了哪些变化
- 当前分支和主分支差了哪些提交
- 某个版本发布时包含哪些修改
常用核心命令:
git loggit log --onelinegit show <commit>git blame <file>1. 普通日志
git loggit log 会按时间倒序显示提交历史,最新提交在最上面。
典型输出:
commit a1b2c3d4e5f6Author: 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 passwordb2c3d4e docs(readme): update setup guidec3d4e5f 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~1git 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.name 和 user.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 origingit 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 password14. 常用日志别名
可以配置一个好用的日志别名:
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/filegit blame path/to/file查当前分支准备合并哪些提交
git fetch origingit log --oneline origin/main..HEAD查某个版本之间的变更
git log --oneline v1.0.0..v1.1.0git diff --stat v1.0.0..v1.1.018. reflog 查看本地操作历史
git log 查看提交历史。
git reflog 查看本地 HEAD 和分支指针移动历史。
git reflog它适合找回:
- 误删的分支
- 误 reset 的提交
- rebase 前的位置
- checkout 过的历史位置
示例:
git refloggit reset --hard HEAD@{1}注意:
reflog是本地记录。- 不同机器上的 reflog 不一样。
- 它不是远程仓库历史。
19. 用历史生成发布说明
查看上一个版本到当前的提交:
git log v1.0.0..HEAD --oneline如果团队使用 Conventional Commits,可以筛选功能和修复:
git log v1.0.0..HEAD --grep="^feat" --onelinegit log v1.0.0..HEAD --grep="^fix" --oneline查看变更文件统计:
git diff --stat v1.0.0..HEAD发布前常用组合:
git fetch --tagsgit log v1.0.0..HEAD --onelinegit diff --stat v1.0.0..HEAD12. 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.txtb.txt如果它们内容完全一样,Git 可以让它们复用同一个 blob。
可以理解为:
blob = 文件内容这也是 Git 节省存储空间的原因之一。
3. tree:保存目录结构
tree 保存目录结构。
它记录:
- 文件名
- 文件权限
- 文件名对应哪个 blob
- 子目录对应哪个 tree
可以理解为:
tree = 文件名 + 目录层级 + 对象引用示意:
tree README.md -> blob src/ -> tree Main.kt -> blobblob 只知道内容,不知道自己叫什么名字。
文件名和目录关系由 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.0tag 通常固定不动,而 branch 会随着提交移动。
| 对比项 | branch | tag |
|---|---|---|
| 是否移动 | 会移动 | 通常固定 |
| 主要用途 | 开发线 | 发布版本 |
| 常见命名 | main、feature/login | v1.0.0、v2.1.3 |
| 是否频繁变化 | 是 | 否 |
6. 对象之间的关系
一次提交不是简单保存一个文件夹副本,而是通过对象引用组成一棵结构。
commit | vtree | +-- 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 | vA---B---C当你在 main 上继续提交 D:
main | vA---B---C---Dmain 会自动移动到 D。
这就是 Git 分支轻量的根本原因:
创建分支不是复制一份代码,而是创建一个指针。3. HEAD:当前所在位置
HEAD 表示当前工作区所在的位置。
通常 HEAD 指向当前分支:
HEAD -> main -> D当你切换分支:
git switch feature/loginHEAD 会指向 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..main5. 引用保存在哪里
分支引用通常在:
.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---CC 这个提交对象可能还在,但 main 不再指向它。
这也是为什么 reflog 能找回一些误操作:本地曾经记录过指针移动历史。
8. rebase 为什么会改变提交 ID
rebase 会把一组提交重新应用到新的基底上。
虽然代码内容可能类似,但新的提交有新的 parent,所以 commit 对象内容变了。
因此提交 ID 也会变。
示意:
rebase 前:main: A---B---Cfeature: \---D---E
rebase 后:main: A---B---C \---D'---E'D' 和 E' 是新提交,不是原来的 D 和 E。
9. 为什么不要随意改公共历史
如果某些提交已经推送并被别人拉取,别人本地也有这些提交。
你如果用:
git commit --amendgit rebasegit resetgit push --force改写公共历史,可能导致:
- 提交 ID 不一致
- 队友分支难以合并
- 远程和本地历史分叉
- PR/MR 变得混乱
基本原则:
本地未共享历史可以整理。公共共享历史谨慎改写。13. 分支管理
分支是 Git 最核心、最常用的能力之一。
它允许你在不影响主线代码的情况下开发新功能、修复 bug、实验方案或准备发布。
可以把分支理解成:
指向某个提交的可移动指针分支不是复制一份完整代码,所以创建和切换都非常快。
1. 查看分支
git branchgit branch -a常用命令:
git branchgit branch -agit branch -r含义:
| 命令 | 说明 |
|---|---|
git branch | 查看本地分支 |
git branch -a | 查看本地和远程跟踪分支 |
git branch -r | 查看远程跟踪分支 |
当前分支前面会有 *:
* main feature/login2. 创建分支
git branch feature/login这条命令只创建分支,不会自动切换过去。
创建分支的本质是:
创建一个新的指针,指向当前提交创建后可以查看:
git branch3. 切换分支
git switch feature/logingit switch 是较新的分支切换命令,语义比旧的 git checkout 更清晰。
旧写法:
git checkout feature/login推荐新项目优先使用:
git switch4. 创建并切换
git switch -c feature/login这等价于:
git branch feature/logingit switch feature/login常见开发流程:
git switch maingit pullgit switch -c feature/user-profile意思是:
- 切回主分支。
- 拉取最新代码。
- 从最新主分支创建功能分支。
5. 删除分支
git branch -d feature/logingit branch -D feature/login区别:
| 命令 | 含义 |
|---|---|
git branch -d | 安全删除,分支未合并时会阻止 |
git branch -D | 强制删除,不管是否合并 |
推荐优先使用:
git branch -d feature/login只有确认不要这个分支时才用:
git branch -D feature/login6. 重命名分支
重命名当前分支:
git branch -m new-name重命名指定分支:
git branch -m old-name new-name如果分支已经推送到远程,还需要处理远程分支:
git push origin --delete old-namegit push -u origin new-name7. 从指定提交创建分支
从当前提交创建:
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. 本地分支和远程分支
本地分支:
mainfeature/login远程跟踪分支:
origin/mainorigin/feature/login注意:
origin/feature/login 是本地记录的远程状态,不是远程服务器上的真实分支本体。
更新远程分支信息:
git fetch origin10. 推送本地分支到远程
第一次推送新分支:
git push -u origin feature/login-u 的作用是设置 upstream。
设置后,以后在该分支上可以直接:
git pushgit pull11. 删除远程分支
删除远程分支:
git push origin --delete feature/login删除后,本地可能还保留远程跟踪引用。可以清理:
git fetch --prune或:
git remote prune origin12. 分支命名规范
好的分支名应该表达用途。
常见格式:
feature/user-profilefix/login-tokenhotfix/payment-timeoutrelease/v1.2.0docs/git-notechore/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 maingit pullgit 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处理方式:
- 提交当前修改。
- 暂存修改。
- 丢弃修改。
暂存修改:
git stashgit switch other-branchgit stash pop14. 远程同步
远程同步是多人协作的核心。
本地 Git 仓库可以独立提交和查看历史,但团队协作需要和远程仓库交换提交。
常见远程平台:
- GitHub
- GitLab
- Gitee
- Bitbucket
- 公司自建 Git 服务
远程同步主要围绕这些命令:
git remotegit fetchgit pullgit push它们分别负责:
remote: 管理远程仓库地址fetch : 拉取远程信息,但不自动合并pull : 拉取远程信息,并整合到当前分支push : 推送本地提交到远程仓库14.1 介绍
1. remote 是什么
remote 是远程仓库的别名。
最常见的远程名是:
origin当你执行:
git clone git@github.com:user/repo.gitGit 通常会自动创建一个远程引用:
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/mainorigin/feature/login但它不会修改你当前工作分支的代码。
可以理解为:
git fetch = 先看看远程有什么新东西,但不动我当前代码常见用法:
git fetch origingit fetch --allgit 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 pullgit pull origin maingit pull --rebase6. pull 前为什么要先 status
执行 git pull 前建议先看:
git status原因:
- 本地有未提交修改时,pull 可能失败
- 本地修改和远程修改可能冲突
- 你可能不在预期分支
推荐流程:
git statusgit fetch origingit log --oneline HEAD..origin/maingit pull如果你在功能分支上:
git switch feature/logingit fetch origingit rebase origin/main或者:
git merge origin/main选择 merge 还是 rebase,要看团队规范。
7. pull 使用 merge 还是 rebase
两种常见策略:
| 策略 | 命令 | 特点 |
|---|---|---|
| merge | git pull 或 git pull --no-rebase | 保留真实合并历史 |
| rebase | git 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/loginpush 用来把本地提交推送到远程仓库。
常见用法:
git pushgit push origin maingit push origin feature/login注意:
commit只是提交到本地仓库。push才会同步到远程仓库。
关系:
工作区 -> git add -> 暂存区暂存区 -> git commit -> 本地仓库本地仓库 -> git push -> 远程仓库9. 第一次推送新分支
第一次推送本地新分支时,常用:
git push -u origin feature/login-u 表示设置 upstream。
设置 upstream 后,以后可以直接:
git pushgit pull不用每次都写:
git push origin feature/login10. 设置 upstream
git push -u origin feature/login之后可以直接:
git pushgit pullupstream 表示当前本地分支默认跟踪哪个远程分支。
查看分支 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/login11. 删除远程分支
删除远程分支:
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.0tag 常用于发布版本。发布流程中要谨慎删除或重建 tag。
13. 强制推送
普通强推:
git push --force更安全的强推:
git push --force-with-lease区别:
| 命令 | 风险 |
|---|---|
--force | 直接覆盖远程分支,可能覆盖别人提交 |
--force-with-lease | 如果远程已有别人新提交,会拒绝覆盖 |
使用场景:
- 整理个人功能分支历史后推送
- rebase 后更新自己的 PR 分支
不要用于:
mainmasterdevelop- release 分支
- 多人共用分支
除非团队明确允许并已沟通。
14.2 常见远程同步流程
1. 开始一天工作
git switch maingit pullgit switch feature/logingit rebase main或:
git switch feature/logingit fetch origingit rebase origin/main2. 完成需求并推送
git statusgit add .git commit -m "feat(auth): add login validation"git push -u origin feature/login3. 主分支更新后同步到功能分支
方式一:merge
git fetch origingit merge origin/main方式二:rebase
git fetch origingit rebase origin/main团队要统一选择。
15. merge 与 rebase
merge 和 rebase 都用于整合分支修改。
它们解决的是同一个问题:
如何把一个分支上的修改整合到另一个分支?但它们处理历史的方式不同:
merge保留分支真实合并历史。rebase改写提交基底,让历史更线性。
理解二者差异,是 Git 协作的关键。
15.1 merge
1. merge
把一个分支的修改合并到当前分支。
git switch maingit merge feature/login意思是:
把 feature/login 分支合并到 main 分支优点:
- 保留真实历史
- 操作安全
- 适合公共分支
缺点:
- 历史可能出现较多 merge commit
2. merge 的历史形态
假设当前历史如下:
main: A---B---C \feature: D---E执行:
git switch maingit 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 maingit merge featureGit 可以直接把 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 maingit merge --squash feature/logingit commit -m "feat(auth): add login flow"特点:
- main 上只出现一个提交
- 不保留 feature 分支的细节历史
- 适合清理比较乱的功能分支提交
适合:
- 功能分支里有很多临时提交
- 团队希望主分支历史简洁
- PR 合并时使用 squash 策略
不适合:
- 需要保留完整提交过程的场景
- 每个小提交都有独立价值的场景
15.2 rebase
1. rebase
把当前分支的提交“移到”另一个基底之后。
git switch feature/logingit rebase main意思是:
把 feature/login 上的提交重新放到 main 最新提交之后优点:
- 提交历史更线性
- 方便阅读
缺点:
- 会重写提交历史
- 不适合随意对公共分支使用
2. rebase 的历史形态
rebase 前:
main: A---B---C \feature: D---E执行:
git switch featuregit rebase mainrebase 后:
main: A---B---C \feature: D'---E'注意:
D'和E'是新提交- 原来的
D和E被复制到了新的基底之后 - 提交 ID 会改变
3. rebase 的本质
rebase 可以理解为:
找到当前分支和目标分支的共同祖先取出当前分支独有的提交把这些提交按顺序重新应用到目标分支之后所以 rebase 会重写提交历史。
这也是为什么公共分支上不能随便 rebase。
4. rebase 黄金规则
不要 rebase 已经共享给别人并被别人基于开发的公共提交。
换句话说:
可以 rebase 自己本地还没推送的提交。不要 rebase 别人可能已经拉取的提交。适合 rebase:
- 自己的本地 feature 分支
- 还没推送的提交
- PR 前整理个人提交
- 同步 main 到自己的功能分支
不适合 rebase:
- main 分支
- release 分支
- 多人共同开发的分支
- 已经被别人基于开发的提交
merge 和 rebase 对比
| 对比项 | merge | rebase |
|---|---|---|
| 是否改写历史 | 否 | 是 |
| 历史形态 | 保留分叉和合并 | 更线性 |
| 是否产生 merge commit | 可能 | 不产生 |
| 冲突处理 | 一次合并中处理 | 可能每个提交都处理 |
| 适合公共分支 | 适合 | 不适合随意使用 |
| 适合整理个人分支 | 可以 | 很适合 |
| 可追踪真实协作历史 | 强 | 较弱 |
| 历史简洁度 | 一般 | 高 |
推荐使用 merge 的场景:
- 合并功能分支到主分支
- 合并 release 分支
- 保留完整协作历史
- 多人共享分支
- 不希望改写历史
示例:
git switch maingit merge --no-ff feature/login团队中常见策略:
- PR/MR 合并到 main 用 merge commit
- 保留功能分支的完整上下文
推荐使用 rebase 的场景:
- 本地功能分支同步 main
- PR 前整理自己的提交
- 保持个人分支历史线性
- 清理临时提交
示例:
git switch feature/logingit fetch origingit 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/logingit fetch origingit rebase origin/maingit push --force-with-leasePR 合并时使用 merge commit。
优点:
- feature 分支干净
- main 保留合并历史
策略二:全部 squash merge
PR 合并时 squash 成一个提交。
优点:
- main 历史非常简洁
- 每个 PR 对应一个提交
缺点:
- 丢失功能分支内部细节
策略三:只允许 fast-forward
要求所有分支先 rebase 到 main,再 fast-forward 合并。
优点:
- 历史完全线性
缺点:
- 对团队 Git 能力要求较高
- 真实合并上下文较少
16. 冲突处理
冲突是 Git 协作中很常见的情况。
它通常发生在 Git 无法自动判断应该保留哪一份修改时。
典型场景:
- 两个人修改了同一文件的同一区域
- 一个分支修改了文件,另一个分支删除了文件
- 两个分支都重命名或移动了同一个文件
- rebase 时旧提交和新基底修改了同一段代码
冲突不是错误,而是 Git 要求开发者人工确认最终内容。
冲突什么时候发生
常见会触发冲突的命令:
git mergegit rebasegit pullgit cherry-pickgit revert其中:
git pull可能触发冲突,因为它内部会执行 merge 或 rebase。git rebase可能多次触发冲突,因为它会逐个重放提交。git cherry-pick也可能冲突,因为它把某个提交应用到当前分支。
冲突标记
冲突标记:
<<<<<<< HEAD当前分支内容=======被合并分支内容>>>>>>> feature/login含义:
| 标记 | 含义 |
|---|---|
<<<<<<< HEAD | 当前分支的内容开始 |
======= | 两边内容的分隔线 |
>>>>>>> feature/login | 被合并分支的内容结束 |
示例:
<<<<<<< HEADreturn "login failed"=======return "invalid username or password">>>>>>> feature/login你需要手动改成最终想要的内容,例如:
return "invalid username or password"并删除所有冲突标记。
查看冲突状态
发生冲突后,先执行:
git statusGit 会列出未解决冲突的文件。
也可以查看冲突内容:
git diff查看未合并文件:
git diff --name-only --diff-filter=Umerge 冲突处理流程
执行 merge:
git switch maingit merge feature/login如果出现冲突,流程是:
git status# 打开冲突文件,手动编辑git add conflict-filegit commit如果 Git 已经生成默认 merge commit message,执行 git commit 即可。
如果想放弃这次 merge:
git merge --abortrebase 冲突处理流程
执行 rebase:
git switch feature/logingit rebase main如果出现冲突,流程是:
git status# 打开冲突文件,手动编辑git add conflict-filegit rebase --continue如果当前这个提交不想要了:
git rebase --skip如果想放弃整个 rebase:
git rebase --abort注意:
rebase 是逐个提交重放,所以可能解决完一个冲突后,后面又出现新的冲突。
cherry-pick 冲突处理
执行:
git cherry-pick <commit>如果冲突:
git status# 手动解决冲突git add conflict-filegit cherry-pick --continue放弃 cherry-pick:
git cherry-pick --abort16.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 vscodegit 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 usmodified by them或:
deleted by themmodified by us意思是:
- 一边删除了文件
- 另一边修改了文件
你需要决定:
- 保留删除
- 保留修改
- 手动创建新的替代文件
保留删除:
git rm path/to/file保留文件:
git add path/to/file冲突解决后要做什么
冲突解决后不要立刻结束,建议检查:
git statusgit 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.txtgit 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~1git commit -m "fix(auth): handle expired token"回退提交并保留到工作区
git reset --mixed HEAD~1--mixed 是 git reset 的默认模式。
状态变化:
提交撤销,修改回到工作区,暂存区清空等价于:
git reset HEAD~1适合:
- 想撤销提交并重新选择暂存内容
- 想拆分最近一次提交
- 误把多个改动提交在一起
回退并丢弃修改
git reset --hard HEAD~1reset --hard 会丢弃修改,使用前必须确认。
状态变化:
提交撤销,暂存区和工作区也一起回退危险点:
- 会丢弃未保存修改
- 会让文件回到指定提交状态
- 如果目标提交写错,可能造成数据丢失
使用前建议:
git statusgit log --oneline -5如果不确定,先创建备份分支:
git branch backup/before-resetreset 三种模式对比
| 命令 | 移动 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 的区别
| 对比项 | reset | revert |
|---|---|---|
| 是否新增提交 | 否 | 是 |
| 是否改写历史 | 是 | 否 |
| 是否适合公共分支 | 通常不适合 | 适合 |
| 是否会改变分支指针 | 会 | 会向前新增提交 |
| 主要用途 | 本地整理历史 | 安全撤销已共享提交 |
选择建议:
本地还没 push:可以考虑 reset。已经 push 到公共分支:优先 revert。找回误操作
git refloggit reset --hard <commit>reflog 是 Git 本地操作记录,常用于找回误删分支或误 reset 的提交。
git reflog 记录 HEAD 和分支指针的移动历史。
示例:
git reflog可能看到:
a1b2c3d HEAD@{0}: reset: moving to HEAD~1b2c3d4e HEAD@{1}: commit: fix(auth): handle tokenc3d4e5f 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 --abortrebase 已完成但想回到之前:
git refloggit 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> |
| 找回误 reset | git reflog |
| 恢复某个历史文件 | git restore --source=<commit> -- file |
| 中止 merge | git merge --abort |
| 中止 rebase | git rebase --abort |
危险操作前的保护流程
执行这些命令前要特别谨慎:
git reset --hardgit clean -fdgit push --forcegit rebase推荐保护流程:
git statusgit log --oneline --graph --decorate -10git branch backup/before-dangerous-operation如果涉及远程分支,再确认:
git fetch origingit branch -vv这样即使操作失误,也可以通过备份分支或 reflog 找回。
公共分支回滚推荐流程
如果问题已经进入 main、release 等公共分支,不建议使用 reset 改写历史。
推荐流程:
git switch maingit pullgit log --onelinegit revert <bad-commit>git push如果要回滚多个提交,可以先在临时分支验证:
git switch -c revert/testgit revert <commit1> <commit2># 运行测试确认没有问题后,再在目标分支执行正式回滚。
公共分支回滚要关注:
- 是否会影响数据库迁移
- 是否会影响配置文件
- 是否会影响接口兼容性
- 是否需要同步回滚发布版本
- 是否需要通知团队和测试人员
18. stash 临时保存
临时切换任务时,可以用 stash 保存当前工作区。
git stash push -m "work in progress"git stash listgit stash popstash 的作用是:
把当前未提交修改临时收起来,让工作区恢复干净。适合:
- 临时切换分支
- 拉取远程更新前保存本地修改
- 暂时搁置未完成工作
stash 是什么
stash 是 Git 提供的临时保存机制。
它适合保存还不适合 commit 的修改。
常见场景:
正在写功能 A突然要切到 hotfix 分支修线上 bug当前修改还没完成,不想提交此时可以 stash流程:
git stash push -m "wip: login form"git switch hotfix/payment# 修复 buggit switch feature/logingit 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 formstash@{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 clearclear 会删除所有 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}这个命令会:
- 基于 stash 创建时的提交创建新分支。
- 应用 stash。
- 如果成功,删除该 stash。
适合:
- stash 很久以后再恢复
- 当前分支变化太大,直接 apply 容易冲突
- 想把临时修改变成正式分支
stash 和 commit 的区别
| 对比项 | stash | commit |
|---|---|---|
| 目的 | 临时保存 | 正式记录历史 |
| 是否进入项目历史 | 否 | 是 |
| 是否适合共享 | 否 | 是 |
| 是否需要 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/paymentpull 前保存本地修改
git stash push -m "wip: before pull"git pullgit 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.0git 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 | 修订版本,向后兼容 bugfix | 1.3.2 |
示例:
v1.0.0v1.1.0v1.1.1v2.0.0常见规则:
- 修 bug:增加 PATCH
- 新增兼容功能:增加 MINOR
- 破坏兼容性:增加 MAJOR
预发布版本
SemVer 还支持预发布版本:
v1.0.0-alpha.1v1.0.0-beta.1v1.0.0-rc.1常见含义:
| 标记 | 含义 |
|---|---|
| alpha | 早期测试版本 |
| beta | 公开测试版本 |
| rc | release candidate,候选发布版本 |
示例流程:
v1.2.0-alpha.1v1.2.0-beta.1v1.2.0-rc.1v1.2.0常见发布流程
一个常见发布流程:
git switch maingit pullgit 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.0git push origin v1.1.0release 分支用于准备发布,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.1git push origin v1.0.1之后再把 hotfix 合并回主线,避免主线缺失修复。
tag 和 CI/CD
很多项目会用 tag 触发发布。
例如:
push tag v1.2.0 -> CI 构建 -> 运行测试 -> 生成制品 -> 发布 ReleaseGitHub 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 不会自动生效。
基础示例
常见内容:
# dependenciesnode_modules/
# build outputsdist/build/target/
# logs*.log
# IDE.idea/.vscode/
# OS.DS_StoreThumbs.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 .gitignoregit 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_StoreThumbs.db*.swp建议:
- 项目共享规则写进项目
.gitignore - 个人习惯规则写进全局
.gitignore_global
哪些文件不应该忽略
不要把这些重要文件随便忽略:
- 源代码
- 构建脚本
- 依赖锁文件
- 示例配置
- 数据库迁移脚本
- CI/CD 配置
.gitignore本身.gitattributes
依赖锁文件是否提交,取决于生态:
| 文件 | 通常建议 |
|---|---|
package-lock.json | 应提交 |
yarn.lock | 应提交 |
pnpm-lock.yaml | 应提交 |
Gemfile.lock | 应提交,应用项目尤其需要 |
gradle.lockfile | 按团队依赖锁策略 |
锁文件有助于保证团队和 CI 使用一致依赖版本。
敏感信息处理
应该忽略:
.env*.key*.pemsecrets.*但应该提交示例:
!.env.example推荐做法:
.env 真实配置,不提交.env.example 示例配置,提交如果敏感信息已经提交到 Git 历史,仅仅加入 .gitignore 不够。
必须:
- 立即废弃泄漏密钥。
- 从历史中清理敏感信息。
- 通知团队重新处理本地仓库。
.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/v1oid 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 .gitattributesgit commit -m "chore: configure git lfs tracking"添加 LFS 文件
配置跟踪规则后,再添加大文件:
git add model.onnxgit commit -m "chore(model): add initial onnx model"推送:
git pushGit 会上传普通提交对象,也会把 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应该进.gitignoremodel.onnx、demo.mp4、design.psd如果需要版本化,可以进 Git LFS
Git LFS 的适用场景
适合使用 Git LFS 的文件:
- 设计源文件
- 游戏资源
- 模型文件
- 数据样例
- 大型图片
- 二进制 SDK
- 演示视频
- 需要跟随代码版本变化的大型资产
不适合使用 Git LFS 的文件:
- 构建产物
- 日志文件
- 缓存目录
- 依赖下载目录
- 临时文件
- 敏感文件
- 频繁生成的大量中间文件
这些通常应该忽略或放到制品仓库。
团队协作注意事项
团队使用 Git LFS 时要统一规则:
.gitattributes必须提交。- 所有成员都要安装 Git LFS。
- 新增大文件前先确认跟踪规则。
- 不要把大文件先普通提交再迁移。
- 注意托管平台的 LFS 容量和流量限制。
- CI 环境也要安装并拉取 LFS 文件。
- 大文件更新频率要控制。
新成员拉仓库后,如果看到 LFS 文件是指针内容,通常说明 LFS 没有正确拉取。
执行:
git lfs installgit lfs pullCI/CD 中的 Git LFS
CI 环境可能默认不拉 LFS 文件。
常见处理:
git lfs installgit lfs pull在 GitHub Actions 中,actions/checkout 可以配置 LFS:
- uses: actions/checkout@v4 with: lfs: true如果构建依赖模型、资源、二进制文件,要确认 CI 真的拿到了 LFS 内容,而不是指针文件。
22. 常见协作工作流
Git 协作工作流是团队围绕代码变更形成的一套规则。
它回答的不是“Git 能做什么”,而是:
- 开发者应该从哪个分支开始工作
- 新功能、缺陷修复、紧急修复应该提交到哪里
- 代码什么时候合并
- 谁来评审
- 如何触发测试、构建、发布
- 线上问题如何回滚或热修复
没有统一工作流时,团队常见问题包括:
- 主分支经常不可用
- 多人修改互相覆盖
- 临近发布时大量冲突集中爆发
- 不知道某个提交是否已经上线
- 修复线上问题时找不到稳定基线
- 分支长期不合并,最后变成“大爆炸式合并”
好的 Git 工作流不一定复杂,但必须让团队对以下事情形成共识:
分支怎么建,代码怎么审,变更怎么测,版本怎么发,问题怎么回滚。22.1 集中式工作流
集中式工作流是最简单的一种 Git 协作模式。
所有开发者都围绕同一个主分支工作,通常是:
mainmastertrunk
基本结构:
developer A ----\developer B ----- maindeveloper C ----/典型流程:
git switch maingit pull origin main
# 修改代码git statusgit 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-profilefeature/payment-orderfix/login-timeoutbugfix/null-pointer-on-startuprefactor/order-servicedocs/api-usagetest/add-login-testschore/update-dependencies典型流程:
git switch maingit pull origin maingit switch -c feature/user-profile
# 修改代码git statusgit 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-profilegit fetch origingit rebase origin/main或者:
git switch feature/user-profilegit fetch origingit merge origin/main两种方式的区别:
| 方式 | 特点 | 适合场景 |
|---|---|---|
merge origin/main | 保留真实合并记录 | 团队重视完整历史 |
rebase origin/main | 历史更线性 | 团队要求提交历史整洁 |
注意:
已经被多人共同使用的远程分支,不要随意 rebase 后强推。Feature Branch 工作流适合大多数团队,尤其适合配合 PR / MR、CI、分支保护和代码评审一起使用。
22.3 Git Flow
Git Flow 是一种较完整、较重的分支模型,最早常用于版本发布周期明确的软件项目。
它通常包含以下长期分支:
maindevelop
以及以下临时分支:
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 developgit pull origin developgit 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 developgit pull origin developgit merge --no-ff feature/report-exportgit push origin develop发布准备流程:
git switch developgit pull origin developgit switch -c release/1.4.0
# 修复发布前问题、更新版本号、补充文档git add .git commit -m "chore(release): prepare 1.4.0"发布分支测试通过后,合并到 main 并打 tag:
git switch maingit pull origin maingit merge --no-ff release/1.4.0git tag -a v1.4.0 -m "Release v1.4.0"git push origin main --tags同时要把发布分支的修复合并回 develop:
git switch developgit pull origin developgit merge --no-ff release/1.4.0git push origin develop线上紧急修复流程:
git switch maingit pull origin maingit switch -c hotfix/1.4.1-login-error
# 修复线上问题git add .git commit -m "fix(auth): handle expired session"
git switch maingit merge --no-ff hotfix/1.4.1-login-errorgit tag -a v1.4.1 -m "Release v1.4.1"git push origin main --tags
git switch developgit merge --no-ff hotfix/1.4.1-login-errorgit push origin developGit Flow 适合:
- 有明确版本号的软件
- 发布周期比较固定
- 生产环境和开发环境差异明显
- 需要维护多个正式版本
- 桌面软件、SDK、嵌入式软件、部分企业系统
Git Flow 的缺点:
- 分支多,理解成本高
- 流程重,容易降低交付速度
develop和main长期分离,可能导致集成成本上升- 不太适合高频部署和持续交付
判断是否应该使用 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 maingit pull origin maingit switch -c feature/search-filter
# 修改代码git add .git commit -m "feat(search): add filter options"git push -u origin feature/search-filter然后:
- 创建 Pull Request
- 触发 CI
- 进行代码评审
- CI 与评审通过后合并到
main - 自动或手动部署
GitHub Flow 的关键要求:
main必须稳定- 所有变更通过 PR
- 自动化测试要足够可靠
- 合并后可以快速部署
- 发现问题可以快速回滚或修复
适合:
- Web 应用
- SaaS 系统
- 移动端后端服务
- 内部平台
- 持续交付项目
不太适合:
- 发布周期很长的传统软件
- 同时维护多个历史版本的项目
- 自动化测试薄弱的团队
- 合并后不能快速验证和回滚的系统
GitHub Flow 的优势是轻量直接:
一个主分支 + 短分支 + PR + CI/CD。这也是很多现代团队默认采用的协作模型。
22.5 GitLab Flow
GitLab Flow 可以理解为在 Feature Branch / GitHub Flow 的基础上,引入环境分支或发布分支来对应真实部署流程。
常见环境分支:
mainpre-productionproduction或者:
mainstagingproduction一种常见流程:
feature/* -> main -> staging -> production示例:
git switch maingit pull origin maingit switch -c feature/invoice-export
# 开发并提交git add .git commit -m "feat(invoice): support csv export"git push -u origin feature/invoice-exportPR / MR 合并到 main 后,再根据部署节奏合并到环境分支:
git switch staginggit pull origin staginggit merge origin/maingit push origin staging
git switch productiongit pull origin productiongit merge origin/staginggit push origin productionGitLab Flow 适合:
- 有多个部署环境
- 需要区分测试环境、预发环境、生产环境
- 企业内部系统
- 发布需要审批
- 不能做到每次合并
main都立即上线的项目
注意:
环境分支不是越多越好。环境分支过多会带来:
- 合并链路变长
- 版本追踪变复杂
- 修复需要在多个分支间传递
- 环境之间容易产生差异
如果使用环境分支,应当明确:
- 每个环境分支对应哪个环境
- 谁有权限合并
- 什么时候从上游分支同步
- 线上问题从哪个分支修复
- 是否需要打 tag 标记发布版本
22.6 Trunk Based Development
Trunk Based Development,简称 TBD,中文通常称为主干开发。
它的核心思想是:
所有开发者围绕一个主干分支进行小步、高频、持续集成。主干通常是:
mainmastertrunk
TBD 不是简单地“大家都往 main 上提交”,它依赖一套工程能力:
- 自动化测试
- 持续集成
- 代码评审
- 小步提交
- 快速回滚
- Feature Flag
- 主分支保护
典型流程:
git switch maingit pull origin maingit 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.gitcd projectgit remote add upstream git@github.com:source-org/project.gitgit remote -v同步主仓库:
git fetch upstreamgit switch maingit merge upstream/maingit push origin main开发功能:
git switch -c fix/readme-typo
# 修改代码git add README.mdgit 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 Flow | main、develop、release、hotfix 分层管理 | 发布控制强 | 流程较重 | 版本发布周期明确的项目 |
| GitHub Flow | 短分支 + PR + 主分支可部署 | 轻量、适合持续交付 | 依赖 CI/CD | Web/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-profilefix/BUG-203-login-timeout不推荐的分支名:
devtestmy-branchnewfinaltempzhangsanfixbug原因是这些名字无法表达任务边界,时间久了很难判断是否还能删除。
分支保护与权限控制
无论使用哪种工作流,关键分支都应该受到保护。
通常需要保护的分支:
mainmasterdeveloprelease/*production
常见保护规则:
- 禁止直接 push
- 必须通过 PR / MR 合并
- 至少 1 到 2 人评审通过
- CI 通过后才能合并
- 不允许未解决评论就合并
- 不允许强制推送
- 不允许删除保护分支
- 要求分支与目标分支保持最新
保护分支的目的不是增加流程负担,而是保证关键分支可追溯、可验证、可发布。
一个常见规则组合:
main 分支:- 禁止直接 push- PR 必须通过 CI- 至少一名 reviewer approve- squash merge 或 merge commit 由团队统一约定合并策略选择
不同工作流通常会配合不同的合并策略。
常见合并策略:
| 策略 | 命令或平台选项 | 特点 |
|---|---|---|
| Merge Commit | git merge --no-ff | 保留分支合并历史 |
| Squash Merge | 平台 Squash and merge | 多个提交压成一个提交 |
| Rebase Merge | 平台 Rebase and merge | 历史线性,但会重写提交基线 |
| Fast-forward | git merge --ff-only | 没有额外合并提交 |
推荐选择:
- 团队重视完整分支上下文:使用 Merge Commit
- 团队重视主分支整洁:使用 Squash Merge
- 团队熟悉 rebase 且要求线性历史:使用 Rebase Merge
- 发布分支、热修复分支:常用 Merge Commit 保留节点
多数业务团队可以使用:
功能分支使用 Squash Merge,发布分支和热修复分支使用 Merge Commit。这样主分支既不会被大量零散提交污染,又能保留关键发布节点。
工作流中的 Commit Message 要求
协作工作流和 Commit Message 规范应该一起设计。
如果团队使用 Conventional Commits,可以把提交类型和分支类型对应起来:
| 分支类型 | Commit 类型 | 示例 |
|---|---|---|
feature/* | feat | feat(user): add avatar upload |
fix/* | fix | fix(auth): reject expired token |
docs/* | docs | docs(api): add auth examples |
refactor/* | refactor | refactor(order): split pricing service |
test/* | test | test(auth): add token expiry cases |
chore/* | chore | chore(ci): update build workflow |
hotfix/* | fix | fix(payment): handle duplicate callback |
PR / MR 标题也建议遵守同样规则:
feat(user): add profile edit pagefix(order): correct discount calculationdocs(git): add branch workflow guide如果使用 Squash Merge,PR 标题往往会成为最终进入主分支的 commit message,因此 PR 标题必须认真写。
23. Pull Request / Merge Request
PR / MR 是团队协作中的核心入口。
不同平台叫法不同:
| 平台 | 名称 |
|---|---|
| GitHub | Pull Request,简称 PR |
| GitLab | Merge Request,简称 MR |
| Gitee | Pull Request |
| Bitbucket | Pull Request |
它们本质上解决的是同一个问题:
把一个分支上的修改,请求合并到另一个目标分支。例如:
feature/login-page -> develophotfix/payment-bug -> mainrelease/1.4.0 -> mainPR / MR 不只是“合并代码”的按钮,它更像是一次完整的协作流程:
提出变更 -> 说明变更 -> 自动检查 -> 同伴评审 -> 修改完善 -> 合并发布PR / MR 的作用
PR / MR 的主要作用包括:
- 让代码变更在合并前被讨论
- 让团队成员提前发现问题
- 记录一次需求、缺陷或技术调整的上下文
- 触发 CI 自动测试、构建、扫描
- 形成可追溯的代码变更历史
- 保护主干分支,避免未经检查的代码直接进入主分支
从工程实践角度看,PR / MR 是连接以下内容的中心节点:
| 内容 | 说明 |
|---|---|
| 分支 | 本次变更来自哪个分支 |
| commit | 本次变更包含哪些提交 |
| issue | 本次变更解决哪个任务或问题 |
| CI | 本次变更是否通过自动检查 |
| review | 谁评审了代码,提出了什么意见 |
| merge | 最终如何合并到目标分支 |
一个成熟团队通常不会直接向 main 或 master 推送代码,而是通过 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,目标分支通常是:
developmainrelease/*- 其他团队约定的集成分支
PR / MR 标题怎么写
标题应该让评审者一眼知道这次变更的目的。
推荐格式:
<type>(<scope>): <summary>示例:
feat(auth): add password reset flowfix(order): correct refund amount calculationdocs(git): expand pull request workflow notesrefactor(api): simplify user query servicetest(payment): add retry scenario coverage不推荐:
updatefix 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 servicePR 2: feat(auth): add login flowPR 3: fix(payment): correct retry statusPR 4: chore(format): apply formatter rules这样每个 PR 的目的更清晰,风险也更容易控制。
创建 PR / MR 前的自查清单
提交 PR / MR 前,作者应该先完成自查。
推荐清单:
- 本地代码可以编译
- 测试已经运行
- 没有提交调试代码
- 没有提交密钥、Token、密码
- 没有无意义格式化大量文件
- 没有把临时文件、日志文件、构建产物提交进去
- commit message 可读
- 变更范围和 PR 描述一致
- 目标分支选择正确
- 关联 issue 或任务单
常用检查命令:
git statusgit diffgit diff --cachedgit 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 maingit merge --no-ff feature/loginSquash merge
特点:
- 多个 commit 合成一个
- 主分支历史干净
- PR 内部的临时提交不会污染主干
- 可能丢失中间提交粒度
适合:
- 小功能
- bugfix
- commit 历史比较碎的 PR
- 希望主分支历史简洁的团队
例如 PR 内部提交:
wipfix typoadjust styleadd test合并后变成:
feat(auth): add login pageRebase merge
特点:
- 不产生 merge commit
- 历史保持线性
- 每个 commit 仍然保留
- 对 commit 质量要求更高
适合:
- 团队强调线性历史
- 每个 commit 都有明确含义
- PR 中提交粒度清晰
注意:如果分支已经多人共同使用,rebase 要谨慎。
处理 PR / MR 冲突
当目标分支发生变化,当前分支可能出现冲突。
常见处理方式一:merge 目标分支。
git checkout feature/logingit fetch origingit merge origin/main解决冲突后:
git add .git commitgit push常见处理方式二:rebase 到目标分支。
git checkout feature/logingit fetch origingit rebase origin/main解决冲突后:
git add .git rebase --continuegit 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 #123Fixes #123Resolves #123Refs #123区别:
| 写法 | 含义 |
|---|---|
Closes #123 | 合并后关闭 issue |
Fixes #123 | 合并后关闭缺陷 issue |
Resolves #123 | 合并后解决 issue |
Refs #123 | 仅引用,不自动关闭 |
好处:
- 需求和代码互相可追溯
- 后续排查问题时能找到背景
- 发布说明更容易整理
- 项目管理状态更准确
PR / MR 模板
可以在仓库中配置 PR / MR 模板,减少遗漏。
GitHub 常见路径:
.github/pull_request_template.mdGitLab 常见路径:
.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 = 3000timeout = 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
- 快速定位问题
- 判断是否可以回滚
- 支撑版本发布流程
更具体地说,它通常具备以下特征:
| 特征 | 说明 |
|---|---|
| 清晰 | 能一眼看出本次提交的主要目的 |
| 具体 | 不使用 update、change、fix 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 enumfeat(order): implement order status transitiontest(order): add status transition testsdocs(order): document order status lifecycle这样的提交历史比一个巨大的 update order 更容易评审,也更容易在出问题时定位。
Commit Message 与 Changelog 的关系
很多团队会根据 commit message 自动生成 changelog。
例如:
feat(payment): support Apple Payfix(auth): reject expired tokenperf(search): reduce query latencydocs(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如果提交信息没有规范,例如全部写成 update 或 fix 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 log、git show、git blame 等命令直接查看,因此仍然需要认真编写。
误区三:提交信息越长越好
好的 commit message 不是越长越好,而是信息足够。
简单改动可以只写一行:
docs(readme): fix setup command typo复杂改动才需要补充正文说明背景、取舍和影响。
误区四:只要最终 squash,单个提交就无所谓
即使最终会 squash,开发过程中的提交历史仍然会影响 review 体验。
如果团队要求 squash merge,至少最终合并提交的信息需要认真整理,不能保留默认的 update、fix、wip。
Commit Message 的最小可用标准
如果暂时不引入复杂规范,至少做到以下几点:
<type>(<scope>): <简短说明>例如:
fix(auth): handle expired tokenfeat(order): add cancel order APIdocs(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 passwordfeat(profile): add avatar uploaddocs(git): add commit message guiderefactor(api): extract request client标题行应该做到:
- 一眼看出变更类型
- 一眼看出影响范围
- 一句话说明做了什么
- 方便
git log --oneline阅读 - 方便自动生成 changelog
查看效果:
git log --oneline好的输出应该像这样:
a1b2c3d feat(auth): add email password loginb2c3d4e fix(api): handle timeout errorc3d4e5f docs(git): add commit message examplestype:说明变更类型
type 表示这次提交属于哪类变更。
常见类型:
| type | 说明 |
|---|---|
feat | 新功能 |
fix | Bug 修复 |
docs | 文档变更 |
style | 代码格式调整,不影响逻辑 |
refactor | 重构,不新增功能也不修复 bug |
perf | 性能优化 |
test | 测试相关 |
build | 构建系统、依赖管理 |
ci | CI/CD 配置 |
chore | 杂项维护 |
revert | 回滚提交 |
示例:
feat(search): add fuzzy searchfix(order): prevent duplicate paymentdocs(readme): update installation guidetest(user): add registration unit testsci: add release workflowtype 的价值是让人和工具都能快速判断提交性质。
例如:
feat通常进入版本发布说明。fix通常进入修复列表。docs通常不触发产品版本号变化。ci和build通常影响工程流程。
scope:说明影响范围
scope 表示这次提交影响哪个模块、目录、功能或系统边界。
格式:
type(scope): subject示例:
fix(auth): reject expired tokenfeat(android): add offline cachebuild(gradle): update kotlin plugindocs(git): add branch workflow notes常见 scope:
| scope | 含义 |
|---|---|
auth | 登录、鉴权、权限 |
api | 接口、请求、响应处理 |
ui | 页面或组件 |
db | 数据库、迁移脚本 |
config | 配置文件 |
deps | 依赖 |
ci | 持续集成 |
docs | 文档 |
android | Android 端 |
server | 服务端 |
scope 不一定必须写,但在中大型项目中非常有用。
建议:
- 不要太宽泛,例如
app、code。 - 不要太细碎,例如
login-button-left-icon-color。 - 优先使用团队已有模块名。
- 同一仓库中保持命名一致。
如果变更影响多个模块,可以:
feat: add unified error handling或者使用较高层级 scope:
refactor(core): unify error handlingsubject:一句话说明做了什么
subject 是标题中的描述部分。
示例:
fix(login): handle empty password其中 handle empty password 就是 subject。
写 subject 的原则:
- 简短直接
- 说明结果,而不是过程
- 不写句号结尾
- 不写模糊词
- 不重复 type 和 scope 中已有的信息
推荐:
fix(login): handle empty passwordfeat(profile): add avatar uploadrefactor(api): extract request client不推荐:
fix login bugupdate codechange filesfix issuemodify auth原因:
fix login bug没说修了什么。update code信息量太低。change files对阅读历史没有帮助。fix issue没说明问题本身。
更好的写法:
fix(login): reject empty passwordfix(auth): refresh token before expirationfix(order): prevent duplicate checkoutbody:解释为什么和怎么做
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 andauthorization 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 #123Fixes #456Refs #789BREAKING 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 #120BREAKING CHANGE: remove page-based pagination from user list APIBreaking 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 #123Fixes #123Refs #123Related 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 failurecaused by client and server clock drift.涉及任务、风险或破坏性变更时,建议写完整结构:
feat(api)!: replace page pagination with cursor pagination
Cursor pagination avoids missing records when new data is inserted duringlist 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 时可以按下面顺序思考:
- 这次提交属于什么类型?确定
type。 - 影响哪个模块?确定
scope。 - 一句话说明做了什么?写
subject。 - 这次变更为什么必要?必要时写
body。 - 是否关联 issue、任务或破坏性变更?必要时写
footer。
模板:
<type>(<scope>): <subject>
<why><what changed><impact>
<footer>24.3 Conventional Commits 规范
Conventional Commits 是目前非常常用的一套提交信息规范,中文通常称为“约定式提交”。
它的目标不是让提交信息看起来更复杂,而是让提交历史具备稳定结构,使人和工具都能理解每次提交的含义。
简单说:
Conventional Commits = 规范化的 commit message 写法它把提交信息拆成固定结构:
- 变更类型
- 影响范围
- 简短描述
- 详细说明
- 关联 issue
- 破坏性变更说明
这样做可以让提交历史不仅能读,还能被自动化工具解析。
Conventional Commits 解决什么问题
没有规范时,提交历史可能长这样:
updatefixmodify调整bug fixedwip这些提交信息的问题是:
- 看不出修改类型
- 看不出影响模块
- 看不出是否是新功能
- 看不出是否修复了 bug
- 无法自动生成 changelog
- 无法判断版本号应该如何升级
- 后期排查问题成本高
使用 Conventional Commits 后,历史会变成:
feat(auth): add email loginfix(order): prevent duplicate paymentdocs(git): add commit message guiderefactor(api): extract request clienttest(user): add profile update tests阅读者可以快速看出:
feat是新功能fix是 bug 修复docs是文档变更auth、order、api是影响范围- 冒号后面是本次提交的简要说明
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 是提交类型,用于说明这次提交属于哪类变更。
常见类型包括:
featfixdocsstylerefactorperftestbuildcichorerevert示例:
feat(search): add fuzzy matchingfix(login): reject expired tokendocs(readme): update installation guidetest(order): add refund teststype 的价值在于:
- 让人快速理解变更性质
- 让 changelog 工具按类型分组
- 让发布工具推断版本号变化
- 让团队形成统一提交语言
更详细的常用 type 会在下一章单独说明。
scope 的作用
scope 表示影响范围,通常写模块名、包名、功能名或子系统名。
格式:
type(scope): description示例:
fix(auth): handle locked account loginfeat(payment): support refund callbackdocs(git): add lfs usage guidescope 可以帮助阅读者快速定位这次提交影响哪里。
常见 scope:
authuserorderpaymentapiuidbconfigdepscidocsandroidbackend建议:
- 使用项目中真实存在的模块名
- 不要过度细分
- 不要全部写成
common - 同一个模块保持同一种写法
- 团队可以维护一份 scope 列表
description / subject 的写法
description 是提交标题中的简短说明。
示例:
fix(cache): clear user cache after logout其中:
clear user cache after logout就是 description。
它应该做到:
- 简短
- 具体
- 说明本次提交做了什么
- 不以句号结尾
- 避免空泛词
推荐:
fix(order): prevent duplicate paymentfeat(profile): add avatar uploaddocs(api): document token refresh flow不推荐:
fix: fix bugfeat: add featureupdate: update codechore: modify files26.6 body 的写法
body 是提交正文,用于说明标题无法表达完整的信息。
适合写在 body 中的内容:
- 为什么要这样改
- 问题产生的原因
- 采用了什么实现方案
- 有没有替代方案
- 是否有兼容性影响
- 是否需要迁移数据或配置
- 测试方式或验证结果
示例:
fix(payment): retry callback verification
The payment provider may return a temporary network error during callbackverification. 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 #123Refs #456Related to #789示例:
fix(login): show error for locked account
Locked users were redirected to the home page without a clear error message.
Closes #128footer 的作用:
- 建立提交与需求、缺陷、任务之间的关联
- 帮助平台自动关闭 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常见对应关系:
| 提交类型 | 版本影响 | 示例 |
|---|---|---|
fix | PATCH | 1.2.3 -> 1.2.4 |
feat | MINOR | 1.2.3 -> 1.3.0 |
BREAKING CHANGE 或 ! | MAJOR | 1.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 APIfix(login): reject locked account logindocs(git): add conventional commits guide建议:
type使用英文,便于工具识别scope尽量使用英文模块名description可按团队习惯使用中文或英文- 一个仓库中保持统一风格
与普通 Commit Message 结构的关系
普通 commit message 可以写成:
标题
正文
footerConventional 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 时,不建议一开始就把规则定得过于复杂。
推荐步骤:
- 先统一基本格式:
type(scope): description - 再统一常用 type 列表
- 再约定 scope 命名方式
- 再接入 commitlint
- 最后接入 changelog 或自动发布
团队可以先使用最小规则:
feat: 新功能fix: 修复问题docs: 文档修改refactor: 重构test: 测试chore: 维护任务等团队习惯后,再增加 perf、build、ci、style、revert 等类型。
24.4 常用 type
type 是 Conventional Commits 中最重要的字段之一,用来说明一次提交的变更类型。
它回答的问题是:
这次提交主要属于哪一类改动?常见的 type 包括:
| type | 含义 | 示例 |
|---|---|---|
feat | 新功能 | feat(search): add fuzzy search |
fix | 修复 bug | fix(login): handle empty password |
docs | 文档修改 | docs(readme): update setup guide |
style | 格式调整,不影响逻辑 | style: format kotlin files |
refactor | 重构,不新增功能不修 bug | refactor(api): extract request client |
perf | 性能优化 | perf(cache): reduce disk reads |
test | 测试相关 | test(auth): add login tests |
build | 构建系统或依赖 | build(gradle): update kotlin plugin |
ci | CI 配置 | ci: add release workflow |
chore | 杂项维护 | chore: update gitignore |
revert | 回滚提交 | revert: remove unstable login change |
type 看起来只是一个短词,但它会影响:
- 提交历史的可读性
- changelog 的分类
- 版本号的自动升级
- PR / MR 的审查效率
- 团队对变更性质的理解
feat:新增功能
feat 表示新增功能。
这里的“功能”通常指用户、调用方或业务能够感知到的新能力。
示例:
feat(auth): add email loginfeat(order): support order cancellationfeat(report): export monthly sales reportfeat(search): add fuzzy search适合使用 feat 的场景:
- 新增页面
- 新增接口
- 新增命令
- 新增配置项
- 新增业务流程
- 新增用户可见能力
- 新增第三方集成
不适合使用 feat 的场景:
- 修复 bug
- 只修改文档
- 只调整格式
- 只重构内部实现
- 只补充测试
判断标准:
这次提交是否让系统增加了一个新的可用能力?如果答案是肯定的,通常使用 feat。
fix:修复问题
fix 表示修复 bug 或错误行为。
示例:
fix(login): handle empty passwordfix(auth): reject expired tokenfix(order): prevent duplicate paymentfix(ui): correct button alignment on mobile适合使用 fix 的场景:
- 修复接口异常
- 修复页面展示错误
- 修复边界条件问题
- 修复数据不一致
- 修复鉴权漏洞
- 修复空指针、崩溃、异常
- 修复线上缺陷
判断标准:
这次提交是否把错误行为改成了正确行为?如果是,通常使用 fix。
注意:如果是安全漏洞修复,有些团队会使用自定义 security,但如果团队没有定义该类型,使用 fix 更稳妥。
docs:文档修改
docs 表示文档相关变更。
示例:
docs(readme): update setup guidedocs(api): document token refresh flowdocs(git): add commit message examplesdocs(android): add release build notes适合使用 docs 的场景:
- README 修改
- API 文档修改
- 学习笔记修改
- 使用说明修改
- 注释文档补充
- 架构说明更新
- changelog 手动维护
如果一次变更既包含代码又包含文档,建议拆成两个提交:
feat(api): add pagination supportdocs(api): document pagination parameters这样历史更清楚,也方便 changelog 分类。
style:格式调整
style 表示不影响代码逻辑的格式调整。
示例:
style: format kotlin filesstyle(api): remove trailing spacesstyle(ui): normalize css indentationstyle(java): apply spotless formatting适合使用 style 的场景:
- 代码格式化
- 调整缩进
- 删除多余空格
- 调整换行
- 统一引号风格
- 统一分号风格
- 自动格式化工具输出
重要区别:
style 不是“界面样式变更”的默认 type。如果改 CSS 只是格式化文件,可以用:
style(ui): format stylesheet如果改 CSS 影响了实际页面显示,应该根据目的选择:
fix(ui): correct button alignmentfeat(theme): add dark moderefactor:代码重构
refactor 表示重构代码。
重构的核心是:
改善内部结构,但不改变外部行为。示例:
refactor(auth): extract token validatorrefactor(api): split request clientrefactor(order): simplify status transition logicrefactor(db): move query builder to repository layer适合使用 refactor 的场景:
- 抽取函数
- 拆分类
- 合并重复逻辑
- 调整模块结构
- 改善命名
- 降低复杂度
- 改善可测试性
不适合使用 refactor 的场景:
- 新增业务功能
- 修复明显 bug
- 性能优化是主要目标
- 修改对外接口行为
如果重构过程中发现并修复 bug,最好拆开:
refactor(auth): extract token validatorfix(auth): reject expired token这样后续排查问题时更容易定位。
27.6 perf:性能优化
perf 表示性能优化。
示例:
perf(search): reduce query latencyperf(cache): reduce memory usageperf(db): batch insert audit logsperf(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 teststest(order): cover cancellation flowtest(api): add contract teststest(ui): add login form snapshot tests适合使用 test 的场景:
- 新增单元测试
- 新增集成测试
- 新增端到端测试
- 修改测试断言
- 修复测试数据
- 调整测试工具
- 增加测试覆盖率
如果功能和测试一起提交也可以,但在较严格团队中,更推荐拆成:
feat(order): add cancellation APItest(order): add cancellation API tests这样 reviewer 可以先看功能实现,再看测试覆盖。
build:构建系统或依赖
build 表示影响构建系统、依赖管理、打包流程的变更。
示例:
build(gradle): update kotlin pluginbuild(maven): add surefire pluginbuild(npm): update vite dependencybuild(docker): add production image适合使用 build 的场景:
- 修改 Gradle 配置
- 修改 Maven 配置
- 修改 npm / pnpm / yarn 配置
- 修改 Dockerfile
- 修改依赖锁文件
- 升级构建插件
- 调整打包脚本
- 修改编译目标版本
常见 scope:
gradlemavennpmdepsdockerandroid依赖升级可以用:
build(deps): update okhttp to 4.12.0如果团队更喜欢单独使用 chore(deps),也可以,但必须保持一致。
ci:持续集成配置
ci 表示 CI/CD 配置或流水线相关变更。
示例:
ci: add release workflowci(github): run tests on pull requestci(gitlab): cache gradle dependenciesci(jenkins): add deploy stage适合使用 ci 的场景:
- GitHub Actions
- GitLab CI
- Jenkins Pipeline
- Gitee Go
- CircleCI
- Travis CI
- 自动测试流程
- 自动部署流程
- CI 缓存策略
- 分支保护检查
build 与 ci 的区别:
| 类型 | 关注点 |
|---|---|
build | 项目如何构建、打包、管理依赖 |
ci | 自动化流水线如何运行 |
示例:
build(gradle): enable configuration cacheci(github): cache gradle wrapperchore:杂项维护
chore 表示维护类杂项变更。
示例:
chore: update gitignorechore(repo): add editorconfigchore(cleanup): remove unused fileschore(config): update local development example适合使用 chore 的场景:
- 更新
.gitignore - 添加
.editorconfig - 清理无用文件
- 调整仓库元信息
- 修改脚手架配置
- 更新非业务性项目文件
注意:chore 是兜底类型,但不能滥用。
不推荐:
chore: fix login bugchore: add payment pagechore: update readmechore: add tests推荐:
fix(login): reject expired tokenfeat(payment): add payment pagedocs(readme): update setup guidetest(auth): add login tests如果能归类到更明确的 type,就不要使用 chore。
revert:回滚提交
revert 表示回滚之前的提交。
示例:
revert: feat(auth): add email loginGit 自动生成的 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 staleafter inventory updates.适合使用 revert 的场景:
- 回滚线上故障提交
- 回滚误合并内容
- 回滚不兼容功能
- 回滚有风险的实验性变更
常见 type 选择速查
| 场景 | 推荐 type |
|---|---|
| 新增用户登录方式 | feat |
| 修复 token 过期仍可访问 | fix |
| 修改 README 安装步骤 | docs |
| 只格式化代码 | style |
| 抽取公共方法但行为不变 | refactor |
| 减少接口响应时间 | perf |
| 新增单元测试 | test |
| 修改 Gradle 配置 | build |
| 修改 GitHub Actions | ci |
更新 .gitignore | chore |
| 回滚某个提交 | 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 可能影响版本号。
常见规则:
| 提交 | 版本影响 |
|---|---|
fix | PATCH |
feat | MINOR |
type! | MAJOR |
BREAKING CHANGE | MAJOR |
docs、test、style、chore | 通常不触发正式版本升级 |
示例:
fix(auth): reject expired token可能生成:
1.2.3 -> 1.2.4feat(order): add cancellation API可能生成:
1.2.3 -> 1.3.0feat(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 APIdocs(order): document cancellation APIfix(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 tokenfeat(order): add cancel order APIdocs(git): add commit message examples其中:
fix、feat、docs是typeauth、order、git是scope- 冒号后面是本次提交的简短说明
简单理解:
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 Payfix(search): handle empty keywordperf(cache): reduce product detail reads这些提交即使不看代码,也能大致知道改动发生在哪个区域。
scope 是可选的,但建议使用
在 Conventional Commits 中,scope 是可选项。
下面两种都合法:
fix: correct typofix(docs): correct typo是否使用 scope 取决于项目规模。
适合省略 scope 的场景:
- 个人项目
- 很小的脚本项目
- 影响范围非常明显
- 修改的是整个仓库级别的配置
建议使用 scope 的场景:
- 多模块项目
- 前后端混合仓库
- Android / iOS / Backend 共用仓库
- Monorepo
- 有自动 changelog 需求
- 团队多人协作
对于团队项目,建议默认写 scope。即使规范允许省略,也应该尽量保持提交历史可读。
scope 的常见来源
常见 scope:
authapiuidbdocsconfigdepsciandroidbackend
也可以按实际项目拆分为更多类型。
按业务模块划分
适合业务系统。
authuserorderpaymentproductcartsearchreportmessagenotification示例:
feat(order): add order cancel reasonfix(payment): handle duplicate callbackperf(search): cache hot keyword result按技术层划分
适合分层清晰的后端或基础设施项目。
apiservicerepositorydbcacheconfigsecurityloggingmetrics示例:
refactor(service): split order validation logicfix(db): add missing index for order querychore(config): update default timeout按端或平台划分
适合多端项目。
webandroidiosbackendadmindesktopminiapp示例:
feat(android): add offline cachefix(web): prevent duplicate form submitbuild(ios): update signing configuration按包或子项目划分
适合 Monorepo。
appserveradminshareduiclisdkdocs示例:
feat(cli): add init commandfix(ui): correct button loading statebuild(shared): publish common package按工程系统划分
适合构建、部署、CI、依赖更新。
cidepsdockergradlevitewebpackreleaselinttest示例:
ci(github): add release workflowbuild(gradle): upgrade kotlin pluginchore(deps): update okhttp versionscope 的命名原则
scope 不要太细,也不要太泛。
能帮助阅读者快速定位模块即可。
好的 scope 应该满足:
- 简短
- 稳定
- 可理解
- 和项目结构或业务模块一致
- 不频繁变化
- 团队成员能达成共识
推荐:
authorderpaymentsearchandroidbackenddocscideps不推荐:
some-codemiscstufftempnewfixcommon-changezhangsantoday原因:
misc、stuff太泛temp、today没有长期意义zhangsan按人命名,不表达影响范围fix是 type,不应该当 scope
scope 粒度怎么控制
scope 最难的是粒度。
太粗:
fix(app): handle expired tokenfeat(project): add refund API问题是 app、project 过于宽泛,不能帮助定位影响范围。
太细:
fix(auth-token-refresh-handler-service): handle expired tokenfeat(order-refund-controller-method): add refund API问题是太长、太依赖具体代码结构,后续重构后容易失效。
更合适:
fix(auth): handle expired tokenfeat(order): add refund API判断标准:
scope 应该让人知道影响哪个模块,但不需要精确到类名、函数名或文件名。一般建议:
- 业务系统:按业务模块划分
- 前端项目:按页面、组件域或功能模块划分
- 后端项目:按业务域或服务模块划分
- Monorepo:按 package、app、service 划分
- 工程配置:按工具或系统划分
多个模块同时修改时 scope 怎么写
如果一次提交影响多个模块,有几种处理方式。
1. 优先拆分提交
如果多个模块的修改可以独立理解,优先拆成多个 commit。
例如:
fix(auth): reject expired tokenfix(user): refresh profile after login这通常比下面这种更清楚:
fix(auth,user): update login behavior2 使用更高层级 scope
如果多个模块属于同一个更大的业务域,可以使用上层 scope。
例如同时修改订单创建、订单支付、订单查询:
feat(order): support order split shipment3 使用 cross-cutting scope
如果是横切改动,可以使用工程类 scope。
例如:
chore(deps): update spring boot versionstyle(format): apply ktlint rulesrefactor(error-handling): unify API error response4 谨慎使用多个 scope
有些团队允许:
fix(auth,user): sync login profile state但多个 scope 会降低一致性,也不一定被工具很好识别。
建议:
能拆就拆;不能拆时使用更合适的上层 scope。scope 和 type 的区别
很多初学者会混淆 type 和 scope。
| 字段 | 作用 | 示例 |
|---|---|---|
type | 说明变更类型 | feat、fix、docs |
scope | 说明影响范围 | auth、order、ci |
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 tokenscope 和分支名的关系
scope 可以和分支名保持一致,但不要求完全相同。
例如分支:
feature/order-cancel提交:
feat(order): add cancel order APItest(order): add cancel order service testsdocs(order): document cancel order flow这样分支名、commit message、PR 标题之间形成统一语义。
如果分支是:
fix/payment-callback-timeout提交可以是:
fix(payment): increase callback timeouttest(payment): cover delayed callback scenario这比所有提交都写成下面这样更清楚:
fix: update callbacktest: add testscope 的最佳实践
推荐做法:
- 默认使用 scope,除非变更确实是全局性的
- scope 使用小写英文
- scope 尽量与模块、业务域或 package 对齐
- 不使用人名、日期、临时词
- 不把 type 写成 scope
- 不要过度细化到类名或函数名
- 多模块改动优先拆分 commit
- 团队维护一份允许的 scope 列表
- PR 标题和 squash commit 也使用相同 scope 规范
一个较好的 scope 列表示例:
authuserorderpaymentproductsearchcartapidbcachewebandroidiosdocscidepsbuildreleases
24.6. subject
subject 是 commit message 标题里的描述部分,也就是冒号后面的那句话。
在下面这个提交信息中:
fix(parser): handle empty input各部分含义是:
| 部分 | 内容 | 说明 |
|---|---|---|
fix | type | 表示这是一次 bug 修复 |
parser | scope | 表示影响解析器模块 |
handle empty input | subject | 表示具体做了什么 |
subject 的作用是用一句简短、明确的话说明本次提交的核心变更。
可以理解为:
type 说明变更类型。scope 说明影响范围。subject 说明具体动作。subject 的核心目标
subject 应该让读者在 git log --oneline 中快速理解这次提交。
例如:
git log --oneline如果输出是:
a1b2c3d fix(parser): handle empty inputb2c3d4e feat(auth): add email loginc3d4e5f docs(git): add commit message examples读者不需要打开 diff,就能大致知道每次提交的目的。
好的 subject 应该回答:
- 这次提交具体做了什么
- 影响哪个行为或能力
- 和其它提交相比有什么区别
不应该只写:
- 改了代码
- 修了 bug
- 做了调整
- 更新了一下
这些信息对后续维护几乎没有帮助。
subject 的基本规则
建议:
- 简短明确
- 使用动词开头
- 只描述一件事
- 不以句号结尾
- 不写模糊词
- 不重复 type 和 scope
- 不写实现细节堆砌
- 不写无意义的情绪化描述
推荐格式:
<动词> <对象>或者:
<动词> <对象> <条件/结果>示例:
add email loginhandle empty inputreject expired tokenremove unused configupdate setup guideextract request serializer完整 commit 示例:
feat(auth): add email loginfix(parser): handle empty inputfix(auth): reject expired tokenchore(config): remove unused optiondocs(readme): update setup guiderefactor(api): extract request serializersubject 应该写“结果”,不是写“过程”
subject 最好描述提交带来的结果,而不是描述自己编辑代码的过程。
不推荐:
change login filemodify parser codeupdate several classesadjust user service这些写法只说明“你改了文件”,没有说明“系统行为发生了什么变化”。
推荐:
fix(login): reject empty passwordfix(parser): handle empty inputrefactor(user): split profile servicefeat(order): add cancellation reason对比:
| 不推荐 | 推荐 |
|---|---|
update auth code | fix(auth): reject expired token |
change order logic | fix(order): prevent duplicate payment |
modify config | chore(config): remove unused timeout option |
update docs | docs(api): add token refresh example |
subject 要具体,不要空泛
subject 最常见的问题是过于空泛。
差的例子:
update codefix bugmisc changeschange logicoptimizeadjustmodify这些写法的问题:
- 不知道修改了哪里
- 不知道解决了什么问题
- 不知道是否影响功能
- 不知道是否可以回滚
- 很难生成有意义的 changelog
好的例子:
fix(parser): handle empty inputfix(order): prevent duplicate checkoutfeat(search): add fuzzy matchingperf(cache): reduce repeated database readsdocs(readme): add local setup steps如果 subject 中出现 update、change、modify,要问自己:
到底更新了什么?到底改变了什么?到底修改后的行为是什么?常用动词选择
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 expirationsubject 的长度控制
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 requestscaused by client and server clock drift.标题负责概括,正文负责解释。
subject 不要以句号结尾
Commit message 标题通常不以句号结尾。
不推荐:
fix(auth): reject expired token.docs(readme): update setup guide.推荐:
fix(auth): reject expired tokendocs(readme): update setup guide原因:
- 标题不是完整段落
git log --oneline中句号没有必要- 与 Conventional Commits 的常见风格保持一致
subject 与 type、scope 不要重复
subject 应该补充 type 和 scope 没有表达的信息。
不推荐:
feat(auth): auth featurefix(order): fix order bugdocs(readme): docs update推荐:
feat(auth): add password reset flowfix(order): correct refund amount calculationdocs(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 uploadfix(login): show error for locked accountdocs(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 flowfeat(search): support fuzzy matchingfeat(order): add cancellation reasonfeat 的 subject 应该说明新增了什么用户可感知或系统可使用的能力。
2. fix
fix(login): reject empty passwordfix(payment): prevent duplicate chargefix(parser): handle empty inputfix 的 subject 应该说明修复后的正确行为,而不只是写 fix bug。
3. docs
docs(readme): add local setup stepsdocs(api): document token refresh flowdocs(git): add commit message examplesdocs 的 subject 应说明补充、修正或更新了哪部分说明。
4. refactor
refactor(api): extract request serializerrefactor(user): split profile servicerefactor(cache): simplify key generationrefactor 的 subject 应说明结构如何变化,同时避免暗示新增功能或修复 bug。
5. perf
perf(search): reduce repeated database readsperf(cache): avoid duplicate serializationperf(image): lazy load gallery thumbnailsperf 的 subject 应说明性能改善点。
6. test
test(auth): add expired token casestest(order): cover refund failure pathtest(api): add timeout retry teststest 的 subject 应说明覆盖了什么场景。
7. build / ci / chore
build(gradle): update kotlin pluginci(github): add release workflowchore(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 = 3000timeout = 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 typostyle: format kotlin fileschore(gitignore): ignore idea filestest(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 thatthe 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 secondsto 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 asuccessful response. Add idempotency check before creating refund records.
Fixes #342其中:
- body 解释问题和修复思路
- footer 关联 issue
body 和 PR 描述的区别
PR / MR 描述通常解释整个变更,commit body 解释单个提交。
| 内容 | commit body | PR / MR 描述 |
|---|---|---|
| 粒度 | 单个 commit | 一个完整需求或一组提交 |
| 保存位置 | Git 历史 | 平台页面 |
| 查看方式 | git show、git log | PR / 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>
原因:说明为什么需要这次修改。
修改:说明关键修改点。
影响:说明影响范围、兼容性、风险或验证方式。24.8. footer
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 duringlist traversal.
Refs #901这里:
- subject 说明新增游标分页
- body 说明为什么要改
- footer 关联对应任务
关联 issue
Closes #123Fixes #456Refs #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 #128Refs #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 #123Refs #456BREAKING CHANGE: remove legacy login endpointReviewed-by: AliceCo-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 #245Reviewed-by: AliceCo-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 | 关闭 issue | Closes #123 |
Fixes | 修复并关闭 issue | Fixes #456 |
Refs | 引用 issue | Refs #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 #123Fixes #123Refs #123BREAKING 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 #123Fixes #123Resolves #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 MIMEtypes 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 #128Breaking 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 #123Breaking 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 passworddocs(readme): update local setup guidetest(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 写成
update、misc changes - reviewer 需要长时间才能理解整体变更
- 回滚时无法只回滚有问题的部分
- 本次提交很难对应一个 issue 或任务
示例:
feat: add user profile, fix login, update docs, format code更好的拆分:
feat(user): add profile edit pagefix(auth): show error for locked accountdocs(user): add profile edit guidestyle: format kotlin files这样每个提交都有独立目的。
粒度太小的表现
提交也不是越小越好。
粒度太小的提交可能是:
wipadd filefix typofix typo againchange variabletry another waydebug这些提交在开发过程中可以临时存在,但不适合直接进入主分支。
问题:
- 历史噪音太多
- 难以看出完整意图
- reviewer 需要在大量碎片提交中拼上下文
- changelog 无法生成有意义内容
开发过程中可以先小步提交,合并前再整理。
例如开发时:
wip login pagefix validationadd testfix typo合并前整理成:
feat(auth): add password login pagetest(auth): add login validation tests按变更类型拆分
最常见的拆分方式是按变更类型拆分。
不要把这些内容混在一个提交里:
- 功能开发
- bug 修复
- 重构
- 文档
- 测试
- 格式化
- 依赖升级
- CI 配置
不推荐:
feat(login): add login page and format project推荐:
style: format source filesfeat(login): add password login pagetest(login): add password validation tests这样做的好处:
- 格式化不会干扰功能 review
- 功能实现和测试关系清楚
- 如果功能有问题,可以单独回滚
按业务步骤拆分
一个较大的功能可以按业务步骤拆分。
例如“订单取消”功能可以拆成:
feat(order): add cancellation statusfeat(order): add cancellation APIfeat(order): add cancellation permission checktest(order): add cancellation flow testsdocs(order): document cancellation API每个提交都是完整的一步。
不推荐拆成这种过细粒度:
add enumadd fieldadd methodadd controlleradd test file这些提交太偏代码操作,不利于理解业务演进。
更好的拆分原则是:
按业务能力拆,而不是按敲代码的顺序拆。按风险拆分
高风险改动最好单独提交。
例如:
- 数据库迁移
- 鉴权规则修改
- 支付逻辑修改
- 缓存策略修改
- 公共 API 变更
- 依赖大版本升级
- 大规模格式化
不推荐:
feat(payment): add refund flow and update database schema更推荐:
feat(payment): add refund status fieldfeat(payment): implement refund request flowtest(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 clientfeat(user): add profile pagetest(user): add profile update testsreviewer 可以按顺序看:
- 先看重构是否保持行为不变。
- 再看功能实现是否正确。
- 最后看测试是否覆盖关键场景。
按回滚视角拆分
提交粒度要考虑回滚。
一个好的提交应该尽量可以独立回滚。
例如:
feat(cache): cache product detail response如果上线后发现商品详情缓存导致数据不刷新,可以单独回滚这个提交。
如果提交是:
feat(product): add detail cache and update product page and fix search回滚缓存时会连产品页面和搜索修复一起回滚,风险变大。
判断标准:
如果这部分出问题,能不能只回滚它?如果不能,就考虑拆分。
功能提交的粒度
功能提交应该围绕“可理解的功能单元”。
合理:
feat(auth): add email loginfeat(auth): add password reset flowfeat(order): add csv export不合理:
feat: add many features也不建议太机械:
add login htmladd login cssadd login jsadd login api call如果这些文件共同构成一个完整登录页面,可以合成:
feat(auth): add password login page如果功能很大,可以按可验证阶段拆分:
feat(auth): add login form validationfeat(auth): submit login requestfeat(auth): persist login sessiontest(auth): add login flow tests修复提交的粒度
修复提交应该围绕一个明确 bug。
合理:
fix(auth): validate empty passwordfix(payment): prevent duplicate refundfix(search): handle empty keyword不合理提交:
fix bugsfix many issuesfix login and payment and search如果一个修复需要补测试,可以选择:
fix(auth): validate empty passwordtest(auth): add invalid login tests也可以把测试和修复放在同一个提交中:
fix(auth): validate empty password是否拆分取决于团队习惯。关键是不要混入无关修复。
重构提交的粒度
重构提交尤其需要控制粒度。
推荐:
refactor(api): extract request serializerrefactor(order): split pricing servicerefactor(auth): isolate token validation不推荐:
refactor: rewrite projectrefactor: clean coderefactor: update architecture重构最好满足:
- 行为不变
- 每次只调整一个结构目标
- 不和功能开发混在一起
- 不和大规模格式化混在一起
如果确实需要大规模重构,最好拆成多个可验证步骤。
例如:
refactor(order): extract order status policyrefactor(order): move pricing logic to pricing servicerefactor(order): split order query repository格式化提交要单独拆
格式化会制造大量 diff。
如果把格式化和功能修改混在一起,reviewer 很难看清真正的逻辑变更。
不推荐:
feat(auth): add login flow and format files推荐:
style: format kotlin filesfeat(auth): add login flow或者先功能后格式化:
feat(auth): add login flowstyle(auth): format login files一般建议:
格式化提交单独做,避免污染功能 diff。依赖升级提交要单独拆
依赖升级可能带来隐性风险。
不推荐:
feat(report): add export and update spring boot推荐:
build(deps): update spring bootfeat(report): add export API原因:
- 依赖升级可能导致运行时行为变化
- 回滚功能时不一定要回滚依赖
- 回滚依赖时不一定要回滚功能
- CI 失败时更容易判断原因
如果是安全补丁,可以写清楚:
build(deps): update log4j for security fix测试提交是否单独拆
测试可以和功能在同一个提交,也可以单独提交。
适合放在同一提交:
fix(auth): validate empty password这个提交同时包含修复和对应测试,整体仍然只解决一个问题。
适合单独提交:
feat(order): add cancellation APItest(order): add cancellation flow tests当测试较多、需要单独 review,或者补历史测试时,单独提交更清晰。
补历史测试示例:
test(payment): add duplicate callback regression tests临时提交如何处理
开发过程中出现临时提交很正常。
例如:
wiptry cachefix againdebug payment这些提交可以用于保存进度,但合并前应该整理。
常用方式:
git rebase -i HEAD~4可以把临时提交整理成:
feat(payment): add callback retrytest(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. 整理提交历史
整理提交历史,是指在合并到主分支前,把开发过程中的临时提交、零散提交、错误提交整理成清晰、可读、可维护的提交历史。
开发过程中产生临时提交很正常:
wipfixfix againtry something这些提交适合保存开发进度,但不适合直接进入主分支。
整理后的历史应该像这样:
feat(auth): add password login pagefix(auth): reject expired tokentest(auth): add login validation tests为什么要整理提交历史
整理提交历史的目的不是追求表面整洁,而是提高维护效率。
清晰的提交历史可以帮助团队:
- 按逻辑顺序 review 代码
- 快速理解一个需求的演进过程
- 使用
git bisect定位问题 - 出问题时精准回滚
- 自动生成更清晰的 changelog
- 避免主分支出现大量
wip、fix again
不整理历史的常见结果:
wipfixfix againtrydebugfinalfinal final几周后再看,很难判断这些提交分别做了什么。
哪些历史适合整理
适合整理:
- 自己本地未推送的功能分支
- 自己远程个人分支,且没人基于它继续开发
- PR / MR 合并前的临时提交
- commit message 写错的提交
- 多个临时提交需要合并成逻辑提交
- 提交顺序不合理的分支
- 有调试提交、试验提交、无意义提交的分支
不建议随意整理:
mainmasterdeveloprelease/*- 多人共用的远程分支
- 已经被别人拉取并继续开发的提交
- 已经发布到生产环境的历史
核心原则:
只改写自己拥有的历史,不改写别人依赖的历史。整理前先查看历史
查看最近提交:
git log --oneline --decorate -10查看当前分支相对主分支新增了哪些提交:
git fetch origingit log --oneline origin/main..HEAD示例:
a1b2c3d fix againb2c3d4e fixc3d4e5f wipd4e5f6a feat(auth): add login page看到这种历史,就适合在合并前整理。
交互式 rebase 基本用法
合并前可以用交互式 rebase 整理:
git rebase -i HEAD~4表示整理最近 4 个提交。
如果想整理当前分支相对 main 的所有提交:
git fetch origingit rebase -i origin/main执行后会打开编辑器,内容类似:
pick d4e5f6a feat(auth): add login pagepick c3d4e5f wippick b2c3d4e fixpick 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 pagepick b2c3d4e fix typopick c3d4e5f add validation整理时:
pick a1b2c3d feat(auth): add login pagesquash b2c3d4e fix typosquash c3d4e5f add validation最终可以整理成:
feat(auth): add password login page适合:
- 多个提交共同组成一个功能
- 中间有修修补补的提交
- 想保留多个提交信息作为参考再整理
fixup:合并提交并丢弃信息
fixup 和 squash 类似,但会丢弃当前提交的提交信息。
整理前:
pick a1b2c3d feat(auth): add login pagepick b2c3d4e fix lintpick c3d4e5f fix typo整理时:
pick a1b2c3d feat(auth): add login pagefixup b2c3d4e fix lintfixup c3d4e5f fix typo最终只保留第一个提交的信息。
适合:
fix typofix lintadjust formatsmall fix- 不值得保留 message 的临时提交
drop:删除提交
drop 用于删除某个提交。
示例:
pick a1b2c3d feat(auth): add login pagedrop b2c3d4e debug login statepick c3d4e5f test(auth): add login tests适合:
- 删除调试提交
- 删除误提交
- 删除不再需要的实验提交
- 删除临时验证代码
也可以直接删除那一行,但显式写 drop 更清楚。
注意:
drop 会让该提交从当前历史中消失。执行前要确认这个提交确实不需要。
调整提交顺序
交互式 rebase 中可以调整提交顺序。
整理前:
pick a1b2c3d test(auth): add login testspick b2c3d4e feat(auth): add login page更合理:
pick b2c3d4e feat(auth): add login pagepick a1b2c3d test(auth): add login tests这样历史更符合逻辑:
先实现功能,再补测试。注意:调整顺序可能产生冲突,因为后面的提交可能依赖前面的代码。
拆分一个过大的提交
如果一个提交太大,可以用 edit 拆分。
流程:
git rebase -i HEAD~3把要拆分的提交前面的 pick 改成:
editGit 停在该提交时,执行:
git reset HEAD^然后分批暂存并提交:
git add -pgit commit -m "feat(auth): add login form"
git add -pgit commit -m "test(auth): add login validation tests"继续 rebase:
git rebase --continue适合:
- 一个提交混入多个无关改动
- 功能和测试需要拆开
- 格式化和逻辑修改混在一起
- 依赖升级和业务代码混在一起
修改最近一次提交:amend
如果只需要修改最近一次提交,可以用:
git commit --amend常见用途:
- 修改最近一次 commit message
- 补充漏提交的文件
- 删除最近提交中的小错误
补文件示例:
git add missing-test.ktgit 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 reflogreflog 会记录 HEAD 移动历史,是找回误操作提交的重要工具。
PR / MR 前推荐整理流程
推荐流程:
git fetch origingit log --oneline origin/main..HEADgit rebase -i origin/maingit log --oneline origin/main..HEADgit status如果分支已推送到个人远程分支:
git push --force-with-lease整理后检查:
- commit message 是否清楚
- 临时提交是否已合并或删除
- 提交顺序是否合理
- 每个提交是否粒度合适
- 测试是否仍然通过
- PR / MR 中提交历史是否可读
整理前后示例
整理前:
wipadd loginfixfix againadd testupdate docs整理后:
feat(auth): add password logintest(auth): add login validation testsdocs(auth): document login flow整理前:
try cachefix cachefix lintfinal整理后:
perf(cache): cache product detail responsetest(cache): add stale product cache tests27. 分支命名规范
分支命名规范用于统一团队创建、识别、协作和清理分支的方式。
一个好的分支名应该让人一眼看出:
- 这个分支属于什么类型
- 这个分支要解决什么问题
- 是否与某个任务或缺陷关联
- 是否是临时分支还是长期分支
- 合并后是否可以删除
分支名不是随便起的标签,它是团队协作中的重要信息。
基本命名格式
推荐格式:
<type>/<short-description>常见命名:
feature/user-profilefix/login-null-pointerhotfix/payment-timeoutrelease/v1.2.0docs/git-notechore/update-deps其中:
type表示分支类型/用于分隔类型和描述short-description用短语说明分支目的
基本命名建议
建议:
- 小写
- 用短横线分隔单词
- 前缀表达类型
- 名称表达目的
- 不使用空格
- 不使用中文标点
- 不使用无意义缩写
- 不使用人名作为主要名称
- 不使用过长描述
推荐:
feature/order-exportfix/payment-duplicate-callbackdocs/commit-message-guide不推荐:
devtestnewtempfinalzhangsanfixbugmy-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-profilefeature/order-exportfeature/password-resetfeature/report-dashboard或者团队统一使用简写:
feat/user-profilefeat/order-export分支名应该表达功能目标,而不是代码实现细节。
不推荐:
feature/add-controllerfeature/write-codefeature/new-page推荐:
feature/order-exportfeature/password-resetfeature/user-avatar-upload修复分支命名
普通 bug 修复使用 fix/ 或 bugfix/。
示例:
fix/login-null-pointerfix/order-total-errorfix/upload-empty-filebugfix/cart-price-rounding分支名应尽量说明错误现象或影响点。
不推荐:
fix/bugfix/errorfix/problemfix/test推荐:
fix/login-empty-passwordfix/payment-duplicate-callbackfix/search-empty-keywordhotfix 分支命名
hotfix/ 用于线上紧急修复。
示例:
hotfix/payment-timeouthotfix/login-500-errorhotfix/order-callback-duplicatehotfix/v1.2.1-auth-token-expiry如果团队按版本维护,可以带版本号:
hotfix/v1.2.1-payment-timeouthotfix 分支通常从生产版本、main 或发布 tag 拉出,修复后要合并回相关长期分支。
常见流向:
main -> hotfix/* -> main -> develop如果使用 release 分支,也可能需要合并回对应 release/*。
release 分支命名
发布分支通常使用版本号命名。
示例:
release/v1.2.0release/1.2.0release/2026.07release/android-2.3.0建议团队统一是否带 v。
推荐统一:
release/v1.2.0release/v1.2.1release/v1.3.0不推荐混用:
release/1.2.0release/v1.2.1release/ver-1.2.2release 分支适合:
- 发布前测试
- 修复发布阻塞问题
- 更新版本号
- 准备 changelog
- 打 tag 前最终验证
文档、重构、测试、维护分支
文档分支:
docs/git-notedocs/api-auth-guidedocs/readme-setup重构分支:
refactor/order-servicerefactor/api-clientrefactor/auth-token-validator测试分支:
test/login-validationtest/payment-callbacktest/order-refund维护分支:
chore/update-depschore/cleanup-assetschore/update-gitignoreCI / 构建分支:
ci/add-release-workflowbuild/update-gradlebuild/docker-production-image带任务编号的分支名
如果团队使用 Jira、Tapd、禅道、GitHub Issue,可以把任务号放进分支名。
格式:
<type>/<ticket-id>-<short-description>示例:
feature/PROJ-123-user-profilefix/BUG-456-login-null-pointerhotfix/INC-789-payment-timeoutdocs/GIT-12-commit-message-note优点:
- 分支和任务容易关联
- CI / 平台可以自动识别任务号
- 项目管理工具更容易追踪
- PR / MR 更容易自动关联需求
注意:
任务号不要替代描述。
不推荐:
feature/PROJ-123fix/BUG-456推荐:
feature/PROJ-123-user-profilefix/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-exportPR 标题:
feat(order): add csv export分支名:
fix/payment-timeoutPR 标题:
fix(payment): increase callback timeout分支名负责快速识别任务,PR 标题负责形成可读的合并记录。
分支名字符规范
建议:
- 使用小写英文
- 单词之间用短横线
- - 类型和描述之间用斜杠
/ - 可包含任务编号
- 不使用空格
- 不使用中文标点
- 不使用特殊符号
推荐:
feature/user-profilefix/order-total-errorrelease/v1.2.0不推荐:
Feature/UserProfilefix/order total errorrelease\v1.2.0需求/用户资料fix#123分支名长度控制
分支名应简短但具体。
太短:
fix/bugfeature/new太长:
feature/add-the-new-user-profile-page-with-avatar-upload-and-basic-information-form更合适:
feature/user-profilefeature/avatar-uploadfix/login-empty-password原则:
能表达目的即可,不要把需求描述全文写进分支名。详细背景应该写在 issue、PR 描述或 commit body 中。
长期分支与短期分支
分支可以分为长期分支和短期分支。
长期分支:
mainmasterdeveloprelease/*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-profilefix/BUG-456-login-null-pointerhotfix/INC-789-payment-timeout28. 团队代码管理规范建议
团队可以制定如下规则:
- 主分支必须受保护。
- 需求必须从主分支拉新分支。
- 合并必须通过 PR / MR。
- PR 至少一人 review。
- CI 通过后才能合并。
- commit message 必须符合 Conventional Commits。
- 禁止向主分支直接 force push。
- 线上修复走 hotfix 分支。
- 发布版本必须打 tag。
- 敏感信息禁止提交到仓库。
29. 安全实践
不要提交:
- 密码
- token
- 私钥
.env- 证书
- 生产数据库地址
如果误提交敏感信息:
- 立即废弃泄漏的密钥。
- 从历史中清理敏感文件。
- 通知团队重新拉取或处理历史。
- 检查访问日志。
仅仅删除文件并再次提交,不等于从 Git 历史中删除。
30. Git 日常命令速查
仓库
git initgit clone <url>git remote -v状态
git statusgit diffgit diff --cached提交
git add .git add -pgit commitgit commit --amend分支
git branchgit switch -c feature/namegit switch maingit branch -d feature/name同步
git fetchgit pullgit pushgit push -u origin feature/name合并与变基
git merge feature/namegit rebase maingit rebase --continuegit rebase --abort撤销恢复
git restore filegit restore --staged filegit reset --soft HEAD~1git revert <commit>git reflog如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时