Documentation
¶
Overview ¶
Package config provides configuration management for gz-git CLI.
Overview ¶
This package implements a flexible configuration system with profiles, global defaults, and project-specific overrides. It follows a 5-layer precedence system to resolve configuration values.
Precedence Order (Highest to Lowest) ¶
- Command flags (e.g., --provider gitlab)
- Project config (.gz-git.yaml in current dir or parent)
- Active profile (~/.config/gz-git/profiles/{active}.yaml)
- Global config (~/.config/gz-git/config.yaml)
- Built-in defaults
File Locations ¶
Global configuration directory: ~/.config/gz-git/
~/.config/gz-git/
├── config.yaml # Global config
├── profiles/
│ ├── default.yaml # Default profile
│ ├── work.yaml # User profiles
│ └── personal.yaml
└── state/
└── active-profile.txt # Currently active profile
Project configuration file: .gz-git.yaml
This file is auto-detected by walking up the directory tree from the current working directory to the home directory.
Usage Example ¶
Basic usage:
// Create a manager
mgr, err := config.NewManager()
if err != nil {
log.Fatal(err)
}
// Initialize config directory
if err := mgr.Initialize(); err != nil {
log.Fatal(err)
}
// Create a profile
profile := &config.Profile{
Name: "work",
Provider: "gitlab",
BaseURL: "https://gitlab.company.com",
Token: "${WORK_GITLAB_TOKEN}",
CloneProto: "ssh",
SSHPort: 2224,
Parallel: 10,
}
if err := mgr.CreateProfile(profile); err != nil {
log.Fatal(err)
}
// Set active profile
if err := mgr.SetActiveProfile("work"); err != nil {
log.Fatal(err)
}
// Load configuration with precedence
loader, err := config.NewLoader()
if err != nil {
log.Fatal(err)
}
if err := loader.Load(); err != nil {
log.Fatal(err)
}
// Resolve effective config (merge flags if any)
flags := map[string]interface{}{
"parallel": 20, // Override profile value
}
effective, err := loader.ResolveConfig(flags)
if err != nil {
log.Fatal(err)
}
// Use effective config
fmt.Printf("Provider: %s (from %s)\n", effective.Provider, effective.GetSource("provider"))
fmt.Printf("Parallel: %d (from %s)\n", effective.Parallel, effective.GetSource("parallel"))
Environment Variables ¶
Configuration files support environment variable expansion using ${VAR_NAME} syntax:
token: ${GITLAB_TOKEN}
baseURL: ${GITLAB_BASE_URL}
This is the recommended way to store sensitive values like API tokens.
Security ¶
- Profile files are created with 0600 permissions (user read/write only) - Config directories are created with 0700 permissions (user access only) - Environment variable expansion is safe (no shell command execution) - Tokens should use ${ENV_VAR} syntax, not plain text
Validation ¶
All configuration structures are validated on load: - Required fields are checked - Enum values (provider, clone protocol, etc.) are validated - Port numbers and counts are range-checked - Profile names must be alphanumeric with dash/underscore
Validation errors are returned with clear messages indicating the problem.
Package config provides configuration management for gz-git, including profile-based settings, global defaults, and project-specific overrides.
Configuration follows a 5-layer precedence system (highest to lowest):
- Command flags (e.g., --provider gitlab)
- Project config (.gz-git.yaml in current dir or parent)
- Active profile (~/.config/gz-git/profiles/{active}.yaml)
- Global config (~/.config/gz-git/config.yaml)
- Built-in defaults
Index ¶
- Constants
- Variables
- func CanonicalReadiness(r Readiness) (string, error)
- func ClearWorkspaceAccessMarker(ctx context.Context, repoPath string) error
- func CreateConfigSymlink(srcPath, targetDir, parentConfigDir string) error
- func CreateConfigSymlinkForce(srcPath, targetDir, parentConfigDir string) error
- func DetectAllConfigFiles(dir string) []string
- func DetectConfigFile(dir string) (string, error)
- func FindConfigRecursive(startPath, configFile string) (string, error)
- func FindProjectConfig() (string, error)
- func GetAllProfiles(config *Config) map[string]*Profile
- func GetAllWorkspaces(config *Config) []struct{ ... }
- func GetConfigWorkspaces(config *Config) map[string]*Workspace
- func GetForgeWorkspaces(config *Config) map[string]*Workspace
- func GetGitWorkspaces(config *Config) map[string]*Workspace
- func GetProfileSource(config *Config, name string) string
- func GetSymlinkTarget(linkPath string) (string, error)
- func HasInlineProfile(config *Config, name string) bool
- func IsConfigSymlink(configPath string) (bool, error)
- func IsValidBaseURL(s string) bool
- func IsValidCloneProto(proto string) bool
- func IsValidProfileName(name string) bool
- func IsValidProvider(provider string) bool
- func IsValidSyncStrategy(strategy string) bool
- func LoadWorkspaces(path string, config *Config, mode DiscoveryMode) error
- func MatchTaskPattern(name, pattern string) bool
- func MatchesAnyTaskPattern(name string, patterns []string) bool
- func NormalizeIntegrationBranchName(raw string, remotes []string) string
- func NormalizeName(raw string, remotes []string) string
- func NormalizeProvider(provider string) string
- func ParsePrepareProfileDocument(data []byte, isJSON bool) (profile string, present bool, err error)
- func ReadinessDigest(r Readiness) (string, error)
- func RecordWorkspaceReadOnly(ctx context.Context, repoPath string) error
- func RejectMultiDocumentYAML(data []byte) error
- func ResolveDeclaredIntegrationBranch(ctx context.Context, repoPath string) (string, error)
- func ResolveTokenFromEnv(provider string) (token, source string)
- func SanitizeToken(s string) string
- func SetTokenStore(s TokenStore)
- func SplitIntegrationRemoteBranch(raw string, remotes []string) (remote, branch string, ok bool)
- func SplitRemoteBranch(raw string, remotes []string) (remote, branch string, ok bool)
- func UpstreamTargetsIntegration(branch, upstream string, resolution Resolution, remotes []string) bool
- func UpstreamTargetsIntegrationBranch(branch, upstream string, resolution IntegrationBranchResolution, ...) bool
- func ValidatePrepareProfile(profile string) error
- func ValidateReadiness(r Readiness) error
- type AuditConfig
- type BranchConfig
- type BranchList
- type ChildConfigFormat
- type ChildConfigMode
- type CloneDefaults
- type Config
- func (c *Config) GetCloneProto() string
- func (c *Config) GetCompactOutput() bool
- func (c *Config) GetExcludePatterns() []string
- func (c *Config) GetFormat() string
- func (c *Config) GetIncludePatterns() []string
- func (c *Config) GetMaxRetries() int
- func (c *Config) GetParallel() int
- func (c *Config) GetSSHKeyContent() string
- func (c *Config) GetSSHKeyPath() string
- func (c *Config) GetSSHPort() int
- func (c *Config) GetScanDepth() int
- func (c *Config) GetScanExcludePatterns() []string
- func (c *Config) GetSyncStrategy() string
- func (c *Config) GetTimeout() string
- type ConfigFileInfo
- type ConfigKind
- type ConfigLoader
- type ConfigMeta
- type ConfigSource
- type ControllerConfig
- type DefaultsConfig
- type DiscoveryConfig
- type DiscoveryMode
- type EffectiveConfig
- type Environment
- type Facts
- type FetchConfig
- type FilterDefaults
- type FlexBranch
- type ForgeSource
- type GlobalConfig
- type Hooks
- type IntegrationBranchFacts
- type IntegrationBranchResolution
- type IntegrationControl
- type IntegrationParticipationAction
- type IntegrationParticipationState
- type KeyringTokenStore
- type Manager
- func (m *Manager) CreateProfile(profile *Profile) error
- func (m *Manager) DeleteProfile(name string) error
- func (m *Manager) FindNearestConfig(configFile string) (string, error)
- func (m *Manager) GetActiveProfile() (string, error)
- func (m *Manager) Initialize() error
- func (m *Manager) ListProfiles() ([]string, error)
- func (m *Manager) LoadConfigRecursiveFromPath(path, configFile string) (*Config, error)
- func (m *Manager) LoadGlobalConfig() (*GlobalConfig, error)
- func (m *Manager) LoadProfile(name string) (*Profile, error)
- func (m *Manager) LoadProjectConfig() (*ProjectConfig, error)
- func (m *Manager) ProfileExists(name string) bool
- func (m *Manager) SaveConfig(path, configFile string, config *Config) error
- func (m *Manager) SaveGlobalConfig(config *GlobalConfig) error
- func (m *Manager) SaveProfile(profile *Profile) error
- func (m *Manager) SaveProjectConfig(config *ProjectConfig) error
- func (m *Manager) SetActiveProfile(name string) error
- type MemoryTokenStore
- type Metadata
- type OutputDefaults
- type Paths
- func (p *Paths) EnsureDirectories() error
- func (p *Paths) Exists() bool
- func (p *Paths) GetActiveProfile() (string, error)
- func (p *Paths) ListProfiles() ([]string, error)
- func (p *Paths) ProfileExists(name string) bool
- func (p *Paths) ProfilePath(name string) string
- func (p *Paths) SetActiveProfile(name string) error
- type Profile
- type ProjectConfig
- type ProjectMetadata
- type PullConfig
- type PushConfig
- type Readiness
- type Resolution
- type ScanDefaults
- type SelfSyncConfig
- type SyncConfig
- type SyncDefaults
- type TaskPatternDecl
- type TokenStore
- type Validator
- func (v *Validator) ExpandEnvVarsInConfig(c *Config) error
- func (v *Validator) ExpandEnvVarsInGlobalConfig(g *GlobalConfig) error
- func (v *Validator) ExpandEnvVarsInProfile(p *Profile) error
- func (v *Validator) ExpandEnvVarsInWorkspace(ws *Workspace) error
- func (v *Validator) ValidateChildConfigMode(mode ChildConfigMode) error
- func (v *Validator) ValidateConfig(c *Config) error
- func (v *Validator) ValidateConfigLink(path string) error
- func (v *Validator) ValidateDiscoveryConfig(d *DiscoveryConfig) error
- func (v *Validator) ValidateForgeSource(s *ForgeSource) error
- func (v *Validator) ValidateGlobalConfig(g *GlobalConfig) error
- func (v *Validator) ValidateHooks(hooks *Hooks) error
- func (v *Validator) ValidateParentPath(path string) error
- func (v *Validator) ValidateProfile(p *Profile) error
- func (v *Validator) ValidateProjectConfig(p *ProjectConfig) error
- func (v *Validator) ValidateSyncConfig(s *SyncConfig) error
- func (v *Validator) ValidateWorkspace(ws *Workspace, name string) error
- type Workspace
- type WorkspaceAccess
- type WorkspaceType
Constants ¶
const ( // IntegrationBranchSourceNone means no integration branch participates. IntegrationBranchSourceNone = "none" // IntegrationBranchSourceHeuristic means the remote-HEAD fallback won. IntegrationBranchSourceHeuristic = "heuristic" // IntegrationBranchSourceConfigPrefix prefixes a declared candidate source. IntegrationBranchSourceConfigPrefix = "config[" )
const ( // ConfigDirName is the config directory name under XDG_CONFIG_HOME. ConfigDirName = "gz-git" // ProfilesDirName is the subdirectory for profile files. ProfilesDirName = "profiles" // StateDirName is the subdirectory for runtime state. StateDirName = "state" // GlobalConfigFileName is the base name for the main config file. GlobalConfigFileName = "config" // ProjectConfigFileName is the base name for the project-specific config file. ProjectConfigFileName = ".gz-git" // ActiveProfileFileName stores the active profile name. ActiveProfileFileName = "active-profile.txt" // DefaultProfileName is the default profile. DefaultProfileName = "default" )
const ( // PrepareProfileFamilybookEntV1 prepares Ent generated code. PrepareProfileFamilybookEntV1 = "familybook-ent-v1" // PrepareProfileFlowTaskchainLocalSubprojectsV1 prepares local taskchain subprojects. PrepareProfileFlowTaskchainLocalSubprojectsV1 = "flow-taskchain-local-subprojects-v1" )
const AutoGeneratedMarker = "# AUTO-GENERATED"
AutoGeneratedMarker is the comment marker that identifies auto-generated configs.
const DefaultConfigFileName = ".gz-git.yaml"
DefaultConfigFileName is the default name for config files.
const ExampleConfig = `` /* 8211-byte string literal not displayed */
ExampleConfig is a documented example configuration showing all available options.
const ( // MaxConfigDepth limits the recursion depth for config loading. // This prevents stack overflow from deeply nested or malformed configs. MaxConfigDepth = 10 )
Variables ¶
ErrKeyringUnavailable is returned when the OS keychain cannot be used.
Functions ¶
func CanonicalReadiness ¶
CanonicalReadiness is a stable, strict representation used to compare the target declaration to the source declaration before executing a runner.
func ClearWorkspaceAccessMarker ¶
ClearWorkspaceAccessMarker removes a previously persisted read-only contract after the owning workspace has successfully synced as read-write.
func CreateConfigSymlink ¶
CreateConfigSymlink creates a symlink from the workspace config location to a source config file.
Parameters:
- srcPath: The source config file path. Supports:
- Absolute paths: /path/to/config.yaml
- Home-relative: ~/configs/myconfig.yaml
- Relative to parent config: ./configs/myconfig.yaml
- targetDir: The workspace directory where the symlink will be created as .gz-git.yaml
- parentConfigDir: The directory containing the parent config (for resolving relative paths)
The symlink is created at {targetDir}/.gz-git.yaml → {resolved srcPath}
If a file already exists at the symlink location:
- If it's a symlink: it will be removed and recreated
- If it's a regular file: returns an error (use force=true to override)
func CreateConfigSymlinkForce ¶
CreateConfigSymlinkForce creates a symlink, removing any existing file (not just symlinks). Use with caution - this will delete existing config files.
func DetectAllConfigFiles ¶
DetectAllConfigFiles finds all config files in a directory. Returns list of found config file paths.
func DetectConfigFile ¶
DetectConfigFile searches upward from the given directory to $HOME for a .gz-git config file and returns the full path to the nearest one. Searching upward (rather than the given directory alone) lets commands run from a workspace subdirectory resolve the workspace's existing config instead of re-initializing a second one in the wrong place. Returns an error when no config exists between dir and $HOME.
func FindConfigRecursive ¶
FindConfigRecursive walks up from startPath to $HOME to find the nearest directory containing configFile. It delegates to the shared findConfigUpward core, so the $HOME ceiling is identical to FindProjectConfig/DetectConfigFile; the configFile parameter lets callers probe a non-default filename.
Parameters:
- startPath: The directory to start searching from
- configFile: The config file name to search for (e.g., ".gz-git.yaml")
Returns:
- string: The directory containing the config file
- error: Error if config file not found
Example:
// Find nearest .gz-git.yaml
configDir, err := FindConfigRecursive("/home/user/mydevbox/project", ".gz-git.yaml")
// Returns: "/home/user/mydevbox" if .gz-git.yaml exists there
func FindProjectConfig ¶
FindProjectConfig walks up from the current working directory to $HOME and returns the path to the nearest .gz-git config file, or "" if none exists.
func GetAllProfiles ¶
GetAllProfiles returns all inline profiles from the config.
func GetAllWorkspaces ¶
GetAllWorkspaces returns all workspaces as a slice with their names.
func GetConfigWorkspaces ¶
GetConfigWorkspaces returns workspaces with type=config that have sync.recursive enabled. These are workspaces whose child config should be loaded and synced by the parent.
func GetForgeWorkspaces ¶
GetForgeWorkspaces returns only workspaces that sync from a forge.
func GetGitWorkspaces ¶
GetGitWorkspaces returns only workspaces that are single git repositories (type=git with URL). These are workspaces with explicit URL that should be cloned/synced directly.
func GetProfileSource ¶
GetProfileSource returns the source location of a profile. Useful for debugging config precedence. Returns empty string if profile not found.
func GetSymlinkTarget ¶
GetSymlinkTarget returns the target of a symlink, or empty string if not a symlink.
func HasInlineProfile ¶
HasInlineProfile checks if a profile exists in the inline profiles.
func IsConfigSymlink ¶
IsConfigSymlink checks if the config file at the given path is a symlink.
func IsValidBaseURL ¶
IsValidBaseURL reports whether s is empty (optional) or a parseable http(s) URL with a host. Used to validate forge base URLs (GitHub Enterprise / GitLab / Gitea). Prefix-only values such as "https://" are rejected (fail-closed).
func IsValidCloneProto ¶
IsValidCloneProto checks if a clone protocol is valid.
func IsValidProfileName ¶
IsValidProfileName checks if a profile name is valid.
func IsValidProvider ¶
IsValidProvider checks if a provider name is valid.
func IsValidSyncStrategy ¶
IsValidSyncStrategy checks if a sync strategy is valid.
func LoadWorkspaces ¶
func LoadWorkspaces(path string, config *Config, mode DiscoveryMode) error
LoadWorkspaces loads workspaces based on discovery mode. This function is called after LoadConfigRecursive to optionally auto-discover workspaces.
Parameters:
- path: The directory to search for workspaces
- config: The config to append discovered workspaces to
- mode: The discovery mode (explicit, auto, hybrid)
Returns:
- error: Any error encountered during discovery
Example:
config, _ := LoadConfigRecursive("/home/user/mydevbox", ".gz-git.yaml")
err := LoadWorkspaces("/home/user/mydevbox", config, HybridMode)
func MatchTaskPattern ¶
MatchTaskPattern reports whether name falls inside a declared task-branch namespace. It delegates to pkg/repository, the single source of truth (see repository.MatchTaskPattern for why ownership sits there).
func MatchesAnyTaskPattern ¶
MatchesAnyTaskPattern reports whether name matches any declared pattern.
func NormalizeIntegrationBranchName ¶
NormalizeIntegrationBranchName strips only a registered remote prefix.
func NormalizeName ¶
NormalizeName is the concise form of NormalizeIntegrationBranchName.
func NormalizeProvider ¶
NormalizeProvider converts provider name to lowercase.
func ParsePrepareProfileDocument ¶
func ParsePrepareProfileDocument(data []byte, isJSON bool) (profile string, present bool, err error)
ParsePrepareProfileDocument extracts branch.prepareProfile from a repository-root config. It deliberately examines the source document rather than a merged config so duplicate keys and YAML multi-document payloads cannot be hidden by a permissive general-purpose decoder.
func ReadinessDigest ¶
ReadinessDigest returns the SHA-256 digest of the canonical contract.
func RecordWorkspaceReadOnly ¶
RecordWorkspaceReadOnly stores the resolved policy in repository-local Git metadata. This preserves an external absolute workspace's contract when a later push is invoked from outside the owning config tree.
func RejectMultiDocumentYAML ¶
RejectMultiDocumentYAML reports an error when data carries more than one YAML document.
yaml.Unmarshal decodes the first document and discards the rest without a word. Every shape check written on top of it — the field allowlists below, the "nothing changed beyond branch.readiness" comparison in pkg/integrate — then inspects document 1 while documents 2..n travel with the commit unread. A config whose first document is an unremarkable, passing contract can carry a second document declaring anything at all, and the checks that exist to keep it off a protected branch never see it.
A leading "---" is not a second document. A trailing one is, because it opens an empty document after the content; both are rejected the same way. The contract is exactly one document, and no .gz-git.yaml in this family uses a document marker at all.
func ResolveDeclaredIntegrationBranch ¶
ResolveDeclaredIntegrationBranch returns the repository's declared canonical integration branch, resolved to a ref that actually exists.
It returns ("", nil) when .gz-git.yaml declares none. That is a distinct answer from an error and callers must keep it distinct: "this repository never said which branch is canonical" is a reason to do nothing, not a reason to fall back to a name heuristic.
func ResolveTokenFromEnv ¶
ResolveTokenFromEnv returns a token from environment variables for the provider. Checks provider-specific vars first, then GZ_GIT_TOKEN.
func SanitizeToken ¶
SanitizeToken removes credentials from URLs for safe logging.
func SetTokenStore ¶
func SetTokenStore(s TokenStore)
SetTokenStore replaces the default store (tests).
func SplitIntegrationRemoteBranch ¶
SplitIntegrationRemoteBranch separates a tracking ref using the longest registered remote prefix, preserving slash-containing remote and branch names.
func SplitRemoteBranch ¶
SplitRemoteBranch is the concise form of SplitIntegrationRemoteBranch.
func UpstreamTargetsIntegration ¶
func UpstreamTargetsIntegration(branch, upstream string, resolution Resolution, remotes []string) bool
UpstreamTargetsIntegration is the concise form of UpstreamTargetsIntegrationBranch.
func UpstreamTargetsIntegrationBranch ¶
func UpstreamTargetsIntegrationBranch(branch, upstream string, resolution IntegrationBranchResolution, remotes []string) bool
UpstreamTargetsIntegrationBranch reports a non-integration branch tracking the integration branch itself.
func ValidatePrepareProfile ¶
ValidatePrepareProfile validates the closed set of repository preparation profiles. An empty value is valid only as an absent declaration; callers that parse a present declaration reject it before reaching this function.
func ValidateReadiness ¶
ValidateReadiness validates the closed V1 contract.
Types ¶
type AuditConfig ¶
type AuditConfig struct {
// Autofix overrides repository.DefaultAutofixPolicy per finding code. Keys
// are finding codes (BRANCH_BEHIND_BASE, …); a code that is absent keeps its
// built-in default, so a project states only where it disagrees.
//
// There are no tier names to choose from. A name like "aggressive" would
// have to be decoded into behavior before anyone could trust it, and it
// would force every code added later into a bucket someone else picked. A
// per-code boolean is the same information without the indirection.
//
// Enabling a code here still cannot make an irreversible remediation
// automatic: reversibility is checked after policy, so configuration widens
// what is permitted but never what is safe.
Autofix map[string]bool `yaml:"autofix,omitempty"`
}
AuditConfig holds `info --audit` defaults.
Example:
audit:
autofix:
BRANCH_BEHIND_BASE: false # this project rebases by hand
MERGED_BRANCH_NOT_RECLAIMED: true
type BranchConfig ¶
type BranchConfig struct {
DefaultBranch BranchList `yaml:"defaultBranch,omitempty"` // main, develop, master (string or list)
ProtectedBranches []string `yaml:"protectedBranches,omitempty"` // Branches to protect
// IntegrationBranch is the ordered list of integration-branch names.
// Consumers must read it from the repo-root file (LoadRepoRootTaskPattern),
// not from the 5-layer merger.
IntegrationBranch BranchList `yaml:"integrationBranch,omitempty"`
// TaskPattern is the reclaim allow-list (first-* namespace prefix).
// Load it only via LoadRepoRootTaskPattern — never findConfigUpward.
TaskPattern BranchList `yaml:"taskPattern,omitempty"`
// PrepareProfile selects a closed integration preparation profile. It is
// preserved on a repository-root project config, but must never be
// inherited or merged from a parent, profile, or workspace configuration.
PrepareProfile string `yaml:"prepareProfile,omitempty" json:"prepareProfile,omitempty"`
// Readiness is a target-owned integration gate. Like TaskPattern, it is
// preserved here for project-config round trips but must never be inherited
// or merged from a parent, profile, workspace, or global configuration.
Readiness *Readiness `yaml:"readiness,omitempty" json:"readiness,omitempty"`
// MakeTimeout bounds one `make check`/`make lint` probe of the legacy
// integration gate, declared as a Go duration string ("90m", "1h30m").
// Like IntegrationBranch and TaskPattern it is read only from the
// repo-root file (LoadRepoRootTaskPattern) and never inherited or merged
// from a parent, profile, workspace, or global configuration: a budget
// declared in a shared layer would silently change every repository's
// gate. Empty means the built-in default; pkg/config rejects a value it
// cannot parse or a non-positive one when loading the declaration.
MakeTimeout string `yaml:"makeTimeout,omitempty" json:"makeTimeout,omitempty"`
// Naming templates the branch names that `gz-git branch name` builds, so a
// task branch is spelled the same way on every machine and by every agent.
Naming *branch.Naming `yaml:"naming,omitempty"`
}
BranchConfig holds branch command defaults. Supports both string shorthand and struct format in YAML:
- branch: develop → BranchConfig{DefaultBranch: ["develop"]}
- branch: develop,master → BranchConfig{DefaultBranch: ["develop", "master"]}
- branch: defaultBranch: develop → standard struct format
func (*BranchConfig) UnmarshalYAML ¶
func (b *BranchConfig) UnmarshalYAML(unmarshal func(any) error) error
UnmarshalYAML implements yaml.Unmarshaler to support string shorthand. When YAML contains `branch: develop` (a plain string), it is converted to BranchConfig{DefaultBranch: ["develop"]}. This follows the same pattern as BranchList.UnmarshalYAML.
type BranchList ¶
type BranchList []string
BranchList supports both string and list formats for branch specification. Examples:
- defaultBranch: develop # single branch
- defaultBranch: develop,master # comma-separated string
- defaultBranch: [develop, master] # YAML list
func (BranchList) First ¶
func (b BranchList) First() string
First returns the first branch in the list, or empty string if empty.
func (BranchList) MarshalYAML ¶
func (b BranchList) MarshalYAML() (any, error)
MarshalYAML implements yaml.Marshaler to output as comma-separated string.
func (BranchList) String ¶
func (b BranchList) String() string
String returns the branch list as comma-separated string.
func (*BranchList) UnmarshalYAML ¶
func (b *BranchList) UnmarshalYAML(unmarshal func(any) error) error
UnmarshalYAML implements yaml.Unmarshaler to support both string and list formats.
type ChildConfigFormat ¶
type ChildConfigFormat string
ChildConfigFormat represents the format/origin of a child config file.
const ( // ChildConfigFormatAutoGenerated indicates the config was auto-generated by gz-git. // It can be safely overwritten during sync. ChildConfigFormatAutoGenerated ChildConfigFormat = "auto-generated" // ChildConfigFormatUserMaintained indicates the config is user-maintained. // It should NOT be overwritten without explicit --force flag. ChildConfigFormatUserMaintained ChildConfigFormat = "user-maintained" // ChildConfigFormatNotFound indicates no config file exists at the path. ChildConfigFormatNotFound ChildConfigFormat = "not-found" )
func DetectChildConfigFormat ¶
func DetectChildConfigFormat(path string) (ChildConfigFormat, error)
DetectChildConfigFormat checks if a config file is auto-generated or user-maintained.
Detection logic:
- File not exists → ChildConfigFormatNotFound
- File contains "# AUTO-GENERATED" marker → ChildConfigFormatAutoGenerated
- Otherwise → ChildConfigFormatUserMaintained
type ChildConfigMode ¶
type ChildConfigMode string
ChildConfigMode controls how child config files are generated during workspace sync.
const ( // ChildConfigModeRepositories generates a flat array format (default). // Example: repositories: [{name: repo1, url: ...}, ...]. ChildConfigModeRepositories ChildConfigMode = "repositories" // ChildConfigModeWorkspaces generates a map structure format. // Example: workspaces: {repo1: {path: repo1, type: git}, ...}. ChildConfigModeWorkspaces ChildConfigMode = "workspaces" // ChildConfigModeNone creates directory only, no config file. // Useful when child config is manually maintained or not needed. ChildConfigModeNone ChildConfigMode = "none" )
func (ChildConfigMode) Default ¶
func (m ChildConfigMode) Default() ChildConfigMode
Default returns the default mode if empty.
func (ChildConfigMode) IsValid ¶
func (m ChildConfigMode) IsValid() bool
IsValid returns true if this is a valid child config mode.
type CloneDefaults ¶
type CloneDefaults struct {
Proto string `yaml:"proto,omitempty"` // ssh, https
SSHPort int `yaml:"sshPort,omitempty"` // Custom SSH port
SSHKeyPath string `yaml:"sshKeyPath,omitempty"` // SSH private key file path
SSHKeyContent string `yaml:"sshKeyContent,omitempty"` // SSH private key content (use ${ENV_VAR})
}
CloneDefaults holds clone-related default settings.
type Config ¶
type Config struct {
// Parent specifies an explicit path to a parent config file.
// When set, the parent config is loaded and merged (child overrides parent).
// Supports: absolute paths, home-relative (~), relative paths.
Parent string `yaml:"parent,omitempty"`
// Profile specifies which profile to use at this level
Profile string `yaml:"profile,omitempty"`
// Profiles defines named profiles inline (no external file needed)
Profiles map[string]*Profile `yaml:"profiles,omitempty"`
// Defaults groups all default settings
Defaults *DefaultsConfig `yaml:"defaults,omitempty"`
Provider string `yaml:"provider,omitempty"` // github, gitlab, gitea
BaseURL string `yaml:"baseURL,omitempty"` // API endpoint
Token string `yaml:"token,omitempty"` // API token (use ${ENV_VAR})
IncludeSubgroups bool `yaml:"includeSubgroups,omitempty"` // GitLab subgroups
SubgroupMode string `yaml:"subgroupMode,omitempty"` // flat, nested
// Default workspace settings
DefaultWorkspaceType WorkspaceType `yaml:"defaultWorkspaceType,omitempty"` // forge/git/config
// Self-sync configuration (sync config directory itself)
SelfSync *SelfSyncConfig `yaml:"selfSync,omitempty"`
// Command-specific overrides
Sync *SyncConfig `yaml:"sync,omitempty"`
Branch *BranchConfig `yaml:"branch,omitempty"`
Fetch *FetchConfig `yaml:"fetch,omitempty"`
Pull *PullConfig `yaml:"pull,omitempty"`
Push *PushConfig `yaml:"push,omitempty"`
// Hooks defines global before/after commands for all workspace syncs
Hooks *Hooks `yaml:"hooks,omitempty"`
// Workspaces is a map of named workspace configurations
Workspaces map[string]*Workspace `yaml:"workspaces,omitempty"`
// Metadata is optional information about this level
Metadata *Metadata `yaml:"metadata,omitempty"`
// Discovery controls how workspaces are discovered
Discovery *DiscoveryConfig `yaml:"discovery,omitempty"`
// ChildConfigMode sets the default child config mode for all workspaces.
// Values: "repositories" (default), "workspaces", "none"
ChildConfigMode ChildConfigMode `yaml:"childConfigMode,omitempty"`
ParentConfig *Config `yaml:"-"` // Resolved parent config
ConfigPath string `yaml:"-"` // Absolute path to this config file
}
Config represents a hierarchical configuration that can be nested recursively. This is the unified config type used at ALL levels: workstation, workspace, project, submodule, etc.
Example usage:
# ~/.gz-git.yaml (workstation level)
profile: polypia
defaults:
clone:
proto: ssh
sync:
strategy: reset
parallel: 10
# Inline profiles (no external file needed!)
profiles:
polypia:
provider: gitlab
baseURL: https://gitlab.polypia.net
token: ${GITLAB_POLYPIA_TOKEN}
github-personal:
provider: github
token: ${GITHUB_TOKEN}
workspaces:
devbox:
path: ~/mydevbox
source:
provider: gitlab
org: devbox
includeSubgroups: true
subgroupMode: flat
sync:
strategy: pull
func GetParentChain ¶
GetParentChain returns all configs in the parent chain (including current). Useful for debugging and displaying config precedence.
func LoadConfigRecursive ¶
LoadConfigRecursive loads a config file and recursively loads all workspaces. This function works at ANY level (workstation, workspace, project, etc.)
Parameters:
- path: The directory containing the config file
- configFile: The config file name (e.g., ".gz-git.yaml", ".gz-git-config.yaml")
Returns:
- *Config: The loaded config with all workspaces recursively loaded
- error: Any error encountered during loading
Example:
// Load workstation config
home, _ := os.UserHomeDir()
config, err := LoadConfigRecursive(home, ".gz-git-config.yaml")
// Load workspace config
config, err := LoadConfigRecursive("/home/user/mydevbox", ".gz-git.yaml")
func (*Config) GetCloneProto ¶
GetCloneProto returns clone protocol from defaults.clone.proto.
func (*Config) GetCompactOutput ¶
GetCompactOutput returns compact output setting from defaults.output.compact.
func (*Config) GetExcludePatterns ¶
GetExcludePatterns returns exclude patterns from defaults.filter.exclude.
Scope: the forge API repository listing in `workspace sync` only. Setting it does not keep a repository out of `push`, `commit`, or any other bulk command — use defaults.scan.exclude for that.
func (*Config) GetIncludePatterns ¶
GetIncludePatterns returns include patterns from defaults.filter.include.
Scope: the forge API repository listing in `workspace sync` only. It does not filter the local directory scan that bulk commands run — see GetScanExcludePatterns for that.
func (*Config) GetMaxRetries ¶
GetMaxRetries returns max retries (sync.maxRetries overrides defaults.sync.maxRetries).
func (*Config) GetParallel ¶
GetParallel returns parallel worker count from defaults.sync.parallel.
func (*Config) GetSSHKeyContent ¶
GetSSHKeyContent returns SSH key content from defaults.clone.sshKeyContent.
func (*Config) GetSSHKeyPath ¶
GetSSHKeyPath returns SSH key path from defaults.clone.sshKeyPath.
func (*Config) GetSSHPort ¶
GetSSHPort returns SSH port from defaults.clone.sshPort.
func (*Config) GetScanDepth ¶
GetScanDepth returns scan depth from defaults.scan.depth.
func (*Config) GetScanExcludePatterns ¶
GetScanExcludePatterns returns regex patterns from defaults.scan.exclude.
These apply to the local directory scan shared by every bulk command, unlike GetExcludePatterns, which applies only to the forge API listing in `workspace sync`.
func (*Config) GetSyncStrategy ¶
GetSyncStrategy returns sync strategy (sync.strategy overrides defaults.sync.strategy).
func (*Config) GetTimeout ¶
GetTimeout returns timeout (sync.timeout overrides defaults.sync.timeout).
type ConfigFileInfo ¶
type ConfigFileInfo struct {
Path string
Kind ConfigKind
}
ConfigFileInfo holds detected config file information.
type ConfigKind ¶
type ConfigKind string
ConfigKind represents the type of configuration file.
const ( // KindRepositories is for simple flat repository lists (repositories array). KindRepositories ConfigKind = "repositories" // KindWorkspace is for hierarchical workspace configurations (workspaces map). KindWorkspace ConfigKind = "workspace" )
func DetectConfigFileWithKind ¶
func DetectConfigFileWithKind(dir string) (string, ConfigKind)
DetectConfigFileWithKind probes a single directory (no parent walk, unlike DetectConfigFile) for a .gz-git config file, checking supported extensions in their defined order. Use it when the caller specifically wants "is there a config in exactly this directory".
func (ConfigKind) IsValid ¶
func (k ConfigKind) IsValid() bool
IsValid returns true if this is a valid config kind.
type ConfigLoader ¶
type ConfigLoader struct {
// contains filtered or unexported fields
}
ConfigLoader handles configuration loading with 5-layer precedence.
Precedence (highest to lowest):
- Command flags
- Project config (.gz-git.yaml)
- Active profile
- Global config
- Built-in defaults
func NewLoader ¶
func NewLoader() (*ConfigLoader, error)
NewLoader creates a new configuration loader.
func (*ConfigLoader) Load ¶
func (l *ConfigLoader) Load() error
Load loads all configuration layers.
func (*ConfigLoader) ResolveConfig ¶
func (l *ConfigLoader) ResolveConfig(flags map[string]any) (*EffectiveConfig, error)
ResolveConfig builds the effective configuration with precedence. Flags parameter contains command-line flag values (highest priority).
func (*ConfigLoader) SetActiveProfileInternal ¶
func (l *ConfigLoader) SetActiveProfileInternal(profile *Profile)
SetActiveProfileInternal sets the active profile without persisting to disk. This is used for temporary profile override (e.g., --profile flag).
type ConfigMeta ¶
type ConfigMeta struct {
// Version is the schema version (currently 1)
Version int `yaml:"version,omitempty"`
// Kind specifies the config type: "repositories" or "workspace"
// If omitted, inferred from content:
// - Has "workspaces" or "profiles" key → workspace
// - Otherwise → repositories (default)
Kind ConfigKind `yaml:"kind,omitempty"`
// Metadata holds optional descriptive information
Metadata *Metadata `yaml:"metadata,omitempty"`
}
ConfigMeta holds common metadata for all config file types. This should be at the top of every config file.
Example:
version: 1 kind: repositories metadata: name: "my-devbox" team: "platform"
type ConfigSource ¶
type ConfigSource string
ConfigSource represents where a config value came from.
const ( SourceFlag ConfigSource = "flag" SourceEnv ConfigSource = "env" SourceKeychain ConfigSource = "keychain" SourceProject ConfigSource = "project" SourceProfile ConfigSource = "profile" SourceGlobal ConfigSource = "global" SourceDefault ConfigSource = "default" )
ConfigSource values identify which layer of the 5-layer precedence provided a value.
func (ConfigSource) String ¶
func (s ConfigSource) String() string
String returns a human-readable source description.
type ControllerConfig ¶
ControllerConfig is an explicitly selected, non-inheriting devbox policy. It does not load parent configs or recursively discover workspaces.
func LoadControllerConfig ¶
func LoadControllerConfig(path string) (*ControllerConfig, error)
LoadControllerConfig loads one explicitly selected controller file without applying repository config discovery or inheritance.
type DefaultsConfig ¶
type DefaultsConfig struct {
// Clone settings
Clone *CloneDefaults `yaml:"clone,omitempty"`
// Sync settings
Sync *SyncDefaults `yaml:"sync,omitempty"`
// Scan settings
Scan *ScanDefaults `yaml:"scan,omitempty"`
// Output settings
Output *OutputDefaults `yaml:"output,omitempty"`
// Filter settings
Filter *FilterDefaults `yaml:"filter,omitempty"`
}
DefaultsConfig groups all default settings for clarity. These settings apply globally unless overridden at workspace level.
type DiscoveryConfig ¶
type DiscoveryConfig struct {
// Mode controls the discovery behavior
// Values: "explicit" (use children only), "auto" (scan directories),
// "hybrid" (use children if defined, otherwise scan)
// Default: "hybrid"
Mode DiscoveryMode `yaml:"mode,omitempty"`
}
DiscoveryConfig controls how children are discovered.
type DiscoveryMode ¶
type DiscoveryMode string
DiscoveryMode represents the children discovery mode.
const ( // ExplicitMode only uses children explicitly defined in the config. ExplicitMode DiscoveryMode = "explicit" // AutoMode scans directories to find children automatically // Ignores explicit children definition. AutoMode DiscoveryMode = "auto" // HybridMode uses children if defined, otherwise scans directories // This is the default mode. HybridMode DiscoveryMode = "hybrid" )
func (DiscoveryMode) Default ¶
func (m DiscoveryMode) Default() DiscoveryMode
Default returns the default discovery mode.
func (DiscoveryMode) IsValid ¶
func (m DiscoveryMode) IsValid() bool
IsValid returns true if this is a valid discovery mode.
type EffectiveConfig ¶
type EffectiveConfig struct {
// Forge provider settings
Provider string
BaseURL string
Token string
// Clone settings
CloneProto string
SSHPort int
SSHKeyPath string
SSHKeyContent string
// Bulk operation settings
Parallel int
IncludeSubgroups bool
SubgroupMode string
// Identity, already resolved against the environment and hostname, so a
// caller can use it without knowing where it came from.
Identity identity.Identity
// Command-specific settings
Sync SyncConfig
Branch BranchConfig
Fetch FetchConfig
Pull PullConfig
Push PushConfig
Audit AuditConfig
// Metadata for debugging
Sources map[string]string // key -> source (e.g., "provider" -> "profile:work")
}
EffectiveConfig represents the final resolved configuration after applying all precedence layers.
func (*EffectiveConfig) GetBool ¶
func (cfg *EffectiveConfig) GetBool(key string) (value, ok bool)
GetBool retrieves a boolean value by key from effective config.
func (*EffectiveConfig) GetInt ¶
func (cfg *EffectiveConfig) GetInt(key string) (int, bool)
GetInt retrieves an integer value by key from effective config.
func (*EffectiveConfig) GetSource ¶
func (cfg *EffectiveConfig) GetSource(key string) string
GetSource returns the source for a given config key.
type Environment ¶
type Environment struct {
GitHubToken string `yaml:"githubToken,omitempty"`
GitLabToken string `yaml:"gitlabToken,omitempty"`
GiteaToken string `yaml:"giteaToken,omitempty"`
}
Environment represents a named set of API tokens.
type Facts ¶
type Facts = IntegrationBranchFacts
Facts are the inputs needed to resolve an integration branch.
type FetchConfig ¶
type FetchConfig struct {
AllRemotes bool `yaml:"allRemotes,omitempty"` // Fetch all remotes
Prune bool `yaml:"prune,omitempty"` // Prune deleted branches
}
FetchConfig holds fetch command defaults.
type FilterDefaults ¶
type FilterDefaults struct {
Include []string `yaml:"include,omitempty"` // Include repos matching these patterns
Exclude []string `yaml:"exclude,omitempty"` // Exclude repos matching these patterns
}
FilterDefaults holds filter pattern settings.
type FlexBranch ¶
type FlexBranch string
FlexBranch is a string type that accepts both string and map YAML formats. Use this for per-repo or per-group branch fields where only the branch name(s) matter.
Accepted formats:
branch: develop → "develop" branch: develop,master → "develop,master" branch: defaultBranch: develop → "develop" branch: defaultBranch: [dev, main] → "dev,main"
func (FlexBranch) MarshalYAML ¶
func (f FlexBranch) MarshalYAML() (any, error)
MarshalYAML implements yaml.Marshaler to output as a plain string.
func (FlexBranch) String ¶
func (f FlexBranch) String() string
String returns the FlexBranch value as a plain string.
func (*FlexBranch) UnmarshalYAML ¶
func (f *FlexBranch) UnmarshalYAML(unmarshal func(any) error) error
UnmarshalYAML implements yaml.Unmarshaler to support both string and map formats.
type ForgeSource ¶
type ForgeSource struct {
// Provider is the forge type: gitlab, github, gitea
Provider string `yaml:"provider"`
// Org is the organization/group to sync from
Org string `yaml:"org"`
// BaseURL is the API endpoint (optional, uses default for provider)
BaseURL string `yaml:"baseURL,omitempty"`
// Token overrides the profile token (use ${ENV_VAR} for security)
Token string `yaml:"token,omitempty"`
// IncludeSubgroups includes subgroups (GitLab only)
IncludeSubgroups bool `yaml:"includeSubgroups,omitempty"`
// SubgroupMode controls directory structure: "flat" or "nested"
SubgroupMode string `yaml:"subgroupMode,omitempty"`
}
ForgeSource defines a forge (GitLab/GitHub/Gitea) to sync repositories from.
Example:
source: provider: gitlab org: devbox baseURL: https://gitlab.polypia.net includeSubgroups: true subgroupMode: flat
type GlobalConfig ¶
type GlobalConfig struct {
// ActiveProfile is the default profile to use
ActiveProfile string `yaml:"activeProfile,omitempty"`
// Defaults apply to all profiles unless overridden
Defaults map[string]any `yaml:"defaults,omitempty"`
// Identity names this machine and, if one is driving, the agent on it.
// It lives here rather than in a project's .gz-git.yaml because that file
// is committed, and a shared device name names nothing.
Identity *identity.Identity `yaml:"identity,omitempty"`
// Environments define named token sets
Environments map[string]Environment `yaml:"environments,omitempty"`
}
GlobalConfig represents ~/.config/gz-git/config.yaml
Example global config file:
activeProfile: work
defaults:
parallel: 5
cloneProto: ssh
format: default
environments:
work:
gitlabToken: ${WORK_GITLAB_TOKEN}
personal:
githubToken: ${PERSONAL_GITHUB_TOKEN}
type Hooks ¶
type Hooks struct {
Before []string `yaml:"before,omitempty"` // Commands to run before sync operation
After []string `yaml:"after,omitempty"` // Commands to run after sync operation
}
Hooks represents before/after hook commands for sync operations. Hooks are executed without shell interpretation for security (no pipes, redirects, etc.).
type IntegrationBranchFacts ¶
type IntegrationBranchFacts struct {
Config []string
Refs []string
Remotes []string
DefaultName string
}
IntegrationBranchFacts are the inputs needed to resolve an integration branch.
type IntegrationBranchResolution ¶
IntegrationBranchResolution is the integration-branch answer for one repository.
func ResolveIntegrationBranch ¶
func ResolveIntegrationBranch(ctx context.Context, exec *gitcmd.Executor, repoPath string, configValues []string) (IntegrationBranchResolution, error)
ResolveIntegrationBranch reads Git facts through exec. Missing refs and remote HEAD are reportable non-participation, not errors.
func ResolveIntegrationBranchFromFacts ¶
func ResolveIntegrationBranchFromFacts(f IntegrationBranchFacts) IntegrationBranchResolution
ResolveIntegrationBranchFromFacts interprets integration-branch participation from already gathered facts. A declared name that does not exist does not fall back to the default branch.
type IntegrationControl ¶
type IntegrationControl struct {
PrepareProfile string `yaml:"prepareProfile,omitempty"`
}
IntegrationControl is deliberately small: it selects a closed preparation profile, while branch.integrationBranch and branch.taskPattern on the same workspace remain the target and reclaim declarations.
type IntegrationParticipationAction ¶
type IntegrationParticipationAction string
IntegrationParticipationAction describes the repository-local change needed to align workflow.integrationBranch with its repo-root declaration.
const ( // IntegrationParticipationNoop leaves repository-local configuration unchanged. IntegrationParticipationNoop IntegrationParticipationAction = "noop" // IntegrationParticipationInstall records a newly managed declaration. IntegrationParticipationInstall IntegrationParticipationAction = "install" // IntegrationParticipationUpdate changes a previously managed declaration. IntegrationParticipationUpdate IntegrationParticipationAction = "update" // IntegrationParticipationRemove clears configuration proven to be managed. IntegrationParticipationRemove IntegrationParticipationAction = "remove" // IntegrationParticipationConflict preserves user-owned or mismatched state. IntegrationParticipationConflict IntegrationParticipationAction = "conflict" )
func PlanIntegrationParticipation ¶
func PlanIntegrationParticipation(state IntegrationParticipationState) IntegrationParticipationAction
PlanIntegrationParticipation applies the ownership state machine without touching Git configuration. Desired is a normalized bare branch name, or empty when the repo-root declaration no longer selects a branch.
func ReconcileIntegrationParticipation ¶
func ReconcileIntegrationParticipation(ctx context.Context, repoPath string) (IntegrationParticipationAction, error)
ReconcileIntegrationParticipation reads only the selected repository's root declaration and local Git metadata. It returns an error before writing when the declaration is invalid, unresolved, or conflicts with user-owned configuration.
type IntegrationParticipationState ¶
IntegrationParticipationState is the complete local state used to decide whether a sync may manage workflow.integrationBranch. Marker ownership is deliberately explicit so a workspace sync never overwrites a manual choice.
type KeyringTokenStore ¶
type KeyringTokenStore struct {
// contains filtered or unexported fields
}
KeyringTokenStore uses the OS keychain via go-keyring. When the backend is unavailable (headless Linux without Secret Service), methods return ErrKeyringUnavailable and Available returns false.
func (*KeyringTokenStore) Available ¶
func (s *KeyringTokenStore) Available() bool
Available reports whether the last keyring operation succeeded.
func (*KeyringTokenStore) Delete ¶
func (s *KeyringTokenStore) Delete(provider string) error
Delete removes a token for the provider from the OS keychain.
func (*KeyringTokenStore) Get ¶
func (s *KeyringTokenStore) Get(provider string) (string, error)
Get retrieves a token for the provider from the OS keychain.
func (*KeyringTokenStore) Set ¶
func (s *KeyringTokenStore) Set(provider, token string) error
Set stores a token for the provider in the OS keychain.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager handles CRUD operations for profiles and configurations.
func NewManager ¶
NewManager creates a new configuration manager.
func (*Manager) CreateProfile ¶
CreateProfile creates a new profile.
func (*Manager) DeleteProfile ¶
DeleteProfile deletes a profile.
func (*Manager) FindNearestConfig ¶
FindNearestConfig finds the nearest config file by walking up from the current directory.
func (*Manager) GetActiveProfile ¶
GetActiveProfile returns the active profile name.
func (*Manager) Initialize ¶
Initialize creates the config directory structure with default profile.
func (*Manager) ListProfiles ¶
ListProfiles returns all available profile names.
func (*Manager) LoadConfigRecursiveFromPath ¶
LoadConfigRecursiveFromPath loads a recursive config from the specified path. This is a wrapper around LoadConfigRecursive with manager validation.
func (*Manager) LoadGlobalConfig ¶
func (m *Manager) LoadGlobalConfig() (*GlobalConfig, error)
LoadGlobalConfig loads the global configuration.
func (*Manager) LoadProfile ¶
LoadProfile loads a profile from disk.
func (*Manager) LoadProjectConfig ¶
func (m *Manager) LoadProjectConfig() (*ProjectConfig, error)
LoadProjectConfig loads project-specific configuration.
func (*Manager) ProfileExists ¶
ProfileExists checks if a profile exists.
func (*Manager) SaveConfig ¶
SaveConfig saves a recursive config to the specified path.
func (*Manager) SaveGlobalConfig ¶
func (m *Manager) SaveGlobalConfig(config *GlobalConfig) error
SaveGlobalConfig saves the global configuration.
func (*Manager) SaveProfile ¶
SaveProfile saves a profile to disk.
func (*Manager) SaveProjectConfig ¶
func (m *Manager) SaveProjectConfig(config *ProjectConfig) error
SaveProjectConfig saves project configuration to current directory.
func (*Manager) SetActiveProfile ¶
SetActiveProfile sets the active profile.
type MemoryTokenStore ¶
type MemoryTokenStore struct {
// contains filtered or unexported fields
}
MemoryTokenStore is an in-memory TokenStore for tests.
func NewMemoryTokenStore ¶
func NewMemoryTokenStore() *MemoryTokenStore
NewMemoryTokenStore creates a test token store.
func (*MemoryTokenStore) Available ¶
func (m *MemoryTokenStore) Available() bool
Available reports the flag SetAvailable last wrote, so a test can simulate a headless machine without a keychain.
func (*MemoryTokenStore) Delete ¶
func (m *MemoryTokenStore) Delete(provider string) error
Delete removes the provider's token, succeeding when there was none.
func (*MemoryTokenStore) Get ¶
func (m *MemoryTokenStore) Get(provider string) (string, error)
Get returns the stored token, or "" with no error when the provider was never set — the same "absent is not a failure" contract KeyringTokenStore has.
func (*MemoryTokenStore) Set ¶
func (m *MemoryTokenStore) Set(provider, token string) error
Set stores a token in memory, or reports ErrKeyringUnavailable while the store is switched off by SetAvailable.
func (*MemoryTokenStore) SetAvailable ¶
func (m *MemoryTokenStore) SetAvailable(v bool)
SetAvailable toggles availability for fallback tests.
type Metadata ¶
type Metadata struct {
Name string `yaml:"name,omitempty"` // workstation, mydevbox, project-name
Type string `yaml:"type,omitempty"` // development, production, personal
Owner string `yaml:"owner,omitempty"` // archmagece, team-name
Team string `yaml:"team,omitempty"` // backend, frontend
Repository string `yaml:"repository,omitempty"` // https://...
}
Metadata holds optional information about a config level.
type OutputDefaults ¶
type OutputDefaults struct {
Compact bool `yaml:"compact,omitempty"` // Omit redundant fields in generated configs
Format string `yaml:"format,omitempty"` // Output format (default, compact, json, llm)
}
OutputDefaults holds output-related default settings.
type Paths ¶
type Paths struct {
// ConfigDir is the root config directory (~/.config/gz-git)
ConfigDir string
// ProfilesDir is the profiles directory (~/.config/gz-git/profiles)
ProfilesDir string
// StateDir is the state directory (~/.config/gz-git/state)
StateDir string
// GlobalConfigFile is the global config file path
GlobalConfigFile string
// ActiveProfileFile tracks the active profile
ActiveProfileFile string
}
Paths provides access to all config file locations.
func NewPaths ¶
NewPaths creates a Paths instance with standard locations. It uses XDG_CONFIG_HOME if set, otherwise falls back to ~/.config.
func (*Paths) EnsureDirectories ¶
EnsureDirectories creates all necessary directories with correct permissions. Directories are created with 0700 (user access only).
func (*Paths) GetActiveProfile ¶
GetActiveProfile reads the active profile name from state file. Returns empty string if not set.
func (*Paths) ListProfiles ¶
ListProfiles returns all available profile names.
func (*Paths) ProfileExists ¶
ProfileExists checks if a profile file exists.
func (*Paths) ProfilePath ¶
ProfilePath returns the path to a specific profile file, checking for supported extensions.
func (*Paths) SetActiveProfile ¶
SetActiveProfile writes the active profile name to state file.
type Profile ¶
type Profile struct {
// Name is the profile identifier (e.g., "work", "personal")
Name string `yaml:"name"`
// Forge provider settings
Provider string `yaml:"provider,omitempty"` // github, gitlab, gitea
BaseURL string `yaml:"baseURL,omitempty"` // API endpoint
Token string `yaml:"token,omitempty"` // API token (use ${ENV_VAR})
// Clone settings
CloneProto string `yaml:"cloneProto,omitempty"` // ssh, https
SSHPort int `yaml:"sshPort,omitempty"` // Custom SSH port
SSHKeyPath string `yaml:"sshKeyPath,omitempty"` // SSH private key file path (priority)
SSHKeyContent string `yaml:"sshKeyContent,omitempty"` // SSH private key content (use ${ENV_VAR})
// Bulk operation settings
Parallel int `yaml:"parallel,omitempty"` // Parallel job count
IncludeSubgroups bool `yaml:"includeSubgroups,omitempty"` // GitLab subgroups
SubgroupMode string `yaml:"subgroupMode,omitempty"` // flat, nested
// Identity names the machine and agent recorded on automated commits.
Identity *identity.Identity `yaml:"identity,omitempty"`
// Command-specific overrides
Sync *SyncConfig `yaml:"sync,omitempty"`
Branch *BranchConfig `yaml:"branch,omitempty"`
Fetch *FetchConfig `yaml:"fetch,omitempty"`
Pull *PullConfig `yaml:"pull,omitempty"`
Push *PushConfig `yaml:"push,omitempty"`
Audit *AuditConfig `yaml:"audit,omitempty"`
}
Profile represents a named configuration profile. A profile contains default values for command flags, eliminating the need to repeatedly specify the same options.
Example profile file (~/.config/gz-git/profiles/work.yaml):
name: work
provider: gitlab
baseURL: https://gitlab.company.com
token: ${WORK_GITLAB_TOKEN}
cloneProto: ssh
sshPort: 2224
parallel: 10
sync:
strategy: reset
maxRetries: 3
func GetProfileByName ¶
GetProfileByName returns a profile by name from the config. Lookup order: inline (config.Profiles) → external (~/.config/gz-git/profiles/) Returns nil if profile not found in either location.
func GetProfileFromChain ¶
GetProfileFromChain looks up a profile by traversing the parent config chain. Lookup order:
- Current config's inline profiles (config.Profiles)
- Parent config's inline profiles (config.ParentConfig.Profiles)
- Grandparent config's inline profiles (and so on...)
Returns nil if profile not found in any level. For external profiles (~/.config/gz-git/profiles/), use Manager.GetProfile().
type ProjectConfig ¶
type ProjectConfig struct {
// Profile specifies which profile to use for this project
Profile string `yaml:"profile,omitempty"`
// Command-specific overrides
Sync *SyncConfig `yaml:"sync,omitempty"`
Branch *BranchConfig `yaml:"branch,omitempty"`
Fetch *FetchConfig `yaml:"fetch,omitempty"`
Pull *PullConfig `yaml:"pull,omitempty"`
Push *PushConfig `yaml:"push,omitempty"`
Audit *AuditConfig `yaml:"audit,omitempty"`
// Metadata is optional project information
Metadata *ProjectMetadata `yaml:"metadata,omitempty"`
}
ProjectConfig represents .gz-git.yaml in a project directory. This file is auto-detected by walking up the directory tree.
Example project config file:
profile: work sync: strategy: pull parallel: 3 branch: defaultBranch: main protectedBranches: [main, develop, release/*] metadata: team: backend repository: https://gitlab.company.com/backend/myproject
type ProjectMetadata ¶
type ProjectMetadata struct {
Team string `yaml:"team,omitempty"`
Repository string `yaml:"repository,omitempty"`
Owner string `yaml:"owner,omitempty"`
}
ProjectMetadata holds optional project information.
type PullConfig ¶
type PullConfig struct {
Rebase bool `yaml:"rebase,omitempty"` // Use rebase instead of merge
FFOnly bool `yaml:"ffOnly,omitempty"` // Fast-forward only
}
PullConfig holds pull command defaults.
type PushConfig ¶
type PushConfig struct {
SetUpstream bool `yaml:"setUpstream,omitempty"` // Auto set upstream
// Policy restricts which branches push may write and how. Unset means no
// branch is protected and only the built-in lease-only force rule applies.
Policy *repository.PushPolicy `yaml:"policy,omitempty"`
}
PushConfig holds push command defaults.
Example:
push:
setUpstream: true
policy:
protected: [main, master]
forceMode: lease-only
foreignWork: block
type Readiness ¶
type Readiness struct {
Version int `yaml:"version" json:"version"`
Runner string `yaml:"runner" json:"runner"`
}
Readiness is the target-owned integration gate contract. It deliberately has no command, argument, environment, or cwd fields: V1 has one fixed runner invocation and accepts no caller-controlled execution surface.
func ParseReadinessDocument ¶
ParseReadinessDocument extracts branch.readiness from a repo-root config. Unknown fields in the readiness node are rejected even though the larger project configuration remains backwards-compatible and extensible.
type Resolution ¶
type Resolution = IntegrationBranchResolution
Resolution is the integration-branch answer for one repository.
func ResolveFromFacts ¶
func ResolveFromFacts(f Facts) Resolution
ResolveFromFacts is the concise form of ResolveIntegrationBranchFromFacts.
type ScanDefaults ¶
type ScanDefaults struct {
// Depth is the default scan depth for bulk operations. Zero means omitted,
// so a child value of zero inherits its parent and a top-level zero keeps the
// CLI default. Positive values opt into a different depth.
Depth int `yaml:"depth,omitempty"`
// Exclude holds regex patterns removed from the *local directory scan*
// that every bulk command runs. It exists because the only way to keep a
// repository out of `push`/`commit` used to be remembering `--exclude` on
// every invocation, and forgetting it failed silently: the repository just
// appeared as another success row.
//
// This is deliberately not `defaults.filter.exclude`. That key filters the
// repository list a forge API returns to `workspace sync` and has never
// affected the local scan, so reusing it would have made one name mean two
// scopes. See GetScanExcludePatterns.
Exclude []string `yaml:"exclude,omitempty"`
}
ScanDefaults holds scan-related default settings.
type SelfSyncConfig ¶
type SelfSyncConfig struct {
// Enabled controls whether the config directory itself should be synced.
// Default: false (config directory is not synced)
Enabled bool `yaml:"enabled,omitempty"`
// Strategy specifies how to sync the config directory.
// Values: "fetch" (default, safe), "pull" (with dirty check), "skip"
// Note: "reset" is not allowed for self-sync to prevent data loss.
Strategy string `yaml:"strategy,omitempty"`
}
SelfSyncConfig controls sync behavior for the config directory itself. This allows the devbox/orchestrator directory to be synced along with workspaces.
type SyncConfig ¶
type SyncConfig struct {
Strategy string `yaml:"strategy,omitempty"` // pull, reset, rebase, skip, clone
MaxRetries int `yaml:"maxRetries,omitempty"` // Retry count
Timeout string `yaml:"timeout,omitempty"` // Operation timeout
Recursive bool `yaml:"recursive,omitempty"` // Auto-sync child workspace repos
CleanupOrphans bool `yaml:"cleanupOrphans,omitempty"` // Delete local repos not in forge
}
SyncConfig holds sync command defaults.
type SyncDefaults ¶
type SyncDefaults struct {
Strategy string `yaml:"strategy,omitempty"` // reset, pull, rebase, fetch, skip, clone
Parallel int `yaml:"parallel,omitempty"` // Parallel workers
MaxRetries int `yaml:"maxRetries,omitempty"` // Retry count
Timeout string `yaml:"timeout,omitempty"` // Operation timeout
}
SyncDefaults holds sync-related default settings.
type TaskPatternDecl ¶
type TaskPatternDecl struct {
Patterns []string
IntegrationBranch BranchList
// MakeTimeout is the declared branch.makeTimeout, already parsed. Zero
// means the key is absent and the consumer applies its built-in default;
// a present-but-invalid value never gets here because the load fails.
MakeTimeout time.Duration
Source string
Facts []string
}
TaskPatternDecl is the repo-root declaration load result.
Missing file → empty Patterns plus a reportable "no declaration" fact. That is not "everything is reclaimable".
func LoadRepoRootTaskPattern ¶
func LoadRepoRootTaskPattern(repoRoot string) (TaskPatternDecl, error)
LoadRepoRootTaskPattern stats only <repoRoot>/.gz-git.{yaml,yml,json}. It does not call findConfigUpward and is not the 5-layer merger.
A non-root .gz-git.yaml that declares taskPattern is ignored and its path is reported. A pattern that equals a literal protected name (main, master, develop, development) rejects the load. Overlap with built-in protect patterns hotfix/* and release/* is allowed.
type TokenStore ¶
type TokenStore interface {
Set(provider, token string) error
Get(provider string) (string, error)
Delete(provider string) error
Available() bool
}
TokenStore abstracts OS keychain access for forge API tokens.
var DefaultTokenStore TokenStore = &KeyringTokenStore{}
DefaultTokenStore is the process-wide token store (overridable in tests).
type Validator ¶
type Validator struct {
// ExpandEnvVars enables environment variable expansion
ExpandEnvVars bool
}
Validator handles configuration validation and environment variable expansion.
func NewValidator ¶
func NewValidator() *Validator
NewValidator creates a new Validator with default settings.
func (*Validator) ExpandEnvVarsInConfig ¶
ExpandEnvVarsInConfig expands environment variables in a recursive config.
func (*Validator) ExpandEnvVarsInGlobalConfig ¶
func (v *Validator) ExpandEnvVarsInGlobalConfig(g *GlobalConfig) error
ExpandEnvVarsInGlobalConfig expands environment variables in global config.
func (*Validator) ExpandEnvVarsInProfile ¶
ExpandEnvVarsInProfile expands environment variables in a profile. Variables use ${VAR_NAME} syntax.
func (*Validator) ExpandEnvVarsInWorkspace ¶
ExpandEnvVarsInWorkspace expands environment variables in a workspace.
func (*Validator) ValidateChildConfigMode ¶
func (v *Validator) ValidateChildConfigMode(mode ChildConfigMode) error
ValidateChildConfigMode validates a child config mode value.
func (*Validator) ValidateConfig ¶
ValidateConfig validates a recursive hierarchical config.
func (*Validator) ValidateConfigLink ¶
ValidateConfigLink validates a configLink path.
func (*Validator) ValidateDiscoveryConfig ¶
func (v *Validator) ValidateDiscoveryConfig(d *DiscoveryConfig) error
ValidateDiscoveryConfig validates discovery configuration.
func (*Validator) ValidateForgeSource ¶
func (v *Validator) ValidateForgeSource(s *ForgeSource) error
ValidateForgeSource validates a forge source configuration.
func (*Validator) ValidateGlobalConfig ¶
func (v *Validator) ValidateGlobalConfig(g *GlobalConfig) error
ValidateGlobalConfig validates global configuration.
func (*Validator) ValidateHooks ¶
ValidateHooks validates hook commands for security. Checks that commands don't contain shell special characters.
func (*Validator) ValidateParentPath ¶
ValidateParentPath validates a parent config path. Checks:
- Path is not empty (already handled by caller)
- Path doesn't contain dangerous patterns
- Path format is valid (allows ~/, ./, absolute, relative)
func (*Validator) ValidateProfile ¶
ValidateProfile validates a profile configuration.
func (*Validator) ValidateProjectConfig ¶
func (v *Validator) ValidateProjectConfig(p *ProjectConfig) error
ValidateProjectConfig validates project configuration.
func (*Validator) ValidateSyncConfig ¶
func (v *Validator) ValidateSyncConfig(s *SyncConfig) error
ValidateSyncConfig validates sync configuration.
type Workspace ¶
type Workspace struct {
// Path is the target directory for this workspace
// Supports: absolute (/foo/bar), relative (./foo), home-relative (~/foo)
Path string `yaml:"path"`
// ConfigLink specifies a config file to symlink into the workspace as .gz-git.yaml
// Supports: absolute paths, home-relative (~/), relative (./), relative to parent config dir
// The symlink is created at {workspace.Path}/.gz-git.yaml → {configLink}
ConfigLink string `yaml:"configLink,omitempty"`
// Hooks defines before/after commands for this workspace sync
// Before hooks run before clone/update, After hooks run after successful sync
Hooks *Hooks `yaml:"hooks,omitempty"`
// Type specifies what kind of workspace this is
// Values: "forge" (sync from forge), "git" (single repo), "config" (has nested config)
// Default: "forge" if Source is set, "git" otherwise
Type WorkspaceType `yaml:"type,omitempty"`
// Profile overrides the parent profile for this workspace
Profile string `yaml:"profile,omitempty"`
// Access controls whether this machine may publish changes to the
// workspace. Read-only workspaces are still cloned and pulled, but bulk
// push and handoff never write to their remotes.
Access WorkspaceAccess `yaml:"access,omitempty"`
// URL is the git clone URL (required for type=git sync)
// Supports: HTTPS, SSH, git:// protocols
URL string `yaml:"url,omitempty"`
// AdditionalRemotes defines extra git remotes to configure after clone
// Map of remote name to URL (e.g., {"upstream": "https://github.com/original/repo.git"})
AdditionalRemotes map[string]string `yaml:"additionalRemotes,omitempty"`
// Source defines the forge to sync from
Source *ForgeSource `yaml:"source,omitempty"`
Sync *SyncConfig `yaml:"sync,omitempty"`
Parallel int `yaml:"parallel,omitempty"`
CloneProto string `yaml:"cloneProto,omitempty"`
SSHPort int `yaml:"sshPort,omitempty"`
SSHKeyPath string `yaml:"sshKeyPath,omitempty"` // SSH private key file path
SSHKeyContent string `yaml:"sshKeyContent,omitempty"` // SSH private key content
Branch *BranchConfig `yaml:"branch,omitempty"`
// Integration is an opt-in controller-owned integration policy. It is read
// only when a caller explicitly selects this workspace config; it is never
// inherited by a repository project config.
Integration *IntegrationControl `yaml:"integration,omitempty"`
Fetch *FetchConfig `yaml:"fetch,omitempty"`
Pull *PullConfig `yaml:"pull,omitempty"`
Push *PushConfig `yaml:"push,omitempty"`
// Filter patterns (override parent patterns)
IncludePatterns []string `yaml:"includePatterns,omitempty"` // Include repos matching these patterns
ExcludePatterns []string `yaml:"excludePatterns,omitempty"` // Exclude repos matching these patterns
// Workspaces allows nested workspace definitions
Workspaces map[string]*Workspace `yaml:"workspaces,omitempty"`
Metadata *Metadata `yaml:"metadata,omitempty"`
// ChildConfigMode controls how child config files are generated during sync.
// Values: "repositories" (default), "workspaces", "none"
ChildConfigMode ChildConfigMode `yaml:"childConfigMode,omitempty"`
}
Workspace represents a named workspace in the hierarchy. Each workspace can sync from a forge source or manage existing git repos.
Example:
devbox:
path: ./devbox
configLink: ./gz-git/mydevbox.yaml
hooks:
before:
- mkdir -p logs
after:
- make setup
source:
provider: gitlab
org: devbox
includeSubgroups: true
subgroupMode: flat
sync:
strategy: pull
workspaces:
subproject:
path: ./subproject
type: git
func GetWorkspaceByName ¶
GetWorkspaceByName returns a workspace by name from the config.
type WorkspaceAccess ¶
type WorkspaceAccess string
WorkspaceAccess describes the remote-write contract for a workspace.
const ( // WorkspaceAccessReadWrite is the default: normal pull and push behavior. WorkspaceAccessReadWrite WorkspaceAccess = "read-write" // WorkspaceAccessReadOnly keeps the checkout current while forbidding push. WorkspaceAccessReadOnly WorkspaceAccess = "read-only" )
func WorkspacePushAccess ¶
func WorkspacePushAccess(repoPath string) (WorkspaceAccess, string, error)
WorkspacePushAccess resolves the nearest configured workspace declaration for repoPath. It walks through every ancestor config rather than stopping at a repository's own config: a parent workspace owns the access decision for a third-party child checkout.
func WorkspacePushAccessContext ¶
func WorkspacePushAccessContext(ctx context.Context, repoPath string) (WorkspaceAccess, string, error)
WorkspacePushAccessContext is WorkspacePushAccess with caller cancellation for the repository-local Git metadata query.
func (WorkspaceAccess) IsReadOnly ¶
func (a WorkspaceAccess) IsReadOnly() bool
IsReadOnly reports whether remote writes must be suppressed.
func (WorkspaceAccess) IsValid ¶
func (a WorkspaceAccess) IsValid() bool
IsValid reports whether the configured access mode is supported. Empty is the backward-compatible read-write default.
type WorkspaceType ¶
type WorkspaceType string
WorkspaceType represents the type of workspace.
const ( // WorkspaceTypeForge indicates the workspace syncs from a forge (GitLab/GitHub/Gitea) // This is the default when Source is defined. WorkspaceTypeForge WorkspaceType = "forge" // WorkspaceTypeGit indicates the workspace is a single git repository // No forge sync, just manages an existing repo. WorkspaceTypeGit WorkspaceType = "git" // WorkspaceTypeConfig indicates the workspace has a nested config file // Loads .gz-git.yaml from the workspace path. WorkspaceTypeConfig WorkspaceType = "config" )
func (WorkspaceType) IsValid ¶
func (t WorkspaceType) IsValid() bool
IsValid returns true if this is a valid workspace type.
func (WorkspaceType) Resolve ¶
func (t WorkspaceType) Resolve(hasSource bool) WorkspaceType
Resolve returns the effective type based on context. If type is empty, it's inferred from Source presence.