Step 1 of the control-marking task, and deliberately server-only: no handle is drawn yet. This project already shipped a range handle for `profile.interval`, which compileGeometry ignores, so the control dragged and changed nothing. The consumer comes first now. Two kinds join the taxonomy — `junction-crosswalk-inset` and `junction-stop-line-offset`, both on the existing `junction-approach` anchor. The solver writes them onto the approach entry, `applyDirectJunctionPlans` carries them onto the compiled approach, and `compileControlMarkings` reads them in place of the module constants it used for every junction. They move markings without reshaping the junction, so unlike width and cutback they deliberately do not trigger a boundary recompute. `applyJunctionConstraint` becomes an explicit switch. Its trailing `else` had meant every kind that was not approach-width fell through to the cutback validator, so a new kind would have been silently validated and written as a cutback. The same non-exhaustive shape in the test fixture's `valueFor` is fixed the same way, and now throws for an unnamed kind rather than answering with a corner radius. design.md's taxonomy is updated with it — a test asserts the two cannot drift, which is what caught the omission. Measured on a 41-road workspace with 8 crossings: both constraints change their marking geometry, neither drags the other, and out-of-range blocks instead of clamping. That measurement is not in the suite: the synthetic junction resolves `junction_inset_m` to 0 because its crossing never binds to a plan, and the committed OSM fixture has no crossings at all. The tests assert the wiring the handles will depend on — values reaching the approach entry, distinct branches, blocking diagnostics — and the gap is recorded in the test itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
75 lines
2.3 KiB
Python
Executable File
75 lines
2.3 KiB
Python
Executable File
"""
|
|
Git command execution utility.
|
|
|
|
Single source of truth for running git commands across all Trellis scripts.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def run_git(
|
|
args: list[str],
|
|
cwd: Path | None = None,
|
|
timeout: float | None = None,
|
|
) -> tuple[int, str, str]:
|
|
"""Run a git command and return (returncode, stdout, stderr).
|
|
|
|
Uses UTF-8 encoding with -c i18n.logOutputEncoding=UTF-8 to ensure
|
|
consistent output across all platforms (Windows, macOS, Linux). Callers
|
|
may provide a timeout for best-effort probes; normal Git operations remain
|
|
unbounded by default.
|
|
"""
|
|
try:
|
|
git_args = ["git", "-c", "i18n.logOutputEncoding=UTF-8"] + args
|
|
result = subprocess.run(
|
|
git_args,
|
|
cwd=cwd,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
timeout=timeout,
|
|
)
|
|
return result.returncode, result.stdout, result.stderr
|
|
except Exception as e:
|
|
return 1, "", str(e)
|
|
|
|
|
|
def resolve_default_branch(repo_root: Path) -> str | None:
|
|
"""Resolve the repository's default branch (origin/HEAD target).
|
|
|
|
Tries the local `refs/remotes/origin/HEAD` symbolic ref first (no
|
|
network access), then falls back to `git remote show origin` (which
|
|
may hit the network but also repairs a missing/stale symbolic-ref).
|
|
Returns None when neither resolves, so callers can fall back to their
|
|
own pre-existing behavior.
|
|
"""
|
|
rc, out, _ = run_git(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd=repo_root)
|
|
if rc == 0 and out.strip():
|
|
return out.strip().rsplit("/", 1)[-1]
|
|
|
|
rc, out, _ = run_git(["remote", "show", "origin"], cwd=repo_root)
|
|
if rc == 0:
|
|
for line in out.splitlines():
|
|
line = line.strip()
|
|
if line.startswith("HEAD branch:"):
|
|
branch = line.split(":", 1)[1].strip()
|
|
if branch and branch != "(unknown)":
|
|
return branch
|
|
|
|
return None
|
|
|
|
|
|
def branch_exists_locally(branch: str, repo_root: Path) -> bool:
|
|
"""Check whether a local branch ref exists in the repository."""
|
|
if not branch:
|
|
return False
|
|
rc, _, _ = run_git(
|
|
["rev-parse", "--verify", "--quiet", f"refs/heads/{branch}"],
|
|
cwd=repo_root,
|
|
)
|
|
return rc == 0
|