|
71 | 71 | def parse_machine_state(content): |
72 | 72 | """Parse the [*] Machine State table from a state file. Returns a dict.""" |
73 | 73 | state = {} |
74 | | - m = re.search(r"## [*] Machine State.*?\n(.*?)(?=\n## |\Z)", content, re.DOTALL) |
| 74 | + m = re.search( |
| 75 | + r"##\s+(?:\[\*\]|\*)\s+Machine State.*?\n(.*?)(?=\n## |\Z)", |
| 76 | + content, |
| 77 | + re.DOTALL, |
| 78 | + ) |
75 | 79 | if not m: |
76 | 80 | return state |
77 | 81 | section = m.group(0) |
78 | 82 | for row in re.finditer(r"\|\s*(.+?)\s*\|\s*(.+?)\s*\|", section): |
79 | 83 | raw_key = row.group(1).strip() |
80 | 84 | raw_val = row.group(2).strip() |
81 | | - if raw_key.lower() in ("field", "---", ":---", ":---:", "---:"): |
| 85 | + if raw_key.lower() == "field" or re.fullmatch(r":?-+:?", raw_key): |
82 | 86 | continue |
83 | 87 | key = raw_key.lower().replace(" ", "_") |
84 | 88 | val = None if raw_val in ("--", "-", "") else raw_val |
@@ -231,6 +235,60 @@ def check_skip_conditions(state, issue_active=False): |
231 | 235 | return False, None |
232 | 236 |
|
233 | 237 |
|
| 238 | +def evaluate_completed_label_recovery( |
| 239 | + name, |
| 240 | + state, |
| 241 | + issue_active, |
| 242 | + issue_completed_label, |
| 243 | + repo, |
| 244 | + github_token, |
| 245 | + find_pr=None, |
| 246 | + check_gate=None, |
| 247 | +): |
| 248 | + """Return stale-completion recovery state for issue-based migrations. |
| 249 | +
|
| 250 | + Completed-label issues are only trustworthy when Crane can positively |
| 251 | + confirm the current PR-head gate. A missing PR, pending checks, failing |
| 252 | + checks, or unavailable gate evidence means the completed state is stale and |
| 253 | + the migration should be selected again. |
| 254 | + """ |
| 255 | + if find_pr is None: |
| 256 | + find_pr = find_existing_pr_for_branch |
| 257 | + if check_gate is None: |
| 258 | + check_gate = get_pr_head_check_gate |
| 259 | + |
| 260 | + has_stale_completed_state = issue_active and is_completed_state(state) |
| 261 | + recovered_completed_issue = False |
| 262 | + recovery_event = None |
| 263 | + |
| 264 | + if issue_completed_label and is_completed_state(state) and not issue_active: |
| 265 | + existing_pr_for_recovery = find_pr(repo, name, github_token) |
| 266 | + if existing_pr_for_recovery: |
| 267 | + gate_passed, gate_reason = check_gate( |
| 268 | + repo, existing_pr_for_recovery, github_token |
| 269 | + ) |
| 270 | + if gate_passed is True: |
| 271 | + recovery_event = ( |
| 272 | + "confirmed", |
| 273 | + existing_pr_for_recovery, |
| 274 | + gate_reason, |
| 275 | + ) |
| 276 | + else: |
| 277 | + has_stale_completed_state = True |
| 278 | + recovered_completed_issue = True |
| 279 | + recovery_event = ( |
| 280 | + "stale_gate", |
| 281 | + existing_pr_for_recovery, |
| 282 | + gate_reason or "gate-unavailable", |
| 283 | + ) |
| 284 | + else: |
| 285 | + has_stale_completed_state = True |
| 286 | + recovered_completed_issue = True |
| 287 | + recovery_event = ("stale_no_pr", None, "no-open-migration-pr") |
| 288 | + |
| 289 | + return has_stale_completed_state, recovered_completed_issue, recovery_event |
| 290 | + |
| 291 | + |
234 | 292 | # --------------------------------------------------------------------------- |
235 | 293 | # I/O helpers |
236 | 294 | # --------------------------------------------------------------------------- |
@@ -765,37 +823,33 @@ def main(): |
765 | 823 | else: |
766 | 824 | print(" {}: no state found (first run)".format(name)) |
767 | 825 |
|
768 | | - has_stale_completed_state = issue_active and is_completed_state(state) |
769 | | - recovered_completed_issue = False |
770 | | - if issue_completed_label and is_completed_state(state) and not issue_active: |
771 | | - existing_pr_for_recovery = find_existing_pr_for_branch(repo, name, github_token) |
772 | | - if existing_pr_for_recovery: |
773 | | - gate_passed, gate_reason = get_pr_head_check_gate( |
774 | | - repo, existing_pr_for_recovery, github_token |
775 | | - ) |
776 | | - if gate_passed is False: |
777 | | - has_stale_completed_state = True |
778 | | - recovered_completed_issue = True |
779 | | - print( |
780 | | - " {}: crane-completed label is stale; PR #{} gate is {}".format( |
781 | | - name, existing_pr_for_recovery, gate_reason |
782 | | - ) |
783 | | - ) |
784 | | - elif gate_passed is True: |
785 | | - print( |
786 | | - " {}: crane-completed label confirmed by PR #{} gate {}".format( |
787 | | - name, existing_pr_for_recovery, gate_reason |
788 | | - ) |
| 826 | + has_stale_completed_state, recovered_completed_issue, recovery_event = ( |
| 827 | + evaluate_completed_label_recovery( |
| 828 | + name, |
| 829 | + state, |
| 830 | + issue_active, |
| 831 | + issue_completed_label, |
| 832 | + repo, |
| 833 | + github_token, |
| 834 | + ) |
| 835 | + ) |
| 836 | + if recovery_event: |
| 837 | + event_kind, recovery_pr, gate_reason = recovery_event |
| 838 | + if event_kind == "confirmed": |
| 839 | + print( |
| 840 | + " {}: crane-completed label confirmed by PR #{} gate {}".format( |
| 841 | + name, recovery_pr, gate_reason |
789 | 842 | ) |
790 | | - else: |
791 | | - print( |
792 | | - " {}: could not evaluate completed-label recovery for PR #{} ({})".format( |
793 | | - name, existing_pr_for_recovery, gate_reason |
794 | | - ) |
| 843 | + ) |
| 844 | + elif event_kind == "stale_gate": |
| 845 | + print( |
| 846 | + " {}: crane-completed label is stale; PR #{} gate is {}".format( |
| 847 | + name, recovery_pr, gate_reason |
795 | 848 | ) |
796 | | - else: |
| 849 | + ) |
| 850 | + elif event_kind == "stale_no_pr": |
797 | 851 | print( |
798 | | - " {}: crane-completed label present, but no open migration PR was found".format( |
| 852 | + " {}: crane-completed label is stale; no open migration PR was found".format( |
799 | 853 | name |
800 | 854 | ) |
801 | 855 | ) |
|
0 commit comments