gitstate

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 16 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrDetachedHEAD = errors.New("HEAD is not on a branch — checkout a named branch first")

ErrDetachedHEAD is returned when HEAD is not on a named branch.

View Source
var ErrDirtyWorktree = errors.New("worktree has uncommitted changes — commit or stash first")

ErrDirtyWorktree is returned when uncommitted changes are present before replay.

View Source
var ErrNoUpstream = errors.New("branch has no upstream tracking ref — run: git branch --set-upstream-to=<remote>/<branch>")

ErrNoUpstream is returned when the current branch has no tracking upstream configured.

View Source
var ErrUpstreamMoved = errors.New("upstream ref changed between fetch and push — retry from fetch")

ErrUpstreamMoved is returned when the remote ref changes between fetch and push.

Functions

func AllChangedFiles

func AllChangedFiles(s git.Status) []string

AllChangedFiles returns paths with any modification (staged, unstaged, or untracked).

func AllDirtyPaths

func AllDirtyPaths(s git.Status) []string

AllDirtyPaths returns paths with staged or unstaged modifications, excluding untracked files. Use for bundle/artifact generation where both staged and unstaged changes must be captured. Output is sorted for deterministic ordering.

func CountCommitsBetween

func CountCommitsBetween(repo *git.Repository, fromRef, toRef string) (int, error)

CountCommitsBetween counts commits reachable from `to` but not from `from`. Equivalent to `git rev-list --count <from>..<to>`.

func DiffStats

func DiffStats(repo *git.Repository, fromRef, toRef string) (files, insertions, deletions int, err error)

DiffStats returns file/insertion/deletion counts between two refs.

func ExactTagAtHEAD

func ExactTagAtHEAD(repo *git.Repository) (string, error)

ExactTagAtHEAD returns the tag name if HEAD is exactly at a tagged commit, matching `git describe --tags --exact-match HEAD`. Returns "" if not at a tag.

func HasStagedChanges

func HasStagedChanges(s git.Status) bool

HasStagedChanges returns true when any file has a staged modification.

func HasUnstagedChanges

func HasUnstagedChanges(s git.Status) bool

HasUnstagedChanges returns true when any tracked file has unstaged modifications.

func IsAncestor

func IsAncestor(repo *git.Repository, ancestorRef, descendantRef string) (bool, error)

IsAncestor returns true if ancestorRef is a (possibly indirect) ancestor of descendantRef. Equivalent to `git merge-base --is-ancestor <ancestor> <descendant>`.

func IsClean

func IsClean(s git.Status) bool

IsClean returns true when the status has no modifications of any kind.

func IsSSHURL

func IsSSHURL(url string) bool

IsSSHURL is the exported form of isSSHURL for use by other packages.

func ListTagsSorted

func ListTagsSorted(repo *git.Repository) ([]string, error)

ListTagsSorted returns all tags sorted by version descending, matching git's --sort=-version:refname behaviour. Semver tags are sorted by semver; non-semver tags are sorted lexicographically after all semver tags.

Parity with git: Masterminds/semver parses the same set of tags as git's version-aware sort for real-world repos (semver + v-prefix). Both classify tags as version-like or lexicographic using equivalent rules, and both produce version-like tags before lexicographic tags in descending order. Edge case: tags with identical version values but different prefixes (v1.0.0 and 1.0.0) are sorted stably by the sort.Slice guarantee — same as git.

func OpenRepo

func OpenRepo(rootDir string) (*git.Repository, error)

OpenRepo is the single entry point for all *git.Repository instances. No package outside src/gitstate/ or src/commit/ may call git.PlainOpen directly. DetectDotGit walks parent directories to find .git, matching git CLI behaviour.

func ParseCommitLog

func ParseCommitLog(repo *git.Repository, fromRef, toRef string) ([]*object.Commit, error)

ParseCommitLog returns commits in the range fromRef..toRef as (hash, subject, body, author) tuples.

func RemoteRefHash

func RemoteRefHash(repo *git.Repository, remoteName, refName string, auth transport.AuthMethod) (plumbing.Hash, error)

RemoteRefHash returns the hash of a specific ref on the remote. Equivalent to `git ls-remote origin refs/heads/<branch>`. Requires network access.

func RemoteURL

func RemoteURL(repo *git.Repository, remoteName string) (string, error)

RemoteURL returns the URL for the given remote (typically "origin").

func RepoRoot

func RepoRoot(repo *git.Repository) (string, error)

RepoRoot returns the absolute path of the repository root directory (the directory containing .git). Use this instead of wt.Filesystem.Root() directly — encapsulates the go-git worktree filesystem contract in one place.

func RequireAttached

func RequireAttached(state RepoState) error

RequireAttached returns ErrDetachedHEAD if the repo is in detached HEAD state.

func RequireClean

func RequireClean(state RepoState) error

RequireClean returns ErrDirtyWorktree if the worktree has uncommitted changes.

func RequireState

func RequireState(state RepoState, action string, allowed ...StateClass) error

RequireState verifies that the current state falls into one of the allowed StateClasses for the given action. Returns ErrInvalidTransition if not.

func RequireUpstream

func RequireUpstream(state RepoState) error

RequireUpstream returns ErrNoUpstream if no upstream tracking branch is configured.

func RequireValid

func RequireValid(state RepoState) error

RequireValid enforces that none of the X-class blocked states are active. This is the universal pre-mutation gate — call it before any operation that touches the repository.

func ResolveAuth

func ResolveAuth(remoteURL string) (transport.AuthMethod, error)

ResolveAuth resolves the go-git SSH transport auth method for a remote URL.

Resolution order (exclusive — first match wins):

  1. SSH_PRIVATE_KEY env var (in-memory, no filesystem dependency)
  2. SSH agent (SSH_AUTH_SOCK)
  3. Standard key files: id_ed25519, id_ecdsa, id_rsa

Host key verification is resolved via sfxssh.ResolveHostKeyCallback (same priority as raw SSH transport — SSH_KNOWN_HOSTS_CONTENT, SSH_KNOWN_HOSTS, ~/.ssh/known_hosts, SSH_INSECURE_SKIP_HOST_KEY_CHECK).

Returns an error when no auth is available — SSH auth failure is always fatal.

func ResolveHTTPAuth

func ResolveHTTPAuth(_ string) (*githttp.BasicAuth, error)

ResolveHTTPAuth returns HTTP basic auth for an HTTPS remote, resolving a credential from the environment so CI write-back (e.g. the deps auto-commit push) authenticates instead of failing with "HTTP Basic: Access denied".

Resolution order (first match wins):

  1. STAGEFREIGHT_GIT_USERNAME + STAGEFREIGHT_GIT_PASSWORD — explicit override.
  2. GITLAB_TOKEN — a Personal/Project Access Token (username "oauth2").
  3. GITHUB_TOKEN — username "x-access-token".
  4. CI_JOB_TOKEN — GitLab's per-job token (username "gitlab-ci-token"). LAST resort: it is read-only for repository writes by default, so a push needs a write-scoped token from (1)/(2); the job token only authenticates reads.

Returns (nil, nil) when nothing is set — preserving anonymous access to public HTTPS repos. A nil return is not an error: SSH remotes never reach here, and an unauthenticated push to a private remote fails loudly at push time.

func ResolveRef

func ResolveRef(repo *git.Repository, ref string) (string, error)

ResolveRef resolves any git ref (tag, branch, commit SHA, HEAD) to a commit SHA. Equivalent to `git rev-parse --verify <ref>^{commit}`.

func StagedFiles

func StagedFiles(s git.Status) []string

StagedFiles returns paths with staged changes (Staging != Unmodified).

func TagMessage

func TagMessage(repo *git.Repository, ref string) string

TagMessage returns the annotation message for an annotated tag. Returns "" for lightweight tags or on error (best-effort).

func UnstagedDirtyPaths

func UnstagedDirtyPaths(s git.Status) []string

UnstagedDirtyPaths returns paths with unstaged modifications (not staged, not untracked).

func WorktreeStatus

func WorktreeStatus(wt *git.Worktree) (git.Status, error)

WorktreeStatus returns the current worktree status (staged + unstaged + untracked). Equivalent to `git status --porcelain`.

Types

type ErrHookRejected

type ErrHookRejected struct {
	Hook     string
	ExitCode int
}

ErrHookRejected is returned when a pre-commit or commit-msg hook exits non-zero.

func (*ErrHookRejected) Error

func (e *ErrHookRejected) Error() string

type ErrIndexDrift

type ErrIndexDrift struct {
	CommitHash plumbing.Hash
}

ErrIndexDrift is returned when the index diverges from the intended tree mid-replay. A hard reset to originalHEAD is performed before this error is returned.

func (*ErrIndexDrift) Error

func (e *ErrIndexDrift) Error() string

type ErrInvalidTransition

type ErrInvalidTransition struct {
	From    StateClass
	Action  string
	Allowed []StateClass
}

ErrInvalidTransition is returned when an operation is requested from a state that does not permit it. This is the definitive "impossible transition" error.

func (*ErrInvalidTransition) Error

func (e *ErrInvalidTransition) Error() string

type ErrPathTraversal

type ErrPathTraversal struct {
	Path string
}

ErrPathTraversal is returned when a diff path would escape the repository root.

func (*ErrPathTraversal) Error

func (e *ErrPathTraversal) Error() string

type ErrReplayCorrupted

type ErrReplayCorrupted struct {
	Original plumbing.Hash
	Replayed plumbing.Hash
}

ErrReplayCorrupted is returned when the post-replay tree does not match the original HEAD tree. Mutation occurred and diverged. A hard reset to originalHEAD is performed before returning.

func (*ErrReplayCorrupted) Error

func (e *ErrReplayCorrupted) Error() string

type ErrReplayUnsafe

type ErrReplayUnsafe struct {
	Reasons []string // descriptions of gate violations per commit
}

ErrReplayUnsafe is returned when the replay gate rejects commits before any mutation. This is distinct from ErrReplayCorrupted: no mutation has occurred.

func (*ErrReplayUnsafe) Error

func (e *ErrReplayUnsafe) Error() string

type ErrTransport

type ErrTransport struct {
	Err error
	Msg string
}

ErrTransport wraps SSH auth or push failures with an actionable message.

func (*ErrTransport) Error

func (e *ErrTransport) Error() string

func (*ErrTransport) Unwrap

func (e *ErrTransport) Unwrap() error

type RepoState

type RepoState struct {
	Branch             string // current branch name (empty if DetachedHEAD)
	UpstreamRef        string // e.g. "origin/main" — empty if not configured
	UpstreamConfigured bool
	AheadCount         int // commits local has that remote does not
	BehindCount        int // commits remote has that local does not
	DetachedHEAD       bool
	WorktreeClean      bool          // true when no staged or unstaged changes exist
	HeadHash           plumbing.Hash // current commit hash
	UpstreamHash       plumbing.Hash // remote tracking hash (zero if not configured)
	RemoteName         string        // e.g. "origin"
}

RepoState is the result of interrogating the current repository condition. ReadRepoState always returns a fully populated struct — callers must check DetachedHEAD and UpstreamConfigured before interpreting other fields.

All fields are resolved once at read time. No polling.

func ReadRepoState

func ReadRepoState(repo *git.Repository) (RepoState, error)

ReadRepoState reads the current repository state. This is the single read-only knowledge pool for all repo facts. No package should read HEAD, upstream, or branch state independently.

func (RepoState) Diverged

func (s RepoState) Diverged() bool

Diverged returns true when local and remote have independent commits.

type StateClass

type StateClass string

StateClass classifies a RepoState into a named state for the transition table.

S-class states are stable — transitions are permitted from them. X-class states are hard stops — no transitions are allowed until resolved.

Transition table:

S0 CLEAN_SYNCED  → no-op
S1 CLEAN_AHEAD   → PUSH       → S0
S2 CLEAN_BEHIND  → FAST_FORWARD → S0
S3 DIVERGED      → REPLAY     → S1 → PUSH → S0

X1 DETACHED_HEAD  → hard stop (checkout a branch)
X2 DIRTY_WORKTREE → hard stop (commit or stash)
X3 NO_UPSTREAM    → hard stop (set upstream tracking)
const (
	// Stable states — transitions permitted
	StateCleanSynced StateClass = "CLEAN_SYNCED" // S0: ahead=0, behind=0, clean
	StateCleanAhead  StateClass = "CLEAN_AHEAD"  // S1: ahead>0, behind=0
	StateCleanBehind StateClass = "CLEAN_BEHIND" // S2: ahead=0, behind>0
	StateDiverged    StateClass = "DIVERGED"     // S3: ahead>0, behind>0

	// Blocked states — hard stops
	StateDetachedHEAD  StateClass = "DETACHED_HEAD"  // X1
	StateDirtyWorktree StateClass = "DIRTY_WORKTREE" // X2
	StateNoUpstream    StateClass = "NO_UPSTREAM"    // X3
)

func Classify

func Classify(s RepoState) StateClass

Classify derives the StateClass from a RepoState snapshot. Classification order: blocked states are checked first (fail-fast), then stable states by ahead/behind counts.

This function is pure — same input always produces the same output.

func (StateClass) IsValid

func (c StateClass) IsValid() bool

IsValid returns true for S-class states where transitions are permitted.

func (StateClass) String

func (c StateClass) String() string

String implements Stringer.

type StatusFileChange

type StatusFileChange struct {
	Path    string
	Deleted bool
}

StatusFileChange represents a file with its change status for the forge backend.

func ChangedFiles

func ChangedFiles(s git.Status) []StatusFileChange

ChangedFiles returns all changed files (staged + unstaged + untracked) with delete status. Equivalent to `git status --porcelain=v1 -z -uall`.

func ChangedFilesInDir

func ChangedFilesInDir(s git.Status, dir string) []StatusFileChange

ChangedFilesInDir returns changed files within a specific directory prefix.

func StagedChanges

func StagedChanges(s git.Status) []StatusFileChange

StagedChanges returns staged files with delete status. Equivalent to `git diff --cached --name-status`.

type SyncSession

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

SyncSession is opened once per sync/push operation. State is resolved once at Open() and explicitly refreshed only after state-changing operations (Fetch, FastForward). Remote polling never happens opportunistically.

func OpenSyncSession

func OpenSyncSession(rootDir string) (*SyncSession, error)

OpenSyncSession opens a SyncSession for the repository at rootDir. Reads repo state and resolves auth once; both are reused throughout the session. Returns an error if the remote URL uses SSH and auth cannot be resolved.

func (*SyncSession) Auth

func (s *SyncSession) Auth() transport.AuthMethod

Auth returns the resolved transport auth (may be nil for public HTTPS remotes).

func (*SyncSession) FastForward

func (s *SyncSession) FastForward(remote string) error

FastForward performs a fast-forward pull from the tracked upstream. Returns git.ErrNonFastForwardUpdate if the histories have diverged.

func (*SyncSession) Fetch

func (s *SyncSession) Fetch(remote string) error

Fetch fetches from the configured remote using targeted refspecs. Only fetches refs/heads/* to avoid fetching tags or other namespaces implicitly. Updates fetched flag and refreshes state.

func (*SyncSession) FetchedUpstreamHash

func (s *SyncSession) FetchedUpstreamHash() plumbing.Hash

FetchedUpstreamHash returns the upstream hash as observed after the last Fetch. Used by the replay race guard to detect concurrent pushes.

func (*SyncSession) Push

func (s *SyncSession) Push(remote, refspec string, setUpstream bool) error

Push pushes to remote. When setUpstream is true, also configures branch tracking.

func (*SyncSession) Refresh

func (s *SyncSession) Refresh() error

Refresh re-reads repo state after a mutation (fetch, fast-forward, reset).

func (*SyncSession) Repo

func (s *SyncSession) Repo() *git.Repository

Repo returns the underlying git.Repository.

func (*SyncSession) State

func (s *SyncSession) State() RepoState

State returns the current resolved repo state.

type TransitionEvent

type TransitionEvent struct {
	From   StateClass `json:"from"`
	Action string     `json:"action"`
	To     StateClass `json:"to,omitempty"`
	Note   string     `json:"note,omitempty"`
}

TransitionEvent is emitted by the Engine for every state transition. Consumers may log, forward to a UI, or record for audit.

JSON encoding is intentional — these are the structured facts that CI logs and diagnostic tooling consume.

Jump to

Keyboard shortcuts

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