关于 sync-fork
sync-fork 是用于同步 fork 与上游仓库的 Skill,同时保留 fork 独有补丁。它会分析分支、创建备份并指导变基和重新合并,在破坏性操作前要求确认。
主要功能
- 01识别共享分支与 fork 独有分支
- 02检查分歧并生成同步计划
- 03创建恢复用备份分支
- 04变基并重新合并本地补丁
- 05显示远程 URL 前保护凭据
适用场景
- 01上游更新后同步 fork
- 02同步时保留本地分支
- 03重置分支前审阅同步计划
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ shhac/ skills --skill sync-fork安装后在 Claude Code 中输入/sync-fork确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
edge-cases/history-rewrite.mdmerge-commits-in-branches.mdorphaned-branches.mdpartial-promotion.mdupstream-reverts.mdexamples/deep-chain.mdfan-out.mdindependent-branches.mdlinear-chain.mdmulti-target.mdpromoted-branch.mdscripts/sync_fork.py详细介绍
同步 Fork
将 Fork 仓库与其上游远程仓库同步。Fork 的共享分支(例如 main)会维护为与上游等效,并在其上重新合并本地补丁——即“带补丁的上游”模式。每次同步都会重置到上游、对仅存在于 Fork 中的分支执行变基,然后重新合并这些分支。
如果上游没有更新(即 upstream/main 已经是 fork/main 的祖先),则同步不会执行任何操作。
用法
/sync-fork [<fork-remote> <upstream-remote>]
- 不带参数 — 如果恰好存在两个远程仓库,则自动检测。如果多于两个,则要求用户指定。
- 带一个参数 — 提供的名称存在歧义。使用
git remote get-url <name>查找其 URL,并询问用户:“<name>指向<url>——这是你的 Fork,还是你 Fork 所自的仓库?” 然后询问另一个远程仓库的名称。 - 带两个参数 — 第一个是 Fork 远程仓库,第二个是上游远程仓库。
自动检测时,如果恰好存在两个远程仓库,请使用启发式方法猜测哪个是 Fork,哪个是上游(例如,名为 origin 的远程仓库更可能是 Fork;URL 所属组织与另一个不同的远程仓库更可能是上游)。告知用户你的猜测并请其确认。
⚠️ 凭据安全: 远程 URL 可能包含内嵌凭据(例如:https://user:[email protected]/...)。向用户显示任何 URL 之前,请隐藏用户信息部分:将 user:token@ 替换为 ***@。绝不要输出 git remote -v 或 git remote get-url 中的原始凭据。
图例
用于与此技能相邻的参考文件:
─── flow (left-to-right = time)
├── X branch X forks here
└── X branch X forks here (last branch at this point)
──┘ merge point (branch merges into line above)
● notable state on the line
辅助脚本
此技能包含 scripts/sync_fork.py——一个用于分支分析的确定性 Python 辅助工具。它负责分类、分歧检查和依赖关系图构建,确保这些操作的结果一致。需要 Python 3.9+(仅使用标准库)。
根据此技能文件的位置查找脚本。按以下方式调用:
python3 <skill-dir>/scripts/sync_fork.py <subcommand> [options]
子命令:classify、divergence、graph、plan。运行时添加 --help 可查看完整用法。默认输出为精简的键值格式(便于大模型使用)。添加 --json 可输出结构化数据。
指令
你正在将一个 Fork 与其上游同步。请按顺序执行以下阶段。
此技能采用渐进式发现——下方主流程涵盖常见情况。遇到特定情况(拓扑结构或边缘情况)时,系统会指引你在对应阶段阅读相关参考文件。不要预先阅读所有参考文件。 仅在触发时阅读。
参考文件位于此技能相邻的两个目录中:
examples/— 分支拓扑图和变基策略edge-cases/— 特殊情况的处理方法
阶段 0:准备检查
-
检查是否有上次运行中断的情况。 查找匹配
sync-fork/*的分支。如果找到,说明上次同步已中断。向用户展示现存的备份分支,并询问:从备份恢复,还是清理(git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -D)后重新开始? -
保存当前分支。 记录
git symbolic-ref --short HEAD(或分离状态下的提交),以便最后恢复。 -
保护有未提交更改的工作区。 运行
git status --porcelain。如果存在未提交的更改,请将其暂存:git stash push -m "sync-fork: uncommitted changes"这些更改将在最后恢复。暂存内容与分支无关,因此在后续的检出/重置操作中会保留。
-
识别远程仓库。 运行
git remote -v,并根据上述“用法”一节的规则确定哪个远程仓库是 Fork,哪个是上游。如果存在任何歧义,请向用户确认。
阶段 1:评估分歧
-
获取两个远程仓库。
-
检查是否无需操作。 对每个共享分支,检查
git merge-base --is-ancestor <upstream>/<branch> <fork>/<branch>。如果对于所有共享分支,上游都已是 fork 的祖先,则同步无需操作——上游没有前进。告知用户并停止。 -
对分支进行分类。 运行辅助脚本:
python3 <script> --fork <fork> --upstream <upstream> classify该脚本会识别共享分支、仅存在于 fork 的分支(已合并、已提升、部分提升、活跃),并以精简格式报告。
- ⚠️ 如果出现
fork-only-partial条目 → 阅读edge-cases/partial-promotion.md。 - ⚠️ 如果出现
fork-only-promoted条目 → 阅读examples/promoted-branch.md,了解完整的处理策略。
- ⚠️ 如果出现
-
检查分歧。 运行辅助脚本:
python3 <script> --fork <fork> --upstream <upstream> divergence该脚本会显示各分支的提交数和标记。
- ⚠️ 如果任何分支显示
rewrite=true且fork_merges_only=false→ 停止并阅读edge-cases/history-rewrite.md,然后再继续。这可能是上游确实重写了历史,也可能是预期中的已修补上游分歧,即仅存在于 fork 的分支提交已合并到 fork 的共享分支中。 - ⚠️ 如果任何分支显示
reverts>0→ 停止并阅读edge-cases/upstream-reverts.md,然后再继续。
- ⚠️ 如果任何分支显示
-
试运行计划。 运行:
python3 <script> --fork <fork> --upstream <upstream> plan向用户展示完整计划。等待确认后再继续。
阶段 2:创建所有备份并重置共享分支
在执行任何破坏性操作之前,预先创建所有备份(包括重置前和变基前备份)。这样可以将恢复点集中在同一步骤中。
2a. 创建所有备份分支
# Pre-reset backups for shared branches
for branch in <shared_branches>; do
git branch sync-fork/pre-reset/$branch <fork>/$branch
done
# Pre-rebase backups for fork-only active branches
for branch in <fork_only_active>; do
git branch sync-fork/pre-rebase/$branch <fork>/$branch
done
这些备份有三种用途:
- 出现问题时用作恢复点
- 用作
--onto变基的旧引用存储(sync-fork/pre-rebase/<parent>的顶端提交就是旧引用) - 为依赖图提供正确的祖先引用(即将重置共享分支)
2b. 重置共享分支
对每个共享分支(顺序为:先默认分支,再处理其他分支):
- 在本地检出该分支。(如果不存在本地跟踪分支,
git checkout会自动创建一个。) - 执行
git reset --hard <upstream>/<branch>,使其与上游保持一致。 - 执行
git push <fork> <branch> --force-with-lease,更新 fork。
阶段 3:对仅存在于 fork 的分支进行变基
3a. 构建依赖图
运行辅助脚本以构建依赖图。该脚本使用 sync-fork/pre-reset/* 备份引用检查共享分支的祖先关系(因为共享分支现在指向上游):
python3 <script> --fork <fork> --upstream <upstream> graph \
--branches <comma-separated-fork-only-active> \
--shared <comma-separated-shared> \
--backup-prefix sync-fork/pre-reset
输出包含:父级映射、拓扑顺序、孤立分支和合并目标。
- ⚠️ 如果
orphaned不是(none)→ 阅读edge-cases/orphaned-branches.md。 - 如果某个分支没有
parent,但有target,说明该分支已包含在重置前的共享分支中。将其视为一个以该共享分支为目标的独立分支,而不是孤立分支。
3b. 必须阅读匹配的拓扑参考
继续之前,必须阅读适用的示例文件。 将刚构建的依赖图与正确的拓扑匹配,并立即阅读对应文件:
| 检测到的拓扑 | 阅读此文件 |
|---|---|
| 所有分支都直接基于共享分支,分支间没有依赖关系 | examples/independent-branches.md |
| 一个分支依赖另一个分支(B 基于 A) | examples/linear-chain.md |
| 多个分支依赖同一个父级(B 和 C 都基于 A) | examples/fan-out.md |
| 三个或更多分支构成链(A → B → C) | examples/deep-chain.md |
| 分支基于不同的共享分支 | examples/multi-target.md |
| 混合情况(上述情况的组合) | 阅读所有适用文件 |
如果图中存在仅属于 fork 的分支之间的依赖关系,继续之前必须理解相关文件中的 --onto 变基策略。操作有误会导致提交重复和误报冲突。
3c. 按拓扑顺序变基
针对每个仅存在于 fork 的分支(先处理父分支,最后处理子分支——使用图表输出中的 order):
-
检查合并提交:
git log --merges sync-fork/pre-rebase/<parent>..<branch>- ⚠️ 如果发现合并提交 → 在对此分支变基之前,阅读
edge-cases/merge-commits-in-branches.md。
- ⚠️ 如果发现合并提交 → 在对此分支变基之前,阅读
-
变基:
- 如果父分支是共享分支:
git rebase --empty=drop <shared-branch> <branch> - 如果父分支是另一个仅存在于 fork 的分支:
git rebase --empty=drop --onto <parent> sync-fork/pre-rebase/<parent> <branch> - 如果没有父分支,但
target=<shared-branch>:git rebase --empty=drop <shared-branch> <branch> --empty=drop会自动丢弃已存在于上游的提交。如果 Git < 2.26,请省略此标志,并在出现提示时使用git rebase --skip。- 如果变基发生冲突,请解决冲突,并向用户说明你解决了什么以及原因。
- 如果父分支是共享分支:
-
检查结果是否为空。 如果所有提交都被丢弃(分支现在指向与其父分支相同的提交),请警告用户:“分支
<branch>似乎已被上游完全吸收。请考虑删除它。” -
运行
git push <fork> <branch> --force-with-lease更新 fork。
阶段 4:重新合并到共享分支
fork 的共享分支以“上游 + 本地补丁”的形式维护。本阶段会在顶部重新应用合并提交,因此 fork/main = upstream/main + fork 独有的工作。
针对每个有仅存在于 fork 的分支指向它的共享分支(使用图表输出中的 target 映射):
- 在本地检出共享分支。
- 按拓扑顺序(父分支先于子分支)使用
--no-ff合并每个经过变基的、仅存在于 fork 的分支。使用以下消息格式:git merge --no-ff <branch> -m "sync-fork: merge <branch> into <shared-branch>"- 由于分支刚刚完成变基,这些合并应该不会产生冲突。如果发生冲突,请解决冲突,并向用户说明你解决了什么。
- 合并父分支(例如 A)后,再合并其子分支(例如 B)只会引入 B 独有的提交。
- 运行
git push <fork> <branch> --force-with-lease更新 fork。
阶段 5:清理
- 删除已完全合并到上游的远程分支(
git push <fork> --delete <branch>)。 - 重置共享分支时,在删除备份前,使用
git range-diff <old-base>..sync-fork/pre-reset/<branch> <upstream>/<branch>..<branch>比较旧备份与新的共享分支,以验证重新应用的补丁堆栈。 - 删除所有备份分支:
git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -D - 恢复阶段 0 中保存的原始分支:
git checkout <saved-branch>。 - 如果阶段 0 中有更改被暂存,请恢复它们:
git stash pop。 - 列出可以清理的所有本地跟踪分支。
- 向用户展示最终摘要,说明同步、变基、合并和删除了哪些内容,以及还剩下哪些分支。
规则
- 重置或强制推送前,始终先与用户确认计划。
- 推送已重置的分支时,使用
--force-with-lease,绝不使用--force。 - 保留仅存在于本地的工作——目的是在与上游当前状态保持一致的同时,保留上游尚未接受的分支。
- 不要删除包含未合并工作的分支——仅清理内容已存在于上游的分支(即使由于 squash 合并导致提交 SHA 不同)。
- 不要操作与 fork 同步无关的分支——不要动那些没有指向共享分支的功能分支。
- 备份分支使用
sync-fork/前缀——这些是临时分支,会在最后清理。如果同步中断,这些分支会保留,以便恢复。使用git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -D手动清理。