Documentation
¶
Overview ¶
Package repo provides git repository operations (clone, checkout, commit, push, annotated tags, tree inspection) backed by go-git, behind the RepoLike abstraction with local-filesystem and in-memory storage backends.
RepoLike is a composite built from focused role interfaces: TreeReader, Opener, Authenticator, WorktreeController, Committer, SourceState, GitAccessor, Brancher, Tagger and WorktreeFS. Consumers should depend on the narrowest role that covers what they touch rather than the full composite. The named-remote operations CreateRemote/Remote remain concrete-only on *Repo and are not part of any role interface.
The init-only primitives form the Initializer role: DiscoverRepository (a read-only upward probe for an enclosing .git), Repo.InitLocal (init that refuses an existing repo with ErrAlreadyRepository), and Repo.AddAll (gitignore-aware staging). It is deliberately NOT embedded in RepoLike: init/discovery and ignore-aware staging are first-commit concerns a clone/checkout consumer never needs, so a scaffold-time caller depends on Initializer directly.
The package is a deliberately thin veneer over go-git: the role interfaces traffic in go-git's own concrete types (*git.Repository, *git.Worktree, *git.PushOptions, plumbing.Hash, …) by design rather than reinventing an abstraction over them. Every network-touching method (Opener.Open, Opener.OpenInMemory, Opener.Clone, Committer.Push and Brancher.CreateBranch) takes a context.Context as its first argument so a hung remote can be cancelled. This matters most on ThreadSafeRepo, which holds its mutex for a method's full duration: without a cancellable context a stalled clone or push would block every other caller indefinitely. The non-network methods on those same roles (Opener.OpenLocal, Committer.Commit) accept a context purely for signature symmetry.
Clone/push authentication is forge-aware without depending on a forge: NewRepo takes plain Settings naming the forge and supplying a lazy TokenSource, so the package resolves neither configuration nor credentials itself. SSH authentication takes priority when SSHSettings are configured, in which case the token source is never consulted. Missing credentials are non-fatal for public repositories; a private repository fails fast.
Index ¶
- Constants
- Variables
- func DiscoverRepository(location string) (found bool, err error)
- func GetSSHKey(filePath string, localfs afero.Fs) (*ssh.PublicKeys, error)
- func GetSSHKeyWithPassphrase(filePath string, localfs afero.Fs, passphrase string) (*ssh.PublicKeys, error)
- func KeyPath(path, env string) string
- type Authenticator
- type Brancher
- type CloneOption
- type CloneOptions
- type Committer
- type GitAccessor
- type Initializer
- type Logger
- type Opener
- type Repo
- func (r *Repo) AddAll() error
- func (r *Repo) AddToFS(fs afero.Fs, gitFile *object.File, fullPath string) error
- func (r *Repo) Checkout(branch plumbing.ReferenceName) error
- func (r *Repo) CheckoutCommit(hash plumbing.Hash) error
- func (r *Repo) Clone(ctx context.Context, uri string, targetPath string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
- func (r *Repo) Commit(_ context.Context, commitMsg string, opts *git.CommitOptions) (plumbing.Hash, error)
- func (r *Repo) CreateBranch(ctx context.Context, branchName string) error
- func (r *Repo) CreateRemote(name string, urls []string) (*git.Remote, error)
- func (r *Repo) CreateTag(_ context.Context, req TagRequest) (plumbing.Hash, error)
- func (r *Repo) DirectoryExists(relPath string) (bool, error)
- func (r *Repo) FileExists(relPath string) (bool, error)
- func (r *Repo) GetAuth() transport.AuthMethod
- func (r *Repo) GetFile(relPath string) (*object.File, error)
- func (r *Repo) InitLocal(location, branch string) (*git.Repository, *git.Worktree, error)
- func (r *Repo) Open(ctx context.Context, repoType RepoType, location string, branch string, ...) (*git.Repository, *git.Worktree, error)
- func (r *Repo) OpenInMemory(ctx context.Context, location string, branch string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
- func (r *Repo) OpenLocal(_ context.Context, location string) (*git.Repository, *git.Worktree, error)
- func (r *Repo) Push(ctx context.Context, opts *git.PushOptions) error
- func (r *Repo) Remote(name string) (*git.Remote, error)
- func (r *Repo) SetBasicAuth(username, password string)
- func (r *Repo) SetKey(key *ssh.PublicKeys)
- func (r *Repo) SetRepo(repo *git.Repository)
- func (r *Repo) SetSource(source int)
- func (r *Repo) SetTree(tree *git.Worktree)
- func (r *Repo) SourceIs(source int) bool
- func (r *Repo) WalkTree(fn func(*object.File) error) error
- func (r *Repo) WithRepo(fn func(*git.Repository) error) error
- func (r *Repo) WithTree(fn func(*git.Worktree) error) error
- func (r *Repo) WithWorkFS(fn func(afero.Fs) error) error
- func (r *Repo) WorkFS() (afero.Fs, error)
- type RepoLike
- type RepoOpt
- type RepoType
- type SSHSettings
- type Settings
- type SourceState
- type TagRequest
- type Tagger
- type ThreadSafeRepo
- func (r *ThreadSafeRepo) AddAll() error
- func (r *ThreadSafeRepo) AddToFS(fs afero.Fs, gitFile *object.File, fullPath string) error
- func (r *ThreadSafeRepo) Checkout(branch plumbing.ReferenceName) error
- func (r *ThreadSafeRepo) CheckoutCommit(hash plumbing.Hash) error
- func (r *ThreadSafeRepo) Clone(ctx context.Context, uri string, targetPath string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
- func (r *ThreadSafeRepo) Commit(ctx context.Context, commitMsg string, opts *git.CommitOptions) (plumbing.Hash, error)
- func (r *ThreadSafeRepo) CreateBranch(ctx context.Context, branchName string) error
- func (r *ThreadSafeRepo) CreateTag(ctx context.Context, req TagRequest) (plumbing.Hash, error)
- func (r *ThreadSafeRepo) DirectoryExists(relPath string) (bool, error)
- func (r *ThreadSafeRepo) FileExists(relPath string) (bool, error)
- func (r *ThreadSafeRepo) GetAuth() transport.AuthMethod
- func (r *ThreadSafeRepo) GetFile(relPath string) (*object.File, error)
- func (r *ThreadSafeRepo) InitLocal(location string, branch string) (*git.Repository, *git.Worktree, error)
- func (r *ThreadSafeRepo) Open(ctx context.Context, repoType RepoType, location string, branch string, ...) (*git.Repository, *git.Worktree, error)
- func (r *ThreadSafeRepo) OpenInMemory(ctx context.Context, location string, branch string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
- func (r *ThreadSafeRepo) OpenLocal(ctx context.Context, location string) (*git.Repository, *git.Worktree, error)
- func (r *ThreadSafeRepo) Push(ctx context.Context, opts *git.PushOptions) error
- func (r *ThreadSafeRepo) SetBasicAuth(username, password string)
- func (r *ThreadSafeRepo) SetKey(key *ssh.PublicKeys)
- func (r *ThreadSafeRepo) SetRepo(repo *git.Repository)
- func (r *ThreadSafeRepo) SetSource(source int)
- func (r *ThreadSafeRepo) SetTree(tree *git.Worktree)
- func (r *ThreadSafeRepo) SourceIs(source int) bool
- func (r *ThreadSafeRepo) WalkTree(fn func(*object.File) error) error
- func (r *ThreadSafeRepo) WithRepo(fn func(*git.Repository) error) error
- func (r *ThreadSafeRepo) WithTree(fn func(*git.Worktree) error) error
- func (r *ThreadSafeRepo) WithWorkFS(fn func(afero.Fs) error) error
- func (r *ThreadSafeRepo) WorkFS() (afero.Fs, error)
- type TokenSource
- type TreeReader
- type WorktreeController
- type WorktreeFS
Constants ¶
const ( ForgeGitHub = "github" ForgeGitLab = "gitlab" ForgeBitbucket = "bitbucket" ForgeGitea = "gitea" ForgeCodeberg = "codeberg" ForgeDirect = "direct" )
Forge identifiers understood by the git-authentication paths. They select the username git-over-HTTPS expects for token auth; any other value falls back to the GitHub convention.
These are plain strings rather than an enum so a caller can name a forge this package has never heard of, and so the package needs no dependency on a forge or release abstraction merely to identify one.
const ( SourceUnknown = iota SourceMemory SourceLocal )
Variables ¶
var ( // ErrNoRepository is returned by WithRepo when no *git.Repository has been set. ErrNoRepository = errors.NewSentinel("repo.no_repository", "repository not initialised; call Open, Clone, or SetRepo first") // ErrNoWorktree is returned by WithTree when no *git.Worktree has been set. ErrNoWorktree = errors.NewSentinel("repo.no_worktree", "worktree not initialised; call Open, Clone, or SetTree first") // ErrTagAlreadyExists reports a tag that is already present. This module // never moves one; see spec 0006 D5. ErrTagAlreadyExists = errors.NewSentinel("repo.tag_exists", "tag already exists") // ErrInvalidCommit reports a commit reference that is not a full SHA. An // abbreviation is refused rather than expanded, because expanding is // resolving. See spec 0006 D3. ErrInvalidCommit = errors.NewSentinel("repo.invalid_commit", "commit must be a full 40-character SHA") // ErrMissingTagger reports an absent or incomplete tagger identity. go-git // would otherwise read one from the machine's git configuration. ErrMissingTagger = errors.NewSentinel("repo.missing_tagger", "an explicit tagger name and email are required") // ErrMissingTagMessage reports an empty tag message. Tags created here are // annotated, so a message is required. ErrMissingTagMessage = errors.NewSentinel("repo.missing_tag_message", "an annotated tag requires a message") // ErrMissingTagField reports a required field absent from a TagRequest. ErrMissingTagField = errors.NewSentinel("repo.missing_tag_field", "missing required field") // ErrNoSparseDirectories reports WithSparseCheckout given no directory, or // one naming the whole tree. Applying either as a full checkout would spend // what the caller asked to save. See spec 0007 D5. ErrNoSparseDirectories = errors.NewSentinel("repo.no_sparse_directories", "sparse checkout requires at least one directory") // ErrNoSparseMatch reports a sparse set that matched no path in the tree. // Without it the first symptom is an empty commit refused three calls // later, which reads as "nothing to do". See spec 0007 D9. ErrNoSparseMatch = errors.NewSentinel("repo.no_sparse_match", "sparse checkout matched no path in the tree") )
var ErrAlreadyRepository = errors.NewSentinel("repo.already_repository", "path is already inside a git repository")
ErrAlreadyRepository is returned by InitLocal when the target path is already inside a git repository. Unlike OpenLocal, which opens an existing repo or initialises a new one and conflates the two outcomes, InitLocal refuses to act on an existing repository so a caller (e.g. a scaffolding tool's first-commit step) can reliably distinguish "newly initialised" from "already tracked".
var ErrMissingGitDir = errors.NewSentinel("repo.missing_git_dir", "git directory is missing or unusable")
ErrMissingGitDir is returned when a location holds a .git file naming a git directory that is not there, or that holds no repository. That is the state a linked worktree, a --separate-git-dir checkout or a submodule lands in when the repository behind it is moved, pruned or re-cloned: the working directory survives with real work in it, and the administrative directory it points at does not.
All three features use the same .git redirect file, so all three produce this error. The working directory is never touched. The point of the sentinel is that a caller can tell "this directory is empty, initialise it" apart from "this directory holds work whose repository has gone missing, stop".
A .git *directory* is a different case and does not produce this: it is handed to go-git's init exactly as "git init" would hand it to git's. See docs/explanation/limitations.md.
Functions ¶
func DiscoverRepository ¶
DiscoverRepository reports whether location is inside a git repository, walking upward from location to find an enclosing .git directory (matching git's own discovery semantics). It is a read-only probe: it never creates or modifies a repository. A subdirectory of an existing repository reports true.
This is distinct from OpenLocal, which init-if-absent and cannot tell an opened existing repository apart from a freshly initialised one.
A location whose .git names a git directory that has gone returns ErrMissingGitDir rather than (false, nil): a repository that cannot be reached is not the same answer as no repository at all. InitLocal propagates that, so it refuses such a location rather than initialising over it.
func GetSSHKey ¶
GetSSHKey loads an SSH private key from the filesystem for git authentication. If the key is passphrase-protected, the returned error wraps gossh.PassphraseMissingError; interactive callers (the CLI layer) should detect it with errors.As, prompt the user, and retry via GetSSHKeyWithPassphrase. This package never blocks on a TUI.
func GetSSHKeyWithPassphrase ¶
func GetSSHKeyWithPassphrase(filePath string, localfs afero.Fs, passphrase string) (*ssh.PublicKeys, error)
GetSSHKeyWithPassphrase loads an SSH private key from the filesystem, decrypting it with the supplied passphrase when non-empty.
func KeyPath ¶
KeyPath resolves an SSH key path from an explicit path with an environment-variable fallback, returning "" when neither yields one.
It is a helper for callers assembling SSHSettings, not something this package calls itself. Reading the environment stays the caller's decision, made in its composition root where the rest of its configuration is resolved:
settings.SSH.Path = repo.KeyPath(cfg.SSHKeyPath, "GITHUB_SSH_KEY")
Types ¶
type Authenticator ¶
type Authenticator interface {
SetKey(*ssh.PublicKeys)
SetBasicAuth(string, string)
GetAuth() transport.AuthMethod
SetRepo(*git.Repository)
}
Authenticator configures credentials and the underlying repository handle.
type Brancher ¶
Brancher creates branches. CreateBranch takes a context.Context because it may pull from a remote for an already-existing branch.
type CloneOption ¶
type CloneOption func(*CloneOptions)
CloneOption represents a function that configures clone options.
func WithNoTags ¶
func WithNoTags() CloneOption
WithNoTags configures the clone to skip fetching tags.
func WithRecurseSubmodules ¶
func WithRecurseSubmodules() CloneOption
WithRecurseSubmodules configures recursive submodule cloning.
func WithShallowClone ¶
func WithShallowClone(depth int) CloneOption
WithShallowClone configures a shallow clone with the specified depth.
func WithSingleBranch ¶
func WithSingleBranch(branch string) CloneOption
WithSingleBranch configures the clone to only fetch a single branch.
func WithSparseCheckout ¶ added in v0.5.0
func WithSparseCheckout(dirs ...string) CloneOption
WithSparseCheckout limits the working tree to the paths under the given directory prefixes. The clone fetches every object either way; what it skips is materialising the rest of the tree, which for an in-memory repository is the second copy of every file.
go-git matches by string prefix, so "data/releases" also keeps "data/releases-old/"; end a prefix with "/" for an exact directory. The working tree stays sparse for the life of the handle: a later Checkout does not widen it. See spec 0007.
type CloneOptions ¶ added in v0.5.0
type CloneOptions struct {
git.CloneOptions
// SparseCheckoutDirectories limits the working tree to the paths under
// these prefixes. Nil means no sparse checkout; empty means one was asked
// for with nothing in it, which is refused.
SparseCheckoutDirectories []string
}
CloneOptions is what a CloneOption configures. It embeds go-git's CloneOptions, so a hand-written option still reaches every go-git field, and carries beside it what the module does after the clone. See spec 0007 D2.
type Committer ¶
type Committer interface {
Commit(context.Context, string, *git.CommitOptions) (plumbing.Hash, error)
Push(context.Context, *git.PushOptions) error
}
Committer records and publishes changes. Push takes a context.Context so a hung remote can be cancelled; Commit accepts one for signature symmetry but performs no network I/O.
type GitAccessor ¶
type GitAccessor interface {
// WithRepo calls fn with the underlying *git.Repository.
// Returns ErrNoRepository if the repository has not been initialised.
WithRepo(func(*git.Repository) error) error
// WithTree calls fn with the underlying *git.Worktree.
// Returns ErrNoWorktree if the worktree has not been initialised.
WithTree(func(*git.Worktree) error) error
}
GitAccessor exposes raw go-git escape hatches.
type Initializer ¶
type Initializer interface {
InitLocal(location, branch string) (*git.Repository, *git.Worktree, error)
AddAll() error
}
Initializer is the init-only role: it initialises a fresh repository on an explicit default branch and stages the worktree honouring .gitignore. It is deliberately NOT embedded in RepoLike, because init/discovery and gitignore-aware staging are first-commit concerns a clone/checkout consumer never needs, so callers that need them (the generator's scaffold step) depend on this narrow role directly. Both *Repo and *ThreadSafeRepo satisfy it.
type Logger ¶
Logger is the diagnostic logging seam. It is deliberately two methods wide, because this package reports what it did and what it skipped and nothing more, so a *slog.Logger satisfies it directly and no logging library is imposed on consumers.
A nil Logger is tolerated: diagnostics are simply discarded.
type Opener ¶
type Opener interface {
Open(context.Context, RepoType, string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
OpenLocal(context.Context, string) (*git.Repository, *git.Worktree, error)
OpenInMemory(context.Context, string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
Clone(context.Context, string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
}
Opener acquires a repository via local open, in-memory open, or clone. The network-touching methods (OpenInMemory and Clone, plus Open when it dispatches to them) take a context.Context so a hung remote can be cancelled; OpenLocal accepts one for signature symmetry but performs no network I/O.
type Repo ¶
type Repo struct {
// contains filtered or unexported fields
}
Repo implements RepoLike using go-git for local and in-memory repositories.
func NewRepo ¶
NewRepo creates a Repo with the given typed settings and options.
Clone/push authentication is resolved from the selected forge (github, gitlab, bitbucket, gitea, codeberg). When Settings.SSH.Configured is set, SSH auth is used and Settings.Token is never invoked; otherwise token auth is resolved lazily from Settings.Token. A missing credential is only an error when Settings.Private is true.
func (*Repo) AddAll ¶
AddAll stages the entire worktree, honouring .gitignore rules so ignored paths (build artefacts and the like) are not committed. The .gitignore file itself is staged. It wraps go-git's Worktree.AddWithOptions{All: true}, which applies the repository's ignore patterns, which a plain Add(".") would not.
Returns ErrNoWorktree if the worktree has not been initialised.
func (*Repo) Checkout ¶
func (r *Repo) Checkout(branch plumbing.ReferenceName) error
Checkout checks out the given branch. Returns ErrNoWorktree if the worktree has not been initialised.
func (*Repo) CheckoutCommit ¶
CheckoutCommit checks out a specific commit by hash. Returns ErrNoWorktree if the worktree has not been initialised.
func (*Repo) Clone ¶
func (r *Repo) Clone(ctx context.Context, uri string, targetPath string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
Clone clones a git repository to a target path on the filesystem Supports both remote URLs and local git repository paths with clone options.
func (*Repo) Commit ¶
func (r *Repo) Commit(_ context.Context, commitMsg string, opts *git.CommitOptions) (plumbing.Hash, error)
Commit records a commit on the current worktree. Returns ErrNoWorktree if the worktree has not been initialised. The context is accepted for Committer signature symmetry; committing to the local worktree performs no network I/O.
func (*Repo) CreateBranch ¶
CreateBranch creates a branch in the git tree. Returns ErrNoRepository / ErrNoWorktree if the repo has not been opened.
func (*Repo) CreateRemote ¶
CreateRemote creates a named remote with the given URLs. Returns ErrNoRepository if the repository has not been initialised.
func (*Repo) CreateTag ¶ added in v0.4.0
CreateTag creates an annotated tag at req.Commit.
It does not push. Publishing is Committer.Push with the refspec the caller wants, which keeps both steps visible and leaves the caller to decide what a failed push should do about the local tag.
Returns an error wrapping ErrTagAlreadyExists when the tag exists. This never moves one: a published tag has been fetched by consumers, and moving it means two artefacts answer to one version.
func (*Repo) DirectoryExists ¶
DirectoryExists checks if a directory exists in the git repository at the given relative path In git, directories don't exist as separate entities - we check if any files exist under the path. Returns ErrNoRepository if the repository has not been initialised.
func (*Repo) FileExists ¶
FileExists checks if a file exists in the git repository at the given relative path. Returns ErrNoRepository if the repository has not been initialised.
func (*Repo) GetAuth ¶
func (r *Repo) GetAuth() transport.AuthMethod
func (*Repo) GetFile ¶
GetFile retrieves a file from the git repository at the given relative path. Returns ErrNoRepository if the repository has not been initialised.
func (*Repo) InitLocal ¶
InitLocal initialises a new git repository at location on the named default branch and binds it (and its worktree) to the receiver. It is the init-only counterpart to OpenLocal: if location is already inside a repository it returns ErrAlreadyRepository rather than silently opening the existing one, so callers can keep init and open as separate, explicit decisions.
branch defaults to "main" when empty.
func (*Repo) Open ¶
func (r *Repo) Open(ctx context.Context, repoType RepoType, location string, branch string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
Open opens a repository of the given type. For a local repository it opens (or initialises) location on disk; for an in-memory repository it clones location into memory. The branch argument applies only to the in-memory path (the local path defaults to "main" when initialising).
func (*Repo) OpenInMemory ¶
func (*Repo) OpenLocal ¶
func (r *Repo) OpenLocal(_ context.Context, location string) (*git.Repository, *git.Worktree, error)
OpenLocal opens the local git repository at location, initialising a fresh repository (on default branch "main") only when none exists there. It deliberately distinguishes "no repository" from "corrupt repository": the init fallback is gated on git.ErrRepositoryNotExists, so any other open failure (a malformed .git, a path that is not a directory, and the like) is returned wrapped rather than masked by an init attempt that would clobber the on-disk state.
A **linked** git worktree (one created by "git worktree add", whose .git is a file redirecting to <parent>/.git/worktrees/<name>/) is read correctly: HEAD and the index come from the worktree's own directory, while refs and objects come from the common directory named by the commondir file beside them. That is what EnableDotGitCommonDir buys, and it is not optional. Without it this returns a nil error and a handle that fails on first use, which is worse than failing to open. An ordinary checkout has no commondir file and takes exactly the same path it always did.
When the git directory a .git file names has gone (the parent pruned, moved or re-cloned) this returns ErrMissingGitDir and writes nothing. The discriminator is that .git is present and is not a directory, so a --separate-git-dir checkout and a submodule reach it identically; all three use the same redirect file. A .git *directory* that go-git cannot open is a different case and still takes the init fallback, matching "git init".
The context is accepted for Opener signature symmetry; opening or initialising a local repository performs no network I/O.
func (*Repo) Push ¶
Push pushes the repository using the configured auth when opts.Auth is nil. Returns ErrNoRepository if the repository has not been initialised.
func (*Repo) Remote ¶
Remote returns the named remote. Returns ErrNoRepository if the repository has not been initialised.
func (*Repo) SetBasicAuth ¶
func (*Repo) SetKey ¶
func (r *Repo) SetKey(key *ssh.PublicKeys)
func (*Repo) SetRepo ¶
func (r *Repo) SetRepo(repo *git.Repository)
func (*Repo) WalkTree ¶
WalkTree walks the git tree and calls the provided function for each file. Returns ErrNoRepository if the repository has not been initialised.
func (*Repo) WithRepo ¶
func (r *Repo) WithRepo(fn func(*git.Repository) error) error
WithRepo calls fn with the underlying *git.Repository. Repo is not safe for concurrent use; callers are responsible for external synchronisation if sharing a *Repo across goroutines.
Returns ErrNoRepository if the repository has not been initialised.
func (*Repo) WithTree ¶
WithTree calls fn with the underlying *git.Worktree. Repo is not safe for concurrent use; callers are responsible for external synchronisation if sharing a *Repo across goroutines.
Returns ErrNoWorktree if the worktree has not been initialised.
func (*Repo) WithWorkFS ¶
WithWorkFS runs fn with an afero view of the worktree.
type RepoLike ¶
type RepoLike interface {
TreeReader
Opener
Authenticator
WorktreeController
Committer
SourceState
GitAccessor
Brancher
Tagger
WorktreeFS
}
RepoLike is the full git-repository interface, composed of focused role interfaces. Prefer depending on the narrower roles (TreeReader, Committer, …) where possible. Its method set is identical to the sum of the embedded roles, so *Repo and *ThreadSafeRepo satisfy it unchanged.
type RepoOpt ¶
RepoOpt is a functional option for configuring a Repo.
func WithConfig ¶
WithConfig sets the go-git config for the repository.
type RepoType ¶
type RepoType string
RepoType identifies the repository backend (local filesystem or in-memory). It is a defined type rather than a bare string alias so the backend discriminator carries its own type at call sites.
type SSHSettings ¶
SSHSettings describes the forge SSH key configuration resolved by the caller. Configured tracks whether the forge's SSH block exists at all; HasKey distinguishes a present-but-scalar SSH block from a structured ssh.key block.
Path is a resolved filesystem path, never an environment-variable name: this package reads no environment of its own, so that every input arrives through Settings. Use KeyPath to apply the usual "explicit path, else named environment variable" precedence at the call site.
type Settings ¶
type Settings struct {
// Forge selects the git-over-HTTPS authentication convention: one of the
// Forge* constants, or any other string (treated as GitHub). Empty defaults
// to GitHub.
Forge string
// Private marks the target repository as private, which makes a missing
// token a fast failure rather than an unauthenticated attempt.
Private bool
// AuthEnabled requests token authentication even when no token has been
// supplied, so a missing credential is reported rather than silently skipped.
AuthEnabled bool
// Token supplies the access token, resolved lazily. See [TokenSource].
Token TokenSource
SSH SSHSettings
Logger Logger
// FS is the filesystem used to read SSH key files. Defaults to
// afero.NewOsFs() when nil.
FS afero.Fs
// Progress receives the remote server's sideband output during clone, pull
// and push: the stream real git renders as "Receiving objects: 45%" and
// "remote: ..." lines. Nil discards it.
//
// This is the server's channel, not this package's: it carries pre-receive
// hook rejection reasons and the "create a merge request at ..." URLs that
// arrive nowhere else, so discarding it loses information Logger cannot
// reproduce. Being remote-controlled text, it belongs on a terminal or in a
// buffer rather than piped unsanitised into a log aggregator.
//
// It is a Settings field rather than an environment-variable switch so the
// decision is made by the caller at construction, is scoped to one
// repository rather than the whole process, and can be captured in a test
// without mutating global state.
Progress io.Writer
}
Settings contains the typed configuration NewRepo needs to resolve git authentication without depending on a forge abstraction, a config container, or a DI container. Callers resolve those concerns and pass the results here.
type SourceState ¶
SourceState discriminates the repository backend (in-memory vs local).
type TagRequest ¶ added in v0.4.0
type TagRequest struct {
// Name is the tag, without the refs/tags/ prefix — "v1.2.3".
Name string
// Commit is the FULL 40-character SHA the tag points at. It is validated,
// never resolved.
//
// A full SHA, and a string rather than a plumbing.Hash, because
// plumbing.NewHash does not report a bad value and its failure mode for
// the likeliest mistake is the dangerous one: an ABBREVIATED sha is
// silently zero-padded into a different, valid-looking hash that is not
// zero. A guard written as hash.IsZero() lets it through, and the tag then
// names an object that does not exist.
//
// Resolving a branch name here is refused rather than supported. A caller
// reaching this point has already established which commit to tag — in a
// release workflow, by asking the forge what actually landed — and
// re-resolving a name would reopen the race that check exists to close.
Commit string
// Message annotates the tag. Required: this creates annotated tags only,
// because a release tag should record who made it and when rather than
// being a bare pointer.
Message string
// Tagger identifies who made the tag. REQUIRED.
//
// go-git reads the name and email from the machine's git configuration
// when this is nil, which attributes the same release to different people
// depending on where it was cut — and in CI, to whatever identity the
// runner image carries.
Tagger *object.Signature
}
TagRequest describes one annotated tag. See spec 0006.
type Tagger ¶ added in v0.4.0
Tagger creates annotated tags.
Its own role rather than a method on Committer because they are not the same job: a caller that tags a release does not commit, and one that writes a branch does not tag. Widening Committer would give every existing consumer a dependency on an operation it does not perform, without changing the interface it names. See spec 0006 D1.
CreateTag accepts a context.Context for signature symmetry with the rest of the module; it performs no network I/O, because tagging is local and publishing is Committer.Push.
type ThreadSafeRepo ¶
type ThreadSafeRepo struct {
// contains filtered or unexported fields
}
ThreadSafeRepo wraps a *Repo with a mutex so that all RepoLike methods are safe to call concurrently from multiple goroutines.
Thread-safety guarantee ¶
Every method acquires the internal mutex for its full duration. Concurrent callers are serialised; no two calls to any method execute simultaneously.
WithRepo and WithTree ¶
These are the only way to interact with the underlying go-git objects safely. The callback executes while the mutex is held. Callers must not:
- Retain the pointer after the callback returns.
- Call any ThreadSafeRepo method from inside the callback (deadlock).
- Spawn goroutines inside the callback that access the pointer after it returns.
go-git concurrency model ¶
go-git mutates internal caches during read operations. ThreadSafeRepo uses sync.Mutex (exclusive) rather than sync.RWMutex; concurrent reads are not permitted.
func NewThreadSafeRepo ¶
func NewThreadSafeRepo(settings Settings, opts ...RepoOpt) (*ThreadSafeRepo, error)
NewThreadSafeRepo creates a ThreadSafeRepo backed by a freshly constructed *Repo. The settings and opts arguments have the same semantics as NewRepo.
func (*ThreadSafeRepo) AddAll ¶
func (r *ThreadSafeRepo) AddAll() error
func (*ThreadSafeRepo) Checkout ¶
func (r *ThreadSafeRepo) Checkout(branch plumbing.ReferenceName) error
func (*ThreadSafeRepo) CheckoutCommit ¶
func (r *ThreadSafeRepo) CheckoutCommit(hash plumbing.Hash) error
func (*ThreadSafeRepo) Clone ¶
func (r *ThreadSafeRepo) Clone(ctx context.Context, uri string, targetPath string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
func (*ThreadSafeRepo) Commit ¶
func (r *ThreadSafeRepo) Commit(ctx context.Context, commitMsg string, opts *git.CommitOptions) (plumbing.Hash, error)
func (*ThreadSafeRepo) CreateBranch ¶
func (r *ThreadSafeRepo) CreateBranch(ctx context.Context, branchName string) error
func (*ThreadSafeRepo) CreateTag ¶ added in v0.4.0
func (r *ThreadSafeRepo) CreateTag(ctx context.Context, req TagRequest) (plumbing.Hash, error)
func (*ThreadSafeRepo) DirectoryExists ¶
func (r *ThreadSafeRepo) DirectoryExists(relPath string) (bool, error)
func (*ThreadSafeRepo) FileExists ¶
func (r *ThreadSafeRepo) FileExists(relPath string) (bool, error)
func (*ThreadSafeRepo) GetAuth ¶
func (r *ThreadSafeRepo) GetAuth() transport.AuthMethod
func (*ThreadSafeRepo) GetFile ¶
func (r *ThreadSafeRepo) GetFile(relPath string) (*object.File, error)
func (*ThreadSafeRepo) InitLocal ¶
func (r *ThreadSafeRepo) InitLocal(location string, branch string) (*git.Repository, *git.Worktree, error)
func (*ThreadSafeRepo) Open ¶
func (r *ThreadSafeRepo) Open(ctx context.Context, repoType RepoType, location string, branch string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
func (*ThreadSafeRepo) OpenInMemory ¶
func (r *ThreadSafeRepo) OpenInMemory(ctx context.Context, location string, branch string, opts ...CloneOption) (*git.Repository, *git.Worktree, error)
func (*ThreadSafeRepo) OpenLocal ¶
func (r *ThreadSafeRepo) OpenLocal(ctx context.Context, location string) (*git.Repository, *git.Worktree, error)
func (*ThreadSafeRepo) Push ¶
func (r *ThreadSafeRepo) Push(ctx context.Context, opts *git.PushOptions) error
func (*ThreadSafeRepo) SetBasicAuth ¶
func (r *ThreadSafeRepo) SetBasicAuth(username, password string)
func (*ThreadSafeRepo) SetKey ¶
func (r *ThreadSafeRepo) SetKey(key *ssh.PublicKeys)
func (*ThreadSafeRepo) SetRepo ¶
func (r *ThreadSafeRepo) SetRepo(repo *git.Repository)
func (*ThreadSafeRepo) SetSource ¶
func (r *ThreadSafeRepo) SetSource(source int)
func (*ThreadSafeRepo) SetTree ¶
func (r *ThreadSafeRepo) SetTree(tree *git.Worktree)
func (*ThreadSafeRepo) SourceIs ¶
func (r *ThreadSafeRepo) SourceIs(source int) bool
func (*ThreadSafeRepo) WalkTree ¶
func (r *ThreadSafeRepo) WalkTree(fn func(*object.File) error) error
func (*ThreadSafeRepo) WithRepo ¶
func (r *ThreadSafeRepo) WithRepo(fn func(*git.Repository) error) error
WithRepo acquires the mutex and calls fn with the underlying *git.Repository. The callback executes while the lock is held.
Returns ErrNoRepository if the repository has not been initialised.
func (*ThreadSafeRepo) WithTree ¶
func (r *ThreadSafeRepo) WithTree(fn func(*git.Worktree) error) error
WithTree acquires the mutex and calls fn with the underlying *git.Worktree. The callback executes while the lock is held.
Returns ErrNoWorktree if the worktree has not been initialised.
func (*ThreadSafeRepo) WithWorkFS ¶
func (r *ThreadSafeRepo) WithWorkFS(fn func(afero.Fs) error) error
WithWorkFS runs fn with an afero view of the worktree while holding the repo lock for the whole callback (via WithTree). The view uses a no-op locker because the lock is already held, and re-locking the non-reentrant mutex would deadlock.
func (*ThreadSafeRepo) WorkFS ¶
func (r *ThreadSafeRepo) WorkFS() (afero.Fs, error)
WorkFS returns the worktree as an afero.Fs whose every operation re-locks the repo mutex, so the returned handle is safe for concurrent use even though the worktree filesystem reference escapes this method (see WorktreeFS.WorkFS).
type TokenSource ¶
type TokenSource func() string
TokenSource returns an access token for git-over-HTTPS authentication.
It is a function rather than a resolved string because resolution can be expensive or interactive: a token may live in the OS keychain, where an eager lookup could prompt the user to unlock it. The repository calls the source only on the code path that actually authenticates with a token: SSH authentication takes priority, and a repository configured for SSH never invokes it at all.
Use StaticToken when the token is already in hand.
func StaticToken ¶
func StaticToken(token string) TokenSource
StaticToken adapts an already-resolved token to a TokenSource. An empty token is treated as "no token available".
type TreeReader ¶
type TreeReader interface {
WalkTree(func(*object.File) error) error
FileExists(string) (bool, error)
DirectoryExists(string) (bool, error)
GetFile(string) (*object.File, error)
AddToFS(fs afero.Fs, gitFile *object.File, fullPath string) error
}
TreeReader provides read-only queries over the committed git tree.
type WorktreeController ¶
type WorktreeController interface {
SetTree(*git.Worktree)
Checkout(plumbing.ReferenceName) error
CheckoutCommit(plumbing.Hash) error
}
WorktreeController manages working-tree and checkout state.
type WorktreeFS ¶
type WorktreeFS interface {
// WorkFS returns the active worktree's filesystem as an afero.Fs, or
// [ErrNoWorktree] if no worktree is open.
//
// On [ThreadSafeRepo] the returned FS is safe for concurrent use: every
// operation re-locks the repo mutex. Operations are individually atomic, but
// a SEQUENCE is not, because a concurrent Commit/Add may interleave between two
// writes. Use [WorktreeFS.WithWorkFS] when a sequence must be atomic.
//
// The returned FS (and any file it opens) MUST NOT be used from inside a
// WithWorkFS / WithTree / WithRepo callback: that region already holds the
// repo's (non-reentrant) mutex, so re-locking would deadlock.
WorkFS() (afero.Fs, error)
// WithWorkFS runs fn with an afero view of the worktree while holding the
// repo lock for the whole callback, so the sequence is atomic relative to
// other repo operations. The afero.Fs passed to fn is unsynchronised (the
// lock is already held); fn MUST NOT retain it past return, call any other
// repo method, or open a WorkFS() handle.
WithWorkFS(fn func(afero.Fs) error) error
}
WorktreeFS is a RepoLike role exposing the active worktree as an afero.Fs, so afero-based code can read and write an in-memory (or local) git worktree directly: single source of truth, no materialise/sync step (contrast with TreeReader.AddToFS, which copies a git tree into a *separate* afero FS).