From 60691a6fb610cb7f7290d6ab3a26bc74f95af611 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 23 Jun 2025 17:24:20 +0300 Subject: Add configuration file support with organization list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create config package with JSON parsing support - Define Organization struct with host and name - Add config file auto-detection in common locations - Add --config/-c flag for custom config path - Add --list-orgs flag to display configured organizations - Create example configuration file - Add comprehensive .gitignore Configuration supports multiple git organizations for future sync functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 39 ++++++++++++++++++++++ cmd/gitsyncer/main.go | 76 ++++++++++++++++++++++++++++++++++++++++-- gitsyncer | Bin 1523896 -> 3066401 bytes gitsyncer.example.json | 12 +++++++ internal/config/config.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 gitsyncer.example.json create mode 100644 internal/config/config.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7101fd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Binaries +gitsyncer +gitsyncer-* + +# Configuration files (except examples) +gitsyncer.json +.gitsyncer.json +*.json +!*.example.json + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Go build artifacts +*.exe +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out + +# Dependency directories +vendor/ + +# Go workspace file +go.work +go.work.sum + +# OS files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/cmd/gitsyncer/main.go b/cmd/gitsyncer/main.go index 3a38d32..d859747 100644 --- a/cmd/gitsyncer/main.go +++ b/cmd/gitsyncer/main.go @@ -3,23 +3,93 @@ package main import ( "flag" "fmt" + "log" "os" + "path/filepath" + "github.com/paul/gitsyncer/internal/config" "github.com/paul/gitsyncer/internal/version" ) func main() { - var versionFlag bool + var ( + versionFlag bool + configPath string + listOrgs bool + ) + + // Define command line flags flag.BoolVar(&versionFlag, "version", false, "print version information") flag.BoolVar(&versionFlag, "v", false, "print version information (short)") + flag.StringVar(&configPath, "config", "", "path to configuration file") + flag.StringVar(&configPath, "c", "", "path to configuration file (short)") + flag.BoolVar(&listOrgs, "list-orgs", false, "list configured organizations") flag.Parse() + // Handle version flag if versionFlag { fmt.Println(version.GetVersion()) os.Exit(0) } + // Determine config file path + if configPath == "" { + // Try default locations + home, err := os.UserHomeDir() + if err != nil { + log.Fatal("Failed to get home directory:", err) + } + + // Check common config locations + configLocations := []string{ + filepath.Join(".", "gitsyncer.json"), + filepath.Join(home, ".config", "gitsyncer", "config.json"), + filepath.Join(home, ".gitsyncer.json"), + } + + for _, loc := range configLocations { + if _, err := os.Stat(loc); err == nil { + configPath = loc + break + } + } + + if configPath == "" { + fmt.Println("No configuration file found. Please create one of:") + for _, loc := range configLocations { + fmt.Printf(" - %s\n", loc) + } + fmt.Println("\nOr specify a config file with --config flag") + fmt.Println("\nExample configuration:") + fmt.Println(`{ + "organizations": [ + {"host": "git@github.com", "name": "myorg"}, + {"host": "git@codeberg.org", "name": "myorg"} + ] +}`) + os.Exit(1) + } + } + + // Load configuration + cfg, err := config.Load(configPath) + if err != nil { + log.Fatal("Failed to load configuration:", err) + } + + fmt.Printf("Loaded configuration from: %s\n", configPath) + + // Handle list organizations flag + if listOrgs { + fmt.Println("\nConfigured organizations:") + for _, org := range cfg.Organizations { + fmt.Printf(" - %s\n", org.GetGitURL()) + } + os.Exit(0) + } + // TODO: Implement main gitsyncer functionality - fmt.Println("gitsyncer - Git repository synchronization tool") - fmt.Println("Use --version to display version information") + fmt.Println("\ngitsyncer - Git repository synchronization tool") + fmt.Printf("Configured with %d organization(s)\n", len(cfg.Organizations)) + fmt.Println("\nUse --list-orgs to display configured organizations") } \ No newline at end of file diff --git a/gitsyncer b/gitsyncer index 1f897cd..9134faa 100755 Binary files a/gitsyncer and b/gitsyncer differ diff --git a/gitsyncer.example.json b/gitsyncer.example.json new file mode 100644 index 0000000..3170286 --- /dev/null +++ b/gitsyncer.example.json @@ -0,0 +1,12 @@ +{ + "organizations": [ + { + "host": "git@codeberg.org", + "name": "snonux" + }, + { + "host": "git@github.com", + "name": "snonux" + } + ] +} \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..91c107a --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,83 @@ +package config + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +// Organization represents a git organization with its host and name +type Organization struct { + Host string `json:"host"` + Name string `json:"name"` +} + +// Config holds the application configuration +type Config struct { + Organizations []Organization `json:"organizations"` +} + +// Load reads and parses the configuration file +func Load(path string) (*Config, error) { + // Expand home directory if needed + if path[:2] == "~/" { + home, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("failed to get home directory: %w", err) + } + path = filepath.Join(home, path[2:]) + } + + // Read config file + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) + } + + // Parse JSON + var cfg Config + if err := json.Unmarshal(data, &cfg); err != nil { + return nil, fmt.Errorf("failed to parse config: %w", err) + } + + // Validate configuration + if err := cfg.Validate(); err != nil { + return nil, fmt.Errorf("invalid configuration: %w", err) + } + + return &cfg, nil +} + +// Validate checks if the configuration is valid +func (c *Config) Validate() error { + if len(c.Organizations) == 0 { + return fmt.Errorf("no organizations configured") + } + + for i, org := range c.Organizations { + if org.Host == "" { + return fmt.Errorf("organization %d: missing host", i) + } + if org.Name == "" { + return fmt.Errorf("organization %d: missing name", i) + } + } + + return nil +} + +// GetGitURL returns the git URL for an organization +func (o *Organization) GetGitURL() string { + return fmt.Sprintf("%s:%s", o.Host, o.Name) +} + +// FindOrganization finds an organization by host +func (c *Config) FindOrganization(host string) *Organization { + for _, org := range c.Organizations { + if org.Host == host { + return &org + } + } + return nil +} \ No newline at end of file -- cgit v1.2.3