git

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: May 16, 2025 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package git provides Git operations for the gitbak application.

This package abstracts Git commands and operations used by gitbak, handling repository interaction, branch management, commit creation, and error recovery. It implements automatic Git operations with built-in retry mechanisms and tracking for sequential commit numbering.

Core Components

  • Gitbak: Main type that manages a Git repository and performs automatic commits
  • CommandExecutor: Interface for executing Git commands
  • UserInteractor: Interface for user interaction during Git operations

Features

  • Automatic Git operations with configurable intervals
  • Sequential commit numbering with the ability to continue from a previous session
  • Branch creation and management
  • Error handling with configurable retry logic
  • Clean session termination with statistics

Usage

Basic usage pattern:

config := git.GitbakConfig{
    RepoPath:        "/path/to/repo",
    IntervalMinutes: 5,
    BranchName:      "gitbak-session",
    CommitPrefix:    "[gitbak]",
    CreateBranch:    true,
    ContinueSession: false,
    Verbose:         true,
    ShowNoChanges:   false,
    MaxRetries:      3,
}

gitbak, err := git.NewGitbak(config, logger)
if err != nil {
    // Handle error
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func() {
    // Cancel context when termination is requested
    // (e.g., signal handling, user input, etc.)
}()

if err := gitbak.Run(ctx); err != nil {
    // Handle error
}

// Show session summary
gitbak.PrintSummary()

Error Handling

The package implements sophisticated error recovery with configurable retry mechanisms:

  • Consecutive identical errors are counted and compared against MaxRetries
  • When errors change or successful operations occur, the error counter resets
  • Setting MaxRetries to 0 makes the system retry indefinitely

Implementation Notes

The package uses the command-line Git executable rather than a Go Git library. This ensures compatibility with all Git features and repository configurations.

Concurrency Model

The gitbak instance is not thread-safe and should be accessed from a single goroutine. Different gitbak instances can safely operate on different repositories concurrently, of course - allowing users to run multiple gitbak instances on different repositories without a concern.

Dependencies

This package requires:

  • A functional Git installation in the system PATH
  • A valid Git repository at the configured path
  • Write permissions for the repository

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsRepository

func IsRepository(path string) (bool, error)

IsRepository checks if the given path is a git repository Returns true if it is a repository, false otherwise. If path is not a repository due to git exit code 128, returns (false, nil). For other errors (git not found, permission issues, etc), returns (false, err).

Types

type CommandExecutor

type CommandExecutor interface {
	// Execute runs a command and returns its exit code
	Execute(ctx context.Context, cmd *exec.Cmd) error

	// ExecuteWithOutput runs a command and returns its output and exit code
	ExecuteWithOutput(ctx context.Context, cmd *exec.Cmd) (string, error)

	// ExecuteWithContext runs a command with context and returns its exit code
	ExecuteWithContext(ctx context.Context, name string, args ...string) error

	// ExecuteWithContextAndOutput runs a command with context and returns its output and exit code
	ExecuteWithContextAndOutput(ctx context.Context, name string, args ...string) (string, error)
}

CommandExecutor defines an interface for executing commands

type DefaultInteractor

type DefaultInteractor struct {
	Reader io.Reader
	Writer io.Writer
	Logger logger.Logger
}

DefaultInteractor is the standard implementation of UserInteractor that reads from stdin and writes to stdout

func NewDefaultInteractor

func NewDefaultInteractor(logger logger.Logger) *DefaultInteractor

NewDefaultInteractor creates a new DefaultInteractor

func (*DefaultInteractor) PromptYesNo

func (i *DefaultInteractor) PromptYesNo(question string) bool

PromptYesNo asks the user a yes/no question and returns their response

type ExecExecutor

type ExecExecutor struct{}

ExecExecutor is the default implementation of CommandExecutor that delegates to the os/exec package

func NewExecExecutor

func NewExecExecutor() *ExecExecutor

NewExecExecutor creates a new ExecExecutor

func (*ExecExecutor) Execute

func (e *ExecExecutor) Execute(ctx context.Context, cmd *exec.Cmd) error

Execute implements CommandExecutor.Execute

func (*ExecExecutor) ExecuteWithContext

func (e *ExecExecutor) ExecuteWithContext(ctx context.Context, name string, args ...string) error

ExecuteWithContext implements CommandExecutor.ExecuteWithContext

func (*ExecExecutor) ExecuteWithContextAndOutput

func (e *ExecExecutor) ExecuteWithContextAndOutput(ctx context.Context, name string, args ...string) (string, error)

ExecuteWithContextAndOutput implements CommandExecutor.ExecuteWithContextAndOutput

func (*ExecExecutor) ExecuteWithOutput

func (e *ExecExecutor) ExecuteWithOutput(ctx context.Context, cmd *exec.Cmd) (string, error)

ExecuteWithOutput implements CommandExecutor.ExecuteWithOutput

type Gitbak

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

Gitbak monitors and auto-commits changes to a git repository. It provides the core functionality for automatically committing changes at regular intervals, with features for branch management, error recovery, and session continuation.

func NewGitbak

func NewGitbak(config GitbakConfig, logger logger.Logger) (*Gitbak, error)

NewGitbak creates a new gitbak instance with default dependencies. This is the primary constructor for creating a gitbak instance with standard components. It validates the configuration and sets up all required dependencies.

Parameters:

  • config: The configuration for this gitbak instance
  • logger: The logger to use for output messages

Returns:

  • A configured gitbak instance ready to run
  • An error if the configuration is invalid or initialization fails

Example:

cfg := GitbakConfig{
    RepoPath: "/path/to/repo",
    IntervalMinutes: 5,
    BranchName: "gitbak-session",
    CommitPrefix: "[gitbak]",
    CreateBranch: true,
}
gitbak, err := NewGitbak(cfg, logger)

func NewGitbakWithDeps

func NewGitbakWithDeps(
	config GitbakConfig,
	logger logger.Logger,
	executor CommandExecutor,
	interactor UserInteractor,
) (*Gitbak, error)

NewGitbakWithDeps creates a new gitbak instance with custom dependencies

func (*Gitbak) PrintSummary

func (g *Gitbak) PrintSummary()

PrintSummary prints a summary of the gitbak session

func (*Gitbak) Run

func (g *Gitbak) Run(ctx context.Context) error

Run starts the gitbak process with the given context for cancellation

func (*Gitbak) RunSingleIteration

func (g *Gitbak) RunSingleIteration(ctx context.Context) error

RunSingleIteration is an exported version of the internal gitbak logic for testing. It runs a single iteration of the gitbak process without the infinite loop. This function is only available in test builds.

type GitbakConfig

type GitbakConfig struct {
	// RepoPath specifies the filesystem path to the Git repository.
	// Can be absolute or relative path. If empty, validation will fail.
	RepoPath string

	// IntervalMinutes defines how often (in minutes) gitbak checks for changes.
	// This can be a fractional value (e.g. 0.5 for 30 seconds).
	// Must be greater than 0.
	IntervalMinutes float64

	// BranchName specifies the Git branch to use for checkpoint commits.
	// If CreateBranch is true, this branch will be created.
	// If CreateBranch is false, this branch must already exist.
	// If ContinueSession is true, this should be an existing gitbak branch.
	BranchName string

	// CommitPrefix is prepended to all commit messages.
	// Used to identify gitbak commits and extract commit numbers.
	CommitPrefix string

	// CreateBranch determines whether to create a new branch or use existing one.
	// If true, a new branch named BranchName will be created.
	// If false, gitbak will use the existing branch specified by BranchName.
	// ContinueSession implicitly sets this to false.
	CreateBranch bool

	// ContinueSession enables continuation mode for resuming a previous session.
	// When true, gitbak finds the last commit number and continues numbering from there.
	// Requires that previous gitbak commits exist on the specified branch.
	ContinueSession bool

	// Verbose controls the amount of informational output.
	// When true, gitbak provides detailed status updates.
	// When false, only essential messages are shown.
	Verbose bool

	// ShowNoChanges determines whether to report when no changes are detected.
	// When true, gitbak logs a message at each interval even if nothing changed.
	// When false, these messages are suppressed.
	ShowNoChanges bool

	// NonInteractive disables any prompts and uses default responses.
	// Useful for running gitbak in automated environments.
	NonInteractive bool

	// MaxRetries defines how many consecutive identical errors are allowed before exiting.
	// If zero, gitbak will retry indefinitely.
	// Errors of different types or successful operations reset this counter.
	MaxRetries int
}

GitbakConfig contains configuration for a gitbak instance. This struct holds all the settings that control gitbak's behavior, including repository location, commit preferences, output options, and error handling settings.

func (*GitbakConfig) Validate

func (c *GitbakConfig) Validate() error

Validate sanity-checks the config and returns an error if something is wrong. It ensures all required fields have valid values before gitbak starts running. This helps prevent runtime errors by catching configuration issues early.

The following validations are performed:

  • RepoPath must not be empty
  • IntervalMinutes must be greater than 0
  • BranchName must not be empty
  • CommitPrefix must not be empty
  • MaxRetries must not be negative

Returns nil if the configuration is valid, or an error describing the issue.

type MockCommandExecutor

type MockCommandExecutor struct {
	// Basic tracking
	ExitCode  int
	Output    string
	LastCmd   *exec.Cmd
	Commands  []*exec.Cmd
	CallCount int

	// Function hooks for customizing behavior
	ExecuteFn           func(ctx context.Context, cmd *exec.Cmd) error
	ExecuteWithOutputFn func(ctx context.Context, cmd *exec.Cmd) (string, error)

	// Git command tracking (for uncommitted changes tests)
	AddCalled    bool
	CommitCalled bool
	AddError     bool
	CommitError  bool

	// Error simulation and signaling (for retry tests)
	ShouldFailCount     int
	PermanentFailAfter  int
	FailWithErr         error
	CurrentErr          error
	ErrorVariant        int
	ShouldResetErrorMsg bool

	// Advanced retry test behavior properties
	// These are used directly by the retry tests
	UseAdvancedRetryBehavior bool

	// Channels for signaling (for retry tests)
	NextFailCall    chan struct{}
	NextSuccessCall chan struct{}
}

MockCommandExecutor is a mock of the CommandExecutor interface that can be configured for different test scenarios.

func NewAdvancedMockRetryExecutor

func NewAdvancedMockRetryExecutor(failCount int, failWithErr error) *MockCommandExecutor

NewAdvancedMockRetryExecutor creates a mock executor with advanced retry behavior for complex testing scenarios like message variation and permanent failures

func NewMockCommandExecutor

func NewMockCommandExecutor() *MockCommandExecutor

NewMockCommandExecutor creates a new mock executor with default values

func NewMockRetryExecutor

func NewMockRetryExecutor(failCount int, failWithErr error) *MockCommandExecutor

NewMockRetryExecutor creates a mock executor configured for retry tests

func NewMockUncommittedChangesExecutor

func NewMockUncommittedChangesExecutor(addError, commitError bool) *MockCommandExecutor

NewMockUncommittedChangesExecutor creates a mock for uncommitted changes tests

func (*MockCommandExecutor) Execute

func (m *MockCommandExecutor) Execute(ctx context.Context, cmd *exec.Cmd) error

Execute implements the CommandExecutor interface

func (*MockCommandExecutor) ExecuteWithContext

func (m *MockCommandExecutor) ExecuteWithContext(ctx context.Context, name string, args ...string) error

ExecuteWithContext implements the CommandExecutor interface

func (*MockCommandExecutor) ExecuteWithContextAndOutput

func (m *MockCommandExecutor) ExecuteWithContextAndOutput(ctx context.Context, name string, args ...string) (string, error)

ExecuteWithContextAndOutput implements the CommandExecutor interface

func (*MockCommandExecutor) ExecuteWithOutput

func (m *MockCommandExecutor) ExecuteWithOutput(ctx context.Context, cmd *exec.Cmd) (string, error)

ExecuteWithOutput implements the CommandExecutor interface

type MockInteractor

type MockInteractor struct {
	// Response to return from PromptYesNo
	PromptYesNoResponse bool

	// Track the number of times PromptYesNo was called
	PromptYesNoCalled bool

	// Store the last prompt that was passed to PromptYesNo
	LastPrompt string
}

MockInteractor is a mock implementation of the UserInteractor interface for testing user interaction scenarios.

func NewMockInteractor

func NewMockInteractor(response bool) *MockInteractor

NewMockInteractor creates a new MockInteractor with default values

func (*MockInteractor) PromptYesNo

func (m *MockInteractor) PromptYesNo(prompt string) bool

PromptYesNo implements the UserInteractor interface

type NonInteractiveInteractor

type NonInteractiveInteractor struct{}

NonInteractiveInteractor always returns default values without prompting

func NewNonInteractiveInteractor

func NewNonInteractiveInteractor() *NonInteractiveInteractor

NewNonInteractiveInteractor creates a new NonInteractiveInteractor

func (*NonInteractiveInteractor) PromptYesNo

func (i *NonInteractiveInteractor) PromptYesNo(question string) bool

PromptYesNo always returns false without prompting

type UserInteractor

type UserInteractor interface {
	// PromptYesNo asks the user a yes/no question and returns their response
	PromptYesNo(question string) bool
}

UserInteractor defines an interface for interacting with the user

Jump to

Keyboard shortcuts

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