diff --git a/.claude/skills/series-land/SKILL.md b/.claude/skills/series-land/SKILL.md index 36534f0ea79..a18bcfad6bf 100644 --- a/.claude/skills/series-land/SKILL.md +++ b/.claude/skills/series-land/SKILL.md @@ -209,9 +209,11 @@ scripts/series-land.sh main --remote origin --canonical upstream --review --seri It reviews the vendor commits above the series' base, its seed and the commits in front of its first vendor commit, and leaves out a tooling sync or a port of a commit `main` has, as the landing's replay does. Each review commit names its series commit in a `Series-commit:` trailer, where a landing's names its `Landing-commit:`. -The run refuses, with nothing written, while `main` has a change the series has not ported: +The run refuses, with nothing written, while its base has a change the series has not ported: outside what it hides, the review has to equal `main-green` without its flavor, but for `NEWS.md`, the READMEs and `DESCRIPTION`'s version. +The base is `main`, or with `--base` the older `main` commit the series has ported, where `main` has moved on since its last port. +A pull request against `main` still shows only the review's commits, because GitHub diffs from the merge base. Its findings are not folded into the running series one by one. Each is verified first, and those that hold are collected on `main-fwd-build`, together with what the [revdep checks](/handbook/testing/revdep/README.md) find. diff --git a/scripts/series-land-test.sh b/scripts/series-land-test.sh index 60738647b65..b499d8781e4 100755 --- a/scripts/series-land-test.sh +++ b/scripts/series-land-test.sh @@ -46,7 +46,9 @@ # but not its C half, the registration or the source and dependency lists. # It writes a local branch, pushes only with --push and to --review-remote, # and refuses a series that lacks a change of `main`'s and a commit among -# its vendor commits that is not `main`'s. +# its vendor commits that is not `main`'s. --base stands it on the older +# `main` the series has ported, and refuses one the series does not carry, +# one below a commit it ported, and one not on `main`. # # Usage: # scripts/series-land-test.sh @@ -660,6 +662,25 @@ is "a series that lacks a change of main's is refused" "$st" 1 has "naming the path" "$out" " R/foo.R" hasnt "but not the version" "$out" " DESCRIPTION" hasnt "nor NEWS.md" "$out" " NEWS.md" +has "and names --base for the main it has ported" "$out" "or name the main commit it has ported with --base" +out=$(scripts/series-land.sh v3.0-y --review --series --base "$M9" --suffix -older 2>&1) && st=0 || st=$? +[ -z "${KEEP:-}" ] || echo "$out" +is "it is reviewed on the older main it has ported, named with --base" "$st" 0 +has "which it says" "$out" "the base is the main v3.0-y-green has ported; main $(git rev-parse --short HEAD) has 1 commit(s) above it" +is "the same review as on that main" "$(git rev-parse -q --verify v3.0-y-land-review-older)" "$REV" +out=$(scripts/series-land.sh v3.0-y --review --series --base "$M" --suffix -older 2>&1) && st=0 || st=$? +[ -z "${KEEP:-}" ] || echo "$out" +is "a base below a commit the series ported is refused" "$st" 1 +has "naming the port" "$out" "fix: Another R-side fix on main (#14) stands among the vendor commits" +MAIN=$(git rev-parse --short HEAD) +out=$(scripts/series-land.sh v3.0-y --review --series --base "$MAIN" --suffix -older 2>&1) && st=0 || st=$? +[ -z "${KEEP:-}" ] || echo "$out" +is "so is a base with a change the series lacks" "$st" 1 +has "naming the path" "$out" " R/foo.R" +has "and the base" "$out" "--base $MAIN is not the main v3.0-y-green has ported" +out=$(scripts/series-land.sh v3.0-y --review --series --base v3.0-y-land-review --suffix -older 2>&1) && st=0 || st=$? +is "and a base not on main" "$st" 1 +has "which it says" "$out" "--base v3.0-y-land-review is not on main" git reset -q --hard "$M9" echo 'glue 1, on main' > src/glue.cpp commit 'fix: Glue main changed since the series took it (#15)' diff --git a/scripts/series-land.sh b/scripts/series-land.sh index 42224550063..31a149ecb5d 100755 --- a/scripts/series-land.sh +++ b/scripts/series-land.sh @@ -118,13 +118,18 @@ # differs from `main` by construction, in `NEWS.md`, the READMEs and the # version, and a change of `main`'s the series has not ported stops it with # nothing written. Each commit names the series commit it halves, -# `Series-commit:`, where a landing's names its `Landing-commit:`. +# `Series-commit:`, where a landing's names its `Landing-commit:`. Where `main` +# has moved on since the series' last port, --base names the older `main` +# commit the series has ported, an ancestor of `main`: the review stands on it, +# leaves out only the ports of its commits, and is held to the series' green +# all the same, so a base the series does not carry is refused like a `main` it +# does not. # # Usage: series-land.sh [--remote ] [--canonical ] [--push] # [--base ] [--placed ]... [--no-check-glue] [--attended] # series-land.sh --status [--remote ] [--canonical ] [--push] -# series-land.sh --review [--landing | --series] [--suffix ] [--remote ] -# [--canonical ] [--review-remote ] [--push] +# series-land.sh --review [--landing | --series [--base ]] [--suffix ] +# [--remote ] [--canonical ] [--review-remote ] [--push] # series-land.sh --abort # is the series whose rc strand lands, `v2.0-cyanoptera-fwd` for # `v2.0-cyanoptera-fwd-rc-dev`; the refs are named for its line, @@ -137,6 +142,7 @@ # `-green` on --remote. --push writes it to --review-remote, by default # --canonical. # --base defaults to `main`; a ref the clone lacks is fetched from --canonical. +# A build takes it at or above `main`, a series review at or below it. # --source names the ref the landing is built up to, on --remote, in place of # `-rc-dev`: it is taken as it stands, with no realignment, for a series # that carries no flavor and so has no rc strand. @@ -323,6 +329,17 @@ fi if [ -n "$review" ]; then rb="$L-land-review$suffix" base=$main + # A series may be reviewed on the older `main` it has ported; the check at the + # end holds the review to the series' green whichever `main` it stands on. + if [ -n "$series" ] && [ -n "$base_arg" ]; then + base=$(git rev-parse -q --verify "$base_arg^{commit}" || + { git fetch -q "$canonical" "$base_arg" && git rev-parse FETCH_HEAD; }) + git merge-base --is-ancestor "$base" "$main" || { + echo "Error: --base $base_arg is not on main $(git rev-parse --short "$main")," \ + "so a series review cannot stand on it" >&2 + exit 1 + } + fi review_remote=${review_remote:-$canonical} git remote get-url "$review_remote" >/dev/null 2>&1 || { echo "Error: no remote \"$review_remote\" to push the review to" >&2; exit 1; } @@ -379,12 +396,12 @@ if [ -n "$review" ]; then ;; esac x=$(git log -1 --format=%B "$c" | sed -n 's/^(cherry picked from commit \([0-9a-f]\{40\}\))$/\1/p' | tail -n 1) - if [ -n "$x" ] && git merge-base --is-ancestor "$x" "$main" 2>/dev/null; then + if [ -n "$x" ] && git merge-base --is-ancestor "$x" "$base" 2>/dev/null; then mains_own[$c]=1 continue fi echo "Error: $(git rev-parse --short "$c") $subj stands among the vendor commits of $S-green" \ - "and is not main's; a fix belongs in the vendor commit it fixes" >&2 + "and is not in main $(git rev-parse --short "$base"); a fix belongs in the vendor commit it fixes" >&2 exit 1 done < <(git log --reverse --first-parent --format='%H%x09%s' "$sbase..$green") twin_of() { # -> its twin on the rc strand @@ -468,6 +485,8 @@ because it is read from \`main\`, not from this branch.") if [ -s "$work/diff" ] && ! git apply --cached --binary --whitespace=nowarn "$work/diff"; then echo "Error: $(git log -1 --format='%h %s' "$named") does not apply onto main" \ "$(git rev-parse --short "$base"), which changed what it changes" >&2 + [ -z "$series" ] || [ -n "$base_arg" ] || + echo " Name the main commit $S-green has ported with --base, where main has moved on since." >&2 exit 1 fi if [ -n "$desc" ]; then @@ -519,7 +538,12 @@ because it is read from \`main\`, not from this branch.") echo "Error: main $(git rev-parse --short "$base") and $S-green $(git rev-parse --short "$green")" \ "differ outside what the review hides, NEWS.md, the READMEs and the version:" >&2 printf ' %s\n' "${off[@]}" >&2 - echo " Port what main has, and review once $S-green carries it. Nothing was written." >&2 + if [ -n "$base_arg" ]; then + echo " --base $base_arg is not the main $S-green has ported. Nothing was written." >&2 + else + echo " Port what main has, and review once $S-green carries it," \ + "or name the main commit it has ported with --base. Nothing was written." >&2 + fi exit 1 fi fi @@ -527,6 +551,9 @@ because it is read from \`main\`, not from this branch.") echo "=== $rb" echo " $picked of $(git rev-list --count "$from..$tip") $kind commits change the R side;" \ "on main $(git rev-parse --short "$base") -> $head" + [ "$base" = "$main" ] || + echo " the base is the main $S-green has ported; main $(git rev-parse --short "$main")" \ + "has $(git rev-list --count --first-parent "$base..$main") commit(s) above it" if [ -n "$series" ]; then echo " up to $S-green $(git rev-parse --short "$green"), without its flavor" [ ${#left[@]} -eq 0 ] || printf " left out, main's own: %s\n" "${left[@]}"