RakullApp 是唯一真源的 monorepo。各微服务还留着自己的独立仓库,但都只是只读的下游镜像,靠本页讲的这套单向同步来维护。普通贡献者不用管这些;这篇是写给维护者的,把同步怎么跑、令牌怎么配、镜像第一次怎么初始化都讲清楚。

工作原理

对每一条「目录 → 镜像」的映射,同步做的都是同一件事:
git subtree split --prefix="<目录>" -b <临时分>   # 把该子目录的历史拆成一条独立分支
git push <镜像 URL> <临时分>:main                  # 首次初始化、或历史分叉时加 --force
git branch -D <临时分>
主仓 main 是一条线性历史,所以第一次初始化之后,后面每次推送都能快进更新镜像的 main。同步只在改动合并进主仓 main 后才触发,而且只碰本次改动涉及的 prefix(连同它上面每一级的祖先目录)。

脚本:scripts/sync_to_mirrors.sh

那张映射表(12 条)就写在脚本里,脚本和工作流都以它为准。
模式作用
--all同步全部镜像(第一次初始化时用)
--changed BEFORE..AFTER只同步这个提交区间内有改动的 prefix
<prefix> [<prefix>…]只同步指定的目录
--force强制推送(第一次初始化、或历史分叉后必须加)
--dry-run只打印要执行的命令,不真的推
# 先空跑一遍全部镜像,看看会做什么(不推送)
bash scripts/sync_to_mirrors.sh --all --dry-run

# 只同步某一个服务
bash scripts/sync_to_mirrors.sh rakull_server/data_server
远端 URL 和鉴权(令牌不会出现在命令行或 git 的输出里):
  • CI 里:设了 MIRROR_SYNC_TOKEN,就走 https + Authorization 头推送。
  • 本地:不设令牌,就走 SSH(git@github.com:AgentEndeavour/<repo>.git),用你自己的密钥推。
脚本依赖 git subtree(git 自带)。

CI 工作流:.github/workflows/mirror-sync.yml

on:
  push:
    branches: [main]      # 每次 PR 合并都会触发
  workflow_dispatch:
    inputs:
      all:   { type: boolean, default: false }   # 第一次初始化时勾
      force: { type: boolean, default: false }   # 第一次初始化 / 历史分叉时勾
concurrency:
  group: mirror-sync      # 保证两次同步不会同时跑、互相打架
  cancel-in-progress: false
  • push 到 main:拿 github.event.before..github.sha 算出改动区间,只同步被影响的镜像;如果是仓库的第一次 push(区间是全零),就自动退回成 --all
  • 手动触发workflow_dispatch):用来做初始化,可以勾上 allforce
  • checkout 用了 fetch-depth: 0(subtree split 需要完整历史);MIRROR_SYNC_TOKEN 从 secret 注入。

配置 MIRROR_SYNC_TOKEN(维护者,一次性)

工作流需要一个对 12 个镜像仓库有 contents:write 权限的凭证,放在仓库 secret MIRROR_SYNC_TOKEN 里。两种做法二选一:
  • 细粒度 PAT:对 AgentEndeavour 下这 12 个镜像仓库授予 Contents: Read and write
  • GitHub App:装到这些仓库上、授予 contents:write,再用它签发 token。
到 RakullApp 仓库的 Settings → Secrets and variables → Actions,新建一个叫 MIRROR_SYNC_TOKEN 的 secret。
这个 secret 得人工创建,AI / 自动化代不了劳。没配的话,mirror-sync 会直接报错退出,不会闷声失败。

第一次初始化镜像(一次性)

扁平化那个 PR 合并进 main 之后,做一次初始化:把各镜像和 monorepo 对齐,建立起 subtree 的同步关系。
1

先给每个镜像打个安全标签

在每个镜像仓库现有的 main 上打个 pre-monorepo-<日期> 标签,把旧历史留住(第一次同步会强制覆盖镜像的 main)。
2

跑 workflow_dispatch,勾上 all + force

到 RakullApp 的 Actions 里手动跑 mirror-sync,勾 all=trueforce=true。第一次推送会强制刷新各镜像的 main;之后每次合并就只快进更新被改到的那几个镜像了。
如果镜像 main 开了分支保护,得允许这个令牌 / App 往里推(或者临时把保护放宽)。初始化之前,先把镜像仓库上还没处理的 PR 收拾干净(合并或关掉)——从这一步起,镜像就是只读的下游了。

在本地手动同步

有需要的话,本地也能跑(用你自己的 SSH 密钥):
# 第一次初始化(强制推送)
bash scripts/sync_to_mirrors.sh --all --force

# 只把某个镜像重推一遍
bash scripts/sync_to_mirrors.sh docs

排障

镜像 main 和 monorepo 这边的 subtree 历史对不上了(多半是还没初始化,或者有人直接改了镜像)。对这个 prefix 加 --force 重跑一遍就行;旧提交还能靠 pre-monorepo-* 标签找回来。
secret 还没配,或者令牌没有 contents:write。照上面那节建好、授好权即可。
放行这个令牌 / App 往 main 推,或者临时放宽保护,再做初始化。
跑的环境里没有 git subtree(contrib 组件)。CI(ubuntu)上 git 自带;本地装个完整版的 git 就有了。

相关

开发与贡献流程(含仓库模型)

monorepo 结构、目录 → 镜像映射,以及分支 → PR → 合并到 main → 自动同步的完整循环。

代码规范与提交检查

pre-commit + Ruff / dart 的安装与日常使用。