直接在 main 分支修改代码,短期看似省事,却会让未完成的功能、紧急修复和稳定版本混在一起。更稳妥的做法是:每项任务使用独立分支,在分支上完成小步提交,通过 Pull Request(PR)审查后再合并到 main

本文以 GitHub 为例,完整演示一次功能开发。读完后,你将能独立完成“更新主分支 → 创建功能分支 → 修改并提交 → 同步上游 → 发起 PR → 合并与清理”的循环。

前提:本地已经安装并配置 Git,拥有一个设置了 origin 远程仓库的项目,并且团队将 main 作为稳定主分支。

1. 先建立正确的心智模型

一次常规开发会经过四个位置。Git 命令的作用,本质上就是让改动在这些位置之间移动。

位置可以把它理解为常用检查命令
工作区(Working Tree)当前磁盘中正在修改的文件git diff
暂存区(Index)下一次提交准备包含的内容git diff --staged
本地仓库已提交、可回退的版本历史git log --oneline --graph
远程仓库GitHub 上供协作和备份的分支git branch -vv

最常用的完整循环如下。无需一次记住所有命令,先记住每一步的目的。

2. 从最新的 main 创建功能分支

2.1 更新本地主分支

先确认当前没有遗漏的修改:

1
git status

切换到 main,再以“只允许快进”的方式拉取远程更新:

1
2
git switch main
git pull --ff-only origin main

--ff-only 会在本地和远程历史已经分叉时停止,而不是自动生成一个意外的合并提交。此时应先检查分支状态,再决定如何处理。

2.2 创建语义清楚的分支

假设本次任务是增加登录页:

1
git switch -c feat/login-page

推荐使用“类型/简短任务”的命名方式:

  • feat/login-page:新增功能;
  • fix/navbar-overflow:修复缺陷;
  • docs/install-guide:修改文档;
  • refactor/user-service:重构但不改变外部行为。

git switch -c 会同时创建并切换分支。运行 git branch --show-current,应看到 feat/login-page

3. 在功能分支上小步开发

3.1 修改前先确认位置

每次开始工作前都运行:

1
git status

重点确认两件事:当前位于正确的功能分支;工作区没有来自其他任务的无关修改。

3.2 修改、检查、暂存

完成一小部分修改并自测后,先查看差异:

1
git diff

只暂存属于本次提交的文件:

1
2
git add src/login.html src/login.css
git diff --staged

不建议初学时无条件使用 git add .。逐个选择文件并用 git diff --staged 复核,可以避免把日志、密钥或临时文件提交进去。

3.3 创建一个含义完整的提交

1
git commit -m "feat: add login page layout"

一个好提交应当只完成一个可描述的目标。例如,“增加登录页布局”和“修复导航栏溢出”最好拆成两个提交。小而完整的提交更容易审查,也更容易单独撤销。

如果任务尚未完成,就继续重复“修改 → 自测 → 暂存 → 提交”:

1
2
3
工作区修改 ──git add──> 暂存区 ──git commit──> 本地功能分支
↑ │
└──────────────── 下一轮小步开发 ───────────────┘

4. 推送分支并创建 Pull Request

首次推送时建立本地分支与远程分支的跟踪关系:

1
git push -u origin feat/login-page

以后在该分支上只需运行 git push。然后进入 GitHub:

  1. feat/login-page 为源分支、main 为目标分支创建 PR;
  2. 标题说明结果,正文说明改动原因、验证方法和影响范围;
  3. 检查自动化任务是否通过;
  4. 邀请成员审查,根据意见继续提交和推送。

PR 不是“任务完成后的上传按钮”,而是围绕一组改动进行检查、讨论和合并的协作界面。审查期间新增的提交会自动进入同一个 PR。

5. 当 main 在开发期间发生变化

你开发功能时,其他成员可能已经向 main 合并了新提交。可先获取远程状态,但不立即修改本地文件:

1
2
git fetch origin
git log --oneline --graph --decorate --all -n 20

对于只有自己使用的功能分支,可以把功能提交变基到最新的 origin/main

1
2
git switch feat/login-page
git rebase origin/main

变基前后的历史可以这样理解:

rebase 会重写功能分支提交,使历史更线性,因此不要对多人共同开发的分支随意变基。共享分支应遵循团队约定,通常使用 git merge origin/main 更稳妥。

5.1 解决变基冲突

出现冲突时不要慌,Git 会暂停并列出冲突文件:

1
git status

打开文件,结合上下文处理 <<<<<<<=======>>>>>>> 标记;完成后继续:

1
2
git add <已解决的文件>
git rebase --continue

如果发现方向不对,可以完整取消本次变基:

1
git rebase --abort

解决冲突后重新运行项目测试。由于变基改变了提交 ID,已经推送过的个人功能分支需要安全地更新远程历史:

1
git push --force-with-lease

使用 --force-with-lease,不要直接使用 --force。前者会在远程分支出现你尚未获取的新提交时拒绝覆盖,能降低误删他人工作的风险。

6. 合并 PR 与清理分支

审查和检查通过后,在 GitHub 按团队规则选择合并方式:

方式结果适用情况
Squash and merge将 PR 压缩为 main 上的一个提交功能分支有较多修正型小提交
Rebase and merge保留各提交但形成线性历史每个提交都清晰、可独立理解
Merge commit保留分支结构并增加合并提交团队希望明确记录分支边界

合并并删除 GitHub 上的功能分支后,清理本地环境:

1
2
3
4
git switch main
git pull --ff-only origin main
git branch -d feat/login-page
git fetch --prune

优先使用安全删除参数 -d。如果 PR 使用 Squash and merge,原功能提交不会直接成为 main 的祖先,因此 Git 可能仍拒绝删除。先在 GitHub 确认 PR 已合并、再检查分支内容;只有确认不再需要时,才使用 git branch -D <branch>

至此,一个开发循环结束。下一项任务再从最新的 main 创建新分支,不要继续复用已经合并的旧功能分支。

7. 一份可直接复用的命令清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1. 从最新主分支开始
git switch main
git pull --ff-only origin main

# 2. 创建任务分支
git switch -c feat/login-page

# 3. 小步开发与提交
git status
git diff
git add <file...>
git diff --staged
git commit -m "feat: add login page layout"

# 4. 首次推送并创建 PR
git push -u origin feat/login-page

# 5. 必要时同步最新主分支(仅个人功能分支使用 rebase)
git fetch origin
git rebase origin/main
git push --force-with-lease

# 6. PR 合并后清理
git switch main
git pull --ff-only origin main
git branch -d feat/login-page
git fetch --prune

8. 常见错误与排查顺序

  • 改错分支:先不要提交,用 git status 确认改动,再通过 git switch -c <new-branch> 从当前位置创建正确分支。
  • 提交混入无关文件:提交前固定运行 git diff --staged;敏感文件应加入 .gitignore,泄露的密钥还必须立即作废。
  • 拉取后出现意外合并提交:开始任务时使用 git pull --ff-only,历史分叉后先查看日志,不要盲目继续。
  • 冲突时不知道保留哪一边:先理解两边修改的目的,再编辑最终结果并测试;冲突标记不是让你机械地任选一边。
  • 在共享分支强制推送:不要这样做。公共历史需要撤销时,通常使用 git revert 创建反向提交。

参考资料