spice

package
v0.7.1 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2024 License: GPL-3.0 Imports: 24 Imported by: 0

Documentation

Overview

Package spice intends to provide the core functionality of the tool.

Index

Constants

This section is empty.

Variables

View Source
var ErrAlreadyRestacked = errors.New("branch is already restacked")

ErrAlreadyRestacked indicates that a branch is already restacked on top of its base.

View Source
var ErrStackEditAborted = errors.New("stack edit aborted")

ErrStackEditAborted is returned when the user requests for a stack edit operation to be aborted.

Functions

func GenerateBranchName

func GenerateBranchName(subject string) string

GenerateBranchName generates a branch name from a commit message subject.

The branch name is generated by converting the subject to lowercase, replacing spaces with hyphens, and removing all non-alphanumeric characters.

If the subject has more than 32 characters, it is truncated to 32 characters at word boundaries.

Types

type BranchNeedsRestackError

type BranchNeedsRestackError struct {
	// Base is the name of the base branch for the branch.
	Base string

	// BaseHash is the hash of the base branch.
	// Note that this is the actual hash, not the hash stored in state.
	BaseHash git.Hash
}

BranchNeedsRestackError is returned by Service.VerifyRestacked when a branch needs to be restacked.

func (*BranchNeedsRestackError) Error

func (e *BranchNeedsRestackError) Error() string

type BranchOntoRequest

type BranchOntoRequest struct {
	// Branch is the branch to move.
	// This must not be the trunk branch.
	Branch string

	// Onto is the target branch to move the branch onto.
	// Onto may be the trunk branch.
	Onto string
}

BranchOntoRequest is a request to move a branch onto another branch.

type Config added in v0.4.0

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

Config defines the git-spice configuration source. It can be passed into Kong as a kong.Resolver to fill in flag values.

Configuration for git-spice is specified via git-config. These can be system, user, repository, or worktree-level.

The configuration keys are read from the root namespace "spice" for keys in the CLI grammar tagged with the `config:"key"` tag. Flags that are not tagged with `config:"key"` are ignored for configuration.

For example:

type someCmd struct {
	Level string `config:"level"`
}

This will read the configuration key "spice.level" from git-config.

[spice]
level = hot

Values are decoded using the same mapper as the flag. For single-valued fields, if multiple values are found in the configuration, the last value is used. For slice fields, all values are combined.

If a flag is passed on the CLI, it takes precedence over the configuration.

func LoadConfig added in v0.4.0

func LoadConfig(ctx context.Context, cfg GitConfigLister, opts ConfigOptions) (*Config, error)

LoadConfig loads configuration from the provided [GitConfig].

func (*Config) ExpandShorthand added in v0.4.0

func (c *Config) ExpandShorthand(name string) ([]string, bool)

ExpandShorthand returns the long form of a custom shorthand command. Returns false if the shorthand is not defined.

func (*Config) Resolve added in v0.4.0

func (c *Config) Resolve(kctx *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error)

Resolve resolves the value for a flag from configuration.

func (*Config) Shorthands added in v0.4.0

func (c *Config) Shorthands() []string

Shorthands returns a sorted list of all defined shorthands.

func (*Config) Validate added in v0.4.0

func (*Config) Validate(*kong.Application) error

Validate checks if the configuration is valid for the given application. This is a no-op, as we allow unknown configuration keys.

type ConfigOptions added in v0.4.0

type ConfigOptions struct {
	// Log specifies the logger to use for logging.
	// Defaults to no logging.
	Log *log.Logger
}

ConfigOptions specifies options for the Config.

type DeletedBranchError

type DeletedBranchError struct {
	Name string

	Base     string
	BaseHash git.Hash
}

DeletedBranchError is returned when a branch was deleted out of band.

This error is used to indicate that the branch does not exist, but its base might.

func (*DeletedBranchError) Error

func (e *DeletedBranchError) Error() string

type GitConfigLister added in v0.4.0

type GitConfigLister interface {
	ListRegexp(context.Context, string) iter.Seq2[git.ConfigEntry, error]
}

GitConfigLister provides access to git-config output.

type GitRepository

type GitRepository interface {
	// MergeBase reports the merge base of the two given commits.
	// This is a commit that is an ancestor of both commits.
	MergeBase(ctx context.Context, a, b string) (git.Hash, error)

	// IsAncestor reports whether commit a is an ancestor of commit b.
	IsAncestor(ctx context.Context, a, b git.Hash) bool

	// ForkPoint reports the git hash at which branch b
	// forked from branch a.
	ForkPoint(ctx context.Context, a, b string) (git.Hash, error)

	// PeelToCommit returns the commit hash for the given commit-ish.
	PeelToCommit(ctx context.Context, ref string) (git.Hash, error)

	// CurrentBranch returns the name of the current branch.
	CurrentBranch(ctx context.Context) (string, error)

	// LocalBranches returns a list of all local branches.
	LocalBranches(ctx context.Context) ([]git.LocalBranch, error)

	// RemoteDefaultBranch reports the default branch of the given remote.
	RemoteDefaultBranch(ctx context.Context, remote string) (string, error)

	// ListRemotes returns the names of all known remotes.
	ListRemotes(ctx context.Context) ([]string, error)
	RemoteURL(ctx context.Context, remote string) (string, error)

	Rebase(context.Context, git.RebaseRequest) error
	RenameBranch(context.Context, git.RenameBranchRequest) error
	DeleteBranch(context.Context, string, git.BranchDeleteOptions) error
	HashAt(context.Context, string, string) (git.Hash, error)
}

GitRepository provides read/write access to the conents of a git repository. It is a subset of the functionality provied by the git.Repository type.

type GuessOp

type GuessOp int

GuessOp specifies the kind of guess operation that the Guesser is performing.

const (
	GuessUnknown GuessOp = iota
	GuessRemote
	GuessTrunk
)

List of guess operations.

type Guesser

type Guesser struct {
	// Select prompts a user to select from a list of options
	// and returns the selected option.
	//
	// selected is the the option that should be selected by default
	// or an empty string if there's no preferred default.
	Select func(op GuessOp, opts []string, selected string) (string, error) // required
}

Guesser attempts to make informed guesses about the state of a repository during initialization.

func (*Guesser) GuessRemote

func (g *Guesser) GuessRemote(ctx context.Context, repo GitRepository) (string, error)

GuessRemote attempts to guess the name of the remote to use for the repository.

It returns an empty string if a remote was not found.

func (*Guesser) GuessTrunk

func (g *Guesser) GuessTrunk(ctx context.Context, repo GitRepository, remote string) (string, error)

GuessTrunk attempts to guess the name of the trunk branch of the repository. If remote is non-empty, it should be the name of the remote for the repository.

type LoadBranchItem

type LoadBranchItem struct {
	// Name is the name of the branch.
	Name string

	// Head is the commit at the head of the branch.
	Head git.Hash

	// Base is the name of the branch that this branch is based on.
	Base string

	// BaseHash is the last known commit hash of the base branch.
	// This may not match the current commit hash of the base branch.
	BaseHash git.Hash

	// Change is the metadata associated with the branch.
	// This is nil if the branch has not been published.
	Change forge.ChangeMetadata

	// UpstreamBranch is the name under which this branch
	// was pushed to the upstream repository.
	UpstreamBranch string
}

LoadBranchItem is a single branch returned by LoadBranches.

type LookupBranchResponse

type LookupBranchResponse struct {
	// Base is the base branch configured
	// for the requested branch.
	Base string

	// BaseHash is the last known hash of the base branch.
	// This may not match the current hash of the base branch.
	BaseHash git.Hash

	// Change is information about the published change
	// associated with the branch.
	//
	// This is nil if the branch hasn't been published yet.
	Change forge.ChangeMetadata

	// UpstreamBranch is the name of the upstream branch
	// or an empty string if the branch is not tracking an upstream branch.
	UpstreamBranch string

	// Head is the commit at the head of the branch.
	Head git.Hash
}

LookupBranchResponse is the response to a LookupBranch request. It includes information about the tracked branch.

type NonLinearStackError

type NonLinearStackError struct {
	Branch string
	Aboves []string
}

NonLinearStackError is returned when a stack is not linear. This means that a branch has more than one upstack branch.

func (*NonLinearStackError) Error

func (e *NonLinearStackError) Error() string

type RebaseRescueRequest

type RebaseRescueRequest struct {
	// Err is the error that caused the rebase operation to be interrupted.
	Err error

	// Command is the command that should be run
	// after the rebase operation has been rescued.
	//
	// If this is unset, a continuation will NOT be recorded.
	Command []string

	// Branch is the branch on which the command should be run.
	//
	// If this is unset, the continuation will run on the interrupted
	// branch.
	Branch string

	// Message is the message that should be recorded
	// for debugging this continuation.
	Message string // optional
}

RebaseRescueRequest is a request to rescue a rebase operation.

type RestackResponse

type RestackResponse struct {
	Base string
}

RestackResponse is the response to a restack operation.

type Service

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

Service provides the core functionality of the tool. It combines together lower level pieces like access to the git repository and the spice state.

func NewService

func NewService(ctx context.Context, repo GitRepository, store Store, log *log.Logger) *Service

NewService builds a new service operating on the given repository and store.

func (*Service) BranchOnto

func (s *Service) BranchOnto(ctx context.Context, req *BranchOntoRequest) error

BranchOnto moves the commits of a branch onto a different base branch, updating internal state to reflect the new branch stack. It DOES NOT modify the upstack branches of the branch being moved. As this involves a rebase operation, the caller should be prepared to rescue the operation if it fails.

func (*Service) FindBottom

func (s *Service) FindBottom(ctx context.Context, start string) (string, error)

FindBottom returns the bottom-most branch in the downstack chain starting at the given branch just before trunk.

Returns an error if no downstack branches are found.

func (*Service) FindTop

func (s *Service) FindTop(ctx context.Context, start string) ([]string, error)

FindTop returns the topmost branches in each upstack chain starting at the given branch.

func (*Service) ForgetBranch

func (s *Service) ForgetBranch(ctx context.Context, name string) error

ForgetBranch stops tracking a branch, updating the upstacks for it to point to its base.

func (*Service) ListAbove

func (s *Service) ListAbove(ctx context.Context, base string) ([]string, error)

ListAbove returns a list of branches that are immediately above the given branch. These are branches that have the given branch as their base. The slice is empty if there are no branches above the given branch.

func (*Service) ListChangeTemplates

func (s *Service) ListChangeTemplates(
	ctx context.Context,
	remoteName string,
	fr forge.Repository,
) ([]*forge.ChangeTemplate, error)

ListChangeTemplates returns the Change templates defined in the repository. For GitHub, these are PR templates.

func (*Service) ListDownstack

func (s *Service) ListDownstack(ctx context.Context, start string) ([]string, error)

ListDownstack lists all branches below the given branch in the downstack chain, not including trunk.

The given branch is the first element in the returned slice, and the bottom-most branch is the last element.

If there are no branches downstack because we're on trunk, or because all branches are downstack from trunk have been deleted, the returned slice will be nil.

func (*Service) ListStack

func (s *Service) ListStack(ctx context.Context, start string) ([]string, error)

ListStack returns the full stack of branches that the given branch is in.

If the start branch has multiple upstack branches, all of them are included in the returned slice. The result is ordered by branch position in the stack with the bottom-most branch as the first element.

func (*Service) ListStackLinear

func (s *Service) ListStackLinear(ctx context.Context, start string) ([]string, error)

ListStackLinear returns the full stack of branches that the given branch is in but only if the stack is linear: each branch has only one upstack branch. If the stack is not linear, NonLinearStackError is returned.

The returned slice is ordered by branch position in the stack with the bottom-most branch as the first element.

func (*Service) ListUpstack

func (s *Service) ListUpstack(ctx context.Context, start string) ([]string, error)

ListUpstack will list all branches that are upstack from the given branch, including those that are upstack from the upstack branches. The given branch is the first element in the returned slice.

The returned slice is ordered by branch position in the upstack. It is guaranteed that for i < j, branch[i] is not a parent of branch[j].

func (*Service) LoadBranches

func (s *Service) LoadBranches(ctx context.Context) ([]LoadBranchItem, error)

LoadBranches loads all tracked branches and all their information as a single operation.

The returned branches are sorted by name.

func (*Service) LookupBranch

func (s *Service) LookupBranch(ctx context.Context, name string) (*LookupBranchResponse, error)

LookupBranch returns information about a branch tracked by gs.

It returns git.ErrNotExist if the branch is nt known to the repository, state.ErrNotExist if the branch is not tracked, or a DeletedBranchError if the branch is tracked, but was deleted out of band.

func (*Service) RebaseRescue

func (s *Service) RebaseRescue(ctx context.Context, req RebaseRescueRequest) error

RebaseRescue helps operations continue from rebase conflicts. To use it, call RebaseRescue with the error that caused the rebase operation to be interrupted.

For example:

func myOperation(...) error {
	if err := repo.Rebase(ctx, ...); err != nil {
		return svc.RebaseRescue(ctx, ...)
	}
	return nil
}

The function returns an error back to the caller so that the program can exit.

For commands that invoke other commands that may be interrupted by a rebase, assuming both commands are idempotent and re-entrant, the parent should also wrap the command in a rebase rescue operation. For example, if we have a leaf operation that rescues:

func childOperation(...) error {
	if err := repo.Rebase(ctx, ...); err != nil {
		return svc.RebaseRescue(ctx, ...)
	}
	return nil
}

func parentOperation(...) error {
	for _, child := range children {
		if err := childOperation(...); err != nil {
			return svc.RebaseRescue(ctx, ...)
		}
	}
}

Note that this tends to not be necessary for commands that end with a single child operation, e.g.

func parentOperation(...) error {
	// ...
	return childOperation(...)
}

As at that point, re-running the child operation is sufficient.

func (*Service) RenameBranch

func (s *Service) RenameBranch(ctx context.Context, oldName, newName string) error

RenameBranch renames a branch tracked by gs. This handles both, renaming the branch in the repository, and updating the internal state to reflect the new name.

func (*Service) Restack

func (s *Service) Restack(ctx context.Context, name string) (*RestackResponse, error)

Restack restacks the given branch on top of its base branch, handling movement of the base branch if necessary.

Returns ErrAlreadyRestacked if the branch does not need to be restacked.

func (*Service) StackEdit

func (s *Service) StackEdit(ctx context.Context, req *StackEditRequest) (*StackEditResult, error)

StackEdit allows the user to edit the order of branches in a stack. The user is presented with an editor containing the list of branches.

Returns ErrStackEditAborted if thee operation is aborted by the user.

func (*Service) VerifyRestacked

func (s *Service) VerifyRestacked(ctx context.Context, name string) error

VerifyRestacked verifies that the branch is on top of its base branch. This also updates the base branch hash if the hash is out of date, but the branch is restacked properly.

It returns [ErrNeedsRestack] if the branch needs to be restacked, state.ErrNotExist if the branch is not tracked. Any other error indicates a problem with checking the branch.

type StackEditRequest

type StackEditRequest struct {
	// Stack of branches to edit, with branch closest to trunk first.
	// The first branch in the list will be merged into trunk first.
	Stack []string

	// Editor to use for editing the stack.
	Editor string
}

StackEditRequest is a request to edit the order of a stack of branches.

type StackEditResult

type StackEditResult struct {
	// Stack is the new order of branches after the edit operation.
	// The branch closest to trunk is first in the list.
	Stack []string
}

StackEditResult is the result of a stack edit operation.

type Store

type Store interface {
	// Trunk returns the name of the trunk branch.
	Trunk() string
	Remote() (string, error)

	// LookupBranch returns the branch state for the given branch,
	// or [state.ErrNotExist] if the branch does not exist.
	LookupBranch(ctx context.Context, name string) (*state.LookupResponse, error)

	// BeginBranchTx begins a transaction to modify state
	// for zero or more branches
	BeginBranchTx() *state.BranchTx

	// ListBranches returns a list of all tracked branch names.
	// This list never includes the trunk branch.
	ListBranches(ctx context.Context) ([]string, error)

	AppendContinuations(context.Context, string, ...state.Continuation) error
	TakeContinuations(context.Context, string) ([]state.Continuation, error)

	LoadCachedTemplates(context.Context, string) ([]*state.CachedTemplate, error)
	CacheTemplates(context.Context, string, []*state.CachedTemplate) error
}

Store provides storage for gs. It is a subset of the functionality provided by the state.Store type.

Directories

Path Synopsis
Package state defines and sores the state for gs.
Package state defines and sores the state for gs.
storage
Package storage provides a key-value storage abstraction where values are JSON-serializable structs.
Package storage provides a key-value storage abstraction where values are JSON-serializable structs.

Jump to

Keyboard shortcuts

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