gitops

package
v0.95.3 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package gitops wraps the git and gh commands needed to read the default branch of a repo and to land a change on it — pushing directly when allowed, or opening an auto-merge PR when the branch is protected. The flow mirrors the proven all-repos-codegrapher.sh script.

Index

Constants

View Source
const SkipSyncKey = "wb.skip-sync"

SkipSyncKey is the git config key marking a repo that wb sync must leave alone. It is always read and written with --local: a plain read falls back to ~/.gitconfig, where one stray key would silently disable sync for the whole fleet.

Variables

This section is empty.

Functions

func AddCommit added in v0.42.0

func AddCommit(repoPath, message string, paths ...string) (bool, error)

AddCommit stages paths and commits them. It reports false without committing when the stage is empty, so callers can publish idempotently.

func Clone

func Clone(cloneURL, dest string) error

Clone clones cloneURL into dest (creating parent dirs).

func CommitEmpty added in v0.38.0

func CommitEmpty(repoPath, message string) error

CommitEmpty creates an empty commit, used to give an unborn branch something to push.

func ConfiguredOriginURL added in v0.42.0

func ConfiguredOriginURL(repoPath string) (string, error)

ConfiguredOriginURL returns repoPath's remote.origin.url exactly as written in the repository config, before any url.<base>.insteadOf rewriting. Use it when comparing against a URL the user configured elsewhere; OriginURL returns the effective (rewritten) URL git will actually contact.

func CurrentBranch added in v0.38.0

func CurrentBranch(repoPath string) (string, error)

CurrentBranch returns the checked-out branch name of repoPath. A detached HEAD reports an empty name rather than an error, because `git branch --show-current` succeeds there and prints nothing; callers that need a branch to push must reject the empty string themselves.

func DefaultBranch

func DefaultBranch(repoPath string) (string, error)

DefaultBranch returns the remote's default branch (e.g. "main"). It first refreshes origin/HEAD from the remote, because a local clone's cached origin/HEAD can be stale (e.g. the repo was renamed master -> main after the clone was made), which would otherwise yield the wrong branch.

func Fetch

func Fetch(repoPath string) error

Fetch updates remote refs.

func HasCommits added in v0.38.0

func HasCommits(repoPath string) (bool, error)

HasCommits reports whether repoPath's HEAD resolves, i.e. whether the checked-out branch has any commits yet. --verify --quiet exits 1 silently on an unborn branch, where a bare `rev-parse HEAD` would exit 128 and print "fatal: ambiguous argument 'HEAD'" into the error text.

func HasUpstream added in v0.42.0

func HasUpstream(repoPath string) (bool, error)

HasUpstream reports whether the checked-out branch has a resolvable upstream (`@{u}`). A branch that has never been pushed, or one cloned from a bare repository with no branches yet, resolves to false rather than an error — that is the ordinary state of a first publish into an empty store, not a fault.

func HeadSHA added in v0.42.0

func HeadSHA(repoPath string) (string, error)

HeadSHA returns the full SHA of HEAD.

func LocalState

func LocalState(repoPath string) (dirty bool, reason string, err error)

LocalState reports whether repoPath has uncommitted changes (including untracked files) or local commits not present on any remote. The reason string names the first condition found.

func OriginURL added in v0.38.0

func OriginURL(repoPath string) (string, error)

OriginURL returns repoPath's origin remote URL after any url.<base>.insteadOf rewriting. Use it for the URL git will actually contact; use ConfiguredOriginURL when comparing against a URL the user configured elsewhere.

func Pull

func Pull(repoPath string) error

Pull runs `git pull --ff-only --quiet` on the currently checked-out branch of repoPath.

Fast-forward-only is deliberate. A canonical clone must never acquire a merge commit from an unattended fleet sync, and whether a plain `git pull` would merge, rebase, or refuse depends on the machine's pull.rebase setting — so without --ff-only the same divergence rewrites history on one machine and errors on another. Refusing is the only safe answer; the caller classifies the refusal.

func PullRebase added in v0.42.0

func PullRebase(repoPath string) error

PullRebase replays local commits on top of the freshly fetched upstream. It is the retry primitive for shared state repositories where several machines push small non-conflicting commits.

It fetches and rebases onto an explicitly named "origin/<branch>" — never a bare `git pull --rebase`. A bare pull resolves what to rebase onto through FETCH_HEAD's "for merge" bookkeeping, which git refuses to rebase onto once it holds more than one candidate, with a message ("fatal: Cannot rebase onto multiple branches.") that names neither the clone nor the ref (wb#175). Two known ways to land more than one candidate there: a branch configured with more than one `branch.<name>.merge` value, and — reproduced directly for this fix — two `wb` processes (e.g. concurrent `worktree cleanup --apply --remote` runs) racing a bare `git pull --rebase` against the very same unlocked local clone, which can interleave two "for merge" lines into one FETCH_HEAD. Naming the remote and the branch explicitly for both the fetch and the rebase sidesteps FETCH_HEAD's merge-head bookkeeping entirely: there is only ever one ref to rebase onto.

func Push added in v0.42.0

func Push(repoPath string) error

Push publishes the current branch to its upstream.

func PushSetUpstream added in v0.38.0

func PushSetUpstream(repoPath, branch string) error

PushSetUpstream pushes branch to origin and sets it as the upstream.

func RebaseAbort added in v0.42.0

func RebaseAbort(repoPath string) error

RebaseAbort aborts an in-progress rebase, restoring repoPath to the state it was in before the rebase started. Callers use it after a failed PullRebase so a conflict never leaves the clone wedged mid-rebase.

func RebaseInProgress added in v0.42.0

func RebaseInProgress(repoPath string) (bool, error)

RebaseInProgress reports whether repoPath has a rebase underway, by checking for the two directories git creates while one is in progress (rebase-merge for interactive/merge-based rebases, rebase-apply for the classic am-based one). It resolves the paths with `git rev-parse --git-path` rather than assuming <repoPath>/.git, so it also works from a linked worktree, whose git-dir lives elsewhere.

func RemoteHasBranches added in v0.39.0

func RemoteHasBranches(repoPath string) (bool, error)

RemoteHasBranches reports whether origin publishes any branch at all.

A repository created on GitHub but never pushed to has none, and there is nothing for sync to pull from it — now or on any future run, until someone pushes. That is distinct from a remote that has branches but not the one the local branch tracks, which is a real problem (a renamed or deleted branch) and must keep failing loudly.

func ResetHardUpstream added in v0.45.0

func ResetHardUpstream(repoPath string) error

ResetHardUpstream discards local commits and tree state, returning the branch to its upstream. Used only on the state-repo clone to roll back a claim commit that lost its CAS race.

func SetSkipSync added in v0.38.0

func SetSkipSync(repoPath string) error

SetSkipSync marks repoPath to be skipped by sync.

func ShowFile

func ShowFile(repoPath, ref, path string) (content string, ok bool, err error)

ShowFile returns the contents of path at ref (e.g. "origin/main"), and a false ok when the file does not exist at that ref.

func SkipSync added in v0.38.0

func SkipSync(repoPath string) (bool, error)

SkipSync reports whether repoPath is marked to be skipped by sync.

Exactly one git exit status means "not marked": 1, for an absent key. Every other failure is a real error and is returned as one — notably 128, which covers both a malformed value and a path that is not a git repository. Swallowing those (as internal/hooks treats `git config --get` failures) would silently resume pulling a repo the user asked wb to leave alone.

func UnpushedCommits added in v0.40.0

func UnpushedCommits(repoPath string) ([]string, error)

UnpushedCommits lists commits present on some local branch and on no remote-tracking branch, newest first, as `<short-sha> <subject>` lines.

Deliberately every local branch, not just the checked-out one: work is abandoned on a side branch at least as often as on the default one, and a clone holding either is holding work that exists nowhere else.

func UnsetSkipSync added in v0.38.0

func UnsetSkipSync(repoPath string) error

UnsetSkipSync clears the skip marker from repoPath, and is idempotent.

--unset-all rather than --unset: on a key that somehow holds multiple values, --unset exits 5 and leaves every value in place, so the caller would report success on a repo that is still marked. --unset-all clears them all, and exits 5 only when the key is genuinely absent — the no-op case, which is success here.

func WorktreeChanged

func WorktreeChanged(dir string) (bool, error)

WorktreeChanged reports whether the worktree at dir has any uncommitted changes (tracked edits or untracked files), via `git status --porcelain`.

Types

type LandOptions

type LandOptions struct {
	DefaultBranch string
	CommitMessage string
	PRBranch      string
	PRTitle       string
	PRBody        string
}

LandOptions configures how a change is committed and pushed.

type Mutator

type Mutator func(worktreePath string) (changed bool, detail string, err error)

Mutator edits files inside the worktree and reports whether it changed anything along with a human-readable detail.

type Outcome

type Outcome struct {
	Changed bool
	Detail  string
	PRURL   string
}

Outcome describes what Land did.

func Land

func Land(repoPath string, opt LandOptions, mutate Mutator) (Outcome, error)

Land fetches, creates a detached worktree at origin/<default>, runs mutate, and — if it changed files — commits and pushes to the default branch, falling back to a PR with auto-merge when the push is rejected (protected branch).

type RepoStatus

type RepoStatus struct {
	Modified         []string         // tracked files with changes
	Untracked        []string         // untracked files
	Conflicted       []string         // merge-conflict paths
	Unpushed         []string         // unique `<short-sha> <subject>` lines
	UnpushedBranches []UnpushedBranch // branch and linked-worktree attribution
	Stashed          []string         // `git stash list` lines
}

RepoStatus is git working-tree/history state relevant to sync decisions and to reporting why a repo needs attention.

func Status

func Status(repoPath string) (RepoStatus, error)

Status reads repoPath's working tree, stash, and unpushed-commit state.

func (RepoStatus) Dirty

func (s RepoStatus) Dirty() bool

Dirty reports whether s represents any state — working tree, stash, or unpushed commits — that should block automatically removing an archived repo's local clone.

func (RepoStatus) Summary

func (s RepoStatus) Summary() string

Summary renders a short human-readable description of s, e.g. "3 modified files, 1 untracked file". Empty when nothing is set.

func (RepoStatus) WorkingTreeDirty

func (s RepoStatus) WorkingTreeDirty() bool

WorkingTreeDirty reports whether the working tree itself has changes (modified, untracked, or conflicted files) — the check used to decide whether it is safe to `git pull`.

type TrackingState added in v0.39.1

type TrackingState struct {
	Branch   string // checked-out branch, empty when HEAD is detached
	Upstream string // e.g. "origin/main", empty when it does not resolve
	Ahead    int    // local commits the upstream does not have
	Behind   int    // upstream commits the local branch does not have
	// Configured records whether the branch names an upstream in git config,
	// which is not the same as Upstream resolving. A branch configured to
	// track a ref the remote no longer publishes has Configured true and
	// Upstream empty — a renamed or deleted branch, and a real problem —
	// while a branch that names no upstream at all simply has nowhere to
	// pull from. Callers must not conflate the two.
	Configured bool
}

TrackingState is the checked-out branch's relationship to its upstream, as of the last fetch. It is what sync needs in order to tell a divergence ("each side has commits the other lacks") apart from a genuine fault.

func Tracking added in v0.39.1

func Tracking(repoPath string) (TrackingState, error)

Tracking reads the checked-out branch's divergence from its upstream.

The counts are only as current as the last fetch. Callers read it after a failed Pull, which has already fetched, so they see current numbers without a second round trip. A missing branch, upstream, or merge base is a state to report, not an error: the zero-ish TrackingState it returns describes exactly the situation the caller has to explain to a human.

func (TrackingState) Diverged added in v0.39.1

func (t TrackingState) Diverged() bool

Diverged reports whether the branch and its upstream have each moved on with commits the other lacks, so no fast-forward is possible.

func (TrackingState) Summary added in v0.39.1

func (t TrackingState) Summary() string

Summary renders the state as one short line for a sync report, e.g. "main is 1 ahead, 2 behind origin/main".

type UnpushedBranch added in v0.55.0

type UnpushedBranch struct {
	Branch   string   `yaml:"branch" json:"branch"`
	Worktree string   `yaml:"worktree,omitempty" json:"worktree,omitempty"`
	Commits  []string `yaml:"commits" json:"commits"`
}

UnpushedBranch attributes commits that exist on no remote to one local branch and, when that branch is checked out, its canonical or linked worktree. Commits can appear under more than one branch; RepoStatus.Unpushed remains the unique flat list used for counts and backward compatibility.

func UnpushedWork added in v0.55.0

func UnpushedWork(repoPath string) ([]string, []UnpushedBranch, error)

UnpushedWork returns both the unique flat commit list and branch/worktree attribution. It requires at least one known remote-tracking ref for the same reason as UnpushedCommits: without one, the whole repository history would be indistinguishable from unpublished work.

Jump to

Keyboard shortcuts

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