reposync

package
v0.0.0-...-af63f47 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 19 Imported by: 2

Documentation

Overview

Package reposync implements repository sync planning and execution. It converts a desired repository list into an action plan (clone, update, delete) and executes it, supporting both local config and forge API sources.

Index

Constants

This section is empty.

Variables

View Source
var ErrMissingDependency = errors.New("missing dependency")

ErrMissingDependency is returned when required collaborators are unset.

View Source
var ErrNoRepositories = errors.New("no repositories provided")

ErrNoRepositories is returned when no repositories are provided.

Functions

func FormatCompactStatus

func FormatCompactStatus(ps *PostSyncStatus) string

FormatCompactStatus renders PostSyncStatus as "master|↓5|↑3|dirty".

func MaskTokenInURL

func MaskTokenInURL(urlStr string) string

MaskTokenInURL masks the token in a URL for safe logging.

Types

type Action

type Action struct {
	Repo      RepoSpec
	Type      ActionType
	Strategy  Strategy
	Reason    string
	PlannedBy string
	Workspace string // workspace name for grouping display (empty = flat repositories)
}

Action describes a single operation in a plan.

type ActionResult

type ActionResult struct {
	Action     Action
	Message    string
	Error      error
	PostStatus *PostSyncStatus
}

ActionResult is a per-repo outcome.

type ActionType

type ActionType string

ActionType enumerates planned operations.

const (
	ActionClone  ActionType = "clone"
	ActionUpdate ActionType = "update"
	ActionSkip   ActionType = "skip"
	ActionDelete ActionType = "delete"
)

ActionType values represent the planned operation for each repository.

type AuthConfig

type AuthConfig struct {
	// Token is used for HTTPS clone URL injection
	Token string

	// Provider is the forge provider type (github, gitlab, gitea)
	// Used to determine the correct token format for URL injection
	Provider string

	// SSHKeyPath is the path to SSH private key file (priority)
	SSHKeyPath string

	// SSHKeyContent is the SSH private key content (used if SSHKeyPath is empty)
	SSHKeyContent string

	// SSHPort is the custom SSH port (0 = default)
	SSHPort int
}

AuthConfig holds authentication settings for git operations.

type AuthResult

type AuthResult struct {
	// CloneURL is the modified clone URL (with token injected for HTTPS)
	CloneURL string

	// Env contains environment variables to set for git commands
	Env []string

	// TempKeyPath is the path to temporary SSH key file (if created from content)
	// Caller should display warning to user about cleanup
	TempKeyPath string

	// Warnings contains non-fatal warnings (e.g., temp file cleanup reminder)
	Warnings []string
}

AuthResult contains the result of authentication setup.

func PrepareAuth

func PrepareAuth(cloneURL string, auth AuthConfig) (*AuthResult, error)

PrepareAuth prepares authentication for a git clone operation. It modifies the clone URL for HTTPS (token injection) and sets up environment variables for SSH (GIT_SSH_COMMAND).

Priority:

  1. If auth config has token/key -> use it
  2. Otherwise -> fallback to system defaults (no modification)

type DiagnosticExecutor

type DiagnosticExecutor struct {
	Client repo.Client
	Logger repo.Logger
}

DiagnosticExecutor performs health checks on repositories.

func (DiagnosticExecutor) CheckHealth

func (e DiagnosticExecutor) CheckHealth(ctx context.Context, repos []RepoSpec, opts DiagnosticOptions) (*HealthReport, error)

CheckHealth performs health diagnostics on multiple repositories.

type DiagnosticOptions

type DiagnosticOptions struct {
	// SkipFetch skips remote fetch before checking divergence.
	// This is faster but may give stale results.
	SkipFetch bool

	// FetchTimeout is max time to wait for remote fetch (per repo).
	// Default: 30s.
	FetchTimeout time.Duration

	// Parallel is number of concurrent health checks.
	// Default: 4.
	Parallel int

	// CheckWorkTree enables working tree status checks.
	// Default: true.
	CheckWorkTree bool

	// IncludeRecommendations generates actionable guidance.
	// Default: true.
	IncludeRecommendations bool

	// Progress is an optional progress callback.
	Progress DiagnosticProgress
}

DiagnosticOptions configures health check behavior.

func DefaultDiagnosticOptions

func DefaultDiagnosticOptions() DiagnosticOptions

DefaultDiagnosticOptions returns sensible defaults.

type DiagnosticProgress

type DiagnosticProgress interface {
	OnRepoStart(repo RepoSpec)
	OnRepoComplete(health RepoHealth)
}

DiagnosticProgress receives progress notifications during health checks.

type DivergenceType

type DivergenceType string

DivergenceType classifies how local and remote branches differ.

const (
	// DivergenceNone means local and remote are identical.
	DivergenceNone DivergenceType = "none"

	// DivergenceFastForward means local is behind remote, can fast-forward.
	DivergenceFastForward DivergenceType = "fast-forward"

	// DivergenceDiverged means local and remote have diverged, requires merge/rebase.
	DivergenceDiverged DivergenceType = "diverged"

	// DivergenceAhead means local is ahead of remote, can push.
	DivergenceAhead DivergenceType = "ahead"

	// DivergenceConflict means there are merge conflicts or incompatible states.
	DivergenceConflict DivergenceType = "conflict"

	// DivergenceNoUpstream means no upstream branch is configured.
	DivergenceNoUpstream DivergenceType = "no-upstream"
)

type ExecutionResult

type ExecutionResult struct {
	Succeeded []ActionResult
	Failed    []ActionResult
	Skipped   []ActionResult
}

ExecutionResult captures aggregated outcomes from a run.

type Executor

type Executor interface {
	Execute(ctx context.Context, plan Plan, opts RunOptions, sink ProgressSink, store StateStore) (ExecutionResult, error)
}

Executor runs a Plan with concurrency, retries, and strategies.

type FSPlanner

type FSPlanner struct{}

FSPlanner inspects the filesystem to decide clone/update/delete actions.

func (FSPlanner) Plan

func (FSPlanner) Plan(ctx context.Context, req PlanRequest) (Plan, error)

Plan implements Planner by checking target paths and optional orphan cleanup.

type FileHistoryStore

type FileHistoryStore struct {
	BaseDir string
}

FileHistoryStore stores health snapshots in JSON files.

func NewFileHistoryStore

func NewFileHistoryStore(baseDir string) *FileHistoryStore

NewFileHistoryStore creates a history store that saves to a directory.

func (*FileHistoryStore) CleanupOld

func (s *FileHistoryStore) CleanupOld(ctx context.Context, olderThan time.Duration) error

CleanupOld removes snapshots older than the specified duration.

func (*FileHistoryStore) GetTrend

func (s *FileHistoryStore) GetTrend(ctx context.Context, repoName string) ([]RepoHealth, error)

GetTrend retrieves health history for a specific repository.

func (*FileHistoryStore) Load

func (s *FileHistoryStore) Load(ctx context.Context, limit int) ([]HistorySnapshot, error)

Load retrieves the most recent N snapshots.

func (*FileHistoryStore) Save

func (s *FileHistoryStore) Save(ctx context.Context, report *HealthReport) error

Save stores a health report snapshot.

type FileStateStore

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

FileStateStore persists run state to a JSON file for resume/audit.

func NewFileStateStore

func NewFileStateStore(path string) *FileStateStore

NewFileStateStore creates a new file-backed state store.

func (*FileStateStore) Load

func (s *FileStateStore) Load(ctx context.Context) (RunState, error)

Load reads state from disk. Missing files return an empty state.

func (*FileStateStore) Save

func (s *FileStateStore) Save(ctx context.Context, state RunState) error

Save writes state to disk atomically.

type ForgePlanner

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

ForgePlanner produces a Plan by querying a gitforge Provider.

func NewForgePlanner

func NewForgePlanner(fp ForgeProvider, config ForgePlannerConfig) *ForgePlanner

NewForgePlanner creates a new ForgePlanner with the given provider and config.

func (*ForgePlanner) Describe

func (p *ForgePlanner) Describe(req PlanRequest) string

Describe returns a description of the planner configuration.

func (*ForgePlanner) Plan

func (p *ForgePlanner) Plan(ctx context.Context, req PlanRequest) (Plan, error)

Plan implements the Planner interface. It queries the provider for repositories and creates a plan based on local state.

type ForgePlannerConfig

type ForgePlannerConfig struct {
	// TargetPath is the base directory for cloning repositories
	TargetPath string

	// Organization is the org/group to sync from
	Organization string

	// IsUser indicates if Organization is actually a user (for ListUserRepos)
	IsUser bool

	// IncludeArchived includes archived repositories
	IncludeArchived bool

	// IncludeForks includes forked repositories
	IncludeForks bool

	// IncludePrivate includes private repositories
	IncludePrivate bool

	// CloneProto is the clone protocol: ssh, https
	CloneProto string

	// SSHPort is the custom SSH port (0 = default 22)
	SSHPort int

	// IncludeSubgroups includes subgroups (GitLab only)
	IncludeSubgroups bool

	// SubgroupMode is flat (dash-separated) or nested (directories)
	SubgroupMode string

	// FlatSeparator is the separator for flat mode (default: "-")
	// Examples: "-", "_", ".", "" (empty = no separator)
	// Invalid characters: / \ : * ? " < > |
	FlatSeparator string

	// Branch is the branch to checkout after clone/update (comma-separated fallback list)
	Branch string

	// Auth contains authentication settings for clone operations
	Auth AuthConfig

	// Metadata filters
	FilterLanguages     []string  // Filter by language (lowercase)
	FilterMinStars      int       // Minimum star count
	FilterMaxStars      int       // Maximum star count (0 = unlimited)
	FilterLastPushAfter time.Time // Only include repos pushed after this time

	// Name/path regex filters
	FilterIncludePatterns []string // Include repos whose name or full name matches
	FilterExcludePatterns []string // Exclude repos whose name or full name matches
}

ForgePlannerConfig configures the ForgePlanner behavior.

type ForgeProvider

type ForgeProvider interface {
	Name() string
	ListOrganizationRepos(ctx context.Context, org string) ([]*provider.Repository, error)
	ListUserRepos(ctx context.Context, user string) ([]*provider.Repository, error)
}

ForgeProvider defines the minimal interface required from gitforge providers. This allows git-sync to work with any gitforge provider without importing the entire gitforge package directly in the core reposync package.

type GitExecutor

type GitExecutor struct {
	Client repo.Client
	Logger repo.Logger
}

GitExecutor executes plans using gzh-cli-gitforge for actual Git operations.

func (GitExecutor) Execute

func (e GitExecutor) Execute(ctx context.Context, plan Plan, opts RunOptions, sink ProgressSink, store StateStore) (ExecutionResult, error)

Execute runs the plan with concurrency, retries, and optional dry-run.

type HealthReport

type HealthReport struct {
	// Results contains per-repository health status.
	Results []RepoHealth

	// Summary provides counts by health status.
	Summary HealthSummary

	// TotalDuration is the total time for all checks.
	TotalDuration time.Duration

	// CheckedAt is when the health check was performed.
	CheckedAt time.Time
}

HealthReport aggregates health check results for multiple repositories.

type HealthStatus

type HealthStatus string

HealthStatus represents the overall health of a repository.

const (
	// HealthHealthy indicates the repository is in good state (up-to-date, clean).
	HealthHealthy HealthStatus = "healthy"

	// HealthWarning indicates the repository needs attention (diverged, can be resolved).
	HealthWarning HealthStatus = "warning"

	// HealthError indicates the repository has serious issues (conflicts, dirty + behind).
	HealthError HealthStatus = "error"

	// HealthUnreachable indicates the repository couldn't be checked (network timeout, invalid repo).
	HealthUnreachable HealthStatus = "unreachable"
)

type HealthSummary

type HealthSummary struct {
	// Healthy is count of healthy repositories.
	Healthy int

	// Warning is count of repositories with warnings.
	Warning int

	// Error is count of repositories with errors.
	Error int

	// Unreachable is count of unreachable repositories.
	Unreachable int

	// Total is total number of repositories checked.
	Total int
}

HealthSummary provides aggregate statistics.

type HistorySnapshot

type HistorySnapshot struct {
	Timestamp time.Time     `json:"timestamp"`
	Report    *HealthReport `json:"report"`
}

HistorySnapshot represents a point-in-time health check result.

type HistoryStore

type HistoryStore interface {
	Save(ctx context.Context, report *HealthReport) error
	Load(ctx context.Context, limit int) ([]HistorySnapshot, error)
	GetTrend(ctx context.Context, repoName string) ([]RepoHealth, error)
}

HistoryStore manages historical health check snapshots.

type InMemoryStateStore

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

InMemoryStateStore is a lightweight StateStore useful for dry-runs and tests.

func NewInMemoryStateStore

func NewInMemoryStateStore() *InMemoryStateStore

NewInMemoryStateStore creates a new in-memory state store.

func (*InMemoryStateStore) Load

Load returns state in memory.

func (*InMemoryStateStore) Save

func (s *InMemoryStateStore) Save(_ context.Context, state RunState) error

Save stores state in memory.

type NetworkStatus

type NetworkStatus string

NetworkStatus represents the network connectivity status.

const (
	// NetworkOK means remote fetch succeeded.
	NetworkOK NetworkStatus = "ok"

	// NetworkTimeout means remote fetch timed out.
	NetworkTimeout NetworkStatus = "timeout"

	// NetworkUnreachable means remote is unreachable (DNS, connection refused, etc).
	NetworkUnreachable NetworkStatus = "unreachable"

	// NetworkAuthFailed means authentication failed.
	NetworkAuthFailed NetworkStatus = "auth-failed"
)

type NoopExecutor

type NoopExecutor struct{}

NoopExecutor records actions and reports success without touching the filesystem or Git. It is intentionally side-effect free for dry-runs and early wiring.

func (NoopExecutor) Execute

func (NoopExecutor) Execute(ctx context.Context, plan Plan, opts RunOptions, sink ProgressSink, store StateStore) (ExecutionResult, error)

Execute implements Executor.

type NoopProgressSink

type NoopProgressSink struct{}

NoopProgressSink is a progress sink that does nothing.

func (NoopProgressSink) OnComplete

func (NoopProgressSink) OnComplete(_ ActionResult)

OnComplete implements ProgressSink.

func (NoopProgressSink) OnProgress

func (NoopProgressSink) OnProgress(_ Action, _ string, _ float64)

OnProgress implements ProgressSink.

func (NoopProgressSink) OnStart

func (NoopProgressSink) OnStart(_ Action)

OnStart implements ProgressSink.

type Orchestrator

type Orchestrator struct {
	Planner    Planner
	Executor   Executor
	StateStore StateStore
}

Orchestrator wires Planner/Executor/StateStore to implement Runner.

func NewOrchestrator

func NewOrchestrator(planner Planner, executor Executor, state StateStore) *Orchestrator

NewOrchestrator creates a Runner from injected collaborators.

func (*Orchestrator) Run

Run executes the plan/execution lifecycle.

type Plan

type Plan struct {
	Actions []Action
}

Plan is the result of planning (e.g., clone/pull/fetch/delete actions). Details will be expanded as the orchestration logic lands.

type PlanInput

type PlanInput struct {
	Repos []RepoSpec
}

PlanInput captures desired repositories and optional context (e.g., host aliases, path rules). It is intentionally minimal for now; richer fields will be added in follow-up steps.

type PlanOptions

type PlanOptions struct {
	DefaultStrategy Strategy
	CleanupOrphans  bool
	Roots           []string // optional roots to detect orphan directories
}

PlanOptions influence how a plan is produced (defaults, cleanup policies).

type PlanRequest

type PlanRequest struct {
	Input   PlanInput
	Options PlanOptions
}

PlanRequest combines the desired repositories with planning-time options.

type Planner

type Planner interface {
	Plan(ctx context.Context, req PlanRequest) (Plan, error)
}

Planner produces a Plan from desired repositories and options. Concrete implementation will live in future steps; this placeholder defines the interface surface for consumers and CLI wiring.

type PostSyncStatus

type PostSyncStatus struct {
	Branch       string
	AheadBy      int
	BehindBy     int
	IsDirty      bool
	HasConflicts bool

	// StatusErr records a failure to read the working tree. IsDirty and
	// HasConflicts are only meaningful when it is nil — their zero values are
	// indistinguishable from a clean, conflict-free repository, so a renderer
	// that ignores this field reports an unreadable repo as a healthy one.
	// Same reasoning as WorkTreeUnknown in diagnostic.go.
	StatusErr error
}

PostSyncStatus captures lightweight git status collected after a successful sync.

type ProgressSink

type ProgressSink interface {
	OnStart(action Action)
	OnProgress(action Action, message string, progress float64)
	OnComplete(result ActionResult)
}

ProgressSink receives progress events from the executor.

type RepoHealth

type RepoHealth struct {
	// Repo is the repository descriptor.
	Repo RepoSpec

	// HealthStatus is the overall health classification.
	HealthStatus HealthStatus

	// NetworkStatus indicates remote connectivity.
	NetworkStatus NetworkStatus

	// DivergenceType classifies local vs remote state.
	DivergenceType DivergenceType

	// WorkTreeStatus indicates working tree state.
	WorkTreeStatus WorkTreeStatus

	// CurrentBranch is the active branch name.
	CurrentBranch string

	// UpstreamBranch is the tracked upstream branch (e.g., "origin/main").
	UpstreamBranch string

	// AheadBy is commits ahead of upstream.
	AheadBy int

	// BehindBy is commits behind upstream.
	BehindBy int

	// ModifiedFiles is count of modified files.
	ModifiedFiles int

	// UntrackedFiles is count of untracked files.
	UntrackedFiles int

	// ConflictFiles is count of files with conflicts.
	ConflictFiles int

	// Recommendation provides actionable guidance.
	Recommendation string

	// Error contains error details if health check failed.
	Error error

	// Duration is how long the health check took.
	Duration time.Duration

	// FetchDuration is how long remote fetch took (if performed).
	FetchDuration time.Duration
}

RepoHealth represents the diagnostic result for a single repository.

type RepoSpec

type RepoSpec struct {
	Name                 string
	Description          string // optional: human-readable description of the repository
	Provider             string
	CloneURL             string
	AdditionalRemotes    map[string]string // Additional git remotes (name: url), configured after clone
	TargetPath           string
	Branch               string // optional: branch to checkout after clone/update (empty = no checkout)
	StrictBranchCheckout bool   // if true, branch checkout failure causes action failure (default: false)
	Strategy             Strategy
	Enabled              *bool // if false, repo is excluded from sync (default: true, nil = true)
	AssumePresent        bool  // if true, planner treats repo as already present (skip clone check)

	// Auth contains authentication config for this repo's clone operation.
	// If empty, system defaults are used (git credential helper, ssh-agent).
	Auth AuthConfig
}

RepoSpec describes a repository to manage.

func (RepoSpec) IsEnabled

func (r RepoSpec) IsEnabled() bool

IsEnabled returns true if the repo should be included in sync operations. Default is true (nil or true).

type RepositoryPatternFilter

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

RepositoryPatternFilter matches repositories by name or forge full name.

func NewRepositoryPatternFilter

func NewRepositoryPatternFilter(includePatterns, excludePatterns []string) (*RepositoryPatternFilter, error)

NewRepositoryPatternFilter compiles include/exclude regex patterns.

func (*RepositoryPatternFilter) Match

Match returns true when the repository passes include/exclude filtering.

type RunOptions

type RunOptions struct {
	Parallel   int
	MaxRetries int
	Resume     bool
	DryRun     bool
}

RunOptions control execution behavior.

type RunRequest

type RunRequest struct {
	PlanRequest PlanRequest
	RunOptions  RunOptions

	Progress ProgressSink
	State    StateStore
}

RunRequest contains everything required for a run.

type RunState

type RunState struct {
	Items []RunStateItem
}

RunState captures progress for resuming operations.

type RunStateItem

type RunStateItem struct {
	Repo    RepoSpec
	Status  RunStatus
	Message string
}

RunStateItem tracks per-repo status.

type RunStatus

type RunStatus string

RunStatus represents the last known state of a repository.

const (
	RunStatusPending RunStatus = "pending"
	RunStatusRunning RunStatus = "running"
	RunStatusDone    RunStatus = "done"
	RunStatusFailed  RunStatus = "failed"
)

RunStatus values track a repository's execution state across a sync run.

type Runner

type Runner interface {
	Run(ctx context.Context, req RunRequest) (ExecutionResult, error)
}

Runner encapsulates a full plan + execute lifecycle.

type StateStore

type StateStore interface {
	Save(ctx context.Context, state RunState) error
	Load(ctx context.Context) (RunState, error)
}

StateStore persists run state for resume/audit.

type StaticPlanner

type StaticPlanner struct{}

StaticPlanner produces a trivial plan that maps every RepoSpec to a single action (clone or update) using the provided defaults. It is primarily useful for early wiring, tests, and dry-runs.

func (StaticPlanner) Describe

func (StaticPlanner) Describe(req PlanRequest) string

Describe returns a short description of what would be planned; useful for logging in CLI integrations.

func (StaticPlanner) Plan

Plan implements Planner.

type Strategy

type Strategy string

Strategy defines how updates are performed.

const (
	StrategyReset  Strategy = "reset"
	StrategyPull   Strategy = "pull"
	StrategyFetch  Strategy = "fetch"
	StrategyRebase Strategy = "rebase"
	StrategyClone  Strategy = "clone"
)

Strategy values control how an existing repository is brought up to date.

func ParseStrategy

func ParseStrategy(value string) (Strategy, error)

ParseStrategy converts a user-supplied string into a Strategy enum.

func (Strategy) String

func (s Strategy) String() string

String implements fmt.Stringer.

type WorkTreeStatus

type WorkTreeStatus string

WorkTreeStatus represents the working tree state.

const (
	// WorkTreeClean means no uncommitted changes.
	WorkTreeClean WorkTreeStatus = "clean"

	// WorkTreeDirty means there are uncommitted changes.
	WorkTreeDirty WorkTreeStatus = "dirty"

	// WorkTreeConflict means there are merge/rebase conflicts.
	WorkTreeConflict WorkTreeStatus = "conflict"

	// WorkTreeRebaseInProgress means a rebase is in progress.
	WorkTreeRebaseInProgress WorkTreeStatus = "rebase-in-progress"

	// WorkTreeMergeInProgress means a merge is in progress.
	WorkTreeMergeInProgress WorkTreeStatus = "merge-in-progress"

	// WorkTreeUnknown means the working tree could not be read (for example a
	// corrupt .git/index). It is deliberately distinct from WorkTreeClean: the
	// question was asked and went unanswered, which is not the same as an answer
	// of "no changes". The empty zero value still means "not checked", which is
	// what CheckWorkTree=false leaves behind.
	WorkTreeUnknown WorkTreeStatus = "unknown"
)

Jump to

Keyboard shortcuts

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