mcpField
S
sync-fork

sync-fork

github.com/shhac开发435 次安装3 周前更新
代码仓库

关于 sync-fork

sync-fork 是用于同步 fork 与上游仓库的 Skill,同时保留 fork 独有补丁。它会分析分支、创建备份并指导变基和重新合并,在破坏性操作前要求确认。

主要功能

  1. 01识别共享分支与 fork 独有分支
  2. 02检查分歧并生成同步计划
  3. 03创建恢复用备份分支
  4. 04变基并重新合并本地补丁
  5. 05显示远程 URL 前保护凭据

适用场景

  1. 01上游更新后同步 fork
  2. 02同步时保留本地分支
  3. 03重置分支前审阅同步计划

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/shhac/skills --skill sync-fork

安装后在 Claude Code 中输入/sync-fork确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

edge-cases/
history-rewrite.md
merge-commits-in-branches.md
orphaned-branches.md
partial-promotion.md
upstream-reverts.md
examples/
deep-chain.md
fan-out.md
independent-branches.md
linear-chain.md
multi-target.md
promoted-branch.md
scripts/
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:准备检查
  1. 检查是否有上次运行中断的情况。 查找匹配 sync-fork/* 的分支。如果找到,说明上次同步已中断。向用户展示现存的备份分支,并询问:从备份恢复,还是清理(git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -D)后重新开始?

  2. 保存当前分支。 记录 git symbolic-ref --short HEAD(或分离状态下的提交),以便最后恢复。

  3. 保护有未提交更改的工作区。 运行 git status --porcelain。如果存在未提交的更改,请将其暂存:

    git stash push -m "sync-fork: uncommitted changes"
    

    这些更改将在最后恢复。暂存内容与分支无关,因此在后续的检出/重置操作中会保留。

  4. 识别远程仓库。 运行 git remote -v,并根据上述“用法”一节的规则确定哪个远程仓库是 Fork,哪个是上游。如果存在任何歧义,请向用户确认。

阶段 1:评估分歧
  1. 获取两个远程仓库。

  2. 检查是否无需操作。 对每个共享分支,检查 git merge-base --is-ancestor <upstream>/<branch> <fork>/<branch>。如果对于所有共享分支,上游都已是 fork 的祖先,则同步无需操作——上游没有前进。告知用户并停止。

  3. 对分支进行分类。 运行辅助脚本:

    python3 <script> --fork <fork> --upstream <upstream> classify
    

    该脚本会识别共享分支、仅存在于 fork 的分支(已合并、已提升、部分提升、活跃),并以精简格式报告。

    • ⚠️ 如果出现 fork-only-partial 条目 → 阅读 edge-cases/partial-promotion.md。
    • ⚠️ 如果出现 fork-only-promoted 条目 → 阅读 examples/promoted-branch.md,了解完整的处理策略。
  4. 检查分歧。 运行辅助脚本:

    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,然后再继续。
  5. 试运行计划。 运行:

    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. 重置共享分支

对每个共享分支(顺序为:先默认分支,再处理其他分支):

  1. 在本地检出该分支。(如果不存在本地跟踪分支,git checkout 会自动创建一个。)
  2. 执行 git reset --hard <upstream>/<branch>,使其与上游保持一致。
  3. 执行 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):

  1. 检查合并提交: git log --merges sync-fork/pre-rebase/<parent>..<branch>

    • ⚠️ 如果发现合并提交 → 在对此分支变基之前,阅读 edge-cases/merge-commits-in-branches.md。
  2. 变基:

    • 如果父分支是共享分支: 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。
    • 如果变基发生冲突,请解决冲突,并向用户说明你解决了什么以及原因。
  3. 检查结果是否为空。 如果所有提交都被丢弃(分支现在指向与其父分支相同的提交),请警告用户:“分支 <branch> 似乎已被上游完全吸收。请考虑删除它。”

  4. 运行 git push <fork> <branch> --force-with-lease 更新 fork。

阶段 4:重新合并到共享分支

fork 的共享分支以“上游 + 本地补丁”的形式维护。本阶段会在顶部重新应用合并提交,因此 fork/main = upstream/main + fork 独有的工作。

针对每个有仅存在于 fork 的分支指向它的共享分支(使用图表输出中的 target 映射):

  1. 在本地检出共享分支。
  2. 按拓扑顺序(父分支先于子分支)使用 --no-ff 合并每个经过变基的、仅存在于 fork 的分支。使用以下消息格式:
    git merge --no-ff <branch> -m "sync-fork: merge <branch> into <shared-branch>"
    
    • 由于分支刚刚完成变基,这些合并应该不会产生冲突。如果发生冲突,请解决冲突,并向用户说明你解决了什么。
    • 合并父分支(例如 A)后,再合并其子分支(例如 B)只会引入 B 独有的提交。
  3. 运行 git push <fork> <branch> --force-with-lease 更新 fork。
阶段 5:清理
  1. 删除已完全合并到上游的远程分支(git push <fork> --delete <branch>)。
  2. 重置共享分支时,在删除备份前,使用 git range-diff <old-base>..sync-fork/pre-reset/<branch> <upstream>/<branch>..<branch> 比较旧备份与新的共享分支,以验证重新应用的补丁堆栈。
  3. 删除所有备份分支:git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -D
  4. 恢复阶段 0 中保存的原始分支:git checkout <saved-branch>。
  5. 如果阶段 0 中有更改被暂存,请恢复它们:git stash pop。
  6. 列出可以清理的所有本地跟踪分支。
  7. 向用户展示最终摘要,说明同步、变基、合并和删除了哪些内容,以及还剩下哪些分支。
规则
  • 重置或强制推送前,始终先与用户确认计划。
  • 推送已重置的分支时,使用 --force-with-lease,绝不使用 --force。
  • 保留仅存在于本地的工作——目的是在与上游当前状态保持一致的同时,保留上游尚未接受的分支。
  • 不要删除包含未合并工作的分支——仅清理内容已存在于上游的分支(即使由于 squash 合并导致提交 SHA 不同)。
  • 不要操作与 fork 同步无关的分支——不要动那些没有指向共享分支的功能分支。
  • 备份分支使用 sync-fork/ 前缀——这些是临时分支,会在最后清理。如果同步中断,这些分支会保留,以便恢复。使用 git for-each-ref --format='%(refname:short)' 'refs/heads/sync-fork/' | xargs git branch -D 手动清理。

评论

支持 Markdown