Documentation
¶
Overview ¶
permalink.go derives the pieces GetPermalink (D-06) needs to construct a GitHub blob URL pinned to the indexed commit: the remote's owner/repo (D-08: GitHub only, everything else is an explicit no-link) and whether that commit is observably present on a remote-tracking branch (D-07: the check is sound in one direction only, so "could not check" and "checked, not there" must never collapse to the same value). D-09's line-range anchor is assembled by the caller (internal/uiserver/permalink.go) from values this file returns.
Every function here follows the package's existing degrade-to-a-value contract (see the package doc in worktree.go): no error is ever returned. But the VALUE degraded to RECORDS WHICH failure occurred — RemotePresenceUnknown rather than a bare false, and a populated GitHubRemote.Reason rather than an empty struct — because a caller that cannot distinguish "I checked and found nothing" from "I could not check" cannot honestly report either (cycle-1 review, Codex, HIGH).
Package gitmeta is the stdlib-only, best-effort git introspection layer that detects when a resolved CodeGraph index belongs to a different git working tree than the caller (WORK-01/02/03). It shells out to the local `git` binary via os/exec — no pure-Go git library, no CGo (D-03/D-04) — and is deliberately free of internal/query and internal/mcp concerns, so Phase 5's git sync hooks can reuse it unchanged.
Every function here degrades to a safe zero value on ANY failure: missing git, a non-repo path, a timeout, or a transient error all report "no signal" rather than an error. A read query must never fail or block on git being unavailable, slow, or absent (WORK-03).
Index ¶
- func CommonDir(ctx context.Context, dir string) string
- func HooksDir(ctx context.Context, projectRoot string) string
- func IsGitRepo(ctx context.Context, dir string) bool
- func RemoteURL(ctx context.Context, dir string) string
- func WorktreeRoot(ctx context.Context, dir string) string
- type CachingDetector
- type GitHubRemote
- type Mismatch
- type RemotePresence
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CommonDir ¶
CommonDir returns the absolute, symlink-resolved git COMMON directory for dir — the shared `.git` every worktree of one repository points at, or "" when dir isn't a repo. Linked worktrees of the SAME repository report the SAME common dir; a submodule or an embedded clone is a DIFFERENT repository and reports its own (e.g. `.git/modules/<name>`, or its own `.git`). That distinction is what separates a genuine borrowed worktree from a nested repo the parent index already covers (see DetectIndexMismatch gate 4).
func HooksDir ¶
HooksDir returns the git hooks directory for projectRoot, resolved via `git rev-parse --git-path hooks` — the only correct way to honor core.hooksPath and linked worktrees (which share the main checkout's common hooks dir). A relative result is joined against projectRoot; an absolute result (the case for linked worktrees) is passed through unchanged. Unlike CommonDir, this deliberately does NOT call realpath: D-04 specifies resolve-or-passthrough only, not symlink resolution. Degrades to "" on any error, empty output, or non-repo projectRoot.
func IsGitRepo ¶
IsGitRepo reports whether dir is inside a git working tree. It follows the same exec contract as WorktreeRoot/CommonDir (gitTimeout, cmd.Dir, cmd.Stdin nil) and degrades to false on any failure — missing git, a non-repo path, a timeout, or a transient error all report "no signal" rather than propagating an error (D-10).
func RemoteURL ¶ added in v0.12.0
RemoteURL returns dir's origin remote URL with any `url.<base>.insteadOf` rewrite already applied, or "" if dir has no origin remote, isn't a git repository, or git is unavailable. Uses `git ls-remote --get-url origin`, which git's own documentation states "exit[s] without talking to the remote" — this must never be swapped for a plain `git remote get-url` / config read, which returns the UNREWRITTEN value and would silently disagree with what git itself resolves for a developer with an insteadOf rule configured.
Note: `git ls-remote --get-url <name>` does not fail when <name> has no configured remote — it echoes the literal argument back unchanged (verified empirically). Callers must treat that echo, not a non-zero exit, as "no such remote".
func WorktreeRoot ¶
WorktreeRoot returns the absolute, symlink-resolved toplevel of the git working tree that dir belongs to, or "" when dir isn't inside a git repo (or git is unavailable/slow). `git rev-parse --show-toplevel` reports the PER-WORKTREE root: the main checkout and each linked worktree resolve to their own distinct directory — exactly the distinction detection needs.
Types ¶
type CachingDetector ¶
type CachingDetector struct {
// contains filtered or unexported fields
}
CachingDetector memoizes DetectIndexMismatch verdicts — POSITIVE and NEGATIVE — keyed on the documented cache key (startPath + "\x00" + indexRoot, per D-13). Detection costs up to four git subprocesses (see DetectIndexMismatch's doc comment); on a long-lived MCP server that would otherwise re-pay that cost on every single tool call.
The cache deliberately lives HERE, not on internal/query.Engine: internal/mcp's openEngine builds a FRESH Engine on every single tool call by design, so an Engine-scoped cache would yield zero cross-call benefit on the exact long-lived surface the cache exists for. internal/mcp constructs one CachingDetector per server and closes over it in every handler; the CLI constructs one per invocation (free — it's one-shot); both surfaces share this identical type (D-13, corrected 2026-07-15).
func NewCachingDetector ¶
func NewCachingDetector() *CachingDetector
NewCachingDetector returns a ready-to-use, empty CachingDetector.
func (*CachingDetector) Detect ¶
func (d *CachingDetector) Detect(ctx context.Context, startPath, indexRoot string) *Mismatch
Detect returns the memoized DetectIndexMismatch verdict for (startPath, indexRoot), computing and caching it on first call. Negative verdicts (nil == "checked, no mismatch") are cached too — a bare nil lookup can't distinguish "not yet checked" from "checked, none found", so presence is tracked via the two-value map form, never a nil test (D-13).
Detect is safe to call on a nil *CachingDetector: it falls through directly to DetectIndexMismatch, uncached, so every consumer can treat the detector as optional.
BL-01: a verdict computed under a CANCELLED ctx is never written to the cache, even though it IS returned for this call (WORK-03: never block or error a read on a failed/aborted git probe). A cancelled git spawn collapses into the same nil DetectIndexMismatch returns for "checked, no mismatch" — caching it would let one cancelled call permanently poison this (startPath, indexRoot) entry for a long-lived server's entire remaining life. See the ctx.Err() check below for the mechanism.
WR-02: a startPath that is not an existing, statable directory can never be inside a working tree — DetectIndexMismatch's gate 1 (WorktreeRoot) would immediately return "" for it anyway, so this is a pure short-circuit, not a behavior change. Rejecting it BEFORE the cache lookup/store means a client that mints a fresh nonexistent "path" on every call (accidentally, via a stale reference, or a malicious/looping MCP client) cannot grow the cache at all, on top of the maxCacheEntries bound below for legitimate, existing paths.
type GitHubRemote ¶ added in v0.12.0
GitHubRemote is the outcome of parsing a repository's origin remote and classifying it against D-08 (GitHub only). On success Owner and Repo are populated and Reason is empty. On any failure — no origin, unsupported host, malformed remote, non-repo directory, or git absent — Owner and Repo are empty, Host names whatever host WAS parsed (empty if none could be), and Reason is a short, credential-free, user-facing sentence naming the SPECIFIC cause. A bare (owner, repo) pair can only say "no"; this struct says WHICH no (cycle-1 review, Codex, MEDIUM).
func RemoteGitHubRepo ¶ added in v0.12.0
func RemoteGitHubRepo(ctx context.Context, dir string) GitHubRemote
RemoteGitHubRepo parses dir's origin remote and classifies it against D-08. Every failure path populates Reason with a distinct, actionable sentence rather than collapsing to one undifferentiated refusal (cycle-1 review, Codex, MEDIUM): git absent, non-repo directory, no origin configured, and an unsupported/lookalike host are each named separately.
type Mismatch ¶
type Mismatch struct {
WorktreeRoot string `json:"worktreeRoot"`
IndexRoot string `json:"indexRoot"`
}
Mismatch describes a detected "borrowed index" situation: startPath lives in one git working tree, but the resolved CodeGraph index belongs to a different one. The json tags match the documented `--json` object shape (`{worktreeRoot, indexRoot}`) so plan 02-04 can embed this directly into StatusResult.
func DetectIndexMismatch ¶
DetectIndexMismatch detects when startPath lives in one git working tree but the resolved CodeGraph index (indexRoot) belongs to a DIFFERENT working tree — the silent "worktree queries the main branch's graph" correctness bug (WORK-01). Implements the worktree/index-mismatch detection semantics (D-02); gate order and polarity are load-bearing, not incidental.
Worst case this spawns four git subprocesses (two WorktreeRoot, two CommonDir) — gates 1-3 each short-circuit before reaching CommonDir, so most calls spawn one or two. This per-call cost is what motivates CachingDetector (Task 3): a long-lived MCP server must not re-pay it on every tool call.
Returns nil ("nothing to warn about") — never an error, never a panic — on every degradation path, including git being absent, slow, or the path not being a git repo at all (WORK-03).
func (*Mismatch) Notice ¶
Notice renders the compact, single-line form of a detected mismatch, prefixed onto the other seven read tools' output (D-12). Returns "" on a nil receiver. This is the documented message text (D-01/D-11); do not paraphrase.
func (*Mismatch) Warning ¶
Warning renders the verbose, multi-line form of a detected mismatch, used by `status` only (D-12). Returns "" on a nil receiver so callers never need a nil guard — the same shape internal/query/render_markdown.go's staleBanner uses. This is the documented message text (D-01/D-11); do not paraphrase, including the quoted "codegraph init -i" advice.
type RemotePresence ¶ added in v0.12.0
type RemotePresence int
RemotePresence is the tri-state result of asking whether a commit is observably present on a remote-tracking branch. It is deliberately NOT a bool and NOT a (bool, error) pair: collapsing "the query ran and found nothing" and "the query could not run at all" into the same false value would make D-07's honesty requirement unimplementable at the only layer that could implement it (cycle-1 review, Codex, HIGH).
const ( // RemotePresenceUnknown is the zero value: the containment query could // not run at all — git absent, dir not a repository, a malformed sha, // or the gitTimeout firing. This is NOT a statement about the commit. RemotePresenceUnknown RemotePresence = iota // RemotePresenceObserved means the query ran and FOUND the commit on // at least one remote-tracking branch. Sound in this direction: a hit // proves the commit is on the remote. RemotePresenceObserved // RemotePresenceNotObserved means the query ran and did NOT find the // commit on any remote-tracking branch. NOT proof the commit is absent // from the remote — only that the last fetch did not see it. RemotePresenceNotObserved )
func CommitOnRemoteTrackingBranch ¶ added in v0.12.0
func CommitOnRemoteTrackingBranch(ctx context.Context, dir, sha string) RemotePresence
CommitOnRemoteTrackingBranch reports whether sha is reachable from any LOCAL remote-tracking branch (`git branch -r --contains <sha>`) — this function never fetches and performs no network I/O of any kind.
The Observed answer is sound in exactly ONE direction: a hit PROVES the commit is on a remote-tracking branch. No hit proves only that the last fetch did not see it — someone else may have pushed it since, or the user may simply not have fetched. A read-only local tool must not fetch to find out, so the "not found" case degrades to RemotePresenceNotObserved (an honest answer about uncertainty), never to a claim that the commit is absent from the remote (D-07).