Git 工作流进阶实战:rebase 与提交规范

当团队从「能提交」走向「协作高效」,Git 工作流的进阶用法就不可或缺。交互式 rebase 整理历史、cherry-pick 精准搬运提交、reflog 兜底误操作、Conventional Commits 统一提交信息——这套组合拳能让仓库历史既干净又可追溯。本文用可直接复制的命令,带你在日常开发中落地进阶 Git 工作流,把每一次提交都变成团队可读的资产。

一、交互式 rebase:把杂乱提交整理成故事线

新手常把 Git 当成「保存按钮」,一次改动产生十几个 “fix”、”tmp” 提交。当这种历史进入公共主干,后来者 git log 看到的是一片噪音,想回滚到某个稳定点都无从下手。交互式 rebase 能把这些提交压缩、重排、改写,让一条分支的提交历史像文档一样易读——它本质上是「先写代码、后写历史」的工作方式。

1.1 改写最近 N 条提交

# 改写最近 3 条提交
git rebase -i HEAD~3

# 进入编辑器后,常见操作
pick   a1b2c3 feat: 新增登录接口
reword b2c3d4 fix: 修登录
squash c3d4e5 tmp
fixup  d4e5f6 又改了点

# 保存后按提示改写 commit message

常用动词:pick 保留、reword 改信息、edit 暂停修改、squash/fixup 合并到上一条(fixup 丢弃被合并者的信息)。一个实用技巧:把「实现过程」的多个提交压成「一个完整功能」,对外只暴露有意义的节点。

1.2 黄金法则:别动已推送的公共分支

rebase 会重写 commit hash。如果你已经 git push 了那些提交,再 rebase 就会和远端分叉,强制推送会改写队友的历史。铁律:只对本地未推送、或仅你自己 feature 分支的提交做 rebase;公共主干(main/master)永远用 merge。一旦需要强制推送自己的分支,用 git push --force-with-lease 而非 --force,前者会在远端有他人新提交时拒绝推送,避免覆盖。

二、cherry-pick:把单个提交搬到任意分支

有时你只想要另一个分支上的某一个修复,不想合整个分支,cherry-pick 正好派上用场。它复制指定提交的差异并作为一个新提交应用到当前分支,原分支不受影响。

# 把某个提交搬到你当前分支
git cherry-pick a1b2c3d

# 一次搬多个,并按原顺序应用
git cherry-pick commitA commitB commitC

# 只取改动能,不提交(用于手动调整后自己提交)
git cherry-pick -n a1b2c3d

# 冲突时解决后继续
git add .
git cherry-pick --continue

典型场景:hotfix 分支修了一个线上 bug,想立刻同步到 develop 而不带其它未发布改动,直接 cherry-pick 那个修复提交即可。若遇到冲突,Git 会暂停并标记文件,解决后 git add--continue 即可。

三、reflog:误操作的后悔药

git reflog 记录了 HEAD 的每一次移动,即使提交被 reset 掉、分支被删,只要没被 GC,都能找回来。这是进阶 Git 工作流里最值得掌握的「救命技能」——它不依赖远端,只存在于本地仓库。

# 查看 HEAD 变动历史
git reflog

# 手滑 reset --hard 后找回
git reset --hard HEAD@{2}

# 误删分支也可从 reflog 重建
git checkout -b recover-branch a1b2c3d

注意:reflog 是本地记录,默认 90 天过期,且不会同步到远端,所以团队协作的兜底仍要靠分支保护。关于分支策略本身,可以参考Git 分支模型:Flow 与 Trunk-Based Development。若偏好图形化浏览分支拓扑,VS Code 内置的源代码管理同样能展示时间线(见 VS Code 高效开发配置)。

四、git worktree:多分支并行不切来切去

要同时看两个分支的改动,传统做法是反复 stash + checkout,容易丢改动也拖慢节奏。worktree 允许同一仓库挂载多个工作目录,各看一个分支,互不干扰,特别适合「主分支修 bug 的同时又在 feature 上写新功能」。

# 为 hotfix 分支开一个独立工作目录
git worktree add ../proj-hotfix hotfix/login-bug

# 列出所有 worktree
git worktree list

# 用完清理
git worktree remove ../proj-hotfix

每个 worktree 是独立的工作区,但共享同一套 .git 对象库,所以切换零成本、不重复下载。记得用完后 git worktree remove 清理,否则残留目录会占用分支引用。

五、提交规范:Conventional Commits

干净的 Git 工作流离不开一致的提交信息。Conventional Commits 用「类型: 描述」格式,既方便人读,也能被工具解析生成 changelog、自动判 SemVer 版本号,是规模化协作的隐形契约。

5.1 格式与类型

类型含义示例
feat新功能feat: 支持 OAuth 登录
fix缺陷修复fix: 修正分页越界
docs文档变更docs: 补充 API 示例
refactor重构(无功能变化)refactor: 抽离校验逻辑
test测试test: 增加边界用例
chore构建/依赖等杂项chore: 升级 webpack

5.2 用 commitlint + husky 自动卡口

规范靠自觉容易烂尾,把校验接到提交钩子里才稳。下面是一份最小可用的 commitlint 配置与 husky 接入,提交信息不合规会被直接挡在本地。

# .commitlintrc.json
{
  "extends": ["@commitlint/config-conventional"]
}

# 安装
npm i -D @commitlint/cli @commitlint/config-conventional husky

# 添加 commit-msg 钩子
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

# 不符合规范的提交会被直接拒绝
#  fix login          (会被拒)
#  fix: 修正登录态过期判断  (通过)

六、.gitignore 与子模块避坑

进阶 Git 工作流还要管住「不该进仓库的东西」。依赖目录、构建产物、密钥文件一旦提交,后续要从历史里抠掉很麻烦,甚至会造成密钥泄露。

# .gitignore 常见项
node_modules/
dist/
.env
*.log
.DS_Store

# 已误提交的文件,先从追踪移除(保留本地)
git rm --cached .env
echo ".env" >> .gitignore

子模块(git submodule)适合复用第三方仓库,但记得初始化时拉全,否则 CI 会缺代码:git submodule update --init --recursive。若想把文档也纳入 Git 管理并自动发布,思路可参考文档即代码实战:用 Git 和 CI 管理技术文档

七、把规范接到 CI,让历史自动保持健康

本地钩子可被 --no-verify 跳过,真正兜底要放到服务端。在 CI 流水线里加一步提交信息校验和分支保护,配合 GitHub Actions 实战:从零搭建 CI/CD 流水线 可以一键落地,让不符合规范的 PR 无法合并。

# .github/workflows/commitlint.yml
name: commitlint
on: [pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - run: npx --yes @commitlint/cli --from=${{ github.event.pull_request.base.sha }}

八、git bisect:二分定位引入 bug 的提交

最让人头疼的不是修 bug,而是「这个 bug 是哪个提交引入的」。当历史有几百个提交,逐个回退排查太慢。git bisect 用二分法,只需你标记「好的起点」和「坏的当前」,Git 会自动切到中间提交让你验证,几次就能锁定元凶。

# 启动二分,标记当前为坏、某个旧提交为好
git bisect start
git bisect bad            # 当前 HEAD 是坏的
git bisect good v1.2.0    # 这个标签处是好的

# Git 切到中间点,你跑测试后告诉它结果
git bisect good           # 这个提交没问题
git bisect bad            # 这个提交已经坏了

# 自动模式:用脚本判定(退出码 0=好,非0=坏)
git bisect run npm test

# 定位结束后回到原分支
git bisect reset

配合 git bisect run 更是威力倍增:给它一个能自动判定好坏的脚本,Git 会全自动二分,几分钟就帮你从成百上千次提交里揪出那一次「作恶」的改动。这是排查回归问题的终极武器。

九、冲突解决与 rerere:让重复冲突一次搞定

频繁 rebase 或合并长生命周期分支,难免反复遇到同样的冲突。rerere(reuse recorded resolution)会记录你解决过的冲突方案,下次遇到相同冲突时自动套用,省去重复劳动。

# 开启 rerere(建议团队统一开启)
git config --global rerere.enabled true

# 合并遇到冲突解决后,Git 会记录方案
git merge feature/login
# 解决冲突 -> git add -> git commit
# 之后再次遇到相同冲突,Git 自动复用上次方案

开启 rerere 后,即使你 git merge --abort 中止了一次合并,记录下的解决方案依然保留,下次相同的冲突仍能自动解决,是大型重构和长期分支协作的省心利器。

十、小结:进阶 Git 工作流清单

把上面串起来,你团队日常可遵循一条清晰路径:用 rebase 整理本地历史、用 cherry-pick 精准同步、用 reflog 兜底意外、用 worktree 并行开发、用 bisect 快速定位回归、用 rerere 消弭重复冲突、用 Conventional Commits + commitlint 统一语言、最后用 CI 卡住质量闸。Git 工作流的进阶不是炫技,而是让每一次 git log 都讲得清「为什么改、改了什么」,让协作的隐形成本肉眼可见地降下来。

上一篇 Java 注解处理器实战:编译期代码生成与 Lombok 原理剖析
下一篇 HTTPS 混合内容与证书链故障排查实战