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.
Files changed (80) hide show
  1. gitgrip-1.5.0.dist-info/METADATA +13 -0
  2. gitgrip-1.5.0.dist-info/RECORD +80 -0
  3. gitgrip-1.5.0.dist-info/WHEEL +5 -0
  4. gitgrip-1.5.0.dist-info/entry_points.txt +2 -0
  5. gitgrip-1.5.0.dist-info/top_level.txt +2 -0
  6. gr2/__init__.py +0 -0
  7. gr2/overlay/__init__.py +6 -0
  8. gr2/overlay/activate.py +196 -0
  9. gr2/overlay/agent_manifest.py +138 -0
  10. gr2/overlay/cli.py +181 -0
  11. gr2/overlay/cross_repo.py +124 -0
  12. gr2/overlay/drivers.py +113 -0
  13. gr2/overlay/introspection.py +155 -0
  14. gr2/overlay/language_drivers.py +115 -0
  15. gr2/overlay/objects.py +412 -0
  16. gr2/overlay/perf.py +251 -0
  17. gr2/overlay/refs.py +36 -0
  18. gr2/overlay/trust.py +150 -0
  19. gr2/overlay/types.py +69 -0
  20. gr2/overlay/units.py +313 -0
  21. gr2/overlay/workspace_spec.py +59 -0
  22. gr2/prototypes/__init__.py +0 -0
  23. gr2/prototypes/cache_materialization_probe.py +190 -0
  24. gr2/prototypes/concurrent_event_stress.py +199 -0
  25. gr2/prototypes/concurrent_lease_stress.py +240 -0
  26. gr2/prototypes/concurrent_workspace_cap_stress.py +231 -0
  27. gr2/prototypes/contribution_protocol.py +665 -0
  28. gr2/prototypes/cross_mode_lane_stress.py +986 -0
  29. gr2/prototypes/jsonl_store.py +158 -0
  30. gr2/prototypes/lane_workspace_prototype.py +2088 -0
  31. gr2/prototypes/layout_model_probe.py +139 -0
  32. gr2/prototypes/propagation_daemon.py +546 -0
  33. gr2/prototypes/propagation_state_machine.py +1478 -0
  34. gr2/prototypes/python_exec_playground.py +194 -0
  35. gr2/prototypes/python_hook_runtime_playground.py +240 -0
  36. gr2/prototypes/python_migration_playground.py +144 -0
  37. gr2/prototypes/python_review_checkout_playground.py +242 -0
  38. gr2/prototypes/python_spec_apply_playground.py +282 -0
  39. gr2/prototypes/real_git_lane_materialization.py +248 -0
  40. gr2/prototypes/real_git_playground.py +334 -0
  41. gr2/prototypes/recall_lane_history.py +274 -0
  42. gr2/prototypes/repo_maintenance_prototype.py +659 -0
  43. gr2/prototypes/repo_transport_probe.py +147 -0
  44. gr2/python_cli/__init__.py +2 -0
  45. gr2/python_cli/__main__.py +6 -0
  46. gr2/python_cli/add.py +51 -0
  47. gr2/python_cli/app.py +2516 -0
  48. gr2/python_cli/branch.py +67 -0
  49. gr2/python_cli/channel_bridge.py +131 -0
  50. gr2/python_cli/clone_exec.py +1019 -0
  51. gr2/python_cli/commit.py +199 -0
  52. gr2/python_cli/config.py +291 -0
  53. gr2/python_cli/env_exec.py +419 -0
  54. gr2/python_cli/events.py +529 -0
  55. gr2/python_cli/execops.py +372 -0
  56. gr2/python_cli/failures.py +98 -0
  57. gr2/python_cli/file_exec.py +256 -0
  58. gr2/python_cli/gitops.py +226 -0
  59. gr2/python_cli/grip.py +1337 -0
  60. gr2/python_cli/grip_cli.py +493 -0
  61. gr2/python_cli/hooks.py +450 -0
  62. gr2/python_cli/launch_exec.py +786 -0
  63. gr2/python_cli/merge_verification.py +274 -0
  64. gr2/python_cli/migration.py +985 -0
  65. gr2/python_cli/open_gr_review.py +699 -0
  66. gr2/python_cli/platform.py +441 -0
  67. gr2/python_cli/pr.py +487 -0
  68. gr2/python_cli/project_review.py +314 -0
  69. gr2/python_cli/prune.py +365 -0
  70. gr2/python_cli/push.py +172 -0
  71. gr2/python_cli/review.py +462 -0
  72. gr2/python_cli/review_ephemeral.py +143 -0
  73. gr2/python_cli/review_run.py +621 -0
  74. gr2/python_cli/spec_apply.py +1285 -0
  75. gr2/python_cli/staging_cleanup.py +205 -0
  76. gr2/python_cli/syncops.py +920 -0
  77. gr2/python_cli/target.py +100 -0
  78. gr2/python_cli/workspace_snapshot.py +105 -0
  79. gr2/schemas/gr2-materialization-plan-v1.schema.json +191 -0
  80. gr2_overlay/__init__.py +37 -0
@@ -0,0 +1,158 @@
1
+ """Torn-line-safe JSONL primitives, shared by the lane prototypes.
2
+
3
+ Both lane prototypes carried a BYTE-IDENTICAL ``append_jsonl`` and a reader with
4
+ the same body under two names. They were not two instances of a defect class;
5
+ they were one function, copied. Fixing them separately would have written the
6
+ same fix and the same witnesses twice and left the copies free to diverge again,
7
+ which is how the situation arose. One home, two consumers.
8
+
9
+ The defect, measured on the sibling surface this shape came from:
10
+
11
+ * the writer appended with no terminator repair and no ``fsync``, so a writer
12
+ killed mid-write left a remnant and the NEXT record glued onto it — a record
13
+ that had itself completed became part of one unparseable line and was lost;
14
+ * the reader parsed with a bare ``json.loads`` over ``read_text().splitlines()``,
15
+ so the remnant raised, and the whole-file decode meant one invalid byte
16
+ anywhere destroyed every record in the file rather than its own line.
17
+
18
+ A line passes through four layers — read, decode, parse, shape-check — and the
19
+ last three are each guarded where they happen. Types are VALIDATED rather than
20
+ coerced, because a coercion over untrusted bytes forces the caller to enumerate
21
+ the exceptions it might raise, which is a denylist, and a denylist leaks.
22
+ Unbounded line length is a resource limit on the READ layer and is deliberately
23
+ NOT defended here; it is named rather than left to be discovered.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ import os
30
+ import sys
31
+ from dataclasses import dataclass, field
32
+ from pathlib import Path
33
+
34
+ from gr2.prototypes.propagation_state_machine import MalformedLine
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class JsonlRead:
39
+ """Rows that could be read, and the lines that could not.
40
+
41
+ The count travels WITH the data. These are module-level functions with no
42
+ object to hang health on, and a module-level accumulator would be hidden
43
+ state and wrong under concurrent readers — so the health of the scan is a
44
+ return value instead. A caller that ignores ``malformed`` still gets correct
45
+ rows; a caller that wants to report health already holds it. Skipping alone
46
+ is silent, and silence is the defect: the remnant sits in the file while the
47
+ caller sees a healthy-looking result.
48
+ """
49
+
50
+ rows: list[dict]
51
+ malformed: tuple[MalformedLine, ...] = field(default=())
52
+
53
+
54
+ def _decode_line(raw: bytes) -> tuple[str | None, str]:
55
+ """Bytes become text HERE, so the failure that can happen HERE is guarded here."""
56
+
57
+ try:
58
+ return raw.decode("utf-8"), ""
59
+ except UnicodeDecodeError as exc:
60
+ return None, str(exc)
61
+
62
+
63
+ def _read_object(line: str) -> tuple[dict | None, str]:
64
+ """``json.loads`` is the only operation over a decoded line that can raise."""
65
+
66
+ try:
67
+ obj = json.loads(line)
68
+ except (ValueError, RecursionError) as exc:
69
+ return None, str(exc)
70
+ if not isinstance(obj, dict):
71
+ return None, f"line is a {type(obj).__name__}, not an object"
72
+ return obj, ""
73
+
74
+
75
+ def append_jsonl(path: Path, payload: dict) -> None:
76
+ """Append one record, repairing a missing terminator first, then fsync.
77
+
78
+ Without the repair a remnant GLUES the next record onto itself and the next
79
+ record is lost. Without the fsync the record is not durable against the very
80
+ crash that produces remnants.
81
+ """
82
+
83
+ path.parent.mkdir(parents=True, exist_ok=True)
84
+ unterminated = False
85
+ try:
86
+ with path.open("rb") as probe:
87
+ if probe.seek(0, os.SEEK_END):
88
+ probe.seek(-1, os.SEEK_END)
89
+ unterminated = probe.read(1) != b"\n"
90
+ except FileNotFoundError:
91
+ pass
92
+ with path.open("ab") as handle:
93
+ if unterminated:
94
+ handle.write(b"\n")
95
+ handle.write((json.dumps(payload) + "\n").encode("utf-8"))
96
+ handle.flush()
97
+ os.fsync(handle.fileno())
98
+
99
+
100
+ def read_jsonl(path: Path) -> JsonlRead:
101
+ """Read every line that can be read, and report every line that cannot."""
102
+
103
+ if not path.exists():
104
+ return JsonlRead(rows=[])
105
+ rows: list[dict] = []
106
+ malformed: list[MalformedLine] = []
107
+ for index, raw in enumerate(path.read_bytes().split(b"\n")):
108
+ stripped = raw.strip()
109
+ if not stripped:
110
+ continue
111
+ line, reason = _decode_line(stripped)
112
+ if line is not None:
113
+ obj, reason = _read_object(line)
114
+ if obj is not None:
115
+ rows.append(obj)
116
+ continue
117
+ malformed.append(
118
+ MalformedLine(
119
+ index=index,
120
+ excerpt=stripped[:120].decode("utf-8", "replace"),
121
+ reason=reason,
122
+ )
123
+ )
124
+ return JsonlRead(rows=rows, malformed=tuple(malformed))
125
+
126
+
127
+ def warn_unreadable(read: JsonlRead, what: str, stream: object = None) -> bool:
128
+ """Report unreadable lines on STDERR. Returns whether anything was reported.
129
+
130
+ Lives HERE, beside the primitives it reports on, because the first version of
131
+ this fix put a helper in one consumer and an inline copy of the same three
132
+ lines in the other — which is byte-adjacent duplication of a reporting rule
133
+ across two files, the same shape as the copied ``append_jsonl`` this module
134
+ exists to eliminate. One home for the rule, or the message formats drift.
135
+
136
+ STDERR specifically: a ``--json`` caller's stdout must stay machine-readable.
137
+ And it is reported at ALL because a count nobody surfaces is the same silence
138
+ as no count — the CLI is the layer that already looks.
139
+ """
140
+
141
+ if not read.malformed:
142
+ return False
143
+ first = read.malformed[0]
144
+ print(
145
+ f"warning: {len(read.malformed)} unreadable line(s) in {what}; "
146
+ f"first at line {first.index}: {first.reason}",
147
+ file=stream if stream is not None else sys.stderr,
148
+ )
149
+ return True
150
+
151
+
152
+ __all__ = [
153
+ "JsonlRead",
154
+ "MalformedLine",
155
+ "append_jsonl",
156
+ "read_jsonl",
157
+ "warn_unreadable",
158
+ ]