Fork 协作完整工作流
Fork 协作完整工作流
从 Fork 开源项目 → 本地开发 → Squash 合并 → 提 PR → 同步上游的全流程方案。
1. Fork & 远程设置
# 克隆你的 fork
git clone git@github.com:你的账号/项目.git
cd 项目
# 添加主库为 upstream
git remote add upstream https://github.com/原作者/项目.git
# 确认
git remote -v
# origin → 你的 fork
# upstream → 主库首次配一次即可,之后每次开发只需要同步。
2. 同步上游 main(每次开新分支前)
git checkout main
git fetch upstream main
git merge upstream/main --ff-only为什么? 确保你的 main 和上游主库完全一致,以此为基线开新分支,避免 PR 时出现意外的冲突。
3. 创建开发分支
🛑 绝不在 main 上开发! 每次开发都建立新分支:
git checkout -b feat/xxx
# 或显式指定基线
git checkout -b feat/xxx main4. 本地开发 & 自由提交
开发过程中随便提,想提多少次都行:
git add <files>
git commit -m "wip: 实现了XXX功能第一部分"
git commit -m "fix: 修复了个小bug"
git commit -m "refine: 优化代码结构"
git commit -m "docs: 补充注释"5. PR 前:合并为一次提交(Squash)
多次零碎的 commit 不方便 reviewer 审视,PR 前应压缩成一次。
方案 A:git rebase -i(推荐,可精确控制)
git rebase -i main编辑器弹出:
pick a1b2c3d wip: 实现了XXX功能第一部分
pick b2e8f56 fix: 修复了个小bug
pick c3a9f87 refine: 优化代码结构
pick d4efb12 docs: 补充注释把后三行的 pick 改为 s(或 squash):
pick a1b2c3d wip: 实现了XXX功能第一部分
s b2e8f56 fix: 修复了个小bug
s c3a9f87 refine: 优化代码结构
s d4efb12 docs: 补充注释- 保存退出 → 编辑合并后的 commit message → 保存
- 第一行
pick的 message 作为新 commit 的标题 - 后几行
squash的 message 自动拼接到正文
方案 B:git reset --soft(一条命令,快速)
git reset --soft main
git commit -m "feat: 完整的改动描述"效果:4 次提交 → 1 次提交,改动内容完全不变。
⚠️ 两个方案都重写了提交历史,都需要 force push。
6. Force Push & 提 PR
git push --force-with-lease origin feat/xxx为什么用 --force-with-lease?
| 选项 | 行为 | 推荐? |
|---|---|---|
--force | 无脑覆盖远程,可能覆盖别人提交 | ❌ |
--force-with-lease | 检查远程分支是否有新提交,有则拒绝 | ✅ |
提交后,登录 GitHub:
https://github.com/你的账号/项目/pull/new/feat/xxx修改 base repository 为原作者的主库,填写 PR 描述,提交。
7. PR 挂在那,主库有新提交了怎么办?
核心结论:什么都不用做。 ✅
PR 是基于你提交时的基线发起的。上游有新提交在基线之后——只要没有文件冲突,GitHub 会自动检测 This branch has no conflicts,reviewer 可以直接合并。
如果你想主动同步上游(可选):
| 方式 | 命令 | 需 force push? | 历史 |
|---|---|---|---|
| Rebase 🥇 | git rebase upstream/main → push --force-with-lease | ✅ | 线性干净 |
| Merge | git merge upstream/main → push | ❌ | 有分叉 |
# Rebase 方案(推荐)
git fetch upstream main
git rebase upstream/main
git push --force-with-lease origin feat/xxx8. 冲突解决全攻略
8.1 冲突是怎么发生的?
Git 冲突的本质:你和别人(或上游)改了同一个文件的同一行。常见触发场景:
| 场景 | 触发时机 | 典型命令 |
|---|---|---|
| 同步上游 main | 自己在 main 上有本地提交 | git merge upstream/main |
| Rebase 到新基线 | 上游改动和你改动重叠 | git rebase upstream/main |
| GitHub PR 冲突 | upstream 在你提 PR 后合并了改动同一文件的 PR | PR 页面显示红色 ⚠️ |
💡 冲突不可怕——它是 Git 在"叫停"让你确认,而不是搞坏了代码。弄清楚原理,处理起来就很轻松。
8.2 场景一:同步 main 时冲突
git checkout main
git fetch upstream main
git merge upstream/main
# CONFLICT (content): Merge conflict in src/utils.ts
# Automatic merge failed; fix conflicts and then commit the result.原因:你在自己的 main 上直接做了提交(违反了原则 1 — 别在 main 上开发)。
解决:
# 如果 main 上的提交是误操作 → 直接抛弃
git merge --abort
git reset --hard upstream/main
# 如果提交是刻意的 → 手动解决(同 8.3 场景二流程)⚠️ 建议:main 分支永远保持干净,只做
--ff-only合并,不直接提交。这样同步上游永远不会冲突。
8.3 场景二:rebase 时冲突(⭐ 最常见)
git rebase upstream/main
# CONFLICT (content): Merge conflict in src/index.ts
# error: could not apply a1b2c3d... feat: 添加导出功能
# Resolve all conflicts manually, mark them as resolved with
# "git add/rm <conflicted_files>", then run "git rebase --continue".Git 已经告诉你该做什么了,跟着走就行:
Step 1 — 查看哪些文件冲突
git status
# both modified: src/index.ts
# both modified: src/utils.tsStep 2 — 手动解决冲突
打开冲突文件,找到 Git 的冲突标记:
<<<<<<< HEAD ← 上游的代码(你要 rebase 到的基线)
export { newUtil } from './new-util';
======= ← 你的代码(当前提交里的改动)
export { oldUtil } from './old-util';
>>>>>>> a1b2c3d... feat: 添加导出功能决定策略:
| 情况 | 操作 |
|---|---|
| 上游代码更新,自己的不需要了 | 保留 HEAD(删除自己的代码 + 标记) |
| 自己的代码是新的,上游已过时 | 保留自己的代码(删除上游代码 + 标记) |
| 两者都要,融合一下 | 手动合并,删除标记符 |
// 融合后的结果示例
export { newUtil } from './new-util';
export { oldUtil } from './old-util';💡 VS Code 用户:冲突文件顶部会弹出
Accept Current|Accept Incoming|Accept Both按钮,点一下就搞定。
Step 3 — 标记为已解决
git add src/index.ts src/utils.tsStep 4 — 继续 rebase
git rebase --continue可能的后续:
- ✅ Rebase 完成 → 进 Step 5
- ⚠️ 又遇到冲突 → 重复 Step 1~4(一个分支有多个 commit,每个 commit 都可能触发冲突)
- 🤔 想放弃 →
git rebase --abort回到 rebase 前状态
Step 5 — Force Push
git push --force-with-lease origin feat/xxx8.4 场景三:GitHub PR 显示冲突
PR 页面出现红条:
⚠️ This branch has conflicts that must be resolved
解法完全等同于场景二——在本地 rebase upstream/main,解决冲突,force push。
git fetch upstream main
git rebase upstream/main
# 如有冲突 → 按 8.3 流程解决
git push --force-with-lease origin feat/xxxpush 完成后刷新 PR 页面,红色警告自动消失 ✅
8.5 rebase 冲突时救急三招
git rebase --abort # 🏃 逃跑:放弃 rebase,回到起点
git rebase --skip # ⏭️ 跳过:丢弃当前这个冲突的 commit(慎用!会丢代码)
git rebase --continue # ▶️ 继续:解决完冲突后告诉 Git 往前走口诀:解决 →
add→continue,搞不定就abort重来。
8.6 减少冲突的最佳实践
| 实践 | 效果 |
|---|---|
| 开分支前先同步 upstream main | 基线最新,冲突概率最低 |
| 小步提交、频繁 rebase | 别憋一个月再 rebase,一次冲突太多会很痛苦 |
| 和队友沟通改动范围 | 避免两人同时改同一个文件的同一块 |
| 单次 PR 聚焦一件事 | 改动文件越少,冲突概率越低 |
🎯 核心心态:冲突不是 bug,是协作的常态。Git 把风险控制在"文件行级",让你从容决定,比直接覆盖要好一万倍。
9. 关键原则速览
| # | 原则 | 说明 |
|---|---|---|
| 1 | 别在 main 上开发 | 永远 git checkout -b feat/xxx |
| 2 | 先同步再开分支 | 开分支前 fetch upstream && merge --ff-only |
| 3 | PR 前 squash | 多次提交压成一次,reviewer 看了舒服 |
| 4 | 安全 force push | 永远 --force-with-lease,不用 --force |
| 5 | 上游有新提交 | 只要没冲突,你的 PR 不用改 |
| 6 | 冲突三步走 | 打开文件 → 删除标记 → add → continue |
全流程速览图
一句话总结: Fork → 同步上游 → 建分支 → 开发 → squash → force push → 提 PR → 上游更新不操心。