Introduces the `gitlocal` command-line tool for managing nested Git repositories. Includes the following main commands: - `convert`: Renames `.git` to `.gitlocal`, allowing a parent repository to ignore the converted child repository. Supports recursive scanning and dry-run options. Tracks converted repositories in a global configuration. - `revert`: Restores `.gitlocal` to `.git`. Includes an option to revert all tracked repositories. - `status`: Displays a list of all repositories currently tracked by `gitlocal`, showing their path, conversion time, and original remote/branch. Establishes internal modules for Git operations, configuration management, and recursive repository scanning. Adds a comprehensive test suite covering core command logic and utility functions. Initializes Go module and basic project `.gitignore`.
112 lines
2.3 KiB
Go
112 lines
2.3 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const (
|
|
ConfigVersion = "1"
|
|
ConfigFile = ".gitlocal.yml"
|
|
)
|
|
|
|
type Repo struct {
|
|
Path string `yaml:"path"`
|
|
ConvertedAt time.Time `yaml:"converted_at"`
|
|
OriginalRemote string `yaml:"original_remote,omitempty"`
|
|
OriginalBranch string `yaml:"original_branch,omitempty"`
|
|
}
|
|
|
|
type Config struct {
|
|
Version string `yaml:"version"`
|
|
Repos []Repo `yaml:"repos"`
|
|
}
|
|
|
|
// GetConfigPath returns the absolute path to the config file
|
|
func GetConfigPath() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get home directory: %w", err)
|
|
}
|
|
return filepath.Join(home, ConfigFile), nil
|
|
}
|
|
|
|
// Load reads the config file or creates a new empty config if it doesn't exist
|
|
func Load() (*Config, error) {
|
|
configPath, err := GetConfigPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If config doesn't exist, return empty config
|
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
|
return &Config{
|
|
Version: ConfigVersion,
|
|
Repos: []Repo{},
|
|
}, nil
|
|
}
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read config file: %w", err)
|
|
}
|
|
|
|
var cfg Config
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// Save writes the config to disk
|
|
func (c *Config) Save() error {
|
|
configPath, err := GetConfigPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := yaml.Marshal(c)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal config: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configPath, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write config file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddRepo adds a new repo to the config
|
|
func (c *Config) AddRepo(repo Repo) {
|
|
// Remove existing entry if it exists
|
|
c.RemoveRepo(repo.Path)
|
|
c.Repos = append(c.Repos, repo)
|
|
}
|
|
|
|
// RemoveRepo removes a repo from the config by path
|
|
func (c *Config) RemoveRepo(path string) {
|
|
filtered := []Repo{}
|
|
for _, r := range c.Repos {
|
|
if r.Path != path {
|
|
filtered = append(filtered, r)
|
|
}
|
|
}
|
|
c.Repos = filtered
|
|
}
|
|
|
|
// FindRepo returns a repo by path, or nil if not found
|
|
func (c *Config) FindRepo(path string) *Repo {
|
|
for _, r := range c.Repos {
|
|
if r.Path == path {
|
|
return &r
|
|
}
|
|
}
|
|
return nil
|
|
}
|