9.3 KiB
协作与开发工作流
本仓库是 xai-org/grok-build 的 patch 化 fork。fork 多了哪些能力见 README.md;这里只讲怎么改、怎么同步、怎么不把自己的活弄丢。
以 CraftBukkit / Spigot 的模型,在不接受外部 PR 的上游之上维护本地改动。
为什么是 patch,而不是 fork 后直接改
上游有两个决定性特征:
- 它是 xAI 内部 monorepo 的周期性单向导出。历史是一串线性的
Synced from monorepo压扁提交,根目录SOURCE_REV记录对应的内部 commit。上游随时会把整棵树重新投放一次。 - 上游
CONTRIBUTING.md明确写着不接受任何外部 PR 或补丁。
也就是说:改动推不回去,而上游会不断整体覆盖。直接在 fork 上改,每次同步都要打一场 merge 混战,而且没人说得清「我们到底改了什么」。
这正是 CraftBukkit 面对 Mojang 的处境。解法也一样——把改动本身作为版本库里的唯一真相:
patches/是源码,work/是编译产物。
目录结构
newgrok/ ← 你提交的仓库
├── patches/ ★ 唯一真相:0001-*.patch,一个补丁一件事
├── scripts/ 工作流脚本
├── upstream.rev 钉住的上游 commit
├── Makefile
├── upstream/ 派生:上游的纯净 clone (.gitignore)
└── work/ 派生:upstream@rev + patches(.gitignore)
对照 Spigot:
| 这里 | Spigot / BuildTools |
|---|---|
upstream/ |
CraftBukkit 的 clone(纯净上游) |
work/ |
打完补丁的工作目录 |
patches/ |
CraftBukkit-Patches/ |
scripts/apply-patches.sh |
applyPatches.sh |
scripts/rebuild-patches.sh |
rebuildPatches.sh |
upstream.rev |
versions/*.json 里的版本钉 |
upstream/ 和 work/ 都不进 git——它们任何时候都能从 upstream.rev + patches/
一字不差地重建。
两条规则,违反任意一条都会默默丢掉工作成果:
- Rust 源码改动写在
work/,不要手改patches/。 补丁文件是生成物,手改之后下次就合不上。 make rebuild是离开work/的唯一出口。make apply会git reset --hard+git clean -fdx。没 commit 并且没 export 的改动,下次 apply 就没了。
日常循环
改代码永远在 work/ 里,一个提交 = 一个补丁:
make apply # 拿到干净的、打好补丁的树
$EDITOR work/crates/codegen/.../foo.rs # 改
cd work && git add -A && git commit -m '...' # 提交(提交信息就是补丁标题)
cd .. && make rebuild # 写回 patches/
git add patches/ && git commit -m '...' # 提交到 fork 仓库
make rebuild 是唯一的出口。 没跑过它的改动,下次 make apply 就没了。
修改已有的补丁,用普通的 git 手段就行——work/ 是个正常的 git 仓库,base 标签是上游和我们的分界:
cd work
git rebase -i base # 改写、合并、重排、删除任意补丁
cd .. && make rebuild # patches/ 会整体重新生成
随时看当前状态:
make status
跟进上游同步
make update-dry # 只看会拉进来什么,不动任何东西
make update # 重新钉 upstream.rev,并把补丁前移到新基线
make update 会:
git fetch上游,列出upstream.rev..origin/main之间的新提交和SOURCE_REV变化- 把
upstream.rev更新为最新 - 在新基线上重放
patches/ - 干净通过则自动
make rebuild,让补丁上下文对齐新代码
冲突时脚本会停下并给出解决步骤——和处理 rebase 冲突完全一样:
cd work
git status # 看冲突文件
$EDITOR <冲突文件> # 消掉冲突标记
git add -A
git am --continue # 或 git am --skip / git am --abort
cd .. && make rebuild # 把前移后的结果写回去
补丁数量多的时候,冲突是这个模型的固有成本,也是它的价值所在:冲突精确地指出上游改动 撞上了你的哪一处改动,而不是让它悄悄消失。
全部命令
从仓库根目录跑 make(make help 列出全部目标)。脚本自己把 ~/.cargo/bin 加进 PATH,交互 shell 里不必先有 cargo。
| 命令 | 作用 |
|---|---|
make setup |
安装工具链(rustup / dotslash / protoc) |
make apply |
upstream.rev + patches/ → work/;加 FORCE=1 丢弃 work/ 里的未提交改动 |
make rebuild |
work/ 中 base 之上的提交 → patches/ |
make update |
跟进最新上游同步并前移补丁 |
make update-dry |
预览上游差异,不做修改 |
make build / make release |
编译 debug / release 二进制 |
make check / make clippy / make fmt |
快速类型检查 / lint / 格式化 |
make test |
跑测试(PKG=<crate> 指定 crate) |
make run |
编译并启动 TUI |
make status |
显示当前钉住的 rev、补丁数、work/ 状态 |
make clean |
删 work/target |
make distclean |
删 work/ 和 upstream/(不动 patches/) |
make build 之后的额外参数会透传给 cargo,例如 ./scripts/build.sh --release --locked。
跑单测。 make test 是薄封装;要带 filter,直接走 build.sh,多出来的参数会原样转给 cargo:
CARGO_CMD=test PKG=xai-grok-version ./scripts/build.sh test_fork_tag
CARGO_CMD=test PKG=xai-grok-pager-bin ./scripts/build.sh version_output_writer -- --nocapture
安全网
脚本刻意不会静默吞掉工作成果:
work/有未提交改动时,make apply拒绝执行(提示先 commit + rebuild,或FORCE=1)work/的提交数和patches/的文件数对不上时同样拒绝——这基本意味着你忘了make rebuild。故意增删补丁文件时也会触发,检查分不清这两种情况git am进行到一半时,make rebuild/make build会拒绝执行,而不是产出半成品FORCE=1(即make apply FORCE=1)是两条 apply 护栏的逃生口,意思是「丢掉work/,按现在的patches/重放」make apply的git clean显式保留work/target,否则每次打补丁都要付一次冷编译的代价
补丁格式
rebuild 用的是:
git format-patch --no-stat -N --zero-commit --full-index --no-signature
--zero-commit—— 否则每次 rebase 后每个补丁的From <sha>行都会变,git diff里全是噪音--full-index—— 给git am --3way留下按 blob 哈希回退的余地,上游漂移时更容易自动合上
构建依赖
make setup 会处理,这里说明它在做什么:
- Rust 1.94.0 —— 由上游
rust-toolchain.toml钉住,rustup 自动装 - protoc —— 只有
xai-grok-tools-api需要(编译proto/grok-tools.proto)。上游的解析顺序是$PROTOC→ 向上查找bin/protoc→PATH。bin/protoc是个 DotSlash wrapper,会按需下载 protoc 29.3;所以setup.sh装 dotslash,失败则回退到系统protobuf-compiler
两个 git 依赖(our-forks/async-openai、helix-editor/nucleo)都是公开可访问的。
在这台机器上(4 核 / 7 GB),xai-grok-pager-bin 冷编译 debug 大约 11 分钟,work/target 会长到约 24 GB,二进制大约 612 MB。cargo check 和 cargo build 各有一份缓存,所以 make build 之后再 make check 并不快(冷跑大约 6 分钟)。不到真的缺盘,别跑 make clean / make distclean。迭代时尽量用单 crate(build.sh 总会带 -p)。
上游树里该改哪
work/ 里大约 91 个 workspace member。常动的几个:
| Crate | 角色 |
|---|---|
xai-grok-pager-bin |
组合根,产出 xai-grok-pager 二进制(发行名 grok) |
xai-grok-pager |
TUI:scrollback、prompt、modal、渲染;用户手册也在这里 |
xai-grok-shell |
Agent runtime + leader / stdio / headless 入口 |
xai-grok-tools |
工具实现(terminal、文件编辑、搜索……) |
xai-grok-workspace |
宿主文件系统、VCS、执行、checkpoint |
从上游继承的约束:
- 根目录
Cargo.toml是生成的,当只读。 改各 crate 自己的Cargo.toml。它还带着[patch.crates-io]里对our-forks/async-openai的 git pin。 - 永远指定 crate(
-p <crate>)。 全 workspace 编译慢到不现实;build.sh会强制带-p。 - 动上游代码时,先看它已有的测试还成不成立。
示例补丁
patches/0001 和 0002 的提交信息标了 [EXAMPLE PATCH],演示「改上游 Rust」和「新增文件」两种补丁,不需要就可以删。0001 同时也是 fork 品牌标记(--version 打出 [newgrok]),删之前想清楚。
删补丁文件要带 FORCE=1——work/ 里还留着对应提交,删完之后 patches/ 比 work/ 少,安全检查会拦下来(它无法区分「你故意删的」和「你忘了 rebuild」):
rm patches/0001-*.patch patches/0002-*.patch
make apply FORCE=1
许可
上游代码为 Apache-2.0(见 work/LICENSE)。patches/ 里的改动同样按 Apache-2.0 分发。
本仓库是非官方 fork,与 xAI 无关。