前言

上一篇把 git 的基本命令都过了一遍,但「会用命令」和「用对流程」是两回事。实际开发里,很多人 git 用得混乱:直接在 main 上改、提交信息写 fix、合并时一团乱麻。这篇文章记录一下我在正常开发流程中会遵循的做法,从分支策略到提交规范、再到合并与同步,把「怎么算用得对」讲清楚。

文章假设读者已经会 init / add / commit / push / pull 这些基础命令,这里只讲「怎么用对」。
示例以 main 为主分支,团队习惯叫 master 的话请自行替换。

分支策略

为什么不能直接在 main 上改

main 是「随时可以发布」的状态。如果直接在 main 上开发,一旦改坏,仓库就处于不可用状态,而且很难定位是哪次改动引起的。正确做法是:

main 只接收「合并过、验证过」的代码,所有开发都在功能分支上进行。

分支命名规范

分支名应该能一眼看出在做什么,建议用前缀区分类型:

前缀 用途 示例
feat/ 新功能 feat/login-page
fix/ 修 bug fix/null-pointer
docs/ 文档 docs/readme
refactor/ 重构 refactor/auth-module
chore/ 杂项 chore/update-deps
hotfix/ 线上紧急修复 hotfix/payment

命名用短横线 - 连接,不用下划线或空格。名字要短但能表意,feat/user-login-v2 比 feat/x 好。

标准开发流程

1
2
3
4
5
6
1. 从 main 切出功能分支
2. 在分支上开发、提交
3. 开发完成,发起合并请求(PR/MR)
4. 代码审查(code review)
5. 审查通过,合并回 main
6. 删除已合并的功能分支

提交规范

提交信息的格式

提交信息是项目的「历史档案」,写清楚能省掉大量沟通。推荐 type(scope): description 的格式:

1
type(scope): 简短描述
  • type:见上一节的分支前缀(feat / fix / docs 等)
  • scope:改动的模块,可省略,如 api / ui / db
  • description:一句话说清做了什么,用祈使句,不加句号

好的提交 vs 坏的提交

1
2
3
4
5
6
7
8
# 坏:信息量约等于零
git commit -m "fix"
git commit -m "update"
git commit -m "wip"

# 好:能看懂改了什么
git commit -m "fix(api): 修复用户未登录时访问 /profile 返回 500 的问题"
git commit -m "feat(ui): 添加深色模式切换按钮"

原子提交

一次提交只做一件事。 不要把「改 bug」和「重构另一个模块」混在一次提交里。

这样做的好处:

  • git log / git blame 时能精确定位改动来源
  • 出问题时 git revert 能干净地撤销单个功能
  • 代码审查时每个提交都易于理解

如果一次改了很多,可以拆成多个 commit:

1
2
3
4
5
git add src/login.js
git commit -m "fix(auth): 修复登录态过期未刷新的问题"

git add src/style.css
git commit -m "style(ui): 调整登录按钮间距"

合并策略:merge vs rebase

两者的区别

操作 效果 适用场景
merge 保留完整的分支历史,产生一个合并提交 多人协作的分支
rebase 把提交「搬」到另一个分支的顶端,历史线性 个人分支整理历史

何时用 rebase

功能分支开发过程中,main 可能已经前进了。合并前先把分支同步到最新的 main:

1
2
3
# 在功能分支上
git fetch origin
git rebase origin/main

这样你的功能分支就基于最新的 main,历史干净。

何时用 merge

合并回 main 时,用 merge,保留分支拓扑:

1
2
# 在 main 上
git merge feat/login-page

铁律:永远不要 rebase 已经推送到共享分支的提交。 rebase 会改写提交历史(生成新的 commit id),如果别人也拉了同一个分支,会严重冲突。只 rebase 自己本地还没推送的提交。

解决 rebase 冲突

1
2
3
4
5
6
7
8
git rebase origin/main
# 如果有冲突
git status # 查看冲突文件
# 手动编辑冲突文件,解决 <<<<<<< / ======= / >>>>>>> 标记
git add .
git rebase --continue
# 想放弃 rebase
git rebase --abort

同步与推送

推送前保持分支最新

推送前先同步远程,避免被拒绝:

1
2
3
git fetch origin
git log --oneline main..feat/login-page # 查看本地领先 main 的提交
git push -u origin feat/login-page

处理 non-fast-forward

推送被拒绝(rejected, non-fast-forward)通常意味着远程分支有你本地没有的提交。两种处理方式:

1
2
3
4
5
6
# 方式一:先合并远程
git pull --no-rebase origin feat/login-page

# 方式二:rebase 到远程之上(历史更干净)
git pull --rebase origin feat/login-page
git push

个人分支推荐 --rebase,团队共享分支推荐 --no-rebase(merge)。

同步本地 main

main 是共享分支,永远用 merge 同步,不要 rebase:

1
2
git checkout main
git pull --no-rebase origin main

代码审查(PR/MR)流程

  1. 功能分支推送后,在 GitHub/GitLab 发起 Pull Request
  2. 填写 PR 描述:做了什么、为什么、怎么测试的
  3. 关联相关的 issue(Closes #123)
  4. 等待 review,根据反馈修改并 push 追加提交
  5. review 通过,由维护者合并

PR 描述写清楚「为什么这么改」比代码本身更重要,能让 reviewer 快速理解意图。

常用辅助命令

查看分支状态

1
2
3
git log --oneline --graph --all   # 全部分支的提交图
git branch -a # 本地 + 远程分支
git branch -vv # 查看每个分支的上游追踪

找回丢失的工作

1
2
3
git reflog                     # 查看所有 HEAD 移动记录
git reflog show # 找回之前 HEAD 指向的 commit
git checkout <commit-id> # 回到某个丢失的提交

reflog 是救命命令,reset 丢掉的提交都能在这里找回来。

清理远程分支

1
2
git fetch --prune              # 删除本地已不存在的远程分支引用
git branch -r # 查看远程分支

常见问题

功能分支和 main 分叉太远怎么办?

定期 rebase 到最新 main,不要等分叉太大再处理。可以养成习惯:

1
2
3
# 每天开始/结束开发前
git fetch origin
git rebase origin/main

合并后发现有遗漏,怎么补?

在功能分支上补提交,再次发起合并(如果 PR 还没关)。不要在 main 上直接补,保持「main 只接受合并」的原则。

如何在合并前预览改动?

1
2
git diff main..feat/login-page        # 查看功能分支相对 main 的所有改动
git diff --stat main..feat/login-page # 只看统计

小结

正常开发流程的核心就几条:

  • main 只接受验证过的合并,开发都在功能分支
  • 分支名带类型前缀,能一眼看出用途
  • 提交原子化,一次提交只做一件事,信息用 type(scope): 描述
  • 个人分支 rebase 整理,合并回 main 用 merge
  • main 同步永远用 merge,不要 rebase 共享分支
  • reflog 是救命命令,丢了提交先找它

这套流程的关键不是命令,而是「保持 main 干净 + 提交有粒度 + 不 rebase 共享历史」这三条原则。把这几条守住,git 用起来就顺手了。

注:本篇文章大部分由 AI 生成。