vcs

package
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package vcs inspects an existing checkout: which version control system drives it, and where the repository containing a given path begins. Both answers come from marker files alone - no subprocess is spawned and no repository is opened - so this package is as pure and offline as the reference parsing in the parent package.

Driving a VCS is deliberately out of scope. Running git or jj belongs to the consumer, whose needs vary too widely (progress reporting, dry-run rendering, signing flags) for a shared primitive to serve without either crippling one caller or dragging os/exec into this module.

Index

Constants

View Source
const (
	Git = "git"
	Hg  = "hg"
	JJ  = "jj"
	SVN = "svn"
)

The version control systems this package recognizes.

Variables

This section is empty.

Functions

func Detect

func Detect(dir string) string

Detect reports which VCS drives the checkout rooted at dir - JJ, Git, or "" when neither marker is present. Only dir itself is examined; use a Resolver to find the root a nested path belongs to.

Mercurial and Subversion checkouts report "", not Hg or SVN: Detect answers "what should I drive this with", and this module's consumers drive only git and jj.

An empty dir reports "" rather than inspecting the process working directory, which is what joining a marker onto "" would otherwise do. That case is not hypothetical: it is exactly what pairing Detect with a Resolver produces for a path in no repository, and the wrong answer would depend on where the process happened to be started. Prefer Resolver.RootVCS, which pairs them safely.

func Markers

func Markers() []string

Markers returns the marker names that delimit a repository root, as a fresh slice. A caller pruning a directory walk builds its skip set from this rather than repeating the literals and letting the two drift.

Types

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver maps a path to the root of the repository it belongs to, caching each directory it resolves so an ancestor is checked at most once. The root is the namespace under which a per-repository identifier is unique, which is what makes the same name in two checkouts not a clash. Safe for concurrent use.

Paths are canonicalised before they are walked, so one repository has one root however a caller spells the way to it - relative or absolute, through a symlink or through the real path. Without that a single repository would yield two namespaces and two identifiers that should collide would not, which is a failure that reports nothing.

func NewResolver

func NewResolver() *Resolver

NewResolver returns an empty resolver. It captures the working directory once - callers hand it relative paths, and filepath.Abs would otherwise re-issue the getwd syscall for every lookup of a value that never changes during a run.

func (*Resolver) Abs

func (r *Resolver) Abs(path string) string

Abs returns path as this resolver keys on it: absolute, cleaned, and with symlinks resolved.

Use it in place of filepath.Abs when relating a file to its repository, so that the two agree:

rel, err := filepath.Rel(r.Root(p), r.Abs(p))

filepath.Abs does not resolve symlinks, so a path reaching a repository through one does not sit under the root it resolves to and Rel yields a "../"-escaping result instead of a path within the repository.

func (*Resolver) Root

func (r *Resolver) Root(path string) string

Root returns the absolute path of the repository the file at path belongs to - the nearest ancestor directory holding any marker in Markers - or "" when the file is not inside a repository. The file itself need not exist.

func (*Resolver) RootDir

func (r *Resolver) RootDir(dir string) string

RootDir returns the absolute path of the repository containing the directory dir - the nearest ancestor, dir itself included, holding a marker - or "" when dir is not inside a repository. Unlike Root, dir is the search start rather than a file whose parent is searched, so a repository root resolves to itself.

func (*Resolver) RootVCS

func (r *Resolver) RootVCS(dir string) (string, string)

RootVCS returns the repository containing dir and which VCS drives it, or ("", "") when dir is in no repository.

It exists because the obvious pairing is a trap: RootDir yields "" outside a repository, and joining a marker onto "" would inspect the process working directory instead, reporting whatever VCS happens to govern where the program was started. Detect guards that case for exactly this reason, so the pairing here is a single expression - but a caller writing it by hand has to know that, and three consumers should not each have to rediscover it.

The two answers can disagree about which VCS "owns" a colocated checkout: Detect prefers jj because it drives the working copy. A caller wanting a different policy - git because it needs no jj binary, say - has the root here and can decide for itself.

Jump to

Keyboard shortcuts

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