Corral API Documentation

Reference manual covering all modules & packages

Package github

import "github.com/sebastienrousseau/corral/internal/github"

Package github provides functionality to interact with the GitHub API.

Functions

func Token

func Token(ctx context.Context, authMode AuthMode) string

Token resolves a GitHub token for the given auth mode, returning an empty string when none can be obtained. It lets the git package authenticate HTTPS clones and pulls of private repositories with the same credential as the API.

func envToken

func envToken() string

func isRetryableNetworkError

func isRetryableNetworkError(err error) bool

func matchesFilters

func matchesFilters(repo Repo, includeLang, excludeLang map[string]struct{}, opts FetchOptions) bool

func orgTypeForVisibility

func orgTypeForVisibility(v string) string

func rateLimitResetDuration

func rateLimitResetDuration(resp *http.Response) (time.Duration, bool)

func resolveToken

func resolveToken(ctx context.Context, authMode AuthMode) (string, error)

func retryAfterDuration

func retryAfterDuration(resp *http.Response) (time.Duration, bool)

func shouldRetry

func shouldRetry(resp *http.Response, err error, attempt, maxRetries int) (bool, time.Duration)

func toLookupSet

func toLookupSet(values []string) map[string]struct{}

Types

type AuthMode

type AuthMode string

AuthMode controls how GitHub API credentials are resolved.

type FetchOptions

type FetchOptions struct {
	// Limit caps the number of repositories returned; 0 means no limit.
	Limit	int
	// Visibility filters repositories by visibility ("all", "public", or "private").
	Visibility	string
	// IncludeForks includes forked repositories when true.
	IncludeForks	bool
	// IncludeArchived includes archived repositories when true.
	IncludeArchived	bool
	// IncludeLanguages, when non-empty, keeps only repositories matching these languages.
	IncludeLanguages	[]string
	// ExcludeLanguages removes repositories matching these languages.
	ExcludeLanguages	[]string
	// AuthMode selects how the GitHub token is resolved.
	AuthMode	AuthMode
	// Type filters repositories by specific category (e.g. "sources", "forks", "archived", "mirrors", etc.).
	Type	string
	// Sort specifies how the returned repositories list should be ordered.
	Sort	string
	// RetryMax is the maximum number of retry attempts for transient failures.
	RetryMax	int
	// RetryMinBackoff is the minimum delay between retry attempts.
	RetryMinBackoff	time.Duration
	// RetryMaxBackoff is the maximum delay between retry attempts.
	RetryMaxBackoff	time.Duration
}

FetchOptions configures repository fetch behavior.

type Repo

type Repo struct {
	// Name is the repository name (without the owner prefix).
	Name	string
	// Language is the primary programming language, or "Other" when unknown.
	Language	string
	// Visibility is the normalized visibility, either "Public" or "Private".
	Visibility	string
	// DefaultBranch is the repository's default branch name.
	DefaultBranch	string
	// CloneURL is the HTTPS clone URL for the repository.
	CloneURL	string
	// SSHURL is the SSH clone URL for the repository.
	SSHURL	string
	// Fork reports whether the repository is a fork.
	Fork	bool
	// Archived reports whether the repository is archived.
	Archived	bool
	// PushedAt is the timestamp of the last push to any branch. The engine
	// compares this against the cached value in <repo>/.corral-state.json to
	// skip a `git pull` when nothing has changed upstream.
	PushedAt	time.Time
	// Stars reports the stargazers count for the repository.
	Stars	int
	// IsTemplate reports whether the repository is a template.
	IsTemplate	bool
	// IsMirror reports whether the repository is a mirror.
	IsMirror	bool
	// CanBeSponsored reports whether the repository has sponsorships enabled.
	CanBeSponsored	bool
}

Repo represents a simplified repository structure returned by the GitHub API.

type retryTransport

type retryTransport struct {
	base		http.RoundTripper
	maxRetries	int
	minBackoff	time.Duration
	maxBackoff	time.Duration
}

Methods

func RoundTrip

func (t *retryTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper, retrying transient failures and rate-limit responses with backoff until the request succeeds, becomes non-retryable, the retry budget is exhausted, or the context is cancelled.

func backoff

func (t *retryTransport) backoff(attempt int) time.Duration

Package git

import "github.com/sebastienrousseau/corral/internal/git"

Package git provides helper functions to execute common Git commands by wrapping the system's git binary using os/exec.

Functions

func Clone

func Clone(ctx context.Context, url, targetDir string, opts CloneOptions) error

Clone executes a git clone command for the given URL into the target directory.

func CurrentBranch

func CurrentBranch(targetDir string) (string, error)

CurrentBranch retrieves the name of the currently checked-out branch.

func Pull

func Pull(ctx context.Context, targetDir string, opts PullOptions) error

Pull executes a `git pull --rebase --autostash` in the target directory. Signature verification (merge.verifySignatures / rebase.verifySignatures) and commit signing (commit.gpgsign) are explicitly disabled for this invocation so an unattended sync never aborts on unsigned commits or blocks on a GPG/SSH passphrase prompt for users who sign commits globally. When opts.RecurseSubmodules is true: - if opts.IgnoreSubmoduleFailures is false, --recurse-submodules is appended to the pull so failures abort the whole operation (existing pre-v0.0.7 behaviour); - if opts.IgnoreSubmoduleFailures is true, the pull runs without --recurse-submodules and submodule updates are attempted in a separate `git submodule update --init --recursive` step whose error is logged but not returned.

func RemoteOrigin

func RemoteOrigin(targetDir string) (string, error)

RemoteOrigin retrieves the remote origin URL of the target directory by invoking `git remote get-url origin`. Prefer RemoteOriginFromConfig on hot paths (e.g. orphan detection over hundreds of clones) to avoid the per-call cost of spawning a subprocess.

func RemoteOriginFromConfig

func RemoteOriginFromConfig(targetDir string) (string, error)

RemoteOriginFromConfig parses the `url =` entry under [remote "origin"] directly from <targetDir>/.git/config, avoiding the ~5-15ms per-call cost of spawning `git remote get-url origin`. Returns the wrapped os.ErrNotExist when the config file is absent, and a clear error when the section or key is missing. Tolerates blank lines, `#` / `;` comments, indented entries, and CRLF line endings.

func ResolveGitBinary

func ResolveGitBinary() error

ResolveGitBinary looks up the absolute path to the git executable on PATH and caches it for the rest of the process. Returns a clear error when git is not installed.

func authEnv

func authEnv() []string

authEnv returns the environment variables that inject an Authorization header for github.com HTTPS requests, or nil when no token is available. The header is scoped to https://github.com/, so it is harmless for SSH remotes.

func nonInteractiveEnv

func nonInteractiveEnv() []string

nonInteractiveEnv returns the environment variables that force git into strict non-interactive mode. They are applied unconditionally to every git invocation so unattended runs (cron, CI) never hang on a missing credential, askpass helper, or GPG pinentry.

func updateSubmodules

func updateSubmodules(ctx context.Context, targetDir string) error

updateSubmodules runs `git submodule update --init --recursive` in targetDir as a separate subprocess. Exposed indirectly via Pull's IgnoreSubmoduleFailures branch.

func withGitEnv

func withGitEnv(cmd *exec.Cmd)

withGitEnv attaches the credentials header (when available) plus the non-interactive env vars to cmd, replacing any prior cmd.Env. It always sets cmd.Env so the non-interactive guards apply even on anonymous clones.

Types

type CloneOptions

type CloneOptions struct {
	// RecurseSubmodules, when true, clones submodules recursively by adding
	// the --recurse-submodules flag.
	RecurseSubmodules	bool
	// SingleBranch, when true, clones only the history of the default branch
	// by adding the --single-branch flag.
	SingleBranch	bool
	// Blobless, when true, performs a blobless partial clone by adding the
	// --filter=blob:none flag, deferring blob downloads until needed.
	Blobless	bool
	// Depth, when greater than zero, creates a shallow clone truncated to the
	// given number of commits by adding the --depth flag.
	Depth	int
}

CloneOptions configures optional clone-time performance and layout flags.

type PullOptions

type PullOptions struct {
	// RecurseSubmodules, when true, also updates submodules after the pull.
	// When IgnoreSubmoduleFailures is set, the submodule update runs as a
	// separate step so its failure does not abort the parent pull.
	RecurseSubmodules	bool
	// IgnoreSubmoduleFailures, when true, logs (but does not propagate)
	// errors from the post-pull submodule update step. Useful when a
	// submodule has been deleted upstream or access has been revoked but
	// the parent repository's history should still update.
	IgnoreSubmoduleFailures	bool
}

PullOptions configures a `git pull` invocation.

Package engine

import "github.com/sebastienrousseau/corral/internal/engine"

Package engine provides the core concurrency and execution logic for Corral.

Functions

func Run

func Run(ctx context.Context, opts RunOptions)

Run executes the core Corral workflow, orchestrating GitHub API fetches, legacy layout migrations, concurrent Git operations, and orphaned repository detection.

func cleanupEmptyFolders

func cleanupEmptyFolders(baseDir string, repos []github.Repo)

cleanupEmptyFolders removes the now-empty legacy top-level language directories left behind by migrateLegacy. It only targets directories whose names match a repository language, and os.Remove deletes a directory only when it is empty, so unrelated entries under baseDir (e.g. .claude, other projects) are never touched.

func detectOrphans

func detectOrphans(owner, baseDir string, repos []github.Repo)

func evaluateLayout

func evaluateLayout(layoutTpl string, repo github.Repo, owner string) (string, error)

func migrateLegacy

func migrateLegacy(baseDir string, repos []github.Repo)

func normalizeLanguage

func normalizeLanguage(lang string) string

func normalizeLanguageDirCase

func normalizeLanguageDirCase(baseDir string, repos []github.Repo)

normalizeLanguageDirCase renames any case-variant language subdirectory under each visibility directory to its lowercase form. On case-insensitive filesystems (APFS, HFS+, NTFS) a direct os.Rename("JavaScript","javascript") is a silent no-op, so the rename is performed via a temporary name. Only directories whose lowercased name matches a normalized language from the fetched repos are touched, so unrelated entries (e.g. "Configurations") are left alone.

func repoNameFromURL

func repoNameFromURL(url string) string

repoNameFromURL extracts the repository name from a git remote URL, stripping any trailing ".git" suffix. It returns an empty string when no segment exists.

func stampCloneState

func stampCloneState(targetDir string, repo github.Repo)

stampCloneState records the upstream pushed_at in the per-clone state sidecar so the next run can skip a no-op git pull. Best-effort: a write failure is logged but does not fail the operation, since the sidecar is purely an optimization (a missing or stale file falls through to the pre-sidecar behaviour of always pulling).

func toLogMsg

func toLogMsg(msg RepoResult) tui.LogMsg

func writeCloneState

func writeCloneState(repoDir string, s cloneState) error

writeCloneState serialises s to repoDir/.corral-state.json atomically by writing to a sibling temp file and renaming it into place. A crash mid-write therefore leaves the previous valid state on disk rather than a half-written file that would fail to parse on the next run.

Types

type Job

type Job struct {
	// Repo is the GitHub repository to be processed.
	Repo	github.Repo
	// Target is the destination directory for the repository under the new layout.
	Target	string
	// Legacy is the directory where the repository may exist under the old layout.
	Legacy	string
}

Job encapsulates a repository to be processed along with its target directories.

type OutputFormat

type OutputFormat string

OutputFormat controls how operation results are emitted.

type RepoResult

type RepoResult struct {
	// RepoName is the name of the processed repository.
	RepoName	string	`json:"repo"`
	// Action is the outcome verb, such as CLONE, SYNC, SKIP, ERROR, or DRY-RUN.
	Action	string	`json:"action"`
	// Message is a human-readable description of the outcome.
	Message	string	`json:"message"`
	// Target is the destination directory for the repository.
	Target	string	`json:"target"`
	// Visibility is the repository visibility (e.g. Public or Private).
	Visibility	string	`json:"visibility"`
	// Language is the normalized primary language directory name.
	Language	string	`json:"language"`
	// DryRun indicates whether the run was performed in dry-run mode.
	DryRun	bool	`json:"dry_run"`
	// Protocol is the clone transport used (https or ssh).
	Protocol	string	`json:"protocol"`
	// ClonedURL is the URL used for cloning, if a clone was attempted.
	ClonedURL	string	`json:"clone_url,omitempty"`
	// SyncAttempt indicates whether a sync (pull) was attempted.
	SyncAttempt	bool	`json:"sync_attempt"`
}

RepoResult represents the final status of processing a repository.

type RunOptions

type RunOptions struct {
	// Owner is the GitHub user or organization whose repositories are processed.
	Owner	string
	// BaseDir is the root directory under which repositories are laid out.
	BaseDir	string
	// Concurrency is the number of worker goroutines processing repositories; must be >= 1.
	Concurrency	int
	// DryRun, when true, reports intended actions without performing clone or pull operations.
	DryRun	bool
	// Orphans, when true, enables detection of local repositories no longer present upstream.
	Orphans	bool
	// Protocol selects the clone transport and must be either "https" or "ssh".
	Protocol	string
	// DoSync, when true, pulls updates into existing repositories.
	DoSync	bool
	// Output selects the result emission format (text, json, or ndjson).
	Output	OutputFormat
	// Interactive, when true, displays an interactive selector before processing.
	Interactive	bool

	// Fetch holds the options passed to the GitHub repository listing call.
	Fetch	github.FetchOptions
	// Clone holds the options passed to each Git clone operation.
	Clone	git.CloneOptions
	// Sync controls when an already-cloned repository is actually pulled.
	Sync	SyncOptions
	// Layout specifies the templated path structure for repositories.
	Layout	string
	// Version is the build version of Corral.
	Version	string
}

RunOptions contains all execution controls for a run.

type Summary

type Summary struct {
	// Total is the number of repositories scheduled for processing.
	Total	int	`json:"total"`
	// Cloned is the number of repositories successfully cloned.
	Cloned	int	`json:"cloned"`
	// Synced is the number of repositories successfully synced.
	Synced	int	`json:"synced"`
	// Skipped is the number of repositories skipped.
	Skipped	int	`json:"skipped"`
	// Failed is the number of repositories that failed to process.
	Failed	int	`json:"failed"`
}

Summary tracks aggregate run outcomes.

Methods

func add

func (s *Summary) add(msg RepoResult)

type SyncOptions

type SyncOptions struct {
	// Force, when true, runs `git pull` even when the cached state shows
	// the upstream pushed_at is unchanged.
	Force	bool
	// IgnoreSubmoduleFailures, when true with Clone.RecurseSubmodules, allows
	// the parent repository to update even when a submodule sync fails (e.g.
	// the submodule repo was deleted upstream or its access revoked). The
	// failure is logged as a WARN but not propagated.
	IgnoreSubmoduleFailures	bool
}

SyncOptions configures the engine's per-repo sync decision. Kept separate from git.CloneOptions because forcing a sync is a corral-level policy choice, not a clone-time git flag.

type cloneState

type cloneState struct {
	// LastSyncedPushedAt is the upstream PushedAt value at the time of the
	// last successful clone or sync.
	LastSyncedPushedAt	time.Time	`json:"last_synced_pushed_at"`
	// LastSyncedAt is the local wall-clock time of the last sync attempt
	// that touched the working tree (clone or successful pull). Used for
	// human display only — never for sync-skip decisions.
	LastSyncedAt	time.Time	`json:"last_synced_at"`
}

cloneState is the JSON shape of <repo>/.corral-state.json. New fields must be added with omitempty so older sidecars continue to round-trip.

Package tui

import "github.com/sebastienrousseau/corral/internal/tui"

Package tui provides a Bubble Tea terminal user interface for Corral.

Functions

func GetStyledLogo

func GetStyledLogo() string

GetStyledLogo returns the colored ASCII logo art as a string.

func NewModel

func NewModel(total int) tea.Model

NewModel initializes a new TUI model with the expected total number of items.

func RunSelector

func RunSelector(ctx context.Context, owner string, fetchOpts github.FetchOptions, fetchFn FetchFunc) ([]github.Repo, bool, error)

RunSelector launches the interactive terminal selector program to choose repositories.

Types

type FetchFunc

type FetchFunc func() ([]github.Repo, error)

FetchFunc represents the function signature used by the selector to fetch repositories.

type LogMsg

type LogMsg struct {
	// RepoName is the name of the repository the entry refers to.
	RepoName	string
	// Action is the operation performed (for example CLONE, SYNC, SKIP, ERROR).
	Action	string
	// Message is a human-readable description of the outcome.
	Message	string
}

LogMsg represents a log entry to be displayed in the TUI.

type fetchedReposMsg

type fetchedReposMsg struct {
	repos	[]github.Repo
	err	error
}

type model

type model struct {
	total		int
	done		int
	logs		[]LogMsg
	prog		progress.Model
	quitting	bool

	cloned		int
	synced		int
	failed		int
	existing	int
}

model represents the state of the Bubble Tea application.

Methods

func Init

func (m model) Init() tea.Cmd

Init initializes the Bubble Tea application (no-op).

func Update

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update handles incoming Bubble Tea messages, advancing progress and stats as repository results arrive and quitting when the run completes or is cancelled.

func View

func (m model) View() string

View renders the current progress bar, recent log lines, and, once finished, the final summary of the run.

func processLogMsg

func (m *model) processLogMsg(msg LogMsg)

type selectorModel

type selectorModel struct {
	repos		[]github.Repo
	filteredRepos	[]github.Repo
	filter		string
	selected	map[string]bool	// key is repo.Name
	table		table.Model
	spinner		spinner.Model
	loading		bool
	loadingErr	error
	confirmed	bool
	quitting	bool
	fetchFn		FetchFunc

	showHelp	bool
	cmdErr		string
}

Methods

func Init

func (m *selectorModel) Init() tea.Cmd

func Update

func (m *selectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func View

func (m *selectorModel) View() string

func applyFilter

func (m *selectorModel) applyFilter()

func executeSlashCommand

func (m *selectorModel) executeSlashCommand(cmdStr string) tea.Cmd

func renderCustomTable

func (m *selectorModel) renderCustomTable() string

func renderFooter

func (m *selectorModel) renderFooter() string

func renderHelpPanel

func (m *selectorModel) renderHelpPanel() string

func updateTableRows

func (m *selectorModel) updateTableRows()