hive-forge: flag stale-branches merge outcome as unknown when the PR fetch is truncated
This commit is contained in:
parent
6e05863f02
commit
fa73cd1ddb
2 changed files with 26 additions and 4 deletions
|
|
@ -33,7 +33,10 @@ dimension directly.
|
||||||
heads of open PRs) and reports each one's merge outcome (PR merged /
|
heads of open PRs) and reports each one's merge outcome (PR merged /
|
||||||
closed unmerged / no PR at all) — squash-merge ancestry can't tell
|
closed unmerged / no PR at all) — squash-merge ancestry can't tell
|
||||||
those apart, so don't delete a stale branch on the age alone; check
|
those apart, so don't delete a stale branch on the age alone; check
|
||||||
the verdict first.
|
the verdict first. On a repo with enough PRs to exceed the verb's own
|
||||||
|
page cap, an old branch's outcome may come back "unknown" instead of
|
||||||
|
a verdict — that's not the same claim as "no PR", treat it as
|
||||||
|
"can't tell, don't delete" too.
|
||||||
|
|
||||||
## Using them as a sweep
|
## Using them as a sweep
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,12 @@ fn run_stale_branches(client: &Client, args: StaleBranchesArgs) -> Result<()> {
|
||||||
// hyperhive squash-merges, a merged branch's tip is never an
|
// hyperhive squash-merges, a merged branch's tip is never an
|
||||||
// ancestor of main, so ancestry can't tell these apart; this is the
|
// ancestor of main, so ancestry can't tell these apart; this is the
|
||||||
// only signal that can.
|
// only signal that can.
|
||||||
|
// Whether the walk below hit `MAX_PAGES` without ever seeing a
|
||||||
|
// short page — i.e. there are more PRs than the cap fetched, so
|
||||||
|
// `latest_pr_by_head` is missing an unknown number of the oldest
|
||||||
|
// ones. Distinct from "no PR was found": that's a claim this walk
|
||||||
|
// can only make honestly when it actually saw everything.
|
||||||
|
let mut prs_truncated = false;
|
||||||
let mut prs = Vec::new();
|
let mut prs = Vec::new();
|
||||||
for page in 1..=MAX_PAGES {
|
for page in 1..=MAX_PAGES {
|
||||||
let query = RepoListPullRequestsQuery {
|
let query = RepoListPullRequestsQuery {
|
||||||
|
|
@ -306,6 +312,9 @@ fn run_stale_branches(client: &Client, args: StaleBranchesArgs) -> Result<()> {
|
||||||
if short {
|
if short {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if page == MAX_PAGES {
|
||||||
|
prs_truncated = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let mut active_heads: std::collections::HashSet<String> = std::collections::HashSet::new();
|
let mut active_heads: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||||
// Highest-numbered (most recent) PR per head branch, in case a
|
// Highest-numbered (most recent) PR per head branch, in case a
|
||||||
|
|
@ -343,14 +352,20 @@ fn run_stale_branches(client: &Client, args: StaleBranchesArgs) -> Result<()> {
|
||||||
if age_days >= args.days {
|
if age_days >= args.days {
|
||||||
// A branch surviving to here can only have a *closed* PR
|
// A branch surviving to here can only have a *closed* PR
|
||||||
// (an open one would already be in `active_heads`), so
|
// (an open one would already be in `active_heads`), so
|
||||||
// `merged` is unambiguous when a PR exists at all.
|
// `merged` is unambiguous when a PR exists at all. But
|
||||||
|
// "no PR found" is only true when the PR walk above wasn't
|
||||||
|
// truncated — otherwise this branch's PR may simply be
|
||||||
|
// older than the page cap reached, and reporting "no PR"
|
||||||
|
// would misrepresent an unknown as a verified negative.
|
||||||
let pr = latest_pr_by_head.get(branch_name);
|
let pr = latest_pr_by_head.get(branch_name);
|
||||||
|
let outcome_unknown = pr.is_none() && prs_truncated;
|
||||||
stale.push(json!({
|
stale.push(json!({
|
||||||
"name": branch_name,
|
"name": branch_name,
|
||||||
"last_commit": rfc3339(Some(ts)),
|
"last_commit": rfc3339(Some(ts)),
|
||||||
"age_days": age_days,
|
"age_days": age_days,
|
||||||
"pr": pr.and_then(|p| p.number),
|
"pr": pr.and_then(|p| p.number),
|
||||||
"merged": pr.and_then(|p| p.merged),
|
"merged": pr.and_then(|p| p.merged),
|
||||||
|
"outcome_unknown": outcome_unknown,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -360,9 +375,13 @@ fn run_stale_branches(client: &Client, args: StaleBranchesArgs) -> Result<()> {
|
||||||
let verdict = match (
|
let verdict = match (
|
||||||
it.get("pr").and_then(Value::as_i64),
|
it.get("pr").and_then(Value::as_i64),
|
||||||
it.get("merged").and_then(Value::as_bool),
|
it.get("merged").and_then(Value::as_bool),
|
||||||
|
it.get("outcome_unknown")
|
||||||
|
.and_then(Value::as_bool)
|
||||||
|
.unwrap_or(false),
|
||||||
) {
|
) {
|
||||||
(Some(pr), Some(true)) => format!("PR #{pr} merged"),
|
(Some(pr), Some(true), _) => format!("PR #{pr} merged"),
|
||||||
(Some(pr), Some(false)) => format!("PR #{pr} closed, not merged"),
|
(Some(pr), Some(false), _) => format!("PR #{pr} closed, not merged"),
|
||||||
|
(_, _, true) => "unknown (PR history too large to fully scan)".to_string(),
|
||||||
_ => "no PR".to_string(),
|
_ => "no PR".to_string(),
|
||||||
};
|
};
|
||||||
format!("{name} ({age}d) — {verdict}")
|
format!("{name} ({age}d) — {verdict}")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue