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/hooks.py
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
import tomllib
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .events import EventType, emit, emit_after_outcome
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
VALID_IF_EXISTS = {"skip", "overwrite", "merge", "error"}
|
|
15
|
+
VALID_ON_FAILURE = {"block", "warn", "skip"}
|
|
16
|
+
VALID_WHEN = {"first_materialize", "always", "dirty", "manual"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclasses.dataclass(frozen=True)
|
|
20
|
+
class FileProjection:
|
|
21
|
+
kind: str
|
|
22
|
+
src: str
|
|
23
|
+
dest: str
|
|
24
|
+
if_exists: str = "error"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclasses.dataclass(frozen=True)
|
|
28
|
+
class LifecycleHook:
|
|
29
|
+
stage: str
|
|
30
|
+
name: str
|
|
31
|
+
command: str
|
|
32
|
+
cwd: str
|
|
33
|
+
when: str
|
|
34
|
+
on_failure: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclasses.dataclass(frozen=True)
|
|
38
|
+
class RepoHooks:
|
|
39
|
+
repo_name: str | None
|
|
40
|
+
file_links: list[FileProjection]
|
|
41
|
+
file_copies: list[FileProjection]
|
|
42
|
+
on_materialize: list[LifecycleHook]
|
|
43
|
+
on_enter: list[LifecycleHook]
|
|
44
|
+
on_exit: list[LifecycleHook]
|
|
45
|
+
policy: dict[str, object]
|
|
46
|
+
path: Path
|
|
47
|
+
|
|
48
|
+
def as_dict(self) -> dict[str, object]:
|
|
49
|
+
return {
|
|
50
|
+
"repo_name": self.repo_name,
|
|
51
|
+
"path": str(self.path),
|
|
52
|
+
"files": {
|
|
53
|
+
"link": [dataclasses.asdict(item) for item in self.file_links],
|
|
54
|
+
"copy": [dataclasses.asdict(item) for item in self.file_copies],
|
|
55
|
+
},
|
|
56
|
+
"lifecycle": {
|
|
57
|
+
"on_materialize": [dataclasses.asdict(item) for item in self.on_materialize],
|
|
58
|
+
"on_enter": [dataclasses.asdict(item) for item in self.on_enter],
|
|
59
|
+
"on_exit": [dataclasses.asdict(item) for item in self.on_exit],
|
|
60
|
+
},
|
|
61
|
+
"policy": self.policy,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclasses.dataclass(frozen=True)
|
|
66
|
+
class HookContext:
|
|
67
|
+
workspace_root: Path
|
|
68
|
+
unit_root: Path
|
|
69
|
+
lane_root: Path
|
|
70
|
+
repo_root: Path
|
|
71
|
+
repo_name: str
|
|
72
|
+
lane_owner: str
|
|
73
|
+
lane_subject: str
|
|
74
|
+
lane_name: str
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclasses.dataclass(frozen=True)
|
|
78
|
+
class HookResult:
|
|
79
|
+
kind: str
|
|
80
|
+
name: str
|
|
81
|
+
status: str
|
|
82
|
+
detail: str
|
|
83
|
+
cwd: str | None = None
|
|
84
|
+
command: str | None = None
|
|
85
|
+
returncode: int | None = None
|
|
86
|
+
stdout: str | None = None
|
|
87
|
+
stderr: str | None = None
|
|
88
|
+
src: str | None = None
|
|
89
|
+
dest: str | None = None
|
|
90
|
+
if_exists: str | None = None
|
|
91
|
+
|
|
92
|
+
def as_dict(self) -> dict[str, object]:
|
|
93
|
+
return dataclasses.asdict(self)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class HookRuntimeError(SystemExit):
|
|
97
|
+
def __init__(self, payload: dict[str, object]) -> None:
|
|
98
|
+
self.payload = payload
|
|
99
|
+
super().__init__(json.dumps(payload, indent=2))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def hook_file(repo_root: Path) -> Path:
|
|
103
|
+
return repo_root / ".gr2" / "hooks.toml"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def load_repo_hooks(repo_root: Path) -> RepoHooks | None:
|
|
107
|
+
path = hook_file(repo_root)
|
|
108
|
+
if not path.exists():
|
|
109
|
+
return None
|
|
110
|
+
with path.open("rb") as fh:
|
|
111
|
+
raw = tomllib.load(fh)
|
|
112
|
+
return RepoHooks(
|
|
113
|
+
repo_name=raw.get("repo", {}).get("name"),
|
|
114
|
+
file_links=_parse_projections(raw, "link"),
|
|
115
|
+
file_copies=_parse_projections(raw, "copy"),
|
|
116
|
+
on_materialize=_parse_lifecycle(raw, "on_materialize", default_on_failure="block"),
|
|
117
|
+
on_enter=_parse_lifecycle(raw, "on_enter", default_on_failure="warn"),
|
|
118
|
+
on_exit=_parse_lifecycle(raw, "on_exit", default_on_failure="warn"),
|
|
119
|
+
policy=dict(raw.get("policy", {})),
|
|
120
|
+
path=path,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _parse_projections(raw: dict, kind: str) -> list[FileProjection]:
|
|
125
|
+
items = raw.get("files", {}).get(kind, [])
|
|
126
|
+
results: list[FileProjection] = []
|
|
127
|
+
for item in items:
|
|
128
|
+
if_exists = str(item.get("if_exists", "error"))
|
|
129
|
+
if if_exists not in VALID_IF_EXISTS:
|
|
130
|
+
raise SystemExit(f"invalid if_exists={if_exists} in {kind} projection")
|
|
131
|
+
results.append(
|
|
132
|
+
FileProjection(
|
|
133
|
+
kind=kind,
|
|
134
|
+
src=str(item["src"]),
|
|
135
|
+
dest=str(item["dest"]),
|
|
136
|
+
if_exists=if_exists,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
return results
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _parse_lifecycle(raw: dict, stage: str, default_on_failure: str) -> list[LifecycleHook]:
|
|
143
|
+
items = raw.get("lifecycle", {}).get(stage, [])
|
|
144
|
+
results: list[LifecycleHook] = []
|
|
145
|
+
for item in items:
|
|
146
|
+
when = str(item.get("when", "always"))
|
|
147
|
+
on_failure = str(item.get("on_failure", default_on_failure))
|
|
148
|
+
if when not in VALID_WHEN:
|
|
149
|
+
raise SystemExit(f"invalid when={when} in lifecycle.{stage}")
|
|
150
|
+
if on_failure not in VALID_ON_FAILURE:
|
|
151
|
+
raise SystemExit(f"invalid on_failure={on_failure} in lifecycle.{stage}")
|
|
152
|
+
results.append(
|
|
153
|
+
LifecycleHook(
|
|
154
|
+
stage=stage,
|
|
155
|
+
name=str(item["name"]),
|
|
156
|
+
command=str(item["command"]),
|
|
157
|
+
cwd=str(item.get("cwd", "{repo_root}")),
|
|
158
|
+
when=when,
|
|
159
|
+
on_failure=on_failure,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
return results
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def render_path(template: str, ctx: HookContext) -> Path:
|
|
166
|
+
rendered = render_text(template, ctx)
|
|
167
|
+
return Path(rendered)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def render_text(template: str, ctx: HookContext) -> str:
|
|
171
|
+
import re
|
|
172
|
+
|
|
173
|
+
result = (
|
|
174
|
+
template.replace("{workspace_root}", str(ctx.workspace_root))
|
|
175
|
+
.replace("{unit_root}", str(ctx.unit_root))
|
|
176
|
+
.replace("{lane_root}", str(ctx.lane_root))
|
|
177
|
+
.replace("{repo_root}", str(ctx.repo_root))
|
|
178
|
+
.replace("{repo_name}", ctx.repo_name)
|
|
179
|
+
.replace("{lane_owner}", ctx.lane_owner)
|
|
180
|
+
.replace("{lane_subject}", ctx.lane_subject)
|
|
181
|
+
.replace("{lane_name}", ctx.lane_name)
|
|
182
|
+
)
|
|
183
|
+
remaining = re.findall(r"\{(\w+)\}", result)
|
|
184
|
+
if remaining:
|
|
185
|
+
raise ValueError(
|
|
186
|
+
f"undefined template variable(s): {', '.join('{' + v + '}' for v in remaining)}"
|
|
187
|
+
)
|
|
188
|
+
return result
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def apply_file_projections(hooks: RepoHooks, ctx: HookContext) -> list[HookResult]:
|
|
192
|
+
results: list[HookResult] = []
|
|
193
|
+
for item in [*hooks.file_links, *hooks.file_copies]:
|
|
194
|
+
rendered_src = render_text(item.src, ctx)
|
|
195
|
+
src = Path(rendered_src)
|
|
196
|
+
if not src.is_absolute():
|
|
197
|
+
src = ctx.repo_root / src
|
|
198
|
+
dest = render_path(item.dest, ctx)
|
|
199
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
|
|
201
|
+
if not src.exists():
|
|
202
|
+
raise HookRuntimeError(
|
|
203
|
+
{
|
|
204
|
+
"kind": "projection",
|
|
205
|
+
"projection": item.kind,
|
|
206
|
+
"status": "blocked",
|
|
207
|
+
"detail": f"projection source does not exist: {src}",
|
|
208
|
+
"repo_hooks_path": str(hooks.path),
|
|
209
|
+
"src": str(src),
|
|
210
|
+
"dest": str(dest),
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if dest.exists() or dest.is_symlink():
|
|
215
|
+
if item.if_exists == "skip":
|
|
216
|
+
results.append(
|
|
217
|
+
HookResult(
|
|
218
|
+
kind="projection",
|
|
219
|
+
name=f"{item.kind}:{dest.name}",
|
|
220
|
+
status="skipped",
|
|
221
|
+
detail=f"destination already exists and if_exists=skip: {dest}",
|
|
222
|
+
src=str(src),
|
|
223
|
+
dest=str(dest),
|
|
224
|
+
if_exists=item.if_exists,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
continue
|
|
228
|
+
if item.if_exists == "error":
|
|
229
|
+
raise HookRuntimeError(
|
|
230
|
+
{
|
|
231
|
+
"kind": "projection",
|
|
232
|
+
"projection": item.kind,
|
|
233
|
+
"status": "blocked",
|
|
234
|
+
"detail": f"projection conflict at {dest}",
|
|
235
|
+
"repo_hooks_path": str(hooks.path),
|
|
236
|
+
"src": str(src),
|
|
237
|
+
"dest": str(dest),
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
if item.if_exists == "merge":
|
|
241
|
+
raise HookRuntimeError(
|
|
242
|
+
{
|
|
243
|
+
"kind": "projection",
|
|
244
|
+
"projection": item.kind,
|
|
245
|
+
"status": "blocked",
|
|
246
|
+
"detail": f"merge projections not implemented yet for {dest}",
|
|
247
|
+
"repo_hooks_path": str(hooks.path),
|
|
248
|
+
"src": str(src),
|
|
249
|
+
"dest": str(dest),
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
if item.if_exists == "overwrite":
|
|
253
|
+
if dest.is_dir() and not dest.is_symlink():
|
|
254
|
+
raise HookRuntimeError(
|
|
255
|
+
{
|
|
256
|
+
"kind": "projection",
|
|
257
|
+
"projection": item.kind,
|
|
258
|
+
"status": "blocked",
|
|
259
|
+
"detail": f"refusing to overwrite directory projection target: {dest}",
|
|
260
|
+
"repo_hooks_path": str(hooks.path),
|
|
261
|
+
"src": str(src),
|
|
262
|
+
"dest": str(dest),
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
dest.unlink(missing_ok=True)
|
|
266
|
+
|
|
267
|
+
if item.kind == "link":
|
|
268
|
+
dest.symlink_to(src)
|
|
269
|
+
else:
|
|
270
|
+
dest.write_bytes(src.read_bytes())
|
|
271
|
+
results.append(
|
|
272
|
+
HookResult(
|
|
273
|
+
kind="projection",
|
|
274
|
+
name=f"{item.kind}:{dest.name}",
|
|
275
|
+
status="applied",
|
|
276
|
+
detail=f"{item.kind} {src} -> {dest}",
|
|
277
|
+
src=str(src),
|
|
278
|
+
dest=str(dest),
|
|
279
|
+
if_exists=item.if_exists,
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
return results
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def run_lifecycle_stage(
|
|
286
|
+
hooks: RepoHooks,
|
|
287
|
+
stage: str,
|
|
288
|
+
ctx: HookContext,
|
|
289
|
+
*,
|
|
290
|
+
repo_dirty: bool,
|
|
291
|
+
first_materialize: bool,
|
|
292
|
+
allow_manual: bool = False,
|
|
293
|
+
) -> list[HookResult]:
|
|
294
|
+
hooks_for_stage = {
|
|
295
|
+
"on_materialize": hooks.on_materialize,
|
|
296
|
+
"on_enter": hooks.on_enter,
|
|
297
|
+
"on_exit": hooks.on_exit,
|
|
298
|
+
}[stage]
|
|
299
|
+
results: list[HookResult] = []
|
|
300
|
+
for hook in hooks_for_stage:
|
|
301
|
+
if not _should_run(
|
|
302
|
+
hook.when,
|
|
303
|
+
repo_dirty=repo_dirty,
|
|
304
|
+
first_materialize=first_materialize,
|
|
305
|
+
allow_manual=allow_manual,
|
|
306
|
+
):
|
|
307
|
+
emit(
|
|
308
|
+
event_type=EventType.HOOK_SKIPPED,
|
|
309
|
+
workspace_root=ctx.workspace_root,
|
|
310
|
+
actor="system",
|
|
311
|
+
owner_unit=ctx.lane_owner,
|
|
312
|
+
payload={
|
|
313
|
+
"stage": stage,
|
|
314
|
+
"hook_name": hook.name,
|
|
315
|
+
"repo": ctx.repo_name,
|
|
316
|
+
"reason": f"when={hook.when} did not match current invocation",
|
|
317
|
+
},
|
|
318
|
+
)
|
|
319
|
+
results.append(
|
|
320
|
+
HookResult(
|
|
321
|
+
kind="lifecycle",
|
|
322
|
+
name=hook.name,
|
|
323
|
+
status="skipped",
|
|
324
|
+
detail=f"hook when={hook.when} did not match current invocation",
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
continue
|
|
328
|
+
cwd = render_path(hook.cwd, ctx)
|
|
329
|
+
command = render_text(hook.command, ctx)
|
|
330
|
+
emit(
|
|
331
|
+
event_type=EventType.HOOK_STARTED,
|
|
332
|
+
workspace_root=ctx.workspace_root,
|
|
333
|
+
actor="system",
|
|
334
|
+
owner_unit=ctx.lane_owner,
|
|
335
|
+
payload={
|
|
336
|
+
"stage": stage,
|
|
337
|
+
"hook_name": hook.name,
|
|
338
|
+
"repo": ctx.repo_name,
|
|
339
|
+
"command": command,
|
|
340
|
+
"cwd": str(cwd),
|
|
341
|
+
},
|
|
342
|
+
)
|
|
343
|
+
t0 = time.monotonic()
|
|
344
|
+
proc = subprocess.run(
|
|
345
|
+
command,
|
|
346
|
+
cwd=cwd,
|
|
347
|
+
shell=True,
|
|
348
|
+
capture_output=True,
|
|
349
|
+
text=True,
|
|
350
|
+
)
|
|
351
|
+
duration_ms = int((time.monotonic() - t0) * 1000)
|
|
352
|
+
if proc.returncode == 0:
|
|
353
|
+
emit_after_outcome(
|
|
354
|
+
event_type=EventType.HOOK_COMPLETED,
|
|
355
|
+
workspace_root=ctx.workspace_root,
|
|
356
|
+
actor="system",
|
|
357
|
+
owner_unit=ctx.lane_owner,
|
|
358
|
+
payload={
|
|
359
|
+
"stage": stage,
|
|
360
|
+
"hook_name": hook.name,
|
|
361
|
+
"repo": ctx.repo_name,
|
|
362
|
+
"duration_ms": duration_ms,
|
|
363
|
+
"exit_code": 0,
|
|
364
|
+
},
|
|
365
|
+
)
|
|
366
|
+
results.append(
|
|
367
|
+
HookResult(
|
|
368
|
+
kind="lifecycle",
|
|
369
|
+
name=hook.name,
|
|
370
|
+
status="applied",
|
|
371
|
+
detail=f"stage {stage} completed successfully",
|
|
372
|
+
cwd=str(cwd),
|
|
373
|
+
command=command,
|
|
374
|
+
returncode=proc.returncode,
|
|
375
|
+
stdout=proc.stdout,
|
|
376
|
+
stderr=proc.stderr,
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
continue
|
|
380
|
+
stderr_tail = proc.stderr[-500:] if proc.stderr else ""
|
|
381
|
+
emit_after_outcome(
|
|
382
|
+
event_type=EventType.HOOK_FAILED,
|
|
383
|
+
workspace_root=ctx.workspace_root,
|
|
384
|
+
actor="system",
|
|
385
|
+
owner_unit=ctx.lane_owner,
|
|
386
|
+
payload={
|
|
387
|
+
"stage": stage,
|
|
388
|
+
"hook_name": hook.name,
|
|
389
|
+
"repo": ctx.repo_name,
|
|
390
|
+
"duration_ms": duration_ms,
|
|
391
|
+
"exit_code": proc.returncode,
|
|
392
|
+
"on_failure": hook.on_failure,
|
|
393
|
+
"stderr_tail": stderr_tail,
|
|
394
|
+
},
|
|
395
|
+
)
|
|
396
|
+
payload = {
|
|
397
|
+
"kind": "lifecycle",
|
|
398
|
+
"stage": stage,
|
|
399
|
+
"hook": hook.name,
|
|
400
|
+
"cwd": str(cwd),
|
|
401
|
+
"command": command,
|
|
402
|
+
"returncode": proc.returncode,
|
|
403
|
+
"stdout": proc.stdout,
|
|
404
|
+
"stderr": proc.stderr,
|
|
405
|
+
"on_failure": hook.on_failure,
|
|
406
|
+
}
|
|
407
|
+
if hook.on_failure == "block":
|
|
408
|
+
raise HookRuntimeError(payload)
|
|
409
|
+
if hook.on_failure == "warn":
|
|
410
|
+
print(json.dumps(payload, indent=2), file=sys.stderr)
|
|
411
|
+
results.append(
|
|
412
|
+
HookResult(
|
|
413
|
+
kind="lifecycle",
|
|
414
|
+
name=hook.name,
|
|
415
|
+
status="warned",
|
|
416
|
+
detail=f"hook failed with on_failure=warn during {stage}",
|
|
417
|
+
cwd=str(cwd),
|
|
418
|
+
command=command,
|
|
419
|
+
returncode=proc.returncode,
|
|
420
|
+
stdout=proc.stdout,
|
|
421
|
+
stderr=proc.stderr,
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
continue
|
|
425
|
+
results.append(
|
|
426
|
+
HookResult(
|
|
427
|
+
kind="lifecycle",
|
|
428
|
+
name=hook.name,
|
|
429
|
+
status="skipped",
|
|
430
|
+
detail=f"hook failed with on_failure=skip during {stage}",
|
|
431
|
+
cwd=str(cwd),
|
|
432
|
+
command=command,
|
|
433
|
+
returncode=proc.returncode,
|
|
434
|
+
stdout=proc.stdout,
|
|
435
|
+
stderr=proc.stderr,
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
return results
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _should_run(when: str, *, repo_dirty: bool, first_materialize: bool, allow_manual: bool) -> bool:
|
|
442
|
+
if when == "always":
|
|
443
|
+
return True
|
|
444
|
+
if when == "first_materialize":
|
|
445
|
+
return first_materialize
|
|
446
|
+
if when == "dirty":
|
|
447
|
+
return repo_dirty
|
|
448
|
+
if when == "manual":
|
|
449
|
+
return allow_manual
|
|
450
|
+
return False
|