git

package
v1.10.10 Latest Latest
Warning

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

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

Documentation

Overview

Package git provides git worktree operations for agent-deck

Index

Constants

View Source
const (
	CollisionWorktreeExists = "worktree_exists"
	CollisionBranchExists   = "branch_exists"
)

Recognized values for DestinationCollisionError.Kind.

View Source
const DefaultWorktreeDestructionTimeout = 60 * time.Second

DefaultWorktreeDestructionTimeout bounds how long .agent-deck/worktree-destruction.sh may run. The setup hook resolves its timeout at the session layer, but RemoveWorktree (package git) cannot import session without an import cycle, so the destruction hook uses a fixed default. ponytail: fixed 60s, add a [worktree].destruction_timeout_seconds config if a user ever needs to tune it.

Variables

This section is empty.

Functions

func BranchExists

func BranchExists(repoDir, branchName string) bool

BranchExists checks if a branch exists in the repository

func CreateWorktree

func CreateWorktree(repoDir, worktreePath, branchName string) error

CreateWorktree creates a new git worktree at worktreePath for the given branch If the branch doesn't exist, it will be created

func CreateWorktreeAtStartPoint added in v1.9.47

func CreateWorktreeAtStartPoint(repoDir, worktreePath, branchName, startPoint string) (createdBranch bool, err error)

CreateWorktreeAtStartPoint creates a new branch worktree from an explicit start point. Returns createdBranch=true only after git successfully creates the branch for this call. Used by fork-with-state to anchor the new worktree at the parent session's HEAD instead of the invocation repo's HEAD.

func CreateWorktreeWithSetup added in v1.3.1

func CreateWorktreeWithSetup(repoDir, worktreePath, branchName string, stdout, stderr io.Writer, setupTimeout time.Duration) (setupErr error, err error)

CreateWorktreeWithSetup creates a worktree and runs the setup script if present. Setup script failure is non-fatal: the worktree is still valid. Output is streamed to the provided writers. A non-positive setupTimeout means "no deadline" — see RunWorktreeSetupScript for the full semantic.

User-visible progress (#768): the start preamble, an explicit completion line on success, and an explicit failure line on error are written to stderr so callers (CLI streaming directly, TUI capturing into a buffer for later display) can show the user what happened. Without these, users couldn't tell whether the script had run, finished, or finished cleanly before claude started.

func CreateWorktreeWithStateAndSetup added in v1.9.15

func CreateWorktreeWithStateAndSetup(repoDir, worktreePath, branchName string, state WorktreeStateOptions, stdout, stderr io.Writer, setupTimeout time.Duration) (setupErr error, err error)

CreateWorktreeWithStateAndSetup is CreateWorktreeWithSetup plus optional materialization of the parent session's working-tree state (#1029). Materialization happens BEFORE worktreeinclude processing and the setup script so both observe the realized state, per @smorin's spec.

func DeleteBranch added in v0.10.20

func DeleteBranch(repoDir, branchName string, force bool) error

DeleteBranch deletes a local branch. If force is true, uses -D (force delete).

func DetectInProgressOperation added in v1.9.47

func DetectInProgressOperation(repoDir string) (string, error)

DetectInProgressOperation returns the in-progress operation kind ("rebase", "merge", "cherry-pick", "revert", "bisect") if the parent's git directory shows an unsafe in-progress operation, or empty string if clean. Returns an error only if the git directory cannot be resolved.

This duplicates the check table used internally by materialize_wip.go's refuseUnsafeParentState. We do our own check so fork-with-state callers can surface actionable error messages BEFORE creating a worktree, instead of letting MaterializeWipFromParent return upstream's terser wording AFTER the worktree exists. Upstream's check remains as a backstop.

Keep this check table aligned with refuseUnsafeParentState in internal/git/materialize_wip.go. If upstream adds a new operation kind, add it here too.

func FindWorktreeDestructionScript added in v1.9.73

func FindWorktreeDestructionScript(repoDir string) (string, os.FileMode)

FindWorktreeDestructionScript mirrors FindWorktreeSetupScript for the pre-removal hook at <repoDir>/.agent-deck/worktree-destruction.sh.

func FindWorktreeSetupScript added in v1.3.1

func FindWorktreeSetupScript(repoDir string) (string, os.FileMode)

FindWorktreeSetupScript returns the path to the worktree setup script if one exists at <repoDir>/.agent-deck/worktree-setup.sh, or empty string. The returned os.FileMode captures the file's permission bits at discovery time, eliminating a TOCTOU race between finding and dispatching the script.

func GeneratePathID added in v0.10.10

func GeneratePathID() string

GeneratePathID returns an 8-character random hex string for path uniqueness. Used to provide a unique identifier in worktree path templates via {session-id}.

func GenerateWorktreePath

func GenerateWorktreePath(repoDir, branchName, location string) string

GenerateWorktreePath generates a worktree directory path based on the repository directory, branch name, and location strategy. Location "subdirectory" places worktrees under <repo>/.worktrees/<branch>. Location "sibling" (or empty) places worktrees as <repo>-<branch> alongside the repo. A custom path (containing "/" or starting with "~") places worktrees at <path>/<repo_name>/<branch>.

True-bare-at-root layout overrides the sibling/subdirectory defaults: linked worktrees live as direct children of the bare dir (<repo>/<branch>), since neither default makes sense when the project root *is* the bare repo. Custom path templates still take precedence (see WorktreePath in template.go).

func GetCurrentBranch

func GetCurrentBranch(dir string) (string, error)

GetCurrentBranch returns the current branch name for the repository at dir

func GetDefaultBranch added in v0.10.20

func GetDefaultBranch(repoDir string) (string, error)

GetDefaultBranch returns the default branch name (e.g. "main" or "master") for the repo

func GetMainWorktreePath

func GetMainWorktreePath(dir string) (string, error)

GetMainWorktreePath returns the path to the "project root" — the directory that hosts shared config like .agent-deck/.

  • Normal repo: the repo root (parent of .git/).
  • Linked worktree of a normal repo: the main worktree (parent of .git/).
  • Nested ".bare/" layout (issue #715): the parent of .bare/. There is no "main" worktree; every linked worktree is equal, shared config lives next to .bare/.
  • True-bare-at-root layout: the bare repo dir itself (e.g. "kslifeinc.git"). Linked worktrees live as direct children, shared config lives inside.

func GetRepoRoot

func GetRepoRoot(dir string) (string, error)

GetRepoRoot returns the root directory of the git repository containing dir

func GetWorktreeBaseRoot added in v0.17.0

func GetWorktreeBaseRoot(dir string) (string, error)

GetWorktreeBaseRoot returns the "project root" suitable for locating shared config (.agent-deck/, setup scripts, etc.). Accepts:

  • A normal repo dir → repo root.
  • A linked worktree → the main worktree (or, for bare-repo layouts, the parent of .bare/ or the bare-at-root dir itself).
  • A nested-bare project root (no .git but contains .bare/) → that root.
  • A bare-at-root project root → the bare dir itself.

This guarantees that downstream .agent-deck/ lookups resolve to a single stable location regardless of which worktree (or the project root) the caller started from.

func GetWorktreeForBranch

func GetWorktreeForBranch(repoDir, branchName string) (string, error)

GetWorktreeForBranch returns the worktree path for a given branch, if any

func HasSubmodules added in v1.9.47

func HasSubmodules(repoDir string) bool

HasSubmodules returns true if repoDir contains a .gitmodules file (regular file, not directory). Used by fork-with-state callers to emit a warning that submodules will be copied as files, not recursed into. Submodule detection is intentionally minimal — just checks for the canonical .gitmodules file at the repo root. No parsing.

func HasUncommittedChanges added in v0.10.20

func HasUncommittedChanges(dir string) (bool, error)

HasUncommittedChanges checks if the repository at dir has uncommitted changes

func HeadCommit added in v1.9.47

func HeadCommit(repoDir string) (string, error)

HeadCommit returns the commit currently checked out at repoDir. Works for normal repos, linked worktrees, and bare-repo project roots.

func IsBareRepo added in v1.7.58

func IsBareRepo(dir string) bool

IsBareRepo returns true if dir itself is a bare git repository. For a linked worktree checkout, this returns false — use IsBareRepoWorktree for that case.

func IsBareRepoAtRoot added in v1.9.11

func IsBareRepoAtRoot(dir string) bool

IsBareRepoAtRoot returns true if dir is a bare repository serving as the project root itself (linked worktrees live as direct children inside it). False for normal repos, linked worktrees, and the nested ".bare/" layout.

Uses isBareRepoSelf rather than IsBareRepo to filter the same false-positive class that findNestedBareRepo addresses: `git rev-parse --is-bare-repository` reports true for any descendant of a bare repo via parent discovery, so `IsBareRepoAtRoot("/repo.git/hooks")` would otherwise return true (basename "hooks" ≠ ".bare", and IsBareRepo says it's bare). isBareRepoSelf confirms the candidate is itself the bare repo.

func IsBareRepoWorktree added in v1.7.58

func IsBareRepoWorktree(dir string) bool

IsBareRepoWorktree returns true if dir is a linked worktree whose shared git-common-dir is itself a bare repository — covers both the nested ".bare/" pattern from issue #715 and the bare-at-root layout. In either case there is no "main" worktree; every linked worktree is equal.

func IsGitRepo

func IsGitRepo(dir string) bool

IsGitRepo checks if the given directory is inside a git repository

func IsGitRepoOrBareProjectRoot added in v1.7.58

func IsGitRepoOrBareProjectRoot(dir string) bool

IsGitRepoOrBareProjectRoot returns true if dir is either a regular git directory (normal repo, linked worktree, or a bare repo) or the project root of a bare-repo layout (contains a nested bare repo such as .bare/). Callers that need to validate "is this a path agent-deck can launch a session from?" should prefer this over IsGitRepo.

func IsLinkedWorktree added in v1.9.38

func IsLinkedWorktree(path string) bool

IsLinkedWorktree reports whether path is a git LINKED (secondary) worktree — i.e. one that agent-deck (or the user) created with `git worktree add`, as opposed to the repository's main working tree or a non-repo directory.

A linked worktree's git directory lives at <repo>/.git/worktrees/<id> (its parent is named "worktrees"), whereas the main working tree's git directory is <repo>/.git. This distinction is the only safe, location-independent way to tell an agent-deck-managed worktree from the user's original repository: worktree placement is user-configurable (sibling, <repo>/.worktrees, or a custom template), so a fixed "managed directory" prefix check is unreliable.

Used as the load-bearing guard against issue #1200 (deleting the original repo on dismiss of a worktree_reuse session). Any error or ambiguity returns false, so callers fail safe (skip deletion) rather than risk data loss.

func IsWorktree

func IsWorktree(dir string) bool

IsWorktree checks if the given directory is a git worktree (not the main repo)

func ListBranchCandidates added in v0.27.0

func ListBranchCandidates(repoDir string) ([]string, error)

ListBranchCandidates returns unique branch names from local branches and the default remote, normalized to plain branch names without a remote prefix.

func MaterializeWipFromParent added in v1.9.15

func MaterializeWipFromParent(parentDir, childDir string, includeIgnored bool) error

MaterializeWipFromParent copies the working-tree state of parentDir (staged changes, unstaged edits, and untracked files) into childDir, which must be a freshly-created worktree pointing at parentDir's HEAD. parentDir may be any path inside the parent worktree; state is materialized from the worktree root so copied paths remain repository-relative. When includeIgnored is true, gitignored files are also copied.

Contract:

  • parentDir is treated read-only — no stash push, no add, no index mutation.
  • childDir's `git status --porcelain` becomes equal to parentDir's `git status --porcelain` after this call.
  • Refuses to run when parent is in mid-rebase / merge / cherry-pick / revert / bisect.

Implements issue #1029 (--with-state / --with-state-and-gitignored).

func MergeBack added in v1.9.6

func MergeBack(projectRoot, sourceBranch, targetBranch string) error

MergeBack merges sourceBranch into targetBranch, handling both regular and bare-repository layouts.

In a regular layout, projectRoot is a working tree: this is the historical path — `git checkout targetBranch` then `git merge sourceBranch`.

In a bare-repo layout (issue #715/#891), projectRoot is the parent of a nested bare repo (typically `.bare/`). The bare dir has no working tree, so `checkout`/`merge` cannot run there directly. Instead:

  • If targetBranch is an ancestor of sourceBranch (fast-forward case), advance targetBranch via `update-ref` on the bare dir.
  • Otherwise, create a throwaway worktree of targetBranch, perform the merge there, then remove the worktree.

Regression test: TestWorktree_MergeBack_BareRepo_RegressionFor891.

func MergeBranch added in v0.10.20

func MergeBranch(repoDir, branchName string) error

MergeBranch merges the given branch into the current branch of the repository

func ProcessWorktreeInclude added in v1.9.2

func ProcessWorktreeInclude(repoDir, worktreePath string, stderr io.Writer) error

ProcessWorktreeInclude copies gitignored files matching patterns in .worktreeinclude from repoDir into worktreePath. Returns nil if no .worktreeinclude exists.

Matches Claude Code Desktop behavior: https://code.claude.com/docs/en/worktrees#copy-gitignored-files-into-worktrees

func PruneWorktrees added in v0.10.20

func PruneWorktrees(repoDir string) error

PruneWorktrees removes stale worktree references

func RemoveWorktree

func RemoveWorktree(repoDir, worktreePath string, force bool) error

RemoveWorktree removes a worktree from the repository. If force is true, it will remove even if there are uncommitted changes. When force is true and git fails (e.g. "Directory not empty" due to untracked files like node_modules), falls back to removing the directory directly and pruning stale worktree references.

func RunWorktreeDestructionBeforeRemove added in v1.9.73

func RunWorktreeDestructionBeforeRemove(repoDir, worktreePath string, stdout, stderr io.Writer, timeout time.Duration) error

RunWorktreeDestructionBeforeRemove runs the destruction script (if present) just before a worktree is removed. Failure is non-fatal: removal proceeds regardless, mirroring setup's "hook failure doesn't block the operation".

func RunWorktreeDestructionScript added in v1.9.73

func RunWorktreeDestructionScript(scriptPath string, scriptMode os.FileMode, repoDir, worktreePath string, stdout, stderr io.Writer, timeout time.Duration) error

RunWorktreeDestructionScript executes the destruction script with the same environment, working directory (worktreePath, which still exists at call time), shebang dispatch and timeout semantics as the setup script.

func RunWorktreeSetupAfterCreate added in v1.9.47

func RunWorktreeSetupAfterCreate(repoDir, worktreePath string, stdout, stderr io.Writer, setupTimeout time.Duration) error

RunWorktreeSetupAfterCreate runs the worktree setup script for an already-created worktree. Extracted from CreateWorktreeWithStateAndSetup so the fork-with-state path can sequence Create → Materialize → Setup with per-step error handling. Returns the script's exit error; nil if no script. Caller is responsible for ProcessWorktreeInclude if desired.

func RunWorktreeSetupScript added in v1.3.1

func RunWorktreeSetupScript(scriptPath string, scriptMode os.FileMode, repoDir, worktreePath string, stdout, stderr io.Writer, timeout time.Duration) error

RunWorktreeSetupScript executes the setup script with AGENT_DECK_REPO_ROOT and AGENT_DECK_WORKTREE_PATH environment variables set. Working directory is set to worktreePath. Output is streamed to the provided writers.

Dispatch (#773):

  • If scriptPath has the user-executable bit set, the script is invoked directly so the kernel honors its shebang line (e.g. #!/usr/bin/env bash, #!/usr/bin/env python3). This lets users write the setup script in any language they like.
  • Otherwise (legacy 0644 setups predating #773), fall back to `sh -e <path>` so existing repos keep working without a chmod.

Timeout semantics (post-#727 follow-up):

  • timeout > 0 → bounded by context.WithTimeout
  • timeout <= 0 → unlimited (context.Background, no deadline)

The session layer resolves the legacy 60s default before calling here; callers that want bounded runs must pass a positive duration explicitly.

func SanitizeBranchName

func SanitizeBranchName(name string) string

SanitizeBranchName converts a string to a valid branch name

func ValidateBranchName

func ValidateBranchName(name string) error

ValidateBranchName validates that a branch name follows git's naming rules

func ValidateForkWithStateDestination added in v1.9.47

func ValidateForkWithStateDestination(repoRoot, branch string) error

ValidateForkWithStateDestination is the shared CLI/TUI destination-collision gate for fork-with-state. Worktree-collision is checked first so the more specific error (with path) is surfaced when both conditions are true.

func WorktreePath added in v0.10.10

func WorktreePath(opts WorktreePathOptions) string

WorktreePath generates a worktree path. If opts.Template is set, it expands the template with variables:

  • {repo-name}, {repo-root}, {session-id}
  • {branch}: sanitized (human-friendly, may collide)
  • {branch-escaped}: URL-escaped (collision-resistant, reversible)

Unknown variables like {foo} are left as-is in the resolved path. Falls back to location-based strategy using opts.Location when template is empty or RepoDir is invalid.

Types

type DestinationCollisionError added in v1.9.47

type DestinationCollisionError struct {
	Kind   string // CollisionWorktreeExists or CollisionBranchExists
	Branch string
	Path   string // populated when Kind == CollisionWorktreeExists
}

DestinationCollisionError is returned by ValidateForkWithStateDestination when the requested destination branch already has a worktree or already exists as a local branch. Callers own user-facing wording.

func (*DestinationCollisionError) Error added in v1.9.47

func (e *DestinationCollisionError) Error() string

type Worktree

type Worktree struct {
	Path   string // Filesystem path to the worktree
	Branch string // Branch name checked out in this worktree
	Commit string // HEAD commit SHA
	Bare   bool   // Whether this is the bare repository
}

Worktree represents a git worktree

func ListWorktrees

func ListWorktrees(repoDir string) ([]Worktree, error)

ListWorktrees returns all worktrees for the repository at repoDir

type WorktreePathOptions added in v0.10.10

type WorktreePathOptions struct {
	Branch    string
	Location  string
	RepoDir   string
	SessionID string
	Template  string
}

WorktreePathOptions configures worktree path generation.

type WorktreeStateOptions added in v1.9.15

type WorktreeStateOptions struct {
	// WithState copies parent's staged/unstaged/untracked files into the
	// new worktree before the setup hook runs.
	WithState bool
	// WithIgnored, when WithState is true, also copies parent's gitignored
	// files (e.g., .env, .mcp.json). Implies WithState.
	WithIgnored bool
}

WorktreeStateOptions controls the issue #1029 with-state behavior of CreateWorktreeWithStateAndSetup. When WithState is false, the worktree is created clean from branch tip — the legacy behavior.

Jump to

Keyboard shortcuts

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