diff --git a/actions/issueflow-reconcile/issueflow-reconcile.sh b/actions/issueflow-reconcile/issueflow-reconcile.sh index 6a2ba11..a77df04 100644 --- a/actions/issueflow-reconcile/issueflow-reconcile.sh +++ b/actions/issueflow-reconcile/issueflow-reconcile.sh @@ -139,6 +139,16 @@ refs_references() { # PR body on stdin -> local issue numbers named by Refs | awk -F '\t' '$1 == "LOCAL" { print $2 }' | sort -nu } +open_pr_issues() { # records on stdin: CLOSING|BODYvalue -> issue numbers + local kind value + while IFS=$'\t' read -r kind value; do + case "$kind" in + CLOSING) [ -n "$value" ] && printf '%s\n' "$value" ;; + BODY) refs_references <<<"$value" ;; + esac + done | sort -nu +} + unchecked_criteria() { # issue body on stdin -> unchecked task-list lines verbatim awk ' /^[[:space:]]*([-*]|[0-9]+\.)[[:space:]]+\[[[:space:]]\]/ { @@ -503,16 +513,22 @@ main() { fi owner="${REPO%%/*}" name="${REPO#*/}" + # crew#321 released a live claim because the open side read only closing + # links while the merged side parsed Refs bodies. One parser now supplies + # the local body references on both sides, so transition and reclaim agree. OPEN_PR_ISSUES="$(gh api graphql --paginate -f owner="$owner" -f name="$name" -f query=' query($owner: String!, $name: String!, $endCursor: String) { repository(owner: $owner, name: $name) { pullRequests(first: 100, states: OPEN, after: $endCursor) { - nodes { closingIssuesReferences(first: 100) { nodes { number } } } + nodes { body closingIssuesReferences(first: 100) { nodes { number } } } pageInfo { hasNextPage endCursor } } } - }' --jq '.data.repository.pullRequests.nodes[].closingIssuesReferences.nodes[].number' \ - | sort -nu)" + }' --jq '.data.repository.pullRequests.nodes[] + | (.closingIssuesReferences.nodes[].number + | ["CLOSING", tostring] | @tsv), + ((.body // "") | split("\n")[] | ["BODY", .] | @tsv)' \ + | open_pr_issues)" MERGED_REF_PR_RECORDS="$(gh api graphql --paginate -f owner="$owner" -f name="$name" -f query=' query($owner: String!, $name: String!, $endCursor: String) { repository(owner: $owner, name: $name) { diff --git a/changelog.d/241.md b/changelog.d/241.md new file mode 100644 index 0000000..e7e7ec5 --- /dev/null +++ b/changelog.d/241.md @@ -0,0 +1,3 @@ +### Fixed + +- Preserve active claims when an open local pull request links them with `Refs #N`. diff --git a/test/issueflow-reconcile.test.sh b/test/issueflow-reconcile.test.sh index 1453cb1..99a2208 100644 --- a/test/issueflow-reconcile.test.sh +++ b/test/issueflow-reconcile.test.sh @@ -95,6 +95,13 @@ check "empty labels do not exempt a claimed issue" 0 "SWEEP" claim_clock_exempt refs_body=$'Refs #12\nAlso refs: #8 and heavy-duty/rig#4.\nCloses #99\nNot refs-ish #7\nfix refs parsing from #200\nCloses #40; refs: none\nRefs #175 (split from #150)' check "Refs parser returns only references owned by a valid Refs marker" 0 "" \ test "$(refs_references <<<"$refs_body")" = $'8\n12\n175' +open_records=$'BODY\tRefs #5\nCLOSING\t9\nBODY\tRefs heavy-duty/rig#112\nBODY\tRefs #5\nCLOSING\t5' +check "open PR linkage unions closing and local Refs body references" 0 $'5\n9' \ + open_pr_issues <<<"$open_records" +check "cross-repo Refs never enter the local open PR set" 0 "" \ + open_pr_issues <<< $'BODY\tRefs heavy-duty/rig#112' +check "an issue named by both linkage paths appears exactly once" 0 "1" \ + grep -cxF 5 <<<"$(open_pr_issues <<<"$open_records")" check "unchecked criteria preserve their source lines verbatim" 0 \ $'- [ ] first criterion\n * [ ] indented criterion\n1. [ ] numbered criterion' \ unchecked_criteria <<< $'- [x] done\n- [ ] first criterion\r\n * [ ] indented criterion\n1. [ ] numbered criterion' @@ -731,6 +738,18 @@ check "...performs no release edit" 1 "" \ grep -qF -- 'issue edit 40 -R owner/repo --remove-assignee builder --remove-label claimed --add-label post-merge' \ "$ARRIVAL/fixtures/edits" +sed 's/"isDraft":false/"isDraft":true/' "$ARRIVAL/fixtures/graphql-open.json" \ + >"$ARRIVAL/fixtures/graphql-open.json.tmp" +mv "$ARRIVAL/fixtures/graphql-open.json.tmp" "$ARRIVAL/fixtures/graphql-open.json" +: >"$ARRIVAL/fixtures/edits" +draft_transition_out="$( + env PATH="$ARRIVAL/stub:$PATH" GH_FIXTURES="$ARRIVAL/fixtures" \ + REPO=owner/repo LABELS_CONF="$ARRIVAL/labels.conf" \ + bash "$ROOT/actions/issueflow-reconcile/issueflow-reconcile.sh" 2>&1 +)" +check "a draft Refs-bodied PR suppresses post-merge transition identically" 1 "" \ + grep -qF '#40: merged Refs PR -> post-merge; claim released' <<<"$draft_transition_out" + # The same body linkage protects the reclaim clock even when no Refs-linked # PR has merged. This is the derived half of crew#321's destructive shape. printf '%s\n' \