#!/usr/bin/env bash # update-wordpress.sh — bring a new WordPress release into the fork. # # tools/update-wordpress.sh 7.1.2 # # Two steps, and the second is the one that matters: # # 1. Import the pristine release onto the `sirius-press/wordpress-upstream` # branch — one commit per version, nothing but upstream, never edited. # 2. `git subtree merge` that branch into wordpress/, which three-way merges # it against the fork's own commits on top of the last import. # # That second step is why core is vendored rather than patched at build time. # A three-way merge understands that upstream changed lines A and B while the # fork changed line C, and only stops when they overlap. It also leaves a # conflict you resolve once, in the file, instead of a patch you re-derive # every release. # # The merge happens in a temporary worktree, not in your checkout. git-subtree # refuses to run at all when anything in the repository is modified, and in a # monorepo shared with other work that is the normal state — the merge would # be blocked by uncommitted files it does not touch. A worktree is clean by # construction, and the result comes back as one ordinary merge commit. # # Nothing is pushed. Run it, look at the merge, run the tests, then push. set -euo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" repo="$(git -C "$here" rev-parse --show-toplevel)" # Ask git for the path rather than subtracting strings: on Windows # --show-toplevel answers "D:/…" while $PWD is "/d/…", and the subtraction # silently leaves an absolute path, which --prefix then rejects. prefix="$(cd "$here" && git rev-parse --show-prefix)wordpress" VERSION="${1:-}" say() { printf '\033[1m→\033[0m %s\n' "$*"; } warn() { printf '\033[33mwarning:\033[0m %s\n' "$*" >&2; } die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; } [ -n "$VERSION" ] || die "usage: tools/update-wordpress.sh " [[ "$VERSION" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || die "'$VERSION' does not look like a WordPress version" # shellcheck source=wordpress.lock source "$here/tools/wordpress.lock" CURRENT="$WP_VERSION" [ "$VERSION" != "$CURRENT" ] || die "the fork is already on WordPress $VERSION" UPSTREAM_BRANCH="sirius-press/wordpress-upstream" git -C "$repo" rev-parse --verify --quiet "$UPSTREAM_BRANCH" >/dev/null \ || die "the branch $UPSTREAM_BRANCH is missing. It holds the pristine upstream imports that the subtree merges against; without it this fork cannot take an upstream release. See docs/upstream-merges.md." # The merge lands on top of whatever is committed, so anything staged or # modified under the subtree itself would be lost or fought over. if ! git -C "$repo" diff --quiet -- "$prefix" 2>/dev/null; then die "${prefix}/ has uncommitted changes. Commit or stash them first." fi CACHE="$here/dist/.cache" mkdir -p "$CACHE" URL="https://wordpress.org/wordpress-${VERSION}.tar.gz" TARBALL="$CACHE/wordpress-${VERSION}.tar.gz" # ------------------------------------------------------------------- fetch if [ ! -f "$TARBALL" ]; then say "downloading WordPress $VERSION" curl -fsSL -o "$TARBALL.part" "$URL" || die "could not download $URL — does that version exist?" mv "$TARBALL.part" "$TARBALL" fi say "checking it against the SHA-1 wordpress.org publishes" published="$(curl -fsSL "${URL}.sha1" 2>/dev/null || true)" actual_sha1="$(sha1sum "$TARBALL" | cut -d' ' -f1)" if [ -z "$published" ]; then warn "wordpress.org served no .sha1 for this release; recording the sha256 below unverified" elif [ "$published" != "$actual_sha1" ]; then rm -f "$TARBALL" die "SHA-1 mismatch — the download is not what wordpress.org publishes. published $published got $actual_sha1" fi NEW_SHA256="$(sha256sum "$TARBALL" | cut -d' ' -f1)" # ------------------------------------------- import onto the upstream branch IMPORT_TREE="$here/dist/.vendor-import" MERGE_TREE="$here/dist/.vendor-merge" MERGE_BRANCH="sirius-press/_merge-${VERSION}" drop_worktree() { git -C "$repo" worktree remove --force "$1" >/dev/null 2>&1 || true } say "importing the pristine tree onto $UPSTREAM_BRANCH" drop_worktree "$IMPORT_TREE" rm -rf "$IMPORT_TREE" git -C "$repo" worktree add --quiet "$IMPORT_TREE" "$UPSTREAM_BRANCH" trap 'drop_worktree "$IMPORT_TREE"' EXIT # Replace the tree wholesale: files upstream deleted have to disappear, or the # subtree merge would keep resurrecting them. find "$IMPORT_TREE" -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + tar -xzf "$TARBALL" -C "$IMPORT_TREE" --strip-components=1 git -C "$IMPORT_TREE" add -A if git -C "$IMPORT_TREE" diff --cached --quiet; then # Already imported — a previous run got this far and then stopped. Carry on # to the merge rather than refusing, so a failure is retryable. say "WordPress $VERSION was already imported; continuing to the merge" else git -C "$IMPORT_TREE" \ -c user.name="Silent Mode" -c user.email="hephaestus@silentmode.st" \ commit -q -m "WordPress ${VERSION} Pristine upstream, unpacked from the official wordpress.org tarball. ${URL} sha1 ${actual_sha1} (published by wordpress.org) sha256 ${NEW_SHA256} This branch carries nothing but upstream releases, one commit each, and is never edited." say "imported as $(git -C "$IMPORT_TREE" rev-parse --short HEAD)" fi drop_worktree "$IMPORT_TREE" trap - EXIT # ------------------------------------------------------------ subtree merge say "merging into ${prefix}/ (in a scratch worktree)" drop_worktree "$MERGE_TREE" rm -rf "$MERGE_TREE" git -C "$repo" branch -D "$MERGE_BRANCH" >/dev/null 2>&1 || true git -C "$repo" worktree add --quiet -b "$MERGE_BRANCH" "$MERGE_TREE" HEAD if git -C "$MERGE_TREE" \ -c user.name="Silent Mode" -c user.email="hephaestus@silentmode.st" \ subtree merge --prefix="$prefix" "$UPSTREAM_BRANCH" \ -m "merge: WordPress ${VERSION} into the vendored subtree" then say "merged cleanly" else cat >&2 </dev/null 2>&1 || true # --------------------------------------------------------------- the lock say "updating tools/wordpress.lock" python3 - "$VERSION" "$URL" "$NEW_SHA256" "$actual_sha1" "$here/tools/wordpress.lock" <<'PY' import io, sys version, url, sha256, sha1, path = sys.argv[1:6] fields = { 'WP_VERSION=': version, 'WP_URL=': url, 'WP_SHA256=': sha256, 'WP_SHA1=': sha1, } out = [] for line in io.open(path, encoding='utf-8'): for key, value in fields.items(): if line.startswith(key): line = f'{key}{value}\n' break out.append(line) io.open(path, 'w', encoding='utf-8', newline='').write(''.join(out)) PY if [ -x "$here/tools/refresh-patches.sh" ]; then say "refreshing patches/ so it still describes the fork's core diff" "$here/tools/refresh-patches.sh" >/dev/null || warn "could not refresh patches/ — do it by hand" fi cat < ${VERSION} The subtree merge is committed. tools/wordpress.lock and patches/ are changed but not committed — look at them, then commit. Before pushing: tests/run.sh tools/build.sh then walk through wp-admin/install.php once — the setup wizard is the only file this fork patches, so it is the only thing this can have broken. EOF