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
- func AddCommit(repoPath, message string, paths ...string) (bool, error)
- func Clone(cloneURL, dest string) error
- func CommitEmpty(repoPath, message string) error
- func ConfiguredOriginURL(repoPath string) (string, error)
- func CurrentBranch(repoPath string) (string, error)
- func DefaultBranch(repoPath string) (string, error)
- func Fetch(repoPath string) error
- func HasCommits(repoPath string) (bool, error)
- func HasUpstream(repoPath string) (bool, error)
- func HeadSHA(repoPath string) (string, error)
- func LocalState(repoPath string) (dirty bool, reason string, err error)
- func OriginURL(repoPath string) (string, error)
- func Pull(repoPath string) error
- func PullRebase(repoPath string) error
- func Push(repoPath string) error
- func PushSetUpstream(repoPath, branch string) error
- func RebaseAbort(repoPath string) error
- func RebaseInProgress(repoPath string) (bool, error)
- func RemoteHasBranches(repoPath string) (bool, error)
- func ResetHardUpstream(repoPath string) error
- func SetSkipSync(repoPath string) error
- func ShowFile(repoPath, ref, path string) (content string, ok bool, err error)
- func SkipSync(repoPath string) (bool, error)
- func UnsetSkipSync(repoPath string) error
- func WorktreeChanged(dir string) (bool, error)
- type LandOptions
- type Mutator
- type Outcome
- type RepoStatus
- type TrackingState
- type UnpushedBranch
Constants ¶
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
AddCommit stages paths and commits them. It reports false without committing when the stage is empty, so callers can publish idempotently.
func CommitEmpty ¶ added in v0.38.0
CommitEmpty creates an empty commit, used to give an unborn branch something to push.
func ConfiguredOriginURL ¶ added in v0.42.0
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
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 ¶
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 HasCommits ¶ added in v0.38.0
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
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 LocalState ¶
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
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 ¶
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
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 PushSetUpstream ¶ added in v0.38.0
PushSetUpstream pushes branch to origin and sets it as the upstream.
func RebaseAbort ¶ added in v0.42.0
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
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
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
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
SetSkipSync marks repoPath to be skipped by sync.
func ShowFile ¶
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
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 UnsetSkipSync ¶ added in v0.38.0
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 ¶
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 ¶
Mutator edits files inside the worktree and reports whether it changed anything along with a human-readable detail.
type Outcome ¶
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 the commit list: without one, the whole repository history would be indistinguishable from unpublished work.