Files
newgrok/README.md
T
iceBear67andClaude Opus 5 27f078331d Fix apply --force against a dirty tree; clarify patch-count guard
Two issues found by exercising the scripts end to end:

- `git checkout -B` refuses to run when the worktree is dirty, so
  `make apply FORCE=1` aborted in exactly the situation FORCE exists
  to handle. Use `checkout -f -B`; the non-forced path has already
  verified the tree is clean, so forcing changes nothing there.

- The commit-count guard claimed the user forgot `make rebuild`, but
  deleting a patch file on purpose trips it identically. Say both,
  and document the FORCE=1 form in the README's "drop the example
  patches" instructions, which would otherwise have failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 04:52:37 +00:00

7.7 KiB
Raw Blame History

newgrok — xai-org/grok-build 的 patch 化开发工作流

以 CraftBukkit / Spigot 的模型,在不接受外部 PR 的上游之上维护本地改动。

为什么是 patch,而不是 fork 后直接改

上游 xai-org/grok-build 有两个决定性特征:

  1. 它是 xAI 内部 monorepo 的周期性单向导出。历史是一串线性的 Synced from monorepo 压扁提交,根目录 SOURCE_REV 记录对应的内部 commit。上游随时会把整棵树重新投放一次。
  2. 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/ 一字不差地重建。

快速开始

make setup     # 装 Rust 工具链(1.94.0,由 rust-toolchain.toml 钉住)+ dotslash/protoc
make apply     # 用 upstream.rev + patches/ 生成 work/
make build     # 编译 work/target/debug/xai-grok-pager
make run       # 直接启动 TUI

make setup 是幂等的,可以随时重跑。

Rust 装在 ~/.cargo,脚本会自己把它加进 PATH。若要在交互 shell 里直接用 cargo fish 执行 source ~/.cargo/env.fishbash 执行 source ~/.cargo/env

日常循环

改代码永远在 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 仓库:

cd work
git rebase -i base        # 改写、合并、重排、删除任意补丁
cd .. && make rebuild     # patches/ 会整体重新生成

随时看当前状态:

make status

跟进上游同步

make update-dry   # 只看会拉进来什么,不动任何东西
make update       # 重新钉 upstream.rev,并把补丁前移到新基线

make update 会:

  1. git fetch 上游,列出 upstream.rev..origin/main 之间的新提交和 SOURCE_REV 变化
  2. upstream.rev 更新为最新
  3. 在新基线上重放 patches/
  4. 干净通过则自动 make rebuild,让补丁上下文对齐新代码

冲突时脚本会停下并给出解决步骤——和处理 rebase 冲突完全一样:

cd work
git status                 # 看冲突文件
$EDITOR <冲突文件>          # 消掉冲突标记
git add -A
git am --continue          # 或 git am --skip / git am --abort
cd .. && make rebuild      # 把前移后的结果写回去

补丁数量多的时候,冲突是这个模型的固有成本,也是它的价值所在:冲突精确地指出上游改动 撞上了你的哪一处改动,而不是让它悄悄消失。

全部命令

命令 作用
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

安全网

脚本刻意不会静默吞掉工作成果:

  • work/未提交改动时,make apply 拒绝执行(提示先 commit + rebuild,或 FORCE=1
  • work/ 的提交数和 patches/ 的文件数对不上时同样拒绝——这基本意味着你忘了 make rebuild
  • git am 进行到一半时,make rebuild / make build 会拒绝执行,而不是产出半成品
  • make applygit 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/protocPATHbin/protoc 是个 DotSlash wrapper,会按需下载 protoc 29.3;所以 setup.sh 装 dotslash,失败则回退到系统 protobuf-compiler

两个 git 依赖(our-forks/async-openaihelix-editor/nucleo)都是公开可访问的。

示例补丁

patches/ 里预置了两个补丁,提交信息都标了 [EXAMPLE PATCH]

  1. 0001 改上游 Rust 源码——给 xai-grok-versionFORK_NAME / fork_tag(), 并接进 pager 的版本输出,于是 --version 显示 grok [newgrok] 1.0.3 (…)。 标记插在 grok 之后而不是追加到末尾,因为 main.rsversion_output_writer_preserves_channel_aware_contract 断言该行仍以 channel label (或 ))结尾——改上游代码时顺手确认它的测试仍然成立,这个补丁本身就是个例子。
  2. 0002 新增文件——根目录 FORK.md

不需要就直接删。注意要带 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 无关。