Stop pick from hiding long-lived PRs behind a creation-ordered window
`gh pr list` orders by creation date, so `--limit 30` cut the candidate set by when PRs were opened, not when they merged. The `sort_by(.mergedAt)` that followed only reordered whatever survived that cut. A PR opened before the window but merged inside it — exactly the kind a release branch still needs — never reached the already-on-branch check at all. #7649 and #7709 were both missing from v4-0-2's list for this reason. The window is now bounded by the branch point instead of a count: pull a wide page and keep what merged after the merge-base's commit date, since anything merged into the dev branch before the release branch left it is already there by ancestry. v4-0-2 went from 11 candidates to 31. Widening it surfaced a second gap. A change re-applied by hand carries neither a PR number nor a cherry-pick trailer, so already_on_branch could not see it and offered it again (#6939, applied as 33d7363c). It now also compares the PR title against the branch's subjects, with any trailing "(#N)" stripped.
This commit is contained in:
+36
-15
@@ -499,13 +499,24 @@ cmd_pick() {
|
|||||||
# on a patch branch. Detect equivalence the way it is actually recorded:
|
# on a patch branch. Detect equivalence the way it is actually recorded:
|
||||||
# the branch's own commit messages since it left quattro name the PR
|
# the branch's own commit messages since it left quattro name the PR
|
||||||
# ("backport of #N", squash titles "(#N)") or the source commit
|
# ("backport of #N", squash titles "(#N)") or the source commit
|
||||||
# ("cherry picked from commit <sha>", which pick -x writes).
|
# ("cherry picked from commit <sha>", which pick -x writes) — or, failing
|
||||||
local base_commit branch_log
|
# both, repeat its title verbatim.
|
||||||
|
local base_commit base_date branch_log
|
||||||
base_commit=$(git -C "$WORK_CLONE" merge-base "origin/$branch" "origin/$DEV_BRANCH" 2>/dev/null) || base_commit=""
|
base_commit=$(git -C "$WORK_CLONE" merge-base "origin/$branch" "origin/$DEV_BRANCH" 2>/dev/null) || base_commit=""
|
||||||
|
base_date=""
|
||||||
|
if [[ -n "$base_commit" ]]; then
|
||||||
|
base_date=$(TZ=UTC git -C "$WORK_CLONE" log -1 --format=%cd \
|
||||||
|
--date=format-local:'%Y-%m-%dT%H:%M:%SZ' "$base_commit" 2>/dev/null) || base_date=""
|
||||||
|
fi
|
||||||
branch_log=$(git -C "$WORK_CLONE" log --format='%s %b' "origin/$branch" ${base_commit:+--not "$base_commit"} 2>/dev/null)
|
branch_log=$(git -C "$WORK_CLONE" log --format='%s %b' "origin/$branch" ${base_commit:+--not "$base_commit"} 2>/dev/null)
|
||||||
|
# Subjects alone, with any trailing "(#N)" stripped, so a change re-applied by
|
||||||
|
# hand — no PR number, no cherry-pick trailer — is still recognised by title.
|
||||||
|
local branch_subjects
|
||||||
|
branch_subjects=$(git -C "$WORK_CLONE" log --format='%s' "origin/$branch" ${base_commit:+--not "$base_commit"} 2>/dev/null |
|
||||||
|
sed -E 's/ \(#[0-9]+\)$//')
|
||||||
|
|
||||||
already_on_branch() { # already_on_branch <merge-sha> <pr-number-or-empty>
|
already_on_branch() { # already_on_branch <merge-sha> <pr-number-or-empty> <title-or-empty>
|
||||||
local sha="$1" number="$2"
|
local sha="$1" number="$2" title="$3"
|
||||||
if [[ -n "$sha" ]]; then
|
if [[ -n "$sha" ]]; then
|
||||||
git -C "$WORK_CLONE" merge-base --is-ancestor "$sha" "origin/$branch" 2>/dev/null && return 0
|
git -C "$WORK_CLONE" merge-base --is-ancestor "$sha" "origin/$branch" 2>/dev/null && return 0
|
||||||
grep -q "cherry picked from commit $sha" <<<"$branch_log" && return 0
|
grep -q "cherry picked from commit $sha" <<<"$branch_log" && return 0
|
||||||
@@ -513,6 +524,9 @@ cmd_pick() {
|
|||||||
if [[ -n "$number" ]]; then
|
if [[ -n "$number" ]]; then
|
||||||
grep -qE "#$number([^0-9]|$)" <<<"$branch_log" && return 0
|
grep -qE "#$number([^0-9]|$)" <<<"$branch_log" && return 0
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "$title" ]]; then
|
||||||
|
grep -Fxq "$title" <<<"$branch_subjects" && return 0
|
||||||
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,13 +541,13 @@ cmd_pick() {
|
|||||||
print_error "PR #$arg has no merge commit (not merged?)"
|
print_error "PR #$arg has no merge commit (not merged?)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if already_on_branch "$sha" "$arg"; then
|
if already_on_branch "$sha" "$arg" "$title"; then
|
||||||
print_info "PR #$arg is already on $branch — skipping"
|
print_info "PR #$arg is already on $branch — skipping"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
shas+=("$sha") labels+=("PR #$arg: $title")
|
shas+=("$sha") labels+=("PR #$arg: $title")
|
||||||
else
|
else
|
||||||
if already_on_branch "$arg" ""; then
|
if already_on_branch "$arg" "" ""; then
|
||||||
print_info "commit $arg is already on $branch — skipping"
|
print_info "commit $arg is already on $branch — skipping"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -542,21 +556,28 @@ cmd_pick() {
|
|||||||
done
|
done
|
||||||
else
|
else
|
||||||
# Interactive: merged dev-branch PRs not already on the release branch
|
# Interactive: merged dev-branch PRs not already on the release branch
|
||||||
# (by ancestry, backport reference, or cherry-pick trailer), oldest first
|
# (by ancestry, backport reference, cherry-pick trailer, or title), oldest
|
||||||
# so picks apply in merge order.
|
# first so picks apply in merge order.
|
||||||
print_info "Loading merged $DEV_BRANCH PRs not yet on $branch..."
|
print_info "Loading merged $DEV_BRANCH PRs not yet on $branch..."
|
||||||
local list
|
# gh pr list orders by CREATION date, so a plain --limit window silently
|
||||||
|
# drops long-lived PRs — opened early, merged recently — which are exactly
|
||||||
|
# the ones a release branch still needs. Pull a wide window and bound it by
|
||||||
|
# the branch point instead: anything merged into $DEV_BRANCH before $branch
|
||||||
|
# left it is already here by ancestry.
|
||||||
|
local list jq_pick
|
||||||
|
jq_pick='sort_by(.mergedAt) | .[] | "\(.mergeCommit.oid)\t\(.number)\t\(.title)"'
|
||||||
|
[[ -n "$base_date" ]] && jq_pick="[.[] | select(.mergedAt >= \"$base_date\")] | $jq_pick"
|
||||||
list=$(gh pr list --repo "$UPSTREAM_REPO" --state merged --base "$DEV_BRANCH" \
|
list=$(gh pr list --repo "$UPSTREAM_REPO" --state merged --base "$DEV_BRANCH" \
|
||||||
--limit 30 --json number,title,mergeCommit,mergedAt \
|
--limit 300 --json number,title,mergeCommit,mergedAt --jq "$jq_pick")
|
||||||
--jq 'sort_by(.mergedAt) | .[] | "\(.mergeCommit.oid)\t\(.number)\t#\(.number) \(.title)"')
|
|
||||||
local -a cand_shas=() cand_labels=()
|
local -a cand_shas=() cand_labels=()
|
||||||
while IFS=$'\t' read -r sha number label; do
|
local sha number title
|
||||||
|
while IFS=$'\t' read -r sha number title; do
|
||||||
[[ -z "$sha" || "$sha" == "null" ]] && continue
|
[[ -z "$sha" || "$sha" == "null" ]] && continue
|
||||||
already_on_branch "$sha" "$number" && continue
|
already_on_branch "$sha" "$number" "$title" && continue
|
||||||
cand_shas+=("$sha") cand_labels+=("$label")
|
cand_shas+=("$sha") cand_labels+=("#$number $title")
|
||||||
done <<<"$list"
|
done <<<"$list"
|
||||||
if [[ ${#cand_shas[@]} -eq 0 ]]; then
|
if [[ ${#cand_shas[@]} -eq 0 ]]; then
|
||||||
print_success "Nothing to pick — every recent merged PR is already on $branch"
|
print_success "Nothing to pick — every $DEV_BRANCH PR merged since $branch opened is already on it"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
Reference in New Issue
Block a user