Set up CraftBukkit-style patch workflow for xai-org/grok-build
Upstream is a periodic one-way export of xAI's monorepo (linear "Synced from monorepo" commits, SOURCE_REV pinning the internal SHA) and explicitly refuses external contributions. So local changes can never be upstreamed, and upstream re-drops the whole tree on every sync -- structurally the same problem CraftBukkit has with Mojang. Adopt the Spigot/BuildTools model: patches/ is the source of truth, work/ is a disposable build artifact regenerated from upstream.rev plus patches/. scripts/apply-patches.sh ~ applyPatches.sh scripts/rebuild-patches.sh ~ rebuildPatches.sh scripts/update-upstream.sh forward-ports patches onto a new sync scripts/setup.sh toolchain: rust 1.94.0, dotslash/protoc upstream.rev ~ BuildTools versions/*.json pin format-patch uses --zero-commit so rebases do not rewrite the From line of every patch, and apply's git clean preserves work/target so replaying patches does not cost a cold Rust rebuild. Ships two [EXAMPLE PATCH] commits demonstrating a source edit and a new-file addition; both are safe to delete. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
# Rebuild work/ from scratch: pristine upstream@upstream.rev + every patch in
|
||||
# patches/, each replayed as its own commit.
|
||||
#
|
||||
# This is the CraftBukkit/Spigot `applyPatches.sh` step. work/ is a DERIVED
|
||||
# artifact -- anything in it that is not a commit is discarded.
|
||||
#
|
||||
# usage: apply-patches.sh [-f|--force]
|
||||
# -f discard uncommitted changes in work/ without asking
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
FORCE="${FORCE:-0}"
|
||||
case "${1:-}" in
|
||||
-f|--force) FORCE=1 ;;
|
||||
"") ;;
|
||||
*) die "unknown argument: $1" ;;
|
||||
esac
|
||||
|
||||
ensure_upstream
|
||||
REV="$(read_rev)"
|
||||
|
||||
if [[ ! -d "$WORK_DIR/.git" ]]; then
|
||||
info "Creating work/ from upstream/"
|
||||
git clone --origin upstream --no-checkout "$UPSTREAM_DIR" "$WORK_DIR"
|
||||
else
|
||||
# Refuse to nuke work the developer has not saved anywhere. patches/ is the
|
||||
# source of truth, so anything only present in work/ is about to be lost.
|
||||
if [[ "$FORCE" != "1" ]]; then
|
||||
assert_no_am_in_progress
|
||||
if [[ -n "$(git -C "$WORK_DIR" status --porcelain)" ]]; then
|
||||
die "work/ has uncommitted changes; they would be destroyed.
|
||||
Commit them (cd work && git add -A && git commit) then 'make rebuild',
|
||||
or re-run with: make apply FORCE=1"
|
||||
fi
|
||||
if git -C "$WORK_DIR" rev-parse -q --verify "refs/tags/$BASE_TAG" >/dev/null; then
|
||||
have="$(git -C "$WORK_DIR" rev-list --count "$BASE_TAG..HEAD" 2>/dev/null || echo 0)"
|
||||
want="$(count_patches)"
|
||||
if [[ "$have" != "$want" ]]; then
|
||||
die "work/ has $have commit(s) above $BASE_TAG but patches/ has $want patch file(s).
|
||||
You probably forgot to run 'make rebuild' after committing.
|
||||
To discard the work/ commits and replay patches/ as-is: make apply FORCE=1"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
step "Fetching into work/"
|
||||
git -C "$WORK_DIR" remote set-url upstream "$UPSTREAM_DIR"
|
||||
git -C "$WORK_DIR" fetch --tags --prune upstream
|
||||
fi
|
||||
|
||||
cd "$WORK_DIR"
|
||||
|
||||
git cat-file -e "${REV}^{commit}" 2>/dev/null \
|
||||
|| die "pinned revision $REV not found in upstream. Run 'make update' to re-pin."
|
||||
|
||||
# Abandon any half-finished am from a previous run before resetting.
|
||||
gitdir="$(git rev-parse --git-dir)"
|
||||
[[ -d "$gitdir/rebase-apply" ]] && git am --abort >/dev/null 2>&1 || true
|
||||
|
||||
info "Resetting work/ to upstream $(git rev-parse --short "$REV")"
|
||||
git checkout -q -B "$WORK_BRANCH" "$REV"
|
||||
git reset -q --hard "$REV"
|
||||
|
||||
# -x removes ignored files too, which is what we want for a pristine tree --
|
||||
# EXCEPT target/, which holds the Rust incremental cache. Wiping it would turn
|
||||
# every apply into a 30-minute cold rebuild.
|
||||
git clean -qfdx -e /target
|
||||
|
||||
# The boundary between "upstream" and "ours". rebuild-patches.sh formats
|
||||
# everything after this tag.
|
||||
git tag -f "$BASE_TAG" HEAD >/dev/null
|
||||
|
||||
n="$(count_patches)"
|
||||
if [[ "$n" -eq 0 ]]; then
|
||||
info "No patches to apply -- work/ is pristine upstream."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
info "Applying $n patch(es)"
|
||||
if ! git am --3way --keep-cr --whitespace=nowarn "$PATCHES_DIR"/*.patch; then
|
||||
failed="$(basename "$(cat "$(git rev-parse --git-dir)/rebase-apply/original-commit" 2>/dev/null || true)" 2>/dev/null || true)"
|
||||
cat >&2 <<EOF
|
||||
|
||||
${C_RED}${C_BOLD}==> patch application failed${C_RESET}
|
||||
|
||||
This is normal after an upstream sync that touched the same code.
|
||||
Resolve it the same way you would a rebase:
|
||||
|
||||
cd work
|
||||
git status # see the conflicting files
|
||||
\$EDITOR <conflicted files> # fix the conflict markers
|
||||
git add -A
|
||||
git am --continue # or: git am --skip / git am --abort
|
||||
|
||||
When 'git am' finishes cleanly, write the result back to patches/:
|
||||
|
||||
make rebuild
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
applied="$(git rev-list --count "$BASE_TAG..HEAD")"
|
||||
info "work/ is ready: upstream $(git rev-parse --short "$BASE_TAG") + $applied patch(es)"
|
||||
Reference in New Issue
Block a user