git

package
v0.12.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrBranchNotMerged = errors.New("git: branch not fully merged")

ErrBranchNotMerged is returned (wrapped) by SafeDeleteBranch / DeleteLocalBranch when git refuses to delete the branch because its tip commit is not fully merged into HEAD or upstream. Detect with errors.Is.

Functions

func ExecPath added in v0.7.0

func ExecPath(ctx context.Context) (string, error)

Types

type BranchRef added in v0.11.1

type BranchRef struct {
	IssueSlug  string `json:"issue_slug"`
	BranchName string `json:"branch_name"`
	ParentSlug string `json:"parent_slug,omitempty"`
	CreatedAt  string `json:"created_at"` // RFC3339
	Merged     bool   `json:"merged,omitempty"`
	// TrackerType records the tracker that created the issue ("" = manual). It
	// is the cross-machine source of truth for whether an issue is tracker-born:
	// stored in the git object (this blob) and fetched by every clone, so a
	// reviewer with an empty local store can still tell whether to offer a
	// tracker status update. omitempty keeps pre-existing refs backward-
	// compatible — an absent field unmarshals to "" (treated as "manual").
	TrackerType string `json:"tracker_type,omitempty"`
}

BranchRef is the JSON payload stored as a git blob at refs/zf/branches/<issueSlug>. It records the branch name and optional parent slug so any clone can resolve the merge target without querying the local SQLite store.

type Client

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

Client wraps a go-git repository and exposes commit operations.

func NewClient

func NewClient(ioStreams *pkg.IO) (*Client, error)

NewClient opens the git repository that contains the current directory. ioStreams configures the streams used for interactive operations; nil uses os.Stdin/Stdout/Stderr.

func NewClientAt added in v0.4.2

func NewClientAt(ioStreams *pkg.IO, dir string) (*Client, error)

NewClientAt opens the git repository rooted at dir. ioStreams configures the streams used for interactive operations; nil uses os.Stdin/Stdout/Stderr.

func (*Client) AbortMerge added in v0.9.3

func (c *Client) AbortMerge(ctx context.Context) error

AbortMerge runs `git merge --abort`. Used after a TUI abort or commit failure in the Classic close flow to clear MERGE_HEAD / MERGE_MSG and restore the working tree. Returns a wrapped error so callers can decide whether to treat a no-active-merge failure as fatal (e.g. by ignoring it when the orchestrator isn't sure whether MergeNoFFNoCommit actually started a merge).

func (*Client) Authors

func (c *Client) Authors(ctx context.Context) ([]string, error)

Authors returns a deduplicated list of commit author identities ("Name <email>") from the repository history, ordered by commit count (most active first). Uses `git shortlog -sne --all` so it walks every ref instead of just HEAD's ancestry, and tolerates partial packfiles that trip go-git's commit iterator (e.g. submodules with a malformed .idx). The current git config identity is prepended as the first (default) entry.

func (*Client) BranchExists added in v0.9.5

func (c *Client) BranchExists(name string) (bool, error)

BranchExists returns true if refs/heads/<name> resolves locally. It does not consult remotes — see resolveBranchConflict for the rationale (no fetch on the happy path of `issue start`).

func (*Client) Checkout added in v0.8.0

func (c *Client) Checkout(ctx context.Context, branchName string) error

Checkout switches the working tree to branchName. Wraps `git checkout <name>`. Idempotent: when the working tree is already on branchName, the call is a no-op — the underlying `git checkout` is skipped so heavyweight `post-checkout` hooks don't fire for a same-branch "switch". Returns a wrapped error from the git CLI on failure (e.g. unknown branch, untracked file collision).

func (*Client) Commit

func (c *Client) Commit(ctx context.Context, msg []byte, opts CommitOptions) error

Commit records a commit with msg and the given options using the system git binary so that all configured hooks (pre-commit, commit-msg, post-commit) run.

func (*Client) CommitsAhead added in v0.11.0

func (c *Client) CommitsAhead(ctx context.Context, branchName, baseBranch string) (int, error)

CommitsAhead returns the number of commits in branchName that are not reachable from baseBranch. Uses `git rev-list --count <baseBranch>..<branchName>`.

func (*Client) ConfigUser added in v0.11.0

func (c *Client) ConfigUser(ctx context.Context) (string, error)

ConfigUser returns the git config user identity as "Name <email>". Returns an empty string when not configured.

func (*Client) CreateBranch

func (c *Client) CreateBranch(name, baseBranch string) error

CreateBranch creates a new branch from baseBranch and checks it out.

baseBranch is resolved via ResolveBranchRef, so it may be a local head (refs/heads/<base>) OR a branch that exists only as a remote-tracking ref (refs/remotes/<remote>/<base>). The latter is the fresh-clone case: a teammate starts a sub-task off a parent integration branch that has been pushed but never checked out locally.

func (*Client) CreateWorktree added in v0.9.0

func (c *Client) CreateWorktree(ctx context.Context, branchName, baseBranch, path string) error

CreateWorktree creates a new branch from baseBranch and checks it out in a linked worktree at path. Wraps `git worktree add -b <branch> <path> <base>`.

func (*Client) CurrentBranch added in v0.4.2

func (c *Client) CurrentBranch() (string, error)

CurrentBranch returns the short name of the branch HEAD points to. On a detached HEAD the returned name will not parse as an issue branch, so callers can simply ignore it.

func (*Client) DefaultBaseBranch

func (c *Client) DefaultBaseBranch() (string, error)

DefaultBaseBranch resolves the default base branch in priority order:

  1. refs/remotes/<remote>/HEAD (skipped when no remote)
  2. "main" if the local ref exists
  3. "master" if the local ref exists

func (*Client) DeleteLocalBranch added in v0.4.2

func (c *Client) DeleteLocalBranch(ctx context.Context, name string, force bool) error

DeleteLocalBranch deletes the local branch by name. force=true uses -D (required after squash merges); force=false uses -d (safe).

func (*Client) DeleteLocalBranchSafe added in v0.11.1

func (c *Client) DeleteLocalBranchSafe(ctx context.Context, branchName string, force bool, cfgBase string) error

DeleteLocalBranchSafe deletes branchName locally, switching to the configured base branch first when the current branch IS branchName (git refuses to delete the currently checked-out branch). cfgBase is used as the switch target; when empty the repo's default base branch (main/master) is auto-detected. On any checkout failure the function returns the error immediately.

func (*Client) DeleteRemoteBranch added in v0.11.0

func (c *Client) DeleteRemoteBranch(ctx context.Context, branchName string) error

DeleteRemoteBranch deletes branchName on the configured remote. No-op when no remote is configured.

func (*Client) DeleteReviewRef added in v0.11.0

func (c *Client) DeleteReviewRef(ctx context.Context, issueID string) error

DeleteReviewRef deletes refs/zf/reviews/<issueID> locally. If a remote is configured, also attempts to delete it there (best-effort; errors are ignored).

func (*Client) FastForwardOnly added in v0.8.0

func (c *Client) FastForwardOnly(ctx context.Context, sourceBranch, targetBranch string) error

FastForwardOnly checks out targetBranch and runs `git merge --ff-only sourceBranch`. Returns a wrapped error when the FF is refused (diverged history) so the caller can render an actionable message.

func (*Client) Fetch added in v0.9.0

func (c *Client) Fetch(ctx context.Context) error

Fetch runs `git fetch <remote>`. Returns nil immediately when no remote is configured (local-only repo). Returns a wrapped error when the remote is unreachable or auth fails.

func (*Client) FetchBranchRefs added in v0.11.1

func (c *Client) FetchBranchRefs(ctx context.Context) error

FetchBranchRefs fetches refs/zf/branches/* from the remote into the local ref namespace. No-op when no remote is configured.

func (*Client) FetchReviewRef added in v0.11.1

func (c *Client) FetchReviewRef(ctx context.Context, issueID string)

FetchReviewRef fetches refs/zf/reviews/<issueID> from the remote into the local ref namespace. Silent — designed for use in pre-push hooks where interactive output would be confusing. No-op when no remote is configured. Errors are silently ignored (fail-open: the caller falls back to the local ref).

func (*Client) FetchReviewRefs added in v0.11.0

func (c *Client) FetchReviewRefs(ctx context.Context) error

FetchReviewRefs fetches refs/zf/reviews/* from the remote into the local ref namespace. No-op when no remote is configured.

func (*Client) ForceDeleteBranch added in v0.10.0

func (c *Client) ForceDeleteBranch(name string) error

ForceDeleteBranch invokes `git branch -D <name>` from the working tree root. Always destructive; safety check skipped.

func (*Client) GitDir added in v0.10.1

func (c *Client) GitDir() (string, error)

GitDir returns the absolute path of the repository's .git directory. For a regular repository this is "<worktree>/.git". For a submodule it is "<parent>/.git/modules/<name>" (because <worktree>/.git is a gitlink file, not a directory). For a linked worktree it is the per-worktree git dir. Resolved by shelling out to `git rev-parse --git-dir` to handle all forms.

func (*Client) IO added in v0.8.0

func (c *Client) IO() *pkg.IO

IO returns the injected IO streams. Callers should write status/diagnostic messages through these instead of os.Stdout/os.Stderr so Cobra-aware redirection (tests, subcommand piping, future TUI capture) keeps working.

func (*Client) IsAncestor added in v0.8.0

func (c *Client) IsAncestor(ctx context.Context, child, ancestor string) (bool, error)

IsAncestor reports whether child is an ancestor of ancestor (or equal). Wraps `git merge-base --is-ancestor child ancestor`: exit code 0 → true, exit code 1 → false, any other exit code → wrapped error.

func (*Client) IsDirty added in v0.8.0

func (c *Client) IsDirty(ctx context.Context) (bool, error)

IsDirty reports whether the working tree has tracked-file modifications or staged-but-uncommitted changes. Wraps `git status --porcelain --untracked-files=no`. Untracked files are intentionally NOT counted as dirty: `git reset --hard` does not touch untracked content, so their presence does not put user work at risk during rollback.

func (*Client) IsMergedInto

func (c *Client) IsMergedInto(branchName, baseBranch string) (bool, error)

IsMergedInto reports whether branchName's tip commit is reachable from baseBranch, i.e. whether the branch has been merged into base (mirrors git merge-base --is-ancestor).

func (*Client) ListReviewRefs added in v0.11.0

func (c *Client) ListReviewRefs(ctx context.Context) (map[string]*ReviewRef, error)

ListReviewRefs returns all locally available review refs as a map of issueID → ReviewRef. Call FetchReviewRefs first to ensure the local namespace is up to date. Does not require the issue to exist in the store.

func (*Client) LocalBranchNames

func (c *Client) LocalBranchNames() ([]string, error)

LocalBranchNames returns the short names of all local branches.

func (*Client) LocalOrRemoteRef added in v0.12.0

func (c *Client) LocalOrRemoteRef(name string) string

LocalOrRemoteRef normalises a branch name into a ref usable by read-only operations (git merge-tree, git merge-base --is-ancestor): the bare name when it resolves as a local head, else "<remote>/<name>" when a remote is configured, else the bare name. This is the "the parent integration branch may exist only as a remote-tracking ref" case — a teammate's fresh clone that never checked the parent out locally. A BranchExists error degrades to the remote form (treated as not-found), matching the prior inline behaviour.

func (*Client) MergeDryRun added in v0.4.2

func (c *Client) MergeDryRun(ctx context.Context, branchName, baseBranch string) ([]string, error)

MergeDryRun checks whether branchName merges cleanly into baseBranch. It uses `git merge-tree --write-tree` (git 2.38+) to perform a 3-way merge in-memory: the working tree is never touched, no hooks run, and submodules are not traversed. Returns the list of conflicting file paths, or nil if clean.

func (*Client) MergeForward added in v0.11.0

func (c *Client) MergeForward(ctx context.Context, sourceBranch, targetBranch string) error

MergeForward checks out targetBranch and runs `git merge --no-edit sourceBranch`, creating a merge commit. Used by `review sync` to integrate a parent integration branch into a drifted sub-task branch without rewriting history (no force-push needed). Returns a wrapped error on conflict; the caller should run AbortMerge to clean up.

func (*Client) MergeNoFFNoCommit added in v0.9.3

func (c *Client) MergeNoFFNoCommit(ctx context.Context, featureBranch, baseBranch string) error

MergeNoFFNoCommit checks out baseBranch and runs `git merge --no-ff --no-commit <featureBranch>`. Leaves MERGE_HEAD + MERGE_MSG in place so the caller can drive the commit step itself (typically via the commitizen TUI form). Caller is responsible for `git merge --abort` on TUI abort or commit failure.

func (*Client) MergeRebase added in v0.8.0

func (c *Client) MergeRebase(ctx context.Context, featureBranch, baseBranch string) error

MergeRebase prepares featureBranch for a single-commit close. When a remote is configured, it merges against remote/<baseBranch> and soft-resets to the same ref. When no remote is available (local-only repo), it uses the local baseBranch directly.

The mechanic is a real `git merge --no-edit <remoteBase>` (submodule-safe — handles gitlinks correctly, unlike `merge --squash`) followed by `git reset --soft <remoteBase>`, leaving HEAD at <remoteBase>, the working tree at the merged state, and the index staged with the consolidated diff. The transient merge commit produced by the merge step is unreachable after the reset and is eventually garbage-collected — `--no-edit` is what prevents git from opening $EDITOR for that throwaway commit message.

Caller is responsible for the final commit (typically via the commitizen TUI form) and for rollback on failure.

func (*Client) MergeSquash added in v0.4.2

func (c *Client) MergeSquash(ctx context.Context, branchName, baseBranch string) error

MergeSquash checks out baseBranch and squash-merges branchName into it, leaving the squashed changes staged. The caller is responsible for the follow-up commit (so it can drive an interactive commit form).

func (*Client) PushBranch added in v0.12.0

func (c *Client) PushBranch(ctx context.Context, branch string) error

PushBranch runs `git push <remote> <branch>:<branch>` through the interactive IO streams (live progress). Never uses --force. No-op when no remote.

func (*Client) PushBranchRef added in v0.11.1

func (c *Client) PushBranchRef(ctx context.Context, issueSlug string) error

PushBranchRef pushes refs/zf/branches/<issueSlug> to the remote. No-op when no remote is configured. Uses --force because the ref points to a blob (not a commit) and git rejects non-force updates of blob refs; it is also needed when stamping Merged:true on an existing ref after close.

func (*Client) PushDryRun added in v0.12.0

func (c *Client) PushDryRun(ctx context.Context, branch string) (PushOutcome, bool, error)

PushDryRun runs `git push --porcelain --dry-run <remote> <branch>:<branch>` (under LC_ALL=C) and parses the per-ref porcelain flag char into a PushOutcome. The remote is contacted for ref negotiation but no objects transfer, so the result reflects the true remote state. ok is false (caller skips the proposal) when no remote is configured, when the branch is up to date, or when the dry-run output cannot be parsed. A real dry-run command failure (e.g. remote unreachable) returns a wrapped error with ok=false.

func (*Client) PushReviewRef added in v0.11.0

func (c *Client) PushReviewRef(ctx context.Context, issueID, expectedOldSHA string) error

PushReviewRef pushes refs/zf/reviews/<issueID> to the remote using --force-with-lease to prevent overwriting a concurrently updated ref. Pass expectedOldSHA="" to allow any prior value (first push). No-op when no remote is configured.

func (*Client) ReadBranchRef added in v0.11.1

func (c *Client) ReadBranchRef(ctx context.Context, issueSlug string) (*BranchRef, error)

ReadBranchRef reads the BranchRef for issueSlug from the local ref store. Returns (nil, nil) when the ref does not exist.

func (*Client) ReadReviewRef added in v0.11.0

func (c *Client) ReadReviewRef(ctx context.Context, issueID string) (*ReviewRef, string, error)

ReadReviewRef reads the ReviewRef for issueID from the local ref store. Returns (nil, "", nil) when the ref does not exist. The returned currentSHA is suitable as oldSHA in the next WriteReviewRef call.

func (*Client) Remote added in v0.9.0

func (c *Client) Remote() (string, error)

Remote returns the resolved remote name, auto-detecting on first call.

Resolution order:

  1. Already pinned via SetRemote → return as-is.
  2. Exactly one remote → cache and return its name.
  3. Zero remotes → return ("", nil); caller treats this as local-only.
  4. Multiple remotes, one named "origin" → use "origin" (git convention).
  5. Multiple remotes, none named "origin" → error with actionable message.

func (*Client) RemoteBranchExists added in v0.11.1

func (c *Client) RemoteBranchExists(ctx context.Context, branchName string) bool

RemoteBranchExists reports whether branchName exists on the configured remote. Uses git ls-remote so no fetch is required. Returns false on any error or when no remote is configured.

func (*Client) RemoteBranchNames added in v0.12.0

func (c *Client) RemoteBranchNames() ([]string, error)

RemoteBranchNames returns the short names of the configured remote's tracking branches (refs/remotes/<remote>/*), with the "<remote>/" prefix stripped and the remote's HEAD symref skipped. Returns nil when no remote is configured.

Used by the issue-start base picker so a parent integration branch that exists only on the remote (fresh clone, never checked out locally) is still offered as a base candidate — mirroring how LocalBranchNames feeds the local ones.

func (*Client) RepoName added in v0.9.0

func (c *Client) RepoName() (string, error)

RepoName returns a short identifier for this repository. Resolution order:

  1. Last path segment of the configured remote URL, with ".git" stripped.
  2. Base name of the working tree root directory (local-only fallback).

func (*Client) ResetHard added in v0.8.0

func (c *Client) ResetHard(ctx context.Context, target string) error

ResetHard runs `git reset --hard <target>`. Used by the close orchestrator to atomically roll the current branch back to its original tip on TUI abort or commit failure. Does not touch untracked files.

func (*Client) ResolveBranchRef added in v0.11.1

func (c *Client) ResolveBranchRef(name string) (plumbing.Hash, error)

ResolveBranchRef resolves a branch short name to its commit hash. It tries refs/heads/<name> first, then falls back to refs/remotes/<remote>/<name> so that sub-task closes work when the parent integration branch was never checked out locally (exists only as a remote tracking ref).

func (*Client) ResolveRef added in v0.8.0

func (c *Client) ResolveRef(name string) (plumbing.Hash, error)

ResolveRef returns the commit hash that `name` resolves to (with reference indirection followed). Use it for read-only ref lookups from packages that need a plumbing.Hash without taking a dependency on go-git's plumbing API.

func (*Client) RunGitAt added in v0.11.0

func (c *Client) RunGitAt(ctx context.Context, dir string, args ...string) error

RunGitAt runs an arbitrary git command in dir with the client's IO streams. Exported for review subcommands that need low-level git operations.

func (*Client) SafeDeleteBranch added in v0.10.0

func (c *Client) SafeDeleteBranch(name string) error

SafeDeleteBranch invokes `git branch -d <name>` from the working tree root. On git's "not fully merged" refusal the returned error wraps ErrBranchNotMerged.

func (*Client) SetRemote added in v0.9.0

func (c *Client) SetRemote(name string)

SetRemote pins the remote name used for all remote operations. Call this when the user has configured branch.remote explicitly.

func (*Client) WorkingTreeRoot

func (c *Client) WorkingTreeRoot() (string, error)

WorkingTreeRoot returns the absolute path of the repository's working tree root.

func (*Client) WriteBranchRef added in v0.11.1

func (c *Client) WriteBranchRef(ctx context.Context, issueSlug string, ref BranchRef) (string, error)

WriteBranchRef writes a BranchRef as a git blob and updates the local ref refs/zf/branches/<issueSlug>. No CAS — branch metadata is write-once; an overwrite (e.g. re-running issue start) simply replaces the blob. Returns the new blob SHA.

func (*Client) WriteReviewRef added in v0.11.0

func (c *Client) WriteReviewRef(ctx context.Context, issueID string, ref ReviewRef, oldSHA string) (string, error)

WriteReviewRef atomically writes a ReviewRef as a git blob and updates the local ref refs/zf/reviews/<issueID> using CAS. oldSHA must be the current ref SHA — pass "" for the first write (no prior value). Returns the new SHA.

type CommitOptions

type CommitOptions struct {
	All        bool
	Amend      bool
	NoVerify   bool
	Signoff    bool
	AllowEmpty bool
	Author     string // "Name <email>"; empty = git config identity
}

CommitOptions configures Client.Commit.

type PushKind added in v0.12.0

type PushKind int

PushKind classifies what a dry-run push to the remote would do for a branch.

const (
	// PushUpToDate means the remote already has the branch tip (nothing to push).
	PushUpToDate PushKind = iota
	// PushNewBranch means the branch does not yet exist on the remote.
	PushNewBranch
	// PushFastForward means the remote branch would advance by fast-forward.
	PushFastForward
	// PushRejected means the push would be rejected (non-fast-forward divergence).
	PushRejected
)

type PushOutcome added in v0.12.0

type PushOutcome struct {
	Kind    PushKind
	Summary string // friendly line for the preview, e.g. "abc1234..def5678" or "[new branch]"
}

PushOutcome is the parsed result of a dry-run push for a single branch.

type ReviewRef added in v0.11.0

type ReviewRef struct {
	Status     string `json:"status"`
	Round      int    `json:"round"`
	FeatureSHA string `json:"feature_sha"`
	Reviewer   string `json:"reviewer,omitempty"`
	CreatedAt  string `json:"created_at"` // RFC3339
}

ReviewRef is the JSON payload stored as a git blob at refs/zf/reviews/<IssueID>.

Jump to

Keyboard shortcuts

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