Files
newgrok/scripts/update-upstream.sh
iceBear67andClaude Opus 5 618c8e31ee 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>
2026-08-14 04:50:07 +00:00

86 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Follow a new upstream sync: re-pin upstream.rev, replay patches/ on top of it,
# and write the forward-ported result back out.
#
# usage: update-upstream.sh [--dry-run] [--to <rev>]
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
DRY_RUN=0
TARGET=""
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--dry-run) DRY_RUN=1; shift ;;
--to) TARGET="${2:?--to needs a revision}"; shift 2 ;;
*) die "unknown argument: $1" ;;
esac
done
ensure_upstream
OLD="$(read_rev)"
if [[ -n "$TARGET" ]]; then
NEW="$(git -C "$UPSTREAM_DIR" rev-parse --verify "${TARGET}^{commit}")" \
|| die "revision '$TARGET' not found in upstream"
else
NEW="$(git -C "$UPSTREAM_DIR" rev-parse --verify "origin/$UPSTREAM_BRANCH")"
fi
if [[ "$OLD" == "$NEW" ]]; then
info "Already pinned to the newest upstream commit ($(git -C "$UPSTREAM_DIR" rev-parse --short "$NEW")). Nothing to do."
exit 0
fi
count="$(git -C "$UPSTREAM_DIR" rev-list --count "$OLD..$NEW" 2>/dev/null || echo '?')"
info "Upstream moved: $(git -C "$UPSTREAM_DIR" rev-parse --short "$OLD") -> $(git -C "$UPSTREAM_DIR" rev-parse --short "$NEW") ($count new commit(s))"
git -C "$UPSTREAM_DIR" log --oneline --no-decorate "$OLD..$NEW" | sed 's/^/ /'
# Each upstream commit is a squashed monorepo drop; SOURCE_REV names the
# internal commit it came from, which is the only human-meaningful version here.
old_src="$(git -C "$UPSTREAM_DIR" show "$OLD:SOURCE_REV" 2>/dev/null | tr -d '[:space:]' || true)"
new_src="$(git -C "$UPSTREAM_DIR" show "$NEW:SOURCE_REV" 2>/dev/null | tr -d '[:space:]' || true)"
[[ -n "$old_src$new_src" ]] && info "SOURCE_REV: ${old_src:-?} -> ${new_src:-?}"
changed="$(git -C "$UPSTREAM_DIR" diff --name-only "$OLD" "$NEW" | wc -l | tr -d ' ')"
info "$changed file(s) changed upstream"
if [[ "$DRY_RUN" == "1" ]]; then
info "--dry-run: upstream.rev left at $OLD"
exit 0
fi
info "Pinning upstream.rev -> $NEW"
write_rev "$NEW"
info "Replaying patches on the new base"
if ! "$ROOT/scripts/apply-patches.sh" --force; then
cat >&2 <<EOF
${C_YELLOW}${C_BOLD}==> upstream.rev has been bumped, but patches did not apply cleanly.${C_RESET}
Finish the 'git am' in work/ (instructions above), then run:
make rebuild
To abandon this update entirely and go back:
cd work && git am --abort
printf '%s\\n' "$OLD" > upstream.rev # or: git checkout upstream.rev
make apply
EOF
exit 1
fi
info "Patches replayed cleanly -- regenerating patches/ against the new base"
"$ROOT/scripts/rebuild-patches.sh"
cat <<EOF
${C_GREEN}${C_BOLD}==> Update complete.${C_RESET}
Review and commit the result:
git diff --stat upstream.rev patches/
make build
git add upstream.rev patches/ && git commit -m 'Update upstream to $(git -C "$UPSTREAM_DIR" rev-parse --short "$NEW")'
EOF