Git 工作流:分支、PR、代码审查
1. 团队协作 vs 个人项目
做后端时你可能熟悉:
main分支直推- 或者
dev+release两条分支 - 出 bug 就 hotfix 补
前端团队和小项目协作稍有不同,不是工具问题,是协作约定问题。一个能运转 N 人的前端工作流:
main ←── 线上跑的(保护分支,不能直推)
↑
develop ←── 日常集成(可选,小团队可省)
↑
feature/xxx ←── 一个功能一个分支
fix/xxx ←── bug 修复
hotfix/xxx ←── 紧急补救
Java 对照:
main≈ 生产环境已部署的代码feature/xxx≈ 一个 Jira 单对应一个分支- PR ≈ 提测 + code review 的关口
2. 常见分支模型
2.1 Trunk-Based(推荐小团队)
只有 main + 短命 feature 分支:
main ─── ⊙ ── ⊙ ── ⊙ ── ⊙
│ │
└── f1 ───┘
│
└── f2 ─┐
│
main ──────────────────── ⊙
特点:
- feature 分支生命周期 1-3 天,尽快合回 main
- 用 feature flag 保护未完成的功能(能合并但对用户不可见)
- 持续集成 / 持续部署友好
适合:团队 < 10 人,产品迭代快,追求持续交付。
2.2 GitFlow(传统大型项目)
main → production
develop → integration
feature/* → 新功能
release/* → 发版准备
hotfix/* → 线上紧急修复
特点:
- 分支多、规则严
- 有明确的"发版"节奏
- 对新人有学习成本
适合:版本周期长、强版本号管理的项目(企业内部系统、SDK)。
2.3 GitHub Flow(开源项目常用)
main ─── ⊙ ── ⊙ ── ⊙
│ │
│ └── 任何 feature / fix / docs
└── 任何 feature
特点:
- 只有
main+ 任意命名的分支 - PR 合并即部署
in4vue 用这个:个人项目 + 持续部署到 Cloudflare,简单即正义。
3. 分支命名规范
推荐格式:<类型>/<简短描述>
feature/add-ai-chat
fix/login-redirect-loop
docs/update-readme
refactor/extract-useTable
chore/bump-vue-3.5
hotfix/patch-xss-vulnerability
类型对应提交类型(见下一节):feat / fix / docs / refactor / style / chore / perf / test
规则:
- 小写 + 短横线
- 不要中文(有些 Git GUI 工具显示会乱)
- 带上 issue 号更好:
feature/123-ai-chat
4. 提交信息规范
in4vue 的 Git 规范要求中文 + 约定式格式:
<类型>: <描述>
<可选的详细说明>
<可选的相关 issue>
类型:
| 类型 | 何时用 |
|---|---|
feat |
新功能 |
fix |
bug 修复 |
docs |
文档变更(README、注释大改) |
refactor |
重构(不改功能) |
style |
代码格式(空格、分号等,无逻辑变化) |
chore |
杂项(依赖升级、构建配置) |
perf |
性能优化 |
test |
测试相关 |
示例:
feat: 新增 AI 问答助手侧边栏
✨ 添加右侧抽屉形式的问答面板
✨ 支持 markdown 格式渲染 AI 回复
✨ 历史对话保存到 localStorage
Closes #12
为什么这样:
- 团队 / 自己后续看
git log --oneline一眼知道每个 commit 干了什么 - 配合
standard-version/semantic-release能自动生成 CHANGELOG - commitlint(见 ESLint 笔记)可以强制执行
4.1 不好的提交信息
✗ update code
✗ fix bug
✗ wip
✗ asdf
坏处:6 个月后你自己都不知道动了啥。
改良:
✓ fix: 修复 Safari 下日期选择器样式错乱
✓ feat: 添加笔记分类筛选
✓ refactor: 抽离 useTable composable 统一分页逻辑
5. 一次完整流程:从拉代码到 PR
5.1 开始新功能
# 1. 同步 main 到本地
git checkout main
git pull --rebase
# 2. 基于 main 建新分支
git checkout -b feature/add-dark-mode
# 3. 改代码...
# 4. 提交
git add src/composables/useTheme.ts src/components/ThemeToggle.vue
git commit -m "feat: 新增暗色模式切换"
# 继续改,继续小步提交
git add ...
git commit -m "feat: 持久化主题选择到 localStorage"
# 5. 推到远程
git push -u origin feature/add-dark-mode
要点:
git add <具体文件>而不是git add .—— 避免误提交.env.local、node_modules- 小步提交:一个逻辑单元一次 commit,不要堆 50 个变化再一口气提交
-u第一次推送设置 upstream,以后git push就够
5.2 保持和 main 同步
分支开着的几天里,main 上有新提交。两种同步方式:
方式 A:rebase(推荐,历史干净)
git fetch origin
git rebase origin/main
效果:把你的 commits "掐下来" 放到最新 main 上面,时间线线性。
方式 B:merge
git fetch origin
git merge origin/main
效果:产生一个"合并提交",时间线有分叉。
对照图:
rebase 后: main ─── A ─── B ─── mine1 ─── mine2
merge 后: main ─── A ─── B ────────────── M
↗
└── mine1 ─── mine2 ──┘
小团队 / 个人分支推荐 rebase——历史干净,git log 好读。
但 rebase 有个铁律:不要对已经推到远程、被别人协作的分支做 rebase。会把别人的 commit 和你重新写的 hash 对不上,合作者再 pull 就灾难。
记忆口诀:"Only rebase branches only you work on."
5.3 推 + 开 PR
git push
# 用 gh CLI 开 PR(装过 github cli 的话)
gh pr create --title "feat: 新增暗色模式" --body "$(cat <<'EOF'
## Summary
- 添加 `useTheme` composable
- 顶栏加切换按钮
- 持久化到 localStorage
## Test plan
- [x] 切换后颜色立刻变
- [x] 刷新页面记住选择
- [x] 跟随系统暗色模式
Closes #12
EOF
)"
没有 gh,打开 GitHub / Gitee 网页手动建 PR 也行。
6. 写一个好 PR 描述
PR 的描述是给 reviewer 的**"用户手册"**:
## 背景
产品要求所有页面支持暗色模式,issue #12
## 改动点
- 新增 `src/composables/useTheme.ts` - 暗色模式状态管理
- 修改 `src/components/Header.vue` - 加切换按钮
- 修改 `src/style.css` - 加 `@custom-variant dark`
- 样式覆盖 `src/styles/dark-overrides.css`
## 截图 / 录屏
[切换动画.gif]
## Test plan
- [x] Chrome 最新版
- [x] Safari iOS 16+
- [x] 刷新页面状态保持
- [x] 跟随系统(未手动选时)
## 风险点
- Element Plus 某些组件(如 DatePicker)暗色下颜色对比度偏低,单独调了
- 旧用户 localStorage 里没 theme 键会用默认浅色
## 待办
- [ ] 与设计师对齐图片/图标的暗色版本
关键元素:
- 为什么做(背景/issue 号)
- 做了什么(改了哪些文件、新增什么)
- 怎么证明(test plan、截图)
- 风险(哪些场景可能出问题)
Java 对照:类似公司 Confluence 里的"上线前检查清单"——让 reviewer 不用猜。
7. 代码审查(Code Review)
作为作者,提 PR 前自己先 review 一遍(git diff main),检查:
- [ ] 没有遗留的
console.log/debugger - [ ] 没有注释掉的死代码
- [ ] 没有
TODO:没说明 - [ ] 没有写死的开发地址(
localhost:xxxx) - [ ] 没有个人试验文件(
test.html、playground.ts) - [ ] CLAUDE.md / 学习路线相关文档是否同步
- [ ] 是否加了必要的注释(WHY,不是 WHAT)
作为reviewer,关注点:
| 优先级 | 看什么 |
|---|---|
| 高 | 正确性(逻辑、边界、异常) |
| 高 | 安全(XSS、token 泄漏、SQL 注入类的前端对应) |
| 中 | 可维护性(命名、分层、魔法值) |
| 中 | 测试覆盖 |
| 低 | 性能(除非明显低效) |
| 低 | 代码风格(Prettier 自动搞定了,不用讨论) |
Java 对照:和后端 review 一样,优先级高的是"代码正不正确"而不是"好不好看"。
7.1 评论的方式
nit: `title` 可以改成 `noteTitle` 更明确
🤔 这里为什么不用现有的 `useTable`? 是不是漏了哪个场景?
🚨 这段在 iOS Safari 下会报错,之前踩过坑:
https://github.com/xxx/issues/123
惯例前缀:
nit:(nitpick) —— 可改可不改的小建议question:—— 我不懂,求解释blocker:/🚨—— 必须改才能合
7.2 对 reviewer 给出的建议
- 不同意:解释为什么,不是硬刚
- 同意但改不动:开新 issue记录,当前 PR 先合
- 小改:当场改 + 新 commit(不要 amend 已推送的)
8. 合并 PR:merge vs squash vs rebase
GitHub 给你三个选项:
8.1 Merge commit(默认)
保留分支所有 commit,加一个"合并提交"。
main: A ─── B ────────────── M
↗
└── f1 ─── f2 ─── f3 ─┘
优点:历史完整 缺点:main 的 commit 图会乱
8.2 Squash and merge(推荐)
把 PR 所有 commit 压成 1 个合到 main。
main: A ─── B ─── S (S = f1+f2+f3 压缩成 1 个)
优点:main 历史干净,一个 feature 对应一个 commit 缺点:丢失 feature 分支内部的细粒度历史(通常无所谓)
8.3 Rebase and merge
把 PR 的 commit 线性 rebase 到 main 末尾。
main: A ─── B ─── f1 ─── f2 ─── f3
优点:线性、保留所有 commit 缺点:main 的 commit 数会膨胀
in4vue 推荐 Squash:个人项目,main 历史每个 commit = 一个完整 feature,看起来最舒服。
9. 冲突解决
拉 main 时有冲突,不要慌:
git rebase origin/main
# CONFLICT (content): Merge conflict in src/router/index.ts
# ...
打开冲突文件,会看到:
<<<<<<< HEAD
{ path: '/dark', component: () => import('@/pages/Dark.vue') }
=======
{ path: '/profile', component: () => import('@/pages/Profile.vue') }
>>>>>>> feature/add-dark-mode
HEAD——目标分支(origin/main)的版本- 下面——你分支的版本
解决思路:
- 别删别人的改动——保留两边,合并逻辑
- 删掉
<<<、===、>>>标记 git add <冲突文件>git rebase --continue
// 解决后
{ path: '/dark', component: () => import('@/pages/Dark.vue') },
{ path: '/profile', component: () => import('@/pages/Profile.vue') },
复杂冲突用 VS Code 的 3-way merge 视图:会并排展示 HEAD / incoming / result,点按钮选哪边。
实在搞不定:git rebase --abort 放弃回到开始状态,冷静再试。
10. 危险操作清单
这些命令用之前三思:
| 命令 | 风险 | 安全替代 |
|---|---|---|
git push --force |
覆盖远程,合作者哭 | --force-with-lease |
git reset --hard |
未提交改动全没 | 先 git stash |
git branch -D |
删分支不检查是否已合并 | git branch -d(大写改小写) |
git clean -fd |
删除未跟踪文件 + 目录 | 先 git clean -n 预览 |
git rebase 远程分支 |
改写公共历史 | 只 rebase 自己的本地分支 |
黄金法则:带 --force / -D / --hard 的命令,三思而后行。
找回误操作:
git reflog # 看自己干了什么(本地 HEAD 变更历史)
git reset --hard HEAD@{3} # 回到 3 步前
reflog 是 Git 的"后悔药"——即便 reset --hard 也能找回。但未 add 的新文件就真没了。
11. 实用命令速查
# 查看当前状态(别用 -uall,大仓库会卡)
git status
# 看改动
git diff # 工作区 vs 暂存区
git diff --cached # 暂存区 vs 上次 commit
git diff main...HEAD # 和 main 的差异(三点,看分叉后所有)
# 看历史
git log --oneline --graph --all -20
git log -p src/file.ts # 看某文件的变化历史
git blame src/file.ts # 每行代码是谁什么时候写的
# 撤销
git restore src/file.ts # 放弃工作区改动(未 add 前)
git restore --staged src/file.ts # 取消 add
git revert <commit-hash> # 新增一个反向 commit(安全)
# 临时保存
git stash # 存起来
git stash list
git stash pop # 取出来
# 合并远程
git fetch origin # 只拉不合(安全)
git pull --rebase # 拉并 rebase
# 分支
git branch -a # 所有分支(含远程)
git branch -d feature/x # 删已合并分支
git switch main # 切分支(比 checkout 更精确)
git switch -c feature/new # 建并切
# 看当前分支差多少
git log origin/main..HEAD # 本地比远程多几个 commit
12. 钩子(Hooks)集成
Git 提供 .git/hooks/ 脚本。用 husky(见 ESLint 笔记)管理团队共享的钩子:
.husky/
├── pre-commit ← commit 前跑 lint-staged
├── commit-msg ← 校验提交信息格式
└── pre-push ← push 前跑测试(可选)
注意:
pre-push跑测试可以防推坏代码,但慢会烦——本地推得多,CI 挡一次就行- 不要
--no-verify跳过钩子(除非 hotfix 紧急)
13. GitHub 生态工具
13.1 gh CLI
gh pr list # 看我的 PR
gh pr create # 开 PR
gh pr checkout 42 # 切到 PR 42 分支(本地试)
gh pr view 42 # 看 PR 详情
gh issue list
gh repo clone xxx/yyy
命令行能干几乎所有 web UI 的事,比点网页快。
13.2 PR 模板
在仓库根目录建 .github/pull_request_template.md:
## Summary
## Test plan
- [ ]
## Screenshots
新 PR 自动预填这个模板,节省重复输入。
13.3 CODEOWNERS
.github/CODEOWNERS:
/src/api/ @backend-team
/src/components/ @frontend-lead
*.md @docs-team
改对应目录会自动 @ 对应人 review。个人项目用不上,团队项目能省很多沟通。
14. 常见坑点
| 现象 | 原因 | 解法 |
|---|---|---|
| commit 后发现写错了文件名/内容 | amend 能救 | 未 push: git commit --amend;已 push: 新增 commit |
| 误提交了敏感文件 | .env 推到远程 |
立刻改密码!然后用 git filter-repo 清历史 |
| 分支历史乱七八糟 | 不断 merge 来 merge 去 | 改用 rebase(本地分支) |
git pull 后一堆 "Merge branch" commit |
默认 pull = fetch + merge | 配 git config --global pull.rebase true |
| PR diff 太大没人 review | 分支开太久攒了太多改动 | 拆小 PR,一次 300 行内 |
| main 的 CI 挂了怎么办 | 不敢推新 PR | 先修 main 上的问题,再继续 |
15. 小决策表
| 场景 | 方案 |
|---|---|
| 个人项目 | GitHub Flow + Squash merge |
| 2-10 人小团队 | Trunk-Based + feature flag |
| 大企业/强版本 | GitFlow |
| 要不要 amend | 未 push 可以 / 已 push 不要 |
| 本地同步 main | rebase |
| 合并 PR | Squash (in4vue) |
| 提交信息 | 约定式 feat: xxx |
| 分支命名 | feature/xxx / fix/xxx |
| 敏感文件泄漏 | 立刻改密钥 + filter-repo |
| 找回误操作 | git reflog |
16. in4vue 的实际工作流
# 每次开新任务
git checkout main
git pull --rebase
git checkout -b feature/xxx
# 写代码 + 小步提交
git add src/...
git commit -m "feat: 新增笔记搜索"
# 推上去
git push -u origin feature/xxx
# 开 PR (Cloudflare 会自动部预览站)
gh pr create
# 自己 review + 合并
# 合并后自动部署到 https://in4vue.pages.dev
一个人开发也按这个流程,好处:
- PR diff 面板天然是 code review 工具
- Cloudflare 预览站测试效果
git log干净可读- 练习的是真实团队协作的肌肉记忆
小练习
- 给 in4vue 建
.github/pull_request_template.md - 装 husky + commitlint,强制约定式提交
- 故意在 feature 分支里积 5 个 commit,用 squash 合并看 main 上只剩 1 个
- 用
git reflog+reset --hard找回一次"误删"的改动 - 用
gh pr create开一个 PR,用gh pr checkout切到别人的 PR 试运行
延伸阅读
- Pro Git 中文版(最权威的 Git 教程)
- Conventional Commits
- GitHub Flow
- Trunk Based Development
- gh CLI 文档
- Oh Shit, Git!?!(搞砸了怎么救回来的冷知识)