gitgrip 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitgrip-1.5.0.dist-info/METADATA +13 -0
- gitgrip-1.5.0.dist-info/RECORD +80 -0
- gitgrip-1.5.0.dist-info/WHEEL +5 -0
- gitgrip-1.5.0.dist-info/entry_points.txt +2 -0
- gitgrip-1.5.0.dist-info/top_level.txt +2 -0
- gr2/__init__.py +0 -0
- gr2/overlay/__init__.py +6 -0
- gr2/overlay/activate.py +196 -0
- gr2/overlay/agent_manifest.py +138 -0
- gr2/overlay/cli.py +181 -0
- gr2/overlay/cross_repo.py +124 -0
- gr2/overlay/drivers.py +113 -0
- gr2/overlay/introspection.py +155 -0
- gr2/overlay/language_drivers.py +115 -0
- gr2/overlay/objects.py +412 -0
- gr2/overlay/perf.py +251 -0
- gr2/overlay/refs.py +36 -0
- gr2/overlay/trust.py +150 -0
- gr2/overlay/types.py +69 -0
- gr2/overlay/units.py +313 -0
- gr2/overlay/workspace_spec.py +59 -0
- gr2/prototypes/__init__.py +0 -0
- gr2/prototypes/cache_materialization_probe.py +190 -0
- gr2/prototypes/concurrent_event_stress.py +199 -0
- gr2/prototypes/concurrent_lease_stress.py +240 -0
- gr2/prototypes/concurrent_workspace_cap_stress.py +231 -0
- gr2/prototypes/contribution_protocol.py +665 -0
- gr2/prototypes/cross_mode_lane_stress.py +986 -0
- gr2/prototypes/jsonl_store.py +158 -0
- gr2/prototypes/lane_workspace_prototype.py +2088 -0
- gr2/prototypes/layout_model_probe.py +139 -0
- gr2/prototypes/propagation_daemon.py +546 -0
- gr2/prototypes/propagation_state_machine.py +1478 -0
- gr2/prototypes/python_exec_playground.py +194 -0
- gr2/prototypes/python_hook_runtime_playground.py +240 -0
- gr2/prototypes/python_migration_playground.py +144 -0
- gr2/prototypes/python_review_checkout_playground.py +242 -0
- gr2/prototypes/python_spec_apply_playground.py +282 -0
- gr2/prototypes/real_git_lane_materialization.py +248 -0
- gr2/prototypes/real_git_playground.py +334 -0
- gr2/prototypes/recall_lane_history.py +274 -0
- gr2/prototypes/repo_maintenance_prototype.py +659 -0
- gr2/prototypes/repo_transport_probe.py +147 -0
- gr2/python_cli/__init__.py +2 -0
- gr2/python_cli/__main__.py +6 -0
- gr2/python_cli/add.py +51 -0
- gr2/python_cli/app.py +2516 -0
- gr2/python_cli/branch.py +67 -0
- gr2/python_cli/channel_bridge.py +131 -0
- gr2/python_cli/clone_exec.py +1019 -0
- gr2/python_cli/commit.py +199 -0
- gr2/python_cli/config.py +291 -0
- gr2/python_cli/env_exec.py +419 -0
- gr2/python_cli/events.py +529 -0
- gr2/python_cli/execops.py +372 -0
- gr2/python_cli/failures.py +98 -0
- gr2/python_cli/file_exec.py +256 -0
- gr2/python_cli/gitops.py +226 -0
- gr2/python_cli/grip.py +1337 -0
- gr2/python_cli/grip_cli.py +493 -0
- gr2/python_cli/hooks.py +450 -0
- gr2/python_cli/launch_exec.py +786 -0
- gr2/python_cli/merge_verification.py +274 -0
- gr2/python_cli/migration.py +985 -0
- gr2/python_cli/open_gr_review.py +699 -0
- gr2/python_cli/platform.py +441 -0
- gr2/python_cli/pr.py +487 -0
- gr2/python_cli/project_review.py +314 -0
- gr2/python_cli/prune.py +365 -0
- gr2/python_cli/push.py +172 -0
- gr2/python_cli/review.py +462 -0
- gr2/python_cli/review_ephemeral.py +143 -0
- gr2/python_cli/review_run.py +621 -0
- gr2/python_cli/spec_apply.py +1285 -0
- gr2/python_cli/staging_cleanup.py +205 -0
- gr2/python_cli/syncops.py +920 -0
- gr2/python_cli/target.py +100 -0
- gr2/python_cli/workspace_snapshot.py +105 -0
- gr2/schemas/gr2-materialization-plan-v1.schema.json +191 -0
- gr2_overlay/__init__.py +37 -0
gr2/python_cli/pr.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"""gr2 PR group orchestration.
|
|
2
|
+
|
|
3
|
+
Implements multi-repo PR lifecycle from PR-LIFECYCLE.md:
|
|
4
|
+
- create_pr_group: Create linked PRs across repos with pr_group_id
|
|
5
|
+
- merge_pr_group: Merge all PRs in a group (stops on first failure)
|
|
6
|
+
- check_pr_group_status: Poll status/checks and emit change events
|
|
7
|
+
- record_pr_review: Record an externally-submitted review event
|
|
8
|
+
|
|
9
|
+
The PlatformAdapter is group-unaware. This module assigns pr_group_id,
|
|
10
|
+
persists group metadata, and emits events per HOOK-EVENT-CONTRACT.md
|
|
11
|
+
section 3.2 (PR Lifecycle).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
from collections.abc import Callable, Mapping
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from .events import EventType, emit, emit_after_outcome
|
|
23
|
+
from .merge_verification import (
|
|
24
|
+
CompletedMerge,
|
|
25
|
+
MergeVerificationTarget,
|
|
26
|
+
consume_merge_receipt,
|
|
27
|
+
)
|
|
28
|
+
from .platform import (
|
|
29
|
+
AdapterError,
|
|
30
|
+
CreatePRRequest,
|
|
31
|
+
MergeEvidenceError,
|
|
32
|
+
MergeMethod,
|
|
33
|
+
MergeReceipt,
|
|
34
|
+
PlatformAdapter,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"MergeMethod",
|
|
39
|
+
"PRMergeError",
|
|
40
|
+
"PRMergeOutcomeUnknownError",
|
|
41
|
+
"PRMergePostconditionError",
|
|
42
|
+
"UnpermittedMergeMethodError",
|
|
43
|
+
"check_pr_group_status",
|
|
44
|
+
"create_pr_group",
|
|
45
|
+
"merge_pr_group",
|
|
46
|
+
"record_pr_review",
|
|
47
|
+
"resolve_merge_method",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class UnpermittedMergeMethodError(RuntimeError):
|
|
52
|
+
"""The requested method is known, but workspace policy does not permit it."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, requested: MergeMethod, permitted: list[str]) -> None:
|
|
55
|
+
self.requested = requested
|
|
56
|
+
self.permitted = list(permitted)
|
|
57
|
+
allowed = ", ".join(self.permitted) or "none"
|
|
58
|
+
super().__init__(
|
|
59
|
+
f"merge method {requested.value!r} is not permitted here "
|
|
60
|
+
f"(permitted: {allowed}); refusing rather than substituting another method"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _parse_method(name: str, *, source: str) -> MergeMethod:
|
|
65
|
+
try:
|
|
66
|
+
return MergeMethod(name)
|
|
67
|
+
except ValueError:
|
|
68
|
+
expected = ", ".join(method.value for method in MergeMethod)
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"unrecognised merge method {name!r} from {source} "
|
|
71
|
+
f"(expected one of: {expected}); refusing rather than falling back"
|
|
72
|
+
) from None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def resolve_merge_method(
|
|
76
|
+
explicit: str | None = None,
|
|
77
|
+
configured: str | None = None,
|
|
78
|
+
permitted: list[str] | None = None,
|
|
79
|
+
) -> MergeMethod:
|
|
80
|
+
"""Resolve explicit, then configured, then merge-commit strategy."""
|
|
81
|
+
|
|
82
|
+
if explicit is not None:
|
|
83
|
+
chosen = _parse_method(explicit, source="--method")
|
|
84
|
+
elif configured is not None:
|
|
85
|
+
chosen = _parse_method(configured, source="workspace setting")
|
|
86
|
+
else:
|
|
87
|
+
chosen = MergeMethod.MERGE
|
|
88
|
+
|
|
89
|
+
if permitted is not None and chosen.value not in permitted:
|
|
90
|
+
raise UnpermittedMergeMethodError(chosen, permitted)
|
|
91
|
+
return chosen
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class PRMergeError(RuntimeError):
|
|
95
|
+
"""A merge command failed, carrying prior completed host operations."""
|
|
96
|
+
|
|
97
|
+
operation_acknowledged = False
|
|
98
|
+
outcome_unknown = False
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
repo: str,
|
|
103
|
+
pr_number: int,
|
|
104
|
+
reason: str,
|
|
105
|
+
*,
|
|
106
|
+
completed: list[CompletedMerge],
|
|
107
|
+
) -> None:
|
|
108
|
+
self.repo = repo
|
|
109
|
+
self.pr_number = pr_number
|
|
110
|
+
self.reason = reason
|
|
111
|
+
self.completed = list(completed)
|
|
112
|
+
already = ", ".join(item.receipt.observed.repo for item in self.completed)
|
|
113
|
+
suffix = (
|
|
114
|
+
f" (ALREADY MERGED, do not retry: {already})"
|
|
115
|
+
if self.completed
|
|
116
|
+
else " (nothing had merged yet)"
|
|
117
|
+
)
|
|
118
|
+
super().__init__(f"merge failed for {repo}#{pr_number}: {reason}{suffix}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class PRMergeOutcomeUnknownError(PRMergeError):
|
|
122
|
+
"""The host acknowledged the command but no immutable receipt was available."""
|
|
123
|
+
|
|
124
|
+
operation_acknowledged = True
|
|
125
|
+
outcome_unknown = True
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
repo: str,
|
|
130
|
+
pr_number: int,
|
|
131
|
+
reason: str,
|
|
132
|
+
*,
|
|
133
|
+
completed: list[CompletedMerge],
|
|
134
|
+
) -> None:
|
|
135
|
+
self.repo = repo
|
|
136
|
+
self.pr_number = pr_number
|
|
137
|
+
self.reason = reason
|
|
138
|
+
self.completed = list(completed)
|
|
139
|
+
already = ", ".join(item.receipt.observed.repo for item in self.completed)
|
|
140
|
+
suffix = f"; earlier completed: {already}" if already else ""
|
|
141
|
+
RuntimeError.__init__(
|
|
142
|
+
self,
|
|
143
|
+
f"merge outcome unknown for {repo}#{pr_number}: {reason}; "
|
|
144
|
+
f"the command was acknowledged, so do not retry{suffix}",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class PRMergePostconditionError(PRMergeError):
|
|
149
|
+
"""The merge is known to have happened but its evidence was not consumed."""
|
|
150
|
+
|
|
151
|
+
operation_acknowledged = True
|
|
152
|
+
outcome_unknown = False
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
receipt: MergeReceipt,
|
|
157
|
+
reason: str,
|
|
158
|
+
*,
|
|
159
|
+
completed: list[CompletedMerge],
|
|
160
|
+
) -> None:
|
|
161
|
+
observed = receipt.observed
|
|
162
|
+
self.receipt = receipt
|
|
163
|
+
self.repo = str(observed.repo)
|
|
164
|
+
self.pr_number = int(observed.number)
|
|
165
|
+
self.reason = reason
|
|
166
|
+
self.completed = list(completed)
|
|
167
|
+
already = ", ".join(item.receipt.observed.repo for item in self.completed)
|
|
168
|
+
suffix = f"; earlier completed: {already}" if already else ""
|
|
169
|
+
RuntimeError.__init__(
|
|
170
|
+
self,
|
|
171
|
+
f"merge postcondition failed for {self.repo}#{self.pr_number}: {reason}; "
|
|
172
|
+
f"the host merge is acknowledged, so do not retry{suffix}",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _pr_groups_dir(workspace_root: Path) -> Path:
|
|
177
|
+
return workspace_root / ".grip" / "pr_groups"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _generate_group_id() -> str:
|
|
181
|
+
return "pg_" + os.urandom(4).hex()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _load_group(workspace_root: Path, pr_group_id: str) -> dict:
|
|
185
|
+
path = _pr_groups_dir(workspace_root) / f"{pr_group_id}.json"
|
|
186
|
+
return json.loads(path.read_text())
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _save_group(workspace_root: Path, group: dict) -> Path:
|
|
190
|
+
d = _pr_groups_dir(workspace_root)
|
|
191
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
192
|
+
path = d / f"{group['pr_group_id']}.json"
|
|
193
|
+
path.write_text(json.dumps(group, indent=2))
|
|
194
|
+
return path
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def create_pr_group(
|
|
198
|
+
workspace_root: Path,
|
|
199
|
+
owner_unit: str,
|
|
200
|
+
lane_name: str,
|
|
201
|
+
title: str,
|
|
202
|
+
base_branch: str,
|
|
203
|
+
head_branch: str,
|
|
204
|
+
repos: list[str],
|
|
205
|
+
adapter: PlatformAdapter,
|
|
206
|
+
actor: str,
|
|
207
|
+
*,
|
|
208
|
+
body: str = "",
|
|
209
|
+
draft: bool = False,
|
|
210
|
+
) -> dict:
|
|
211
|
+
"""Create linked PRs across repos and emit pr.created."""
|
|
212
|
+
pr_group_id = _generate_group_id()
|
|
213
|
+
prs: list[dict] = []
|
|
214
|
+
|
|
215
|
+
for repo in repos:
|
|
216
|
+
request = CreatePRRequest(
|
|
217
|
+
repo=repo,
|
|
218
|
+
title=title,
|
|
219
|
+
body=body,
|
|
220
|
+
head_branch=head_branch,
|
|
221
|
+
base_branch=base_branch,
|
|
222
|
+
draft=draft,
|
|
223
|
+
)
|
|
224
|
+
ref = adapter.create_pr(request)
|
|
225
|
+
prs.append({"repo": repo, "pr_number": ref.number, "url": ref.url})
|
|
226
|
+
|
|
227
|
+
group = {
|
|
228
|
+
"pr_group_id": pr_group_id,
|
|
229
|
+
"owner_unit": owner_unit,
|
|
230
|
+
"lane_name": lane_name,
|
|
231
|
+
"title": title,
|
|
232
|
+
"base_branch": base_branch,
|
|
233
|
+
"head_branch": head_branch,
|
|
234
|
+
"platform": getattr(adapter, "name", "github"),
|
|
235
|
+
"prs": prs,
|
|
236
|
+
"status": {repo: "OPEN" for repo in repos},
|
|
237
|
+
}
|
|
238
|
+
path = _save_group(workspace_root, group)
|
|
239
|
+
|
|
240
|
+
emit_after_outcome(
|
|
241
|
+
event_type=EventType.PR_CREATED,
|
|
242
|
+
workspace_root=workspace_root,
|
|
243
|
+
actor=actor,
|
|
244
|
+
owner_unit=owner_unit,
|
|
245
|
+
payload={"pr_group_id": pr_group_id, "lane_name": lane_name, "repos": prs},
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
group["state_path"] = str(path)
|
|
249
|
+
return group
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def merge_pr_group(
|
|
253
|
+
workspace_root: Path,
|
|
254
|
+
pr_group_id: str,
|
|
255
|
+
adapter: PlatformAdapter,
|
|
256
|
+
actor: str,
|
|
257
|
+
*,
|
|
258
|
+
method: MergeMethod,
|
|
259
|
+
verification_targets: Mapping[str, MergeVerificationTarget],
|
|
260
|
+
report: Callable[[str], Any],
|
|
261
|
+
) -> dict:
|
|
262
|
+
"""Merge all PRs, consuming host evidence before recording completion."""
|
|
263
|
+
group = _load_group(workspace_root, pr_group_id)
|
|
264
|
+
merged: list[CompletedMerge] = []
|
|
265
|
+
targets = dict(verification_targets)
|
|
266
|
+
|
|
267
|
+
missing_targets = [
|
|
268
|
+
str(item["repo"]) for item in group["prs"] if str(item["repo"]) not in targets
|
|
269
|
+
]
|
|
270
|
+
if missing_targets:
|
|
271
|
+
raise ValueError(
|
|
272
|
+
"merge verification has no explicit local target for: "
|
|
273
|
+
+ ", ".join(missing_targets)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
for pr_info in group["prs"]:
|
|
277
|
+
repo = str(pr_info["repo"])
|
|
278
|
+
number = int(pr_info["pr_number"])
|
|
279
|
+
try:
|
|
280
|
+
receipt = adapter.merge_pr(repo, number, method=method)
|
|
281
|
+
except MergeEvidenceError as exc:
|
|
282
|
+
_record_merge_failure(
|
|
283
|
+
workspace_root=workspace_root,
|
|
284
|
+
group=group,
|
|
285
|
+
actor=actor,
|
|
286
|
+
repo=repo,
|
|
287
|
+
number=number,
|
|
288
|
+
reason=str(exc),
|
|
289
|
+
completed=merged,
|
|
290
|
+
operation_acknowledged=True,
|
|
291
|
+
)
|
|
292
|
+
raise PRMergeOutcomeUnknownError(
|
|
293
|
+
repo,
|
|
294
|
+
number,
|
|
295
|
+
str(exc),
|
|
296
|
+
completed=merged,
|
|
297
|
+
) from exc
|
|
298
|
+
except AdapterError as exc:
|
|
299
|
+
_record_merge_failure(
|
|
300
|
+
workspace_root=workspace_root,
|
|
301
|
+
group=group,
|
|
302
|
+
actor=actor,
|
|
303
|
+
repo=repo,
|
|
304
|
+
number=number,
|
|
305
|
+
reason=str(exc),
|
|
306
|
+
completed=merged,
|
|
307
|
+
operation_acknowledged=False,
|
|
308
|
+
)
|
|
309
|
+
raise PRMergeError(repo, number, str(exc), completed=merged) from exc
|
|
310
|
+
|
|
311
|
+
target = targets[repo]
|
|
312
|
+
try:
|
|
313
|
+
completed = consume_merge_receipt(
|
|
314
|
+
repo_root=target.repo_root,
|
|
315
|
+
receipt=receipt,
|
|
316
|
+
remote=target.remote,
|
|
317
|
+
report=report,
|
|
318
|
+
)
|
|
319
|
+
except Exception as exc: # noqa: BLE001 - host operation already happened
|
|
320
|
+
reason = f"could not consume merge evidence: {exc}"
|
|
321
|
+
_record_merge_failure(
|
|
322
|
+
workspace_root=workspace_root,
|
|
323
|
+
group=group,
|
|
324
|
+
actor=actor,
|
|
325
|
+
repo=repo,
|
|
326
|
+
number=number,
|
|
327
|
+
reason=reason,
|
|
328
|
+
completed=merged,
|
|
329
|
+
operation_acknowledged=True,
|
|
330
|
+
)
|
|
331
|
+
raise PRMergePostconditionError(
|
|
332
|
+
receipt,
|
|
333
|
+
reason,
|
|
334
|
+
completed=merged,
|
|
335
|
+
) from exc
|
|
336
|
+
merged.append(completed)
|
|
337
|
+
|
|
338
|
+
records = _completed_records(merged)
|
|
339
|
+
|
|
340
|
+
group["completed"] = records
|
|
341
|
+
group["group_state"] = "merged"
|
|
342
|
+
_save_group(workspace_root, group)
|
|
343
|
+
|
|
344
|
+
emit_after_outcome(
|
|
345
|
+
event_type=EventType.PR_MERGED,
|
|
346
|
+
workspace_root=workspace_root,
|
|
347
|
+
actor=actor,
|
|
348
|
+
owner_unit=group.get("owner_unit", actor),
|
|
349
|
+
payload={"pr_group_id": pr_group_id, "repos": records},
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return group
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _record_merge_failure(
|
|
356
|
+
*,
|
|
357
|
+
workspace_root: Path,
|
|
358
|
+
group: dict[str, object],
|
|
359
|
+
actor: str,
|
|
360
|
+
repo: str,
|
|
361
|
+
number: int,
|
|
362
|
+
reason: str,
|
|
363
|
+
completed: list[CompletedMerge],
|
|
364
|
+
operation_acknowledged: bool,
|
|
365
|
+
) -> None:
|
|
366
|
+
"""Best-effort durable layer; never replaces the in-process error."""
|
|
367
|
+
|
|
368
|
+
try:
|
|
369
|
+
emit(
|
|
370
|
+
event_type=EventType.PR_MERGE_FAILED,
|
|
371
|
+
workspace_root=workspace_root,
|
|
372
|
+
actor=actor,
|
|
373
|
+
owner_unit=str(group.get("owner_unit", actor)),
|
|
374
|
+
payload={
|
|
375
|
+
"pr_group_id": group["pr_group_id"],
|
|
376
|
+
"repo": repo,
|
|
377
|
+
"pr_number": number,
|
|
378
|
+
"reason": reason,
|
|
379
|
+
"completed": _completed_records(completed),
|
|
380
|
+
"operation_acknowledged": operation_acknowledged,
|
|
381
|
+
},
|
|
382
|
+
)
|
|
383
|
+
except Exception as emit_exc: # noqa: BLE001 - event logging is best effort
|
|
384
|
+
print(
|
|
385
|
+
f"gr2: could not record partial merge ({emit_exc}); "
|
|
386
|
+
"the completed list remains on the raised error",
|
|
387
|
+
file=sys.stderr,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _completed_records(completed: list[CompletedMerge]) -> list[dict[str, object]]:
|
|
392
|
+
"""Flatten earned evidence only at a JSON serialization boundary."""
|
|
393
|
+
|
|
394
|
+
return [item.as_dict() for item in completed]
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def check_pr_group_status(
|
|
398
|
+
workspace_root: Path,
|
|
399
|
+
pr_group_id: str,
|
|
400
|
+
adapter: PlatformAdapter,
|
|
401
|
+
actor: str,
|
|
402
|
+
) -> dict:
|
|
403
|
+
"""Poll PR status/checks for all repos in a group. Emit change events."""
|
|
404
|
+
group = _load_group(workspace_root, pr_group_id)
|
|
405
|
+
cached_status = group.get("status", {})
|
|
406
|
+
|
|
407
|
+
for pr_info in group["prs"]:
|
|
408
|
+
repo = pr_info["repo"]
|
|
409
|
+
number = pr_info["pr_number"]
|
|
410
|
+
status = adapter.pr_status(repo, number)
|
|
411
|
+
old_state = cached_status.get(repo, "OPEN")
|
|
412
|
+
|
|
413
|
+
if status.state != old_state:
|
|
414
|
+
emit(
|
|
415
|
+
event_type=EventType.PR_STATUS_CHANGED,
|
|
416
|
+
workspace_root=workspace_root,
|
|
417
|
+
actor=actor,
|
|
418
|
+
owner_unit=group.get("owner_unit", actor),
|
|
419
|
+
payload={
|
|
420
|
+
"pr_group_id": pr_group_id,
|
|
421
|
+
"repo": repo,
|
|
422
|
+
"pr_number": number,
|
|
423
|
+
"old_status": old_state,
|
|
424
|
+
"new_status": status.state,
|
|
425
|
+
},
|
|
426
|
+
)
|
|
427
|
+
cached_status[repo] = status.state
|
|
428
|
+
|
|
429
|
+
if status.checks:
|
|
430
|
+
completed = [c for c in status.checks if c.status == "COMPLETED"]
|
|
431
|
+
if completed and len(completed) == len(status.checks):
|
|
432
|
+
failed = [c.name for c in completed if c.conclusion != "SUCCESS"]
|
|
433
|
+
if failed:
|
|
434
|
+
emit(
|
|
435
|
+
event_type=EventType.PR_CHECKS_FAILED,
|
|
436
|
+
workspace_root=workspace_root,
|
|
437
|
+
actor=actor,
|
|
438
|
+
owner_unit=group.get("owner_unit", actor),
|
|
439
|
+
payload={
|
|
440
|
+
"pr_group_id": pr_group_id,
|
|
441
|
+
"repo": repo,
|
|
442
|
+
"pr_number": number,
|
|
443
|
+
"failed_checks": failed,
|
|
444
|
+
},
|
|
445
|
+
)
|
|
446
|
+
else:
|
|
447
|
+
emit(
|
|
448
|
+
event_type=EventType.PR_CHECKS_PASSED,
|
|
449
|
+
workspace_root=workspace_root,
|
|
450
|
+
actor=actor,
|
|
451
|
+
owner_unit=group.get("owner_unit", actor),
|
|
452
|
+
payload={
|
|
453
|
+
"pr_group_id": pr_group_id,
|
|
454
|
+
"repo": repo,
|
|
455
|
+
"pr_number": number,
|
|
456
|
+
"passed_checks": [c.name for c in completed],
|
|
457
|
+
},
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
group["status"] = cached_status
|
|
461
|
+
_save_group(workspace_root, group)
|
|
462
|
+
return group
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def record_pr_review(
|
|
466
|
+
workspace_root: Path,
|
|
467
|
+
pr_group_id: str,
|
|
468
|
+
repo: str,
|
|
469
|
+
pr_number: int,
|
|
470
|
+
reviewer: str,
|
|
471
|
+
state: str,
|
|
472
|
+
actor: str,
|
|
473
|
+
) -> None:
|
|
474
|
+
"""Record an externally-submitted PR review and emit pr.review_submitted."""
|
|
475
|
+
emit(
|
|
476
|
+
event_type=EventType.PR_REVIEW_SUBMITTED,
|
|
477
|
+
workspace_root=workspace_root,
|
|
478
|
+
actor=actor,
|
|
479
|
+
owner_unit=actor,
|
|
480
|
+
payload={
|
|
481
|
+
"pr_group_id": pr_group_id,
|
|
482
|
+
"repo": repo,
|
|
483
|
+
"pr_number": pr_number,
|
|
484
|
+
"reviewer": reviewer,
|
|
485
|
+
"state": state,
|
|
486
|
+
},
|
|
487
|
+
)
|