diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index a6fd6383f0..08409c3ab2 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -17099,3 +17099,19 @@ roadmap: labels: - kind:triage notes: null +- id: PMAT-3186 + github_issue: 3186 + item_type: task + title: 'guard: resolve_base refuses every stacked merge-group entry (#3186, packing P0)' + status: planned + priority: medium + assigned_to: null + created: 2026-09-12T16:34:18Z + updated: 2026-09-12T16:34:18Z + spec: null + acceptance_criteria: [] + phases: [] + subtasks: [] + estimated_effort: null + labels: [] + notes: null diff --git a/scripts/check_roadmap_diff_additive.sh b/scripts/check_roadmap_diff_additive.sh index 4625028cf2..90e5e2a253 100755 --- a/scripts/check_roadmap_diff_additive.sh +++ b/scripts/check_roadmap_diff_additive.sh @@ -375,6 +375,34 @@ EOF *"never the tree against itself"*) printf 'ok row %-2s push shape, parent not fetched (depth-1): refused by name, never HEAD itself\n' "$row" ;; *) printf 'FAIL row %-2s push shape, parent not fetched: refused for the wrong reason (rc=%s): %s\n' "$row" "$rc5" "$err5"; fails=1 ;; esac + # Rows 19-20: a STACKED merge-group entry at depth 1 — the head's single parent + # is the previous entry's squash, not the origin/main tip. Under merge_group it + # deepens and names that parent; with deepening disabled (the mutation) it is + # refused by name — the shape that turned two of every three queue builds RED + # on 2026-09-12 (runs 34704287677, 34704288441). Self-contained repo: main = + # c1 (the tip the queue started from) -> c2 (entry 1's squash) -> c3 (entry 2's + # squash); origin/main := c1; a depth-1 clone sees c3 with c2 not fetched. + row=$((row + 1)) + Q="$TD/stacked-src"; rm -rf "${Q:?}"; ( git init -q -b main "$Q" && cd "$Q" && git config user.email t@t && git config user.name t \ + && cp "$TD/base.yaml" r.yaml && git add r.yaml && git commit -qm c1 \ + && cp "$TD/append.yaml" r.yaml && git commit -qam c2-entry-1-squash \ + && printf 'entry-2\n' > other.txt && git add other.txt && git commit -qm c3-entry-2-squash && git branch -q queue-base 'HEAD~2' ) 2>/dev/null + # The job fetches origin/main at depth 1 (its object IS present); c2 is not. A shallow clone cannot + # point a ref at an object it lacks, so the tip is fetched by name instead of update-ref'd. + rm -rf "${Q:?}.clone"; git clone -q --depth=1 -b main "file://$Q" "$Q.clone" 2>/dev/null; git -C "$Q.clone" fetch -q --depth=1 origin '+queue-base:refs/remotes/origin/main' 2>/dev/null + want6=$( git -C "$Q" rev-parse 'HEAD^1' ) + got6=$( cd "$Q.clone" && GITHUB_EVENT_NAME=merge_group bash -c '. "$0" --lib-only; REPO_ROOT="$1"; resolve_base HEAD && printf "%s|%s" "$BASE_REF" "$BASE_HOW"' "$SELF" "$Q.clone" 2>/dev/null ) || true + case "$got6" in "$want6|single parent (stacked merge_group entry"*) printf 'ok row %-2s stacked merge_group entry at depth-1 -> deepened, base = the previous entry squash\n' "$row" ;; + *) printf 'FAIL row %-2s stacked merge_group entry: wanted %s|single parent (stacked merge_group entry..., got %s\n' "$row" "$want6" "$got6"; fails=1 ;; esac + row=$((row + 1)) + rm -rf "${Q:?}.clone"; git clone -q --depth=1 -b main "file://$Q" "$Q.clone" 2>/dev/null; git -C "$Q.clone" fetch -q --depth=1 origin '+queue-base:refs/remotes/origin/main' 2>/dev/null + rc7=0; err7=$( cd "$Q.clone" && GITHUB_EVENT_NAME=merge_group ROADMAP_DIFF_NO_DEEPEN=1 bash -c '. "$0" --lib-only; REPO_ROOT="$1"; resolve_base HEAD' "$SELF" "$Q.clone" 2>&1 >/dev/null ) || rc7=$? + case "$rc7:$err7" in + 0:*) printf 'FAIL row %-2s stacked entry with deepening disabled: resolved a base (rc=0) — the mutation did not bite\n' "$row"; fails=1 ;; + *"is not a merge commit nor a commit on the origin/main tip"*) printf 'ok row %-2s stacked entry with deepening disabled (mutation): refused by name\n' "$row" ;; + *) printf 'FAIL row %-2s stacked entry with deepening disabled: refused for the wrong reason (rc=%s): %s\n' "$row" "$rc7" "$err7"; fails=1 ;; + esac + rm -rf "${Q:?}" "${Q:?}.clone" if [ "$fails" -ne 0 ]; then printf '\nSELF-TEST FAILED (%s/%s rows)\n' "$((row - fails + fails))" "$row" diff --git a/scripts/lib/resolve_base.sh b/scripts/lib/resolve_base.sh index 5e6d9d72e1..7af57d51bc 100644 --- a/scripts/lib/resolve_base.sh +++ b/scripts/lib/resolve_base.sh @@ -47,6 +47,24 @@ resolve_base() { if [ -n "$p1" ] && [ "$p1" = "$main_tip" ]; then BASE_REF="$p1"; BASE_HOW="single parent == origin/main tip (merge_group squash head, shallow checkout)"; return 0 fi + # A STACKED merge-group entry (queue position >= 2). GitHub builds entry N as a + # squash on top of entry N-1's squash, so the head has ONE parent that is NOT the + # origin/main tip and, at fetch-depth 1, is not fetched either. Every rule above + # refuses it, so with max_entries_to_build=3 two of every three queue builds were + # RED on this resolver alone (2026-09-12: groups 34704287677 and 34704288441, each + # after passing 82,085 workspace tests on gx10). The base that names THIS entry's + # own diff is exactly that parent: deepen the shallow history by one commit and + # use it. Only a merge_group run may deepen — a push-shape depth-1 head stays + # refused (never the tree against itself) — and ROADMAP_DIFF_NO_DEEPEN=1 is the + # case table's mutation. + if [ -n "$p1" ] && [ "${GITHUB_EVENT_NAME:-}" = merge_group ] && [ "${ROADMAP_DIFF_NO_DEEPEN:-0}" != 1 ]; then + if ! git -C "$REPO_ROOT" cat-file -e "$p1^{commit}" 2>/dev/null; then + git -C "$REPO_ROOT" fetch -q --deepen=1 origin 2>/dev/null || git -C "$REPO_ROOT" fetch -q origin "$p1" 2>/dev/null || true + fi + if git -C "$REPO_ROOT" cat-file -e "$p1^{commit}" 2>/dev/null; then + BASE_REF="$p1"; BASE_HOW="single parent (stacked merge_group entry: the previous entry's squash, fetched by deepening the shallow checkout)"; return 0 + fi + fi printf '%s: merge-base(origin/main, %s) is unresolvable (shallow checkout) and %s is not a merge commit nor a commit on the origin/main tip,\n' "$PROG" "$head" "$head" >&2 printf ' so no base can be named. A pull_request job checks out refs/pull/N/merge, a merge_group job the queue head; run with an explicit otherwise.\n' >&2 return 1