跳到主要内容

Git版本控制与团队协作

Git 是 CI/CD 的输入层。流水线能不能稳定,首先取决于代码历史是否可追溯、分支是否可合并、发布点是否可复现、回滚路径是否明确。

这篇不把 Git 当命令大全,而是把它当团队协作系统:本地如何组织改动,分支如何进入主干,tag 如何驱动发布,事故时如何恢复。

1. Git 的核心模型

Git 管理的是一张 commit graph,不是文件夹快照列表。每个 commit 指向父 commit,branch / tag / HEAD 都只是指向 commit 的引用。

working tree --git add--> index --git commit--> repository
当前文件 暂存区 commit graph
概念本质工程意义
working tree当前磁盘文件还没进入版本历史,最容易误删
index下一次提交的暂存快照用来拆 commit、控制提交边界
commit一次不可变快照 + 元信息可审计、可回滚、可定位问题
branch可移动指针团队协作的临时工作线
tag通常不移动的发布指针CI/CD 的版本锚点
HEAD当前检出的 commit / branch决定下一次 commit 接到哪里
remote-tracking branch本地记录的远端状态例如 origin/main,不是远端实时状态

为什么这很重要:大多数 Git 事故都来自混淆了“移动 branch 指针”“改暂存区”“改工作区”这三件事。先判断命令影响哪一层,再执行。

2. 日常工作流

2.1 开始前同步主干

git switch main
git fetch origin
git pull --ff-only
git switch -c feat/login-rate-limit

--ff-only 的价值是发现本地分支已经分叉时立即停止,避免无意识产生 merge commit。

2.2 小步提交

git status --short
git diff
git add src/auth/rate-limit.ts
git diff --cached
git commit -m "feat(auth): add login rate limit"

提交边界建议按“可独立 review / 可独立回滚”拆,而不是按“今天写了哪些文件”拆。

2.3 推送并创建 PR

git push -u origin feat/login-rate-limit

PR 不是补流程,而是主干保护的一部分。最低要求:

  • CI 状态全绿
  • 至少 1 人 review
  • 不允许直接 push main
  • 合并后删除 feature branch
  • 对生产相关变更要求变更说明和回滚方案

3. 看懂历史

3.1 常用历史视图

git log --oneline --graph --decorate --all
git show HEAD
git diff main...HEAD
git diff --name-only main...HEAD

这里要区分 git log 的“提交集合”和 git diff 的“内容比较”。main..HEADmain...HEAD 在不同子命令里的语义不完全一样:

写法含义常见用途
git log main..HEADHEAD 可达、main 不可达的提交看当前分支多了哪些 commit
git diff main..HEAD比较 main 和 HEAD 两个端点的内容差异看两个分支当前快照差异
git diff main...HEAD从 main 和 HEAD 的共同祖先比较到 HEADPR diff 更接近这个语义

不要把 git log main...HEAD 直接等同于 PR diff;在提交集合里,三点表示两边可达但不同时可达的对称差集。

3.2 blame 不是甩锅工具

git blame src/api/client.ts
git show <commit>

git blame 只能告诉你某一行最近是谁改的,不能直接说明事故原因。真实排查应该结合 commit message、PR、测试、发布时间和监控。

4. 分支策略

4.1 GitHub Flow

main ──●──●──●──●
\ \
feature ●──● PR -> merge

适合大多数前端团队:

  • main 永远可发布
  • feature branch 通过 PR 合并
  • main 合并后自动部署 staging
  • release tag 或审批后部署 production

4.2 Trunk-Based Development

只有短生命周期分支,甚至直接向主干集成,但依赖强 CI 和 feature flag。

适用条件:

  • 单测 / E2E / 类型检查速度足够快
  • 变更可通过 feature flag 隐藏
  • 团队能接受高频小 PR

不适合:测试薄弱、发布周期长、多人长期占用同一模块的大改。

4.3 Git Flow

main / develop / release/* / hotfix/* 的重型流程适合版本周期明确、发布窗口严格的产品。前端业务迭代通常不建议默认使用,因为长期分支会放大冲突和集成风险。

5. merge、rebase、cherry-pick

5.1 merge

git switch main
git merge --no-ff feat/login-rate-limit

merge 保留分支结构,不改已有 commit。适合公共分支、集成分支、需要保留完整上下文的合并。

5.2 rebase

git switch feat/login-rate-limit
git fetch origin
git rebase origin/main

rebase 会把当前分支的 commit 重新接到新的 base 上,历史更线性,但会重写当前分支 commit id。

团队规则:

  • 本地未共享分支:可以 rebase。
  • 自己的 PR 分支:可以 rebase 后 --force-with-lease,前提是确认没有别人基于它提交。
  • mainrelease/* 等公共分支:不要 rebase。

5.3 cherry-pick

git switch release/1.8
git cherry-pick <fix-commit>

cherry-pick 适合把一个已验证的小修复移植到 release / hotfix 分支。不适合批量搬复杂 feature;那通常说明分支策略已经失控。

6. reset、revert、restore、checkout

这四类命令最容易造成事故。

命令影响是否改历史团队建议
git restore &lt;file>恢复工作区文件丢弃本地文件改动时首选
git restore --staged &lt;file>从暂存区撤出文件拆 commit 时常用
git reset --soft &lt;commit>移动 HEAD,保留 index 和工作区本地整理 commit 可用
git reset --mixed &lt;commit>移动 HEAD,重置 index,保留工作区默认模式,本地重新组织提交
git reset --hard &lt;commit>移动 HEAD,覆盖 index 和工作区是且丢本地改动只在明确备份后使用
git revert &lt;commit>新建一个反向 commit公共分支回滚首选
git checkout &lt;ref>老命令,多义视场景而定新脚本中优先用 switch / restore

公共分支上的错误提交,用 revert

git switch main
git pull --ff-only
git revert <bad-commit>
git push origin main

本地提交还没推送,想重新整理:

git reset --soft HEAD~2
git commit -m "feat(auth): add rate limit with tests"

reset --hard 前至少先看:

git status --short
git branch backup/before-hard-reset

7. 冲突处理

冲突不是 Git 失败,而是 Git 无法判断业务语义。

git status
# 打开冲突文件,处理 <<<<<<< ======= >>>>>>> 区域
git add <resolved-file>
git rebase --continue # rebase 场景
# 或
git merge --continue # merge 场景

处理原则:

  • 先理解双方意图,不只保留“自己的代码”。
  • 每个冲突文件处理后跑相关测试。
  • 大范围格式化和业务改动不要混一个 PR,否则冲突会被放大。
  • 长期分支每天同步主干,别等上线前一次性合并。

8. tag 与发布

发布系统应该绑定不可变引用。生产发布推荐使用 tag 或唯一镜像 digest,而不是“当前 main”。

npm version patch
git push origin main
git push origin v1.8.3

常见约定:

产物推荐标识
npm packagesemver,例如 1.8.3
Docker imagev1.8.3 + sha-&lt;short-sha>
静态资源包frontend-v1.8.3-&lt;short-sha>.tar.gz
Sentry releasefrontend@1.8.3+&lt;short-sha>

为什么要同时带版本和 sha:版本给人看,sha 给机器追溯。线上问题能精确定位到源码。

9. 回滚与恢复

9.1 回滚公共分支

git revert <bad-commit>

如果要回滚一组提交:

git revert --no-commit <oldest>^..<newest>
git commit -m "revert(release): rollback broken checkout flow"

回滚 merge commit 需要指定主线父节点:

git revert -m 1 <merge-commit>

这类操作建议在 PR 中完成,让 CI 和 reviewer 帮你兜底。

9.2 找回误删提交

git reflog
git show HEAD@{1}
git branch rescue/lost-work HEAD@{1}

reflog 记录本地 HEAD 和分支引用移动轨迹。它能救很多 reset --hard、误删 branch、rebase 失误,但不是远端备份,也会过期。

9.3 暂存临时改动

git stash push -m "wip: debug login redirect"
git stash list
git stash pop

stash 适合临时切分支,不适合长期保存工作。超过一天的改动更应该建 WIP commit 或临时分支。

10. worktree 并行开发

git worktree 可以让同一个仓库有多个工作目录,共享同一份 Git 对象库,但每个目录检出不同分支。

git worktree add ../app-hotfix -b hotfix/login-loop origin/main
git worktree list

适合:

  • 当前分支有大改,但线上 hotfix 要立刻处理。
  • 同时跑两个 Agent / 两套实验,避免互相覆盖工作区。
  • 一个目录跑长时间构建,另一个目录继续开发。

清理:

git worktree remove ../app-hotfix
git branch -d hotfix/login-loop

不要直接 rm -rf worktree 目录,否则 Git 还会保留已失效记录,需要额外 git worktree prune

11. bisect 定位回归

当你只知道“上周是好的,今天坏了”,用 git bisect 二分定位第一个坏 commit。

git bisect start
git bisect bad HEAD
git bisect good v1.8.0
# Git 检出中间 commit,手动跑验证
npm test -- --run login
git bisect good # 或 git bisect bad
git bisect reset

可以自动化:

git bisect start HEAD v1.8.0
git bisect run npm test -- --run login
git bisect reset

前提是测试能稳定复现问题。flaky test 会让 bisect 给出错误结论。

12. 团队保护规则

12.1 branch protection

main 至少应配置:

  • Require pull request before merging
  • Require status checks to pass
  • Require branches to be up to date before merging
  • Require linear history 或统一 squash merge 策略
  • Restrict force pushes
  • Restrict deletions

12.2 commit message

使用 Conventional Commits:

feat(auth): add login rate limit
fix(payment): handle Alipay callback timeout
docs(devops): add Git workflow guide

scope 要表达业务或模块,不要写成 fix(common): update。好的 commit message 是 release note、回滚判断和 blame 排查的基础。

12.3 review 清单

PR 合并前至少确认:

  • diff 范围和 PR 描述一致
  • CI 全绿,失败项有明确解释
  • 新增配置没有泄露密钥
  • 需要回滚的地方有明确方案
  • 数据结构、路由、缓存、环境变量变更已经同步部署文档

13. 前端项目高频场景

13.1 拆掉误加的大文件

文件还没提交:

git restore --staged dist/bundle.js
git restore dist/bundle.js

已经提交但没 push:

git reset --soft HEAD~1
git restore --staged dist/bundle.js
git commit -m "feat(build): add bundle analyzer config"

已经 push 到公共分支:不要改历史,走 revert 或新 PR 删除。

13.2 PR 落后主干

git fetch origin
git rebase origin/main
git push --force-with-lease

如果团队禁止 rebase PR 分支,用 merge:

git fetch origin
git merge origin/main
git push

关键不是选哪个,而是团队统一策略。

13.3 线上版本出问题

优先顺序:

  1. 用监控确认问题版本和影响范围。
  2. 找到发布 tag / sha。
  3. 如果部署系统支持,直接回滚到上一产物。
  4. 代码层面用 git revert 生成回滚 PR。
  5. 事后用 bisect / PR diff 找根因,补测试。

14. 常见反模式

  • 公共分支 reset --hard 后 force push:会破坏所有协作者历史。
  • 大 PR 堆几千行:review 失效,冲突集中爆发。
  • 格式化和业务改动混在一起:diff 噪音掩盖真实逻辑。
  • 只打 latest 镜像:线上无法对应源码。
  • tag 发布后又移动 tag:发布锚点失真。
  • 长期依赖 stash:本地状态不可审计,机器坏了就丢。
  • 冲突时无脑选 ours/theirs:容易吞掉别人逻辑。
  • commit message 写 update:以后排查和生成 changelog 都痛苦。

15. 延伸阅读

核对日期:2026-07-07