stack

package
v0.0.1 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var LockTimeout = 5 * time.Second

LockTimeout is how long Lock() will wait for the exclusive lock before giving up. With the lock held only during file writes (milliseconds), this timeout primarily guards against a hung process holding the lock.

Functions

func Save

func Save(gitDir string, sf *StackFile) error

Save acquires an exclusive lock on the stack file, verifies the file hasn't been modified since Load (optimistic concurrency), writes sf as JSON, and releases the lock. The lock is held only for the read-compare-write window. Returns *LockError if the lock times out, or *StaleError if another process modified the file since it was loaded.

func SaveNonBlocking

func SaveNonBlocking(gitDir string, sf *StackFile)

SaveNonBlocking attempts to save without blocking. If another process holds the lock or the file was modified since Load, the save is silently skipped. Use this for best-effort metadata persistence (e.g. syncing PR state in view).

Types

type BranchRef

type BranchRef struct {
	Branch      string          `json:"branch"`
	Head        string          `json:"head,omitempty"`
	Base        string          `json:"base,omitempty"`
	PullRequest *PullRequestRef `json:"pullRequest,omitempty"`

	// Queued is a transient (not persisted) flag indicating the branch's
	// PR is currently in a merge queue. It is populated by syncStackPRs
	// from the GitHub API on each command run.
	Queued bool `json:"-"`
}

BranchRef represents a branch and its associated commit hash. For the trunk, Head stores the HEAD commit SHA. For stacked branches, Base stores the parent branch's HEAD SHA at the time of last sync/rebase, used to identify unique commits.

func (*BranchRef) IsMerged

func (b *BranchRef) IsMerged() bool

IsMerged returns whether a branch's PR has been merged.

func (*BranchRef) IsQueued

func (b *BranchRef) IsQueued() bool

IsQueued returns whether a branch's PR is currently in a merge queue. This is a transient state populated from the GitHub API on each run.

func (*BranchRef) IsSkipped

func (b *BranchRef) IsSkipped() bool

IsSkipped returns whether a branch should be skipped during push/sync/submit. A branch is skipped if its PR has been merged or is currently queued.

type FileLock

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

FileLock provides an exclusive advisory lock on the stack file to prevent concurrent writes between multiple gh-stack processes.

func Lock

func Lock(gitDir string) (*FileLock, error)

Lock acquires an exclusive lock on the stack file in the given git directory. It retries with a non-blocking attempt every 100ms for up to LockTimeout.

Most callers should not use Lock directly — stack.Save() acquires the lock automatically. Use Lock only when you need to hold the lock across multiple operations (e.g. Load-Modify-Save as an atomic unit).

func (*FileLock) Unlock

func (l *FileLock) Unlock()

Unlock releases the lock. The lock file is intentionally left on disk to avoid a race where another process opens the same path, blocks on flock, then wakes up holding a lock on an unlinked inode while a third process creates a new file and locks a different inode.

type LockError

type LockError struct {
	Err error
}

LockError is returned when the stack file lock cannot be acquired. Callers can check for this with errors.As to distinguish lock failures from other errors.

func (*LockError) Error

func (e *LockError) Error() string

func (*LockError) Unwrap

func (e *LockError) Unwrap() error

type PullRequestRef

type PullRequestRef struct {
	Number int    `json:"number"`
	ID     string `json:"id,omitempty"`
	URL    string `json:"url,omitempty"`
	Merged bool   `json:"merged,omitempty"`
}

PullRequestRef holds relatively immutable metadata about an associated PR.

type Stack

type Stack struct {
	ID       string      `json:"id,omitempty"`
	Prefix   string      `json:"prefix,omitempty"`
	Numbered bool        `json:"numbered,omitempty"`
	Trunk    BranchRef   `json:"trunk"`
	Branches []BranchRef `json:"branches"`
}

Stack represents a single stack of branches.

func (*Stack) ActiveBaseBranch

func (s *Stack) ActiveBaseBranch(branch string) string

ActiveBaseBranch returns the effective parent for a branch, skipping merged and queued ancestors. For the first active branch (or any branch whose downstack is all merged/queued), this returns the trunk.

func (*Stack) ActiveBranchIndices

func (s *Stack) ActiveBranchIndices() []int

ActiveBranchIndices returns the indices of all active (not merged, not queued) branches.

func (*Stack) ActiveBranches

func (s *Stack) ActiveBranches() []BranchRef

ActiveBranches returns only branches that are pushable (not merged, not queued).

func (*Stack) BaseBranch

func (s *Stack) BaseBranch(branch string) string

BaseBranch returns the base branch for the given branch in the stack. For the first branch, this is the trunk. For others, it's the previous branch.

func (*Stack) BranchNames

func (s *Stack) BranchNames() []string

BranchNames returns the list of branch names in order.

func (*Stack) Contains

func (s *Stack) Contains(branch string) bool

Contains returns true if the branch is part of this stack (including trunk).

func (*Stack) DisplayChain

func (s *Stack) DisplayChain() string

DisplayChain returns a human-readable chain representation of the stack. Format: (trunk) <- branch1 <- branch2 <- branch3

func (*Stack) FirstActiveBranchIndex

func (s *Stack) FirstActiveBranchIndex() int

FirstActiveBranchIndex returns the index of the first active (not merged, not queued) branch, or -1.

func (*Stack) IndexOf

func (s *Stack) IndexOf(branch string) int

IndexOf returns the index of the given branch in the stack, or -1 if not found.

func (*Stack) IsFullyMerged

func (s *Stack) IsFullyMerged() bool

IsFullyMerged returns true if all branches in the stack have been merged.

func (*Stack) MergedBranches

func (s *Stack) MergedBranches() []BranchRef

MergedBranches returns only merged branches, preserving order.

func (*Stack) QueuedBranches

func (s *Stack) QueuedBranches() []BranchRef

QueuedBranches returns only queued branches, preserving order.

type StackFile

type StackFile struct {
	SchemaVersion int     `json:"schemaVersion"`
	Repository    string  `json:"repository"`
	Stacks        []Stack `json:"stacks"`
	// contains filtered or unexported fields
}

StackFile represents the JSON file stored in .git/gh-stack.

func Load

func Load(gitDir string) (*StackFile, error)

Load reads the stack file from the given git directory. Returns an empty StackFile if the file does not exist. The returned StackFile records a checksum of the on-disk content so that Save can detect concurrent modifications.

func (*StackFile) AddStack

func (sf *StackFile) AddStack(s Stack)

AddStack adds a new stack to the file.

func (*StackFile) FindAllStacksForBranch

func (sf *StackFile) FindAllStacksForBranch(branch string) []*Stack

FindAllStacksForBranch returns all stacks that contain the given branch.

func (*StackFile) FindStackByPRNumber

func (sf *StackFile) FindStackByPRNumber(prNumber int) (*Stack, *BranchRef)

FindStackByPRNumber returns the first stack and branch whose PR number matches. Returns nil, nil if no match is found.

func (*StackFile) RemoveStack

func (sf *StackFile) RemoveStack(idx int)

RemoveStack removes the stack at the given index.

func (*StackFile) RemoveStackForBranch

func (sf *StackFile) RemoveStackForBranch(branch string) bool

RemoveStackForBranch removes the stack containing the given branch.

func (*StackFile) ValidateNoDuplicateBranch

func (sf *StackFile) ValidateNoDuplicateBranch(branch string) error

ValidateNoDuplicateBranch checks that the branch is not already in any stack.

type StaleError

type StaleError struct {
	Err error
}

StaleError is returned when the stack file was modified on disk since it was loaded. This indicates another process wrote to the file concurrently. Callers can check for this with errors.As.

func (*StaleError) Error

func (e *StaleError) Error() string

func (*StaleError) Unwrap

func (e *StaleError) Unwrap() error

Jump to

Keyboard shortcuts

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