Help us shape the future of Go! Take the 2026 Go Developer Survey

gitmeta

package
v0.16.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 9 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func CommonDir

func CommonDir(ctx context.Context, dir string) string

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

func HooksDir(ctx context.Context, projectRoot string) string

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

func IsGitRepo(ctx context.Context, dir string) bool

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 LinkedWorktreeMain added in v0.16.0

func LinkedWorktreeMain(ctx context.Context, root string) string

LinkedWorktreeMain returns the absolute, symlink-resolved root of the main worktree of the repository that root is a linked worktree of, or "" when root is not the root of a linked worktree that repository records, the repository is bare (it has no main worktree), or git is unavailable or slow.

The link must hold both ways, as `git worktree add` records it: root's git directory is <common dir>/worktrees/<name>, and that directory's gitdir file names root/.git. A .git file on its own proves nothing: one line pointing at another repository's git directory would otherwise borrow that repository's index for an unrelated tree. A main checkout, a submodule and a --separate-git-dir checkout fail the first test.

`git worktree list --porcelain` then names the main worktree: its first record, from any worktree of the repository, which stays right for a main checkout whose common dir is not <main>/.git.

An answer from git runs that exited 0, including "" for an unrecorded or bare worktree, is remembered for the life of the process, keyed by filepath.Clean(root): the MCP server resolves its index on every request, and one git spawn costs tens of milliseconds. A failure is not remembered, so the next call tries again.

func RemoteURL added in v0.12.0

func RemoteURL(ctx context.Context, dir string) string

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

func WorktreeRoot(ctx context.Context, dir string) string

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

type GitHubRemote struct {
	Owner  string
	Repo   string
	Host   string
	Reason string
}

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

func DetectIndexMismatch(ctx context.Context, startPath, indexRoot string) *Mismatch

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

func (m *Mismatch) Notice() string

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

func (m *Mismatch) Warning() string

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL