Adds initial gitlocal CLI and core functionality
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`.
This commit is contained in:
111
internal/config/config.go
Normal file
111
internal/config/config.go
Normal file
@@ -0,0 +1,111 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user