Git 工作流:分支、PR、代码审查

1. 团队协作 vs 个人项目

做后端时你可能熟悉:

前端团队和小项目协作稍有不同,不是工具问题,是协作约定问题。一个能运转 N 人的前端工作流:

main          ←── 线上跑的(保护分支,不能直推)
  ↑
develop       ←── 日常集成(可选,小团队可省)
  ↑
feature/xxx   ←── 一个功能一个分支
fix/xxx       ←── bug 修复
hotfix/xxx    ←── 紧急补救

Java 对照


2. 常见分支模型

2.1 Trunk-Based(推荐小团队)

只有 main + 短命 feature 分支:

main ─── ⊙ ── ⊙ ── ⊙ ── ⊙
         │         │
         └── f1 ───┘
                   │
                   └── f2 ─┐
                           │
main ──────────────────── ⊙

特点

适合:团队 < 10 人,产品迭代快,追求持续交付。

2.2 GitFlow(传统大型项目)

main → production
develop → integration
feature/* → 新功能
release/* → 发版准备
hotfix/* → 线上紧急修复

特点

适合:版本周期长、强版本号管理的项目(企业内部系统、SDK)。

2.3 GitHub Flow(开源项目常用)

main ─── ⊙ ── ⊙ ── ⊙
         │    │
         │    └── 任何 feature / fix / docs
         └── 任何 feature

特点

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

规则


4. 提交信息规范

in4vue 的 Git 规范要求中文 + 约定式格式

<类型>: <描述>

<可选的详细说明>

<可选的相关 issue>

类型

类型 何时用
feat 新功能
fix bug 修复
docs 文档变更(README、注释大改)
refactor 重构(不改功能)
style 代码格式(空格、分号等,无逻辑变化)
chore 杂项(依赖升级、构建配置)
perf 性能优化
test 测试相关

示例

feat: 新增 AI 问答助手侧边栏

✨ 添加右侧抽屉形式的问答面板
✨ 支持 markdown 格式渲染 AI 回复
✨ 历史对话保存到 localStorage

Closes #12

为什么这样

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

要点

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 键会用默认浅色

## 待办
- [ ] 与设计师对齐图片/图标的暗色版本

关键元素

Java 对照:类似公司 Confluence 里的"上线前检查清单"——让 reviewer 不用猜。


7. 代码审查(Code Review)

作为作者,提 PR 前自己先 review 一遍(git diff main),检查:

作为reviewer,关注点:

优先级 看什么
正确性(逻辑、边界、异常)
安全(XSS、token 泄漏、SQL 注入类的前端对应)
可维护性(命名、分层、魔法值)
测试覆盖
性能(除非明显低效)
代码风格(Prettier 自动搞定了,不用讨论)

Java 对照:和后端 review 一样,优先级高的是"代码正不正确"而不是"好不好看"

7.1 评论的方式

nit: `title` 可以改成 `noteTitle` 更明确
🤔 这里为什么不用现有的 `useTable`? 是不是漏了哪个场景?
🚨 这段在 iOS Safari 下会报错,之前踩过坑:
https://github.com/xxx/issues/123

惯例前缀

7.2 对 reviewer 给出的建议


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

解决思路

  1. 别删别人的改动——保留两边,合并逻辑
  2. 删掉 <<<===>>> 标记
  3. git add <冲突文件>
  4. 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 前跑测试(可选)

注意


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

一个人开发也按这个流程,好处:


小练习

  1. 给 in4vue 建 .github/pull_request_template.md
  2. 装 husky + commitlint,强制约定式提交
  3. 故意在 feature 分支里积 5 个 commit,用 squash 合并看 main 上只剩 1 个
  4. git reflog + reset --hard 找回一次"误删"的改动
  5. gh pr create 开一个 PR,用 gh pr checkout 切到别人的 PR 试运行

延伸阅读