前置知识: GitHub

Fork 工作流:给没有写权限的仓库提交你的第一个 PR

7 min中级

以「给一个开源项目修复文档错误」这个真实任务切入,走完 fork、双远程配置、分支修改、PR、同步上游、冲突解决的完整链路,讲清 Fork 与 Branch 的本质区别和 force-with-lease 这条安全红线。

知识点地图

  • 知识类别:Fork 工作流——没有写权限时向任意仓库贡献改动的标准路径,GitHub 开源协作的基本功。
  • 解决什么问题:git push 被拒(Permission to ... denied)之后怎么办;如何在不动原仓库的前提下维护自己的长期副本;以及你的副本如何持续跟上游保持同步。
  • 什么时候用到:给开源项目提第一个 PR;社团/班级仓库你有投稿权但没有推送权;接手一个外部项目做本地实验。与分支保护(分支模型与分支保护规则)的区别:那条链路管「有写权限的人怎么进主干」,本文管「没写权限的人怎么递改动」。

前置知识

学习目标

读完全文你将能够:

  1. 说出 Fork 和 Branch 的本质区别,判断一个协作场景该用哪个;
  2. 配好 origin 与 upstream 双远程,让你的副本始终跟得上上游;
  3. 走完「fork 到合并」全流程,给任意开源项目提交第一个 PR;
  4. PR 提示 This branch has conflicts 时,知道该敲哪几条命令。

1. 问题:仓库不是你的,怎么改?

真实场景:你在用一个开源工具,发现 README 里有个明显的配置错误,顺手就能修。但这个仓库在别人账号下,你没有写权限——git push 会被直接拒绝:

text
ERROR: Permission to someone/cool-tool.git denied to your-name.

解决思路不是「找管理员要权限」,而是 Fork 工作流:在 GitHub 云端把仓库复制一份到你账号下,这份副本你拥有全部权限,改完之后向原作者发起合并申请(PR)。整条链路涉及三个仓库、一个申请:

1 Fork:网页点一下

2 Clone 到本地

3 分支上修改提交,push 回 origin

4 Pull Request 申请合回

原仓库 upstream(别人的,只读)

你的云端副本 origin(你的账号下)

本地仓库(你的电脑)

1 Fork:网页点一下

2 Clone 到本地

3 分支上修改提交,push 回 origin

4 Pull Request 申请合回

原仓库 upstream(别人的,只读)

你的云端副本 origin(你的账号下)

本地仓库(你的电脑)

mermaid diagram

先分清两个常被混淆的概念:

对比维度ForkBranch
位置独立的新仓库(你的账号下)同一个仓库内部
权限要求无需原仓库任何权限需要仓库写权限
适用场景开源贡献、无写权限协作团队内部开发

一句话:没写权限用 Fork,有写权限用 Branch。FANDEX 这类自己维护的仓库走分支 + PR(见 180 篇);给别人的项目贡献代码,走本文的 Fork 流。

2. 动手:从 fork 到 PR 的完整六步

以下以给 someone/cool-tool 修文档为例,你自己的用户名替换 your-name。

第一步:Fork

网页上打开仓库,右上角点 Fork,确认归属后点 Create fork,几秒后你就有了 github.com/your-name/cool-tool。

命令行一条也行:

bash
gh repo fork someone/cool-tool --clone   # fork 并自动克隆到本地

第二步:配置双远程

bash
git clone https://github.com/your-name/cool-tool.git
cd cool-tool
git remote add upstream https://github.com/someone/cool-tool.git
git remote -v
# origin    https://github.com/your-name/cool-tool.git (fetch/push)
# upstream  https://github.com/someone/cool-tool.git (fetch/push)

分工:origin 是你唯一能 push 的地方;upstream 只用来 fetch,让副本跟上原仓库。Fork 出来的副本是静止的,不会自动同步上游。

第三步:同步 main,再开功能分支

bash
git fetch upstream
git checkout main
git merge --ff-only upstream/main   # 副本落后时快进对齐
git push origin main                # 同步结果推回自己的副本

git checkout -b docs/fix-install-flag   # 基于最新 main 开分支

三条分支纪律:永远不在 main 上直接改;分支名用「类型/描述」格式;一次 PR 只做一个主题。基于过时 main 开分支,是后面 PR 冲突的最大来源。

第四步:改、提交、推送

bash
# ……修正 README 的参数说明……
git add README.md
git commit -m "docs: 修正 install 命令的 --global 参数说明"
git push origin docs/fix-install-flag

第五步:发起 PR

推完代码,仓库页面会出现 Compare & pull request 横幅;或者直接:

bash
gh pr create --repo someone/cool-tool --fill

注意方向:base 是原仓库的 main,compare 是你副本的功能分支。描述写清「改了什么、为什么、怎么验证」,修复了某个 Issue 就写 Fixes #123,合并时会自动关闭它:

markdown
## 改动内容
修正 README 中 install 命令的参数说明。

## 验证方式
- 按新说明执行安装命令成功
- 本地渲染无 Markdown 语法错误

Fixes #102

第六步:合并后清理

bash
git fetch upstream
git checkout main
git merge --ff-only upstream/main    # 拿回你被合并的改动
git branch -d docs/fix-install-flag
git push origin --delete docs/fix-install-flag

3. 讲原理:审查反馈来了怎么办,PR 怎么保持新鲜

PR 不是提交完就结束。维护者的常见反馈和应对:

审查反馈应对
「请补充测试」加代码、提交、git push(PR 自动更新,无需重开)
「风格不符」按项目 CONTRIBUTING 改完重新 push
「和 main 冲突了」见下一节
「请 rebase 到最新」git rebase upstream/main 后 git push --force-with-lease

PR 的本质是「分支头指针的比较」:你往同一分支推新提交,PR 自动纳入。所以回应审查永远不需要重新创建 PR。

4. 冲突:This branch has conflicts 怎么解

冲突的本质是你和别人改了同一段代码,而上游先合并了。在本地解:

bash
git fetch upstream
git checkout docs/fix-install-flag
git rebase upstream/main          # 或 merge,选团队惯例
# 逐个打开冲突文件,处理 <<<<<<< / ======= / >>>>>>> 标记
git add README.md
git rebase --continue
git push origin docs/fix-install-flag --force-with-lease

rebase 过的分支历史变了,必须强推。安全红线:用 --force-with-lease,不用裸 --force——前者只在远程分支没被别人动过时才覆盖,能避免误伤。目标是你自己 Fork 里的分支,影响面可控,但习惯要现在养成。

不想记命令的话,Fork 仓库主页有 Sync fork → Update branch 按钮,能处理简单同步;复杂冲突仍要回命令行。

5. 坑点与自检

现象原因处理
fatal: 'upstream' does not appear to be a git repository克隆后没加 upstream补 git remote add upstream
往原仓库 push 被拒你没有写权限推 origin,走 PR
PR 混入大量无关提交基于过时 main 开的分支先同步 main,重开干净分支
rejected ... non-fast-forward远程分支与本地分叉git pull --rebase origin 分支名 再推
裸 --force 覆盖了别人提交强推无保护改用 --force-with-lease
合并后 Issue 还开着描述没写 Fixes #123PR 描述补上关键字
用了别人的代码但项目要求不同许可证Fork 前没看许可先读 LICENSE,见 开源许可证选择

自检三问:

  1. 你的 git remote -v 里 upstream 指向哪?
  2. 每次开工前,你的第一个命令是不是 git fetch upstream?
  3. 强推时你的手指是落在 --force-with-lease 上吗?

6. 练习

  1. 找一个你用过的开源项目里的小问题(文档错字即可),完整走一遍六步,直到 PR 被合并或关闭。
  2. 故意在一个 fork 里落后上游 20 个提交,分别用命令行和 Sync fork 按钮同步一次,对比体验。
  3. 制造一次冲突:改一个上游最近也改过的文件,触发 This branch has conflicts,按第四节流程解开。

动手实践:本周任务——给社团仓库递第一份投稿

社团最经典的第一次开源协作演练(来自真实的社团活动设计):注册 GitHub -> Fork 社团笔记仓库 MyNotebook -> 提交你的第一个 PR -> 截图打卡。四步全做完,Fork 工作流就算出师了。

任务:以「本周任务打卡」为目标完成一次完整投稿。在仓库的成员笔记目录下新建一篇 <你的名字>-week1.md,写 100 字以上本周学习记录,走完 fork 到 PR 的全流程,最后把 PR 链接截图发到社团群里打卡。

提示:

  • fork 前 git remote -v 检查双远程(第 2 节第二步),没有 upstream 就补上;
  • 分支名用 feat/<你的名字>-week1,避免与他人撞名;
  • PR 描述照第 2 节第五步的模板写,标题一句话说清改了什么;
  • 「打卡截图」截 PR 页面顶部:分支名、Open 状态、描述三样齐全才算合格证据。

参考实现(先自己写,再对照):

bash
# 1. Fork:网页上打开 club-org/MyNotebook,点 Fork(复制到个人账号)
# 2. 双远程
git clone https://github.com/your-name/MyNotebook.git
cd MyNotebook
git remote add upstream https://github.com/club-org/MyNotebook.git
# 3. 开分支、写笔记、提交
git fetch upstream
git checkout -b feat/zhangsan-week1 upstream/main
echo "# 第一周学习记录:装好了 Git,跑通了第一次提交" > notes/zhangsan-week1.md
git add notes/zhangsan-week1.md
git commit -m "docs: 添加张三第一周学习记录"
git push origin feat/zhangsan-week1
# 4. 网页上从 feat/zhangsan-week1 向 club-org/MyNotebook:main 发起 PR,
#    描述写「新增成员笔记 + 说明改动原因」,提交后截图打卡

对照要点:这套打卡流程把六步压缩成了「能被别人验证的最小闭环」——截图 之所以是证据,是因为 PR 页面自带分支名与状态元信息,造不了假。第一次走 通之后,给任何开源项目投稿都是同一套动作换个仓库地址。

下一步