Repository navigation
fix(source-control): run ready's verify gate against the PR's worktree #10521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
melodic-automation-lanes
merged 2 commits into
main
from
work-resolve-issue/issue-9075-40572879795-1
Oct 27, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
.changes/source-control/fix-9075-ready-verify-pr-worktree-5c2e8a17.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| bump: patch | ||
| --- | ||
|
|
||
| ### Fixed | ||
|
|
||
| - `/source-control:pull-request ready` runs its verify gate against the pull request's worktree and records that worktree's HEAD, whichever checkout the shell sits in. A session in the main checkout running `ready <pr>` for a PR built in another worktree used to run the main checkout's `scripts/check-affected.sh` over the main checkout. The new `scripts/pr-worktree.sh --pr <n>` prints the worktree that has the PR's head branch checked out with the PR's head commit in its history, and exits 2 when none has (a fork's `main` beside this repository's main checkout included), so nothing is recorded and the flip refuses. The flip's `HEAD_SHA` comes from the same worktree. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
plugins/source-control/skills/pull-request/scripts/pr-worktree.sh
|
kyle-sexton marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| #!/usr/bin/env bash | ||
| # pr-worktree.sh: print the worktree that has a pull request's head branch checked out. | ||
| # | ||
| # The `ready` action's verify gate checks the PR's tree and records that tree's HEAD, whatever | ||
| # checkout the session's shell sits in: a session in the main checkout running `ready <pr>` for a | ||
| # PR built in a worktree would otherwise check the main checkout. The head branch and commit come | ||
| # from `gh pr view`, and the worktree is the one `git worktree list` shows on that branch, matched | ||
| # by local branch name as push-branch.sh --pr matches it, whose HEAD contains the PR's head | ||
| # commit. A name alone can match the wrong tree, such as a fork PR opened from the contributor's | ||
| # main beside this repository's main checkout. A shell already in the PR's worktree gets its own | ||
| # top level back. | ||
| # | ||
| # Usage: | ||
| # pr-worktree.sh --pr <number> | ||
| # | ||
| # Exit codes: | ||
| # 0 the worktree's absolute path is on stdout | ||
| # 1 invalid argument | ||
| # 2 the head branch or commit could not be read, or no worktree has the branch checked out | ||
| # with the head commit in its history | ||
|
|
||
| # portability-scope: the head branch is read through gh; the worktree list is local git. | ||
| set -uo pipefail | ||
|
|
||
| usage() { | ||
| sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' | ||
| exit 0 | ||
| } | ||
|
|
||
| die() { | ||
| printf 'pr-worktree: %s\n' "$2" >&2 | ||
| exit "$1" | ||
| } | ||
|
|
||
| PR="" | ||
| while (($# > 0)); do | ||
| case "$1" in | ||
| -h | --help) usage ;; | ||
| --pr) | ||
| (($# >= 2)) && [[ -z "$PR" ]] || die 1 "--pr needs one value and is given once" | ||
| PR="$2" | ||
| shift 2 | ||
| ;; | ||
| *) die 1 "unknown argument $(printf '%q' "$1") (use --help)" ;; | ||
| esac | ||
| done | ||
| [[ "$PR" =~ ^[0-9]+$ ]] || die 1 "--pr needs a pull request number" | ||
|
|
||
| HEAD_READ=$(gh pr view "$PR" --json headRefName,headRefOid -q '.headRefName + " " + .headRefOid' 2>/dev/null | tr -d '\r') || | ||
| die 2 "the head branch of PR $PR could not be read" | ||
| read -r BRANCH HEAD_OID <<<"$HEAD_READ" | ||
| [[ -n "${BRANCH:-}" && "${HEAD_OID:-}" =~ ^[0-9a-f]{40}$ ]] || die 2 "the head branch and commit of PR $PR could not be read" | ||
|
|
||
| WT="" | ||
| while IFS= read -r LINE; do | ||
| case "$LINE" in | ||
| "worktree "*) WT="${LINE#worktree }" ;; | ||
| "branch refs/heads/$BRANCH") | ||
| if [[ -d "$WT" ]]; then | ||
| git -C "$WT" merge-base --is-ancestor "$HEAD_OID" HEAD 2>/dev/null || | ||
| die 2 "the worktree $WT on '$BRANCH' does not contain PR $PR's head commit $HEAD_OID; it is another branch of that name, or behind the PR: check the PR out with the forge's PR checkout, or bring that worktree up to date, then run the gate again" | ||
| printf '%s\n' "$WT" | ||
| exit 0 | ||
| fi | ||
| ;; | ||
| *) ;; | ||
| esac | ||
| done < <(git worktree list --porcelain 2>/dev/null) | ||
|
|
||
| die 2 "PR $PR's head branch '$BRANCH' is checked out in no worktree of this repository; check it out with the forge's PR checkout, then run the gate again" |
124 changes: 124 additions & 0 deletions
124
plugins/source-control/skills/pull-request/scripts/pr-worktree.test.sh
|
melodic-automation-lanes[bot] marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| #!/usr/bin/env bash | ||
| # Regression tests for pr-worktree.sh and the ready action's verify gate command that uses it: a | ||
| # session in the main checkout running `ready <pr>` for a PR built in a worktree checks the PR's | ||
| # tree and records that tree's HEAD. The gate command is extracted from the ready | ||
| # reference and run in a fixture repository against a stubbed `gh`. | ||
|
|
||
| set -uo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" | ||
| SCRIPT="$SCRIPT_DIR/pr-worktree.sh" | ||
| REF="$SKILL_DIR/reference/ready-for-review.md" | ||
| TEST_TMPDIR="$(mktemp -d)" | ||
| trap 'rm -rf "$TEST_TMPDIR"' EXIT | ||
|
|
||
| # shellcheck source=../../../scripts/test-helpers.sh | ||
| source "$SCRIPT_DIR/../../../scripts/test-helpers.sh" | ||
|
|
||
| # A gh stand-in: the head read answers STUB_BRANCH and STUB_OID, and fails when STUB_FAIL is set. | ||
| STUB_DIR="$TEST_TMPDIR/stubs" | ||
| mkdir -p "$STUB_DIR" | ||
| cat >"$STUB_DIR/gh" <<'STUB_EOF' | ||
| #!/usr/bin/env bash | ||
| [[ -z "${STUB_FAIL:-}" ]] || exit 1 | ||
| [[ "$1 $2" == "pr view" && " $* " == *" headRefName,headRefOid "* ]] || exit 99 | ||
| echo "$STUB_BRANCH $STUB_OID" | ||
| STUB_EOF | ||
| chmod +x "$STUB_DIR/gh" | ||
| export PATH="$STUB_DIR:$PATH" | ||
|
|
||
| # Fixture: the main checkout on main, and the PR's branch in a linked worktree with one more | ||
| # commit. Each tree's scripts/check-affected.sh prints the files the checked tree changed since | ||
| # main and which tree it checked, so the gate output shows whose tree was checked. The stub takes | ||
| # that tree from CHECK_DATA_ROOT, else from the working directory, never from its own location, so | ||
| # the gate assertions fail unless the gate itself names the PR's tree. | ||
| MAIN="$TEST_TMPDIR/main" | ||
| git init -q -b main "$MAIN" | ||
| mkdir -p "$MAIN/scripts" | ||
| cat >"$MAIN/scripts/check-affected.sh" <<'EOF' | ||
| #!/usr/bin/env bash | ||
| top=$(git -C "${CHECK_DATA_ROOT:-$PWD}" rev-parse --show-toplevel) || exit 2 | ||
| top=$(cd "$top" && pwd -P) | ||
| echo "checked $top" | ||
| git -C "$top" diff --name-only main HEAD | sed 's/^/changed: /' | ||
| EOF | ||
| git -C "$MAIN" add scripts/check-affected.sh | ||
| git -C "$MAIN" -c user.name=t -c user.email=t@t commit -q -m base | ||
| MAIN_HEAD=$(git -C "$MAIN" rev-parse HEAD) | ||
| PR_WT="$TEST_TMPDIR/pr-tree" | ||
| git -C "$MAIN" worktree add -q -b feat/pr "$PR_WT" | ||
| echo change >"$PR_WT/feature.md" | ||
| git -C "$PR_WT" add feature.md | ||
| git -C "$PR_WT" -c user.name=t -c user.email=t@t commit -q -m feature | ||
| PR_HEAD=$(git -C "$PR_WT" rev-parse HEAD) | ||
| PR_WT_REAL=$(physical_dir "$PR_WT") | ||
| MAIN_REAL=$(physical_dir "$MAIN") | ||
| export STUB_BRANCH=feat/pr STUB_OID="$PR_HEAD" | ||
|
|
||
| out=$(cd "$MAIN" && bash "$SCRIPT" --pr 7) | ||
| assert_exit "from the main checkout: the PR's worktree is found" 0 "$?" | ||
| assert_eq "from the main checkout: the path is the PR's worktree" "$PR_WT_REAL" "$(physical_dir "$out")" | ||
|
|
||
| out=$(cd "$PR_WT" && bash "$SCRIPT" --pr 7) | ||
| assert_exit "from the PR's worktree: found" 0 "$?" | ||
| assert_eq "from the PR's worktree: its own top level comes back" "$PR_WT_REAL" "$(physical_dir "$out")" | ||
|
|
||
| out=$(cd "$MAIN" && STUB_BRANCH=main STUB_OID="$MAIN_HEAD" bash "$SCRIPT" --pr 7) | ||
| assert_eq "a branch the main checkout holds resolves to the main checkout" "$MAIN_REAL" "$(physical_dir "$out")" | ||
|
|
||
| out=$(cd "$PR_WT" && STUB_OID="$MAIN_HEAD" bash "$SCRIPT" --pr 7) | ||
| assert_eq "a worktree ahead of the PR's head commit (local commits not pushed yet) is still the PR's" "$PR_WT_REAL" "$(physical_dir "$out")" | ||
|
|
||
| out=$(cd "$MAIN" && STUB_BRANCH=main bash "$SCRIPT" --pr 7 2>&1) | ||
| assert_exit "a same-named branch without the PR's head commit (a fork's main) exits 2" 2 "$?" | ||
| assert_contains "the refusal names the missing head commit" "$out" "$PR_HEAD" | ||
|
|
||
| (cd "$MAIN" && STUB_OID=ffffffffffffffffffffffffffffffffffffffff bash "$SCRIPT" --pr 7 >/dev/null 2>&1) | ||
| assert_exit "a head commit this repository lacks exits 2" 2 "$?" | ||
| (cd "$MAIN" && STUB_OID=abc bash "$SCRIPT" --pr 7 >/dev/null 2>&1) | ||
| assert_exit "a malformed head commit exits 2" 2 "$?" | ||
|
|
||
| out=$(cd "$MAIN" && STUB_BRANCH=feat/elsewhere bash "$SCRIPT" --pr 7 2>&1) | ||
| assert_exit "a head branch checked out nowhere exits 2" 2 "$?" | ||
| assert_contains "the refusal names the branch" "$out" "'feat/elsewhere'" | ||
|
|
||
| out=$(cd "$MAIN" && STUB_BRANCH=feat bash "$SCRIPT" --pr 7 2>&1) | ||
| assert_exit "a branch name that is only a prefix of the checked-out one is not a match" 2 "$?" | ||
|
|
||
| (cd "$MAIN" && STUB_FAIL=1 bash "$SCRIPT" --pr 7 >/dev/null 2>&1) | ||
| assert_exit "a failed head-branch read exits 2" 2 "$?" | ||
| (cd "$MAIN" && STUB_BRANCH="" bash "$SCRIPT" --pr 7 >/dev/null 2>&1) | ||
| assert_exit "an empty head-branch read exits 2" 2 "$?" | ||
|
|
||
| bash "$SCRIPT" --pr abc >/dev/null 2>&1 | ||
| assert_exit "a non-numeric --pr is a usage error" 1 "$?" | ||
| bash "$SCRIPT" >/dev/null 2>&1 | ||
| assert_exit "a missing --pr is a usage error" 1 "$?" | ||
| bash "$SCRIPT" --pr 7 --pr 8 >/dev/null 2>&1 | ||
| assert_exit "--pr given twice is a usage error" 1 "$?" | ||
| bash "$SCRIPT" --bogus >/dev/null 2>&1 | ||
| assert_exit "an unknown argument is a usage error" 1 "$?" | ||
|
|
||
| # The verify gate command of the ready reference (2.5.3), run from the main checkout. | ||
| GATE=$(grep -F 'pr-worktree.sh" --pr' "$REF" | grep -F 'check-affected.sh" --tests' | sed -e 's/^ *//' -e "s#<skill-dir>#$SKILL_DIR#g") | ||
| if [[ -z "$GATE" ]]; then | ||
| echo "FAIL: the verify gate command was not found in $REF" >&2 | ||
| exit 1 | ||
| fi | ||
| export READY_VERIFY_DIR="$TEST_TMPDIR/verify" | ||
| out=$(cd "$MAIN" && PR_NUMBER=7 bash -c "$GATE" 2>&1) | ||
| assert_exit "the gate command from the main checkout succeeds" 0 "$?" | ||
| assert_contains "the gate checks the PR's worktree" "$out" "checked $PR_WT_REAL" | ||
| assert_contains "the gate output lists the PR's changed files" "$out" "changed: feature.md" | ||
| assert_eq "the gate records a pass for the PR worktree's HEAD" "pass" "$(cat "$READY_VERIFY_DIR/$PR_HEAD" 2>/dev/null)" | ||
| assert_file_absent "nothing is recorded for the main checkout's HEAD" "$READY_VERIFY_DIR/$MAIN_HEAD" | ||
|
|
||
| rm -rf "$READY_VERIFY_DIR" | ||
| out=$(cd "$MAIN" && STUB_BRANCH=feat/elsewhere PR_NUMBER=7 bash -c "$GATE" 2>&1) | ||
| assert_exit "the gate command for a head checked out nowhere fails" 1 "$?" | ||
| [[ -e "$READY_VERIFY_DIR" ]] && recorded=yes || recorded=no | ||
| assert_eq "the gate command for a head checked out nowhere records nothing" "no" "$recorded" | ||
|
|
||
| [[ $FAILED -eq 0 ]] || exit 1 | ||
| exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.