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,67 @@
1
+ """Native `branch` command (grip#752 slice 1, D3).
2
+
3
+ Create/switch, --base support (grip#746: gr1's `gr branch` has no base-ref
4
+ argument, forcing raw-git fallbacks), existing-branch-switches-not-errors
5
+ (mirrors gr1's grip#401 fix), worktree-conflict detection (mirrors gr1's
6
+ real, cited pain -- grip#750).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ from .gitops import git
14
+
15
+
16
+ class BranchError(Exception):
17
+ pass
18
+
19
+
20
+ def _branch_exists(repo: Path, name: str) -> bool:
21
+ proc = git(repo, "rev-parse", "--verify", f"refs/heads/{name}")
22
+ return proc.returncode == 0
23
+
24
+
25
+ def _ref_exists(repo: Path, ref: str) -> bool:
26
+ proc = git(repo, "rev-parse", "--verify", ref)
27
+ return proc.returncode == 0
28
+
29
+
30
+ def _raise_for_checkout_failure(name: str, stderr: str) -> None:
31
+ if "is already used by worktree at" in stderr:
32
+ marker = "worktree at '"
33
+ start = stderr.find(marker)
34
+ if start != -1:
35
+ start += len(marker)
36
+ end = stderr.find("'", start)
37
+ worktree_path = stderr[start:end] if end != -1 else "another worktree"
38
+ raise BranchError(
39
+ f"Branch '{name}' is checked out in another worktree at '{worktree_path}'. "
40
+ f"Use that worktree or choose a different branch name."
41
+ )
42
+ raise BranchError(f"Branch '{name}' is already checked out in another worktree.")
43
+ raise BranchError(stderr.strip())
44
+
45
+
46
+ def create_branch(repo: Path, name: str, base: str | None = None) -> None:
47
+ """Create and switch to branch `name` in `repo`.
48
+
49
+ If `name` already exists, switch to it instead of erroring (grip#401).
50
+ If `base` is given, the branch is created (or reset, if it already
51
+ exists) to point at `base` -- resolved per-repo, matching `git checkout
52
+ -B`'s own create-or-reset semantics. A missing `base` ref raises
53
+ immediately, before any git mutation -- never a silent fallback to HEAD
54
+ (grip#746's acceptance criteria).
55
+ """
56
+ if base is not None and not _ref_exists(repo, base):
57
+ raise BranchError(f"base ref '{base}' does not exist in {repo}")
58
+
59
+ if base is not None:
60
+ proc = git(repo, "checkout", "-B", name, base)
61
+ elif _branch_exists(repo, name):
62
+ proc = git(repo, "checkout", name)
63
+ else:
64
+ proc = git(repo, "checkout", "-b", name)
65
+
66
+ if proc.returncode != 0:
67
+ _raise_for_checkout_failure(name, proc.stderr)
@@ -0,0 +1,131 @@
1
+ """gr2 channel bridge consumer.
2
+
3
+ Translates outbox events into channel messages per the mapping table in
4
+ HOOK-EVENT-CONTRACT.md section 8. Uses cursor-based consumption from
5
+ events.read_events_detailed(), which also reports lines it could not read.
6
+
7
+ The bridge is a pure function layer: format_event() maps an event dict to
8
+ a message string (or None), and run_bridge() orchestrates cursor reads and
9
+ posts via a caller-provided post_fn. This keeps the MCP/recall_channel
10
+ dependency out of the module and makes it fully testable.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+ from typing import Callable
16
+
17
+ from .events import read_events_detailed, warn_unreadable
18
+
19
+
20
+ _CONSUMER_NAME = "channel_bridge"
21
+
22
+
23
+ def format_event(event: dict[str, object]) -> str | None:
24
+ """Apply the section 8 mapping table to produce a channel message.
25
+
26
+ Returns None if the event type is not mapped (silently dropped).
27
+ """
28
+ etype = event.get("type", "")
29
+
30
+ if etype == "lane.created":
31
+ return (
32
+ f"{event['actor']} created lane {event['lane_name']}"
33
+ f" [{event.get('lane_type', 'unknown')}]"
34
+ f" repos={event.get('repos', [])}"
35
+ )
36
+
37
+ if etype == "lane.entered":
38
+ return f"{event['actor']} entered {event['owner_unit']}/{event['lane_name']}"
39
+
40
+ if etype == "lane.exited":
41
+ return f"{event['actor']} exited {event['owner_unit']}/{event['lane_name']}"
42
+
43
+ if etype == "pr.created":
44
+ repos = event.get("repos", [])
45
+ if isinstance(repos, list) and repos and isinstance(repos[0], dict):
46
+ repo_names = [r.get("repo", "") for r in repos]
47
+ else:
48
+ repo_names = repos
49
+ return (
50
+ f"{event['actor']} opened PR group {event['pr_group_id']}: {repo_names}"
51
+ )
52
+
53
+ if etype == "pr.merged":
54
+ return f"{event['actor']} merged PR group {event['pr_group_id']}"
55
+
56
+ if etype == "pr.checks_failed":
57
+ failed = event.get("failed_checks", [])
58
+ return f"CI failed on {event['repo']}#{event['pr_number']}: {failed}"
59
+
60
+ if etype == "hook.failed":
61
+ # Only blocking hook failures produce channel messages.
62
+ if event.get("on_failure") != "block":
63
+ return None
64
+ return (
65
+ f"Hook {event['hook_name']} failed in {event['repo']}"
66
+ f" (blocking): {event.get('stderr_tail', '')}"
67
+ )
68
+
69
+ if etype == "sync.conflict":
70
+ files = event.get("conflicting_files", [])
71
+ return f"Sync conflict in {event['repo']}: {files}"
72
+
73
+ if etype == "lease.force_broken":
74
+ return (
75
+ f"Lease on {event['lane_name']} force-broken"
76
+ f" by {event['broken_by']}: {event.get('reason', '')}"
77
+ )
78
+
79
+ if etype == "failure.resolved":
80
+ return (
81
+ f"{event['resolved_by']} resolved failure"
82
+ f" {event['operation_id']} on {event['lane_name']}"
83
+ )
84
+
85
+ if etype == "lease.reclaimed":
86
+ return (
87
+ f"Stale lease on {event['lane_name']} reclaimed"
88
+ f" (was held by {event['previous_holder']})"
89
+ )
90
+
91
+ # Unmapped event type: silently dropped.
92
+ return None
93
+
94
+
95
+ def run_bridge(
96
+ workspace_root: Path,
97
+ *,
98
+ post_fn: Callable[[str], object],
99
+ ) -> int:
100
+ """Read new events from the outbox and post mapped messages.
101
+
102
+ Uses the 'channel_bridge' cursor. Returns the number of messages posted.
103
+ The post_fn receives formatted message strings; the caller decides how to
104
+ deliver them (recall_channel, print, log, etc.).
105
+ """
106
+ read = read_events_detailed(workspace_root, _CONSUMER_NAME)
107
+ # An unreadable outbox line is a message that never reaches a channel, and
108
+ # the bridge is the only place that says so.
109
+ #
110
+ # It is reported on EVERY read, not once: the cursor filter compares seq
111
+ # against the cursor and only lines that PARSE have a usable seq, so an
112
+ # unreadable line can never be advanced past, wherever it sits in the file
113
+ # (witnessed terminal AND mid-file). An UNTERMINATED record never reaches
114
+ # this report at all: the reader splits on b"\n", so a COMPLETE record whose
115
+ # only loss was its terminator is the final chunk and parses on the spot --
116
+ # before any emit, repair or no repair (W11, and its truncated control).
117
+ # What seam repair protects is the NEXT append, which would otherwise be
118
+ # glued onto it. So everything reported here is a TRUNCATED record, and for
119
+ # those the report is permanent: a bridge polling in a loop warns every
120
+ # cycle until someone repairs the file. That cost is real and is recorded
121
+ # as a residual.
122
+ #
123
+ # stderr keeps stdout parseable for callers that consume the posted count.
124
+ warn_unreadable(read)
125
+ posted = 0
126
+ for event in read.events:
127
+ msg = format_event(event)
128
+ if msg is not None:
129
+ post_fn(msg)
130
+ posted += 1
131
+ return posted