metrale_bench/gate/
telemetry_order.rs1use super::PrView;
15
16pub const CHART_PR_BOUND: usize = 10;
18
19fn contend(a: &PrView, b: &PrView) -> bool {
23 let a_gated = a.whole_repo || !a.targets.is_empty();
24 let b_gated = b.whole_repo || !b.targets.is_empty();
25 if (a.whole_repo && b_gated) || (b.whole_repo && a_gated) {
26 return true;
27 }
28 !a.targets.is_disjoint(&b.targets)
29}
30
31fn orderable(views: &[PrView]) -> Vec<&PrView> {
35 views
36 .iter()
37 .filter(|v| !v.facts.merged && !v.facts.draft && !v.facts.paths_unknown)
38 .collect()
39}
40
41pub fn merge_order(views: &[PrView]) -> Vec<u64> {
51 let open = orderable(views);
52 let mut ranked: Vec<(usize, usize, usize, u64)> = open
53 .iter()
54 .map(|v| {
55 let partners = open
56 .iter()
57 .filter(|o| o.facts.number != v.facts.number && contend(v, o))
58 .count();
59 let breadth = if v.whole_repo {
60 usize::MAX
61 } else {
62 v.targets.len()
63 };
64 (
65 partners,
66 breadth,
67 v.facts.changed_paths.len(),
68 v.facts.number,
69 )
70 })
71 .collect();
72 ranked.sort_unstable();
73 ranked.into_iter().map(|(_, _, _, n)| n).collect()
74}
75
76pub fn cap_prs(numbers: &[u64]) -> String {
79 let shown = numbers.len().min(CHART_PR_BOUND);
80 let mut s = numbers[..shown]
81 .iter()
82 .map(|n| format!("#{n}"))
83 .collect::<Vec<_>>()
84 .join(", ");
85 if numbers.len() > shown {
86 s.push_str(&format!(" … +{} more", numbers.len() - shown));
87 }
88 s
89}
90
91fn commit_label(v: &PrView) -> String {
96 const TITLE_CLIP: usize = 24;
97 let clean = v.facts.title.replace('"', "'").replace('\n', " ");
98 let clipped: String = clean.chars().take(TITLE_CLIP).collect();
99 let ellipsis = if clean.chars().count() > TITLE_CLIP {
100 "…"
101 } else {
102 ""
103 };
104 format!("#{} {}{}", v.facts.number, clipped, ellipsis)
105}
106
107pub fn render_order_chart(views: &[PrView]) -> String {
112 let order = merge_order(views);
113 if order.is_empty() {
114 if views
115 .iter()
116 .any(|v| !v.facts.merged && v.facts.paths_unknown)
117 {
118 return "_Nothing could be ordered: the changed files of the open PR(s) came \
119 back unreadable on this run._\n"
120 .to_string();
121 }
122 return "_No open, non-draft PRs to order._\n".to_string();
123 }
124 let shown = order.len().min(CHART_PR_BOUND);
125 let mut out = String::from("```mermaid\ngitGraph\n commit id: \"main\"\n");
126 for number in &order[..shown] {
127 let v = views
128 .iter()
129 .find(|v| v.facts.number == *number)
130 .expect("order is drawn from these views");
131 out.push_str(&format!(
132 " branch pr-{number}\n commit id: \"{}\"\n checkout main\n merge pr-{number}\n",
133 commit_label(v)
134 ));
135 }
136 out.push_str("```\n\n");
137 out.push_str(&format!(
138 "Showing {shown} of {} open PRs, left to right in recommended merge order.",
139 order.len()
140 ));
141 if order.len() > shown {
142 out.push_str(&format!(" Not charted: {}.", cap_prs(&order[shown..])));
143 }
144 out.push_str(
145 "\nOrder: fewest conflict partners first (a partner shares a gate target; \
146 a whole-repo diff contends with every gated PR), then fewest targets \
147 re-opened, then smallest diff, then lowest PR number. Drafts and merged \
148 PRs are not ranked.\n",
149 );
150 out
151}
152
153pub fn render_next_steps(views: &[PrView]) -> String {
159 let mut out = String::from("\n### Recommended next steps\n\n");
160 let order = merge_order(views);
161 match order.first() {
162 None if views
163 .iter()
164 .any(|v| !v.facts.merged && v.facts.paths_unknown) =>
165 {
166 out.push_str(
167 "- **No recommendation.** Every rankable PR's changed files came \
168 back unreadable on this run.\n",
169 )
170 }
171 None => out.push_str("- Nothing to merge: no open, non-draft PRs.\n"),
172 Some(head) => {
173 let v = views.iter().find(|v| v.facts.number == *head).unwrap();
174 out.push_str(&format!(
175 "- **Merge next: #{head}** ({}) — least disruptive open PR under the \
176 order rule above.\n",
177 super::escape(&v.facts.title)
178 ));
179 }
180 }
181
182 let blind: Vec<u64> = views
183 .iter()
184 .filter(|v| !v.facts.merged && v.facts.paths_unknown)
185 .map(|v| v.facts.number)
186 .collect();
187 if !blind.is_empty() {
188 out.push_str(&format!(
189 "- **Cannot rank {}:** their changed files came back unreadable, so this \
190 run has no input to order them by. They are counted as touching \
191 everything elsewhere in this comment; re-run before trusting the order.\n",
192 cap_prs(&blind)
193 ));
194 }
195
196 let merged: Vec<&PrView> = views.iter().filter(|v| v.facts.merged).collect();
197 let mut regate: Vec<u64> = Vec::new();
198 for v in views.iter().filter(|v| !v.facts.merged) {
199 if merged.iter().any(|m| contend(v, m)) {
200 regate.push(v.facts.number);
201 }
202 }
203 if !regate.is_empty() {
204 out.push_str(&format!(
205 "- **Re-gate before merging:** {} — a merged PR in this window moved a \
206 baseline they were measured against.\n",
207 cap_prs(®ate)
208 ));
209 }
210
211 let mut owing: Vec<u64> = merged
212 .iter()
213 .filter(|v| !v.promotion_debt.is_empty())
214 .map(|v| v.facts.number)
215 .collect();
216 owing.sort_unstable();
217 if !owing.is_empty() {
218 out.push_str(&format!(
219 "- **Discharge promotion debt:** merged {} shipped without a \
220 promotion-candidate gate (see the debt table below).\n",
221 cap_prs(&owing)
222 ));
223 }
224
225 out.push_str(
226 "\n_Derived only from changed paths and merge state. This section cannot \
227 see check or required-context status, review state, true git \
228 mergeability, or whether one PR unblocks another — those live on each \
229 PR._\n",
230 );
231 out
232}
233
234#[cfg(test)]
235#[path = "telemetry_order_tests.rs"]
236mod telemetry_order_tests;