diff --git a/cmd/root.go b/cmd/root.go index f6942da..78eb6e4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -32,6 +32,8 @@ var rootCmd = &cobra.Command{ var cfg config.Config cfg, err := config.LoadConfig(cfgFile) + config.SetSensibleDefaults(&cfg) + if err != nil { logger.Debugf("Error in loading config file: ", err) logger.Info("Config file not found, creating a new one...") diff --git a/pkg/config/config.go b/pkg/config/config.go index 4e37a90..e11125c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -40,37 +40,6 @@ type Config struct { Retry RetryConfig `mapstructure:"retry"` } -// PreprocessConfig handles backward compatibility for token field -func PreprocessConfig(cfg *Config) { - // TODO: Remove these before v1.0.0 release - // If concurrency is not set, set it to 5 - if cfg.Concurrency == 0 { - logger.Warn("Concurrency is required but not set. Add the 'concurrency' field to the config file as mentioned in the docs: https://github.com/AkashRajpurohit/git-sync/wiki/Configuration. Setting it to 5.") - cfg.Concurrency = 5 - } - - // If no clone_type is not set in the config file, set it to bare - if cfg.CloneType == "" { - logger.Warn("Clone type is required but not set. Add the 'clone_type' field to the config file as mentioned in the docs: https://github.com/AkashRajpurohit/git-sync/wiki/Configuration. Setting it to 'bare'.") - cfg.CloneType = "bare" - } - - // If both are set, merge them with single token being first - if cfg.Token != "" && len(cfg.Tokens) > 0 { - logger.Warn("Both 'token' and 'tokens' fields are set. 'token' field is deprecated and will be merged with 'tokens'.") - cfg.Tokens = append([]string{cfg.Token}, cfg.Tokens...) - } - - // If single token is set and tokens array is empty, convert single token to array - if cfg.Token != "" && len(cfg.Tokens) == 0 { - logger.Warn("Using 'token' field is deprecated. Please use 'tokens' array instead.") - cfg.Tokens = []string{cfg.Token} - } - - // Clear the deprecated token field - cfg.Token = "" -} - func expandPath(path string) string { if strings.HasPrefix(path, "~/") { homeDir, _ := os.UserHomeDir() @@ -128,7 +97,6 @@ func LoadConfig(cfgFile string) (Config, error) { return config, err } - PreprocessConfig(&config) return config, nil } diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go new file mode 100644 index 0000000..c3ab6ee --- /dev/null +++ b/pkg/config/defaults.go @@ -0,0 +1,55 @@ +package config + +import "github.com/AkashRajpurohit/git-sync/pkg/logger" + +func SetSensibleDefaults(cfg *Config) { + if cfg.Platform != "" && (cfg.Server.Domain == "" || cfg.Server.Protocol == "") { + if cfg.Platform == "github" { + cfg.Server.Domain = "github.com" + cfg.Server.Protocol = "https" + } + + if cfg.Platform == "gitlab" { + cfg.Server.Domain = "gitlab.com" + cfg.Server.Protocol = "https" + } + + if cfg.Platform == "bitbucket" { + cfg.Server.Domain = "bitbucket.org" + cfg.Server.Protocol = "https" + } + + if cfg.Platform == "forgejo" { + cfg.Server.Domain = "v9.next.forgejo.org" + cfg.Server.Protocol = "https" + } + } + + // TODO: Remove these before v1.0.0 release + // If concurrency is not set, set it to 5 + if cfg.Concurrency == 0 { + logger.Warn("Concurrency is required but not set. Add the 'concurrency' field to the config file as mentioned in the docs: https://github.com/AkashRajpurohit/git-sync/wiki/Configuration. Setting it to 5.") + cfg.Concurrency = 5 + } + + // If no clone_type is not set in the config file, set it to bare + if cfg.CloneType == "" { + logger.Warn("Clone type is required but not set. Add the 'clone_type' field to the config file as mentioned in the docs: https://github.com/AkashRajpurohit/git-sync/wiki/Configuration. Setting it to 'bare'.") + cfg.CloneType = "bare" + } + + // If both are set, merge them with single token being first + if cfg.Token != "" && len(cfg.Tokens) > 0 { + logger.Warn("Both 'token' and 'tokens' fields are set. 'token' field is deprecated and will be merged with 'tokens'.") + cfg.Tokens = append([]string{cfg.Token}, cfg.Tokens...) + } + + // If single token is set and tokens array is empty, convert single token to array + if cfg.Token != "" && len(cfg.Tokens) == 0 { + logger.Warn("Using 'token' field is deprecated. Please use 'tokens' array instead.") + cfg.Tokens = []string{cfg.Token} + } + + // Clear the deprecated token field + cfg.Token = "" +} diff --git a/pkg/config/defaults_test.go b/pkg/config/defaults_test.go new file mode 100644 index 0000000..8d13d1d --- /dev/null +++ b/pkg/config/defaults_test.go @@ -0,0 +1,191 @@ +package config + +import ( + "testing" + + "github.com/AkashRajpurohit/git-sync/pkg/logger" +) + +func TestSetSensibleDefaults(t *testing.T) { + tests := []struct { + name string + cfg Config + expected Config + }{ + { + name: "GitHub platform defaults", + cfg: Config{ + Platform: "github", + }, + expected: Config{ + Platform: "github", + Server: Server{ + Domain: "github.com", + Protocol: "https", + }, + CloneType: "bare", + Concurrency: 5, + }, + }, + { + name: "GitLab platform defaults", + cfg: Config{ + Platform: "gitlab", + }, + expected: Config{ + Platform: "gitlab", + Server: Server{ + Domain: "gitlab.com", + Protocol: "https", + }, + CloneType: "bare", + Concurrency: 5, + }, + }, + { + name: "Bitbucket platform defaults", + cfg: Config{ + Platform: "bitbucket", + }, + expected: Config{ + Platform: "bitbucket", + Server: Server{ + Domain: "bitbucket.org", + Protocol: "https", + }, + CloneType: "bare", + Concurrency: 5, + }, + }, + { + name: "Forgejo platform defaults", + cfg: Config{ + Platform: "forgejo", + }, + expected: Config{ + Platform: "forgejo", + Server: Server{ + Domain: "v9.next.forgejo.org", + Protocol: "https", + }, + CloneType: "bare", + Concurrency: 5, + }, + }, + { + name: "Default concurrency when not set", + cfg: Config{ + Platform: "github", + Server: Server{ + Domain: "github.com", + Protocol: "https", + }, + }, + expected: Config{ + Platform: "github", + Server: Server{ + Domain: "github.com", + Protocol: "https", + }, + CloneType: "bare", + Concurrency: 5, + }, + }, + { + name: "Default clone type when not set", + cfg: Config{ + Platform: "github", + Server: Server{ + Domain: "github.com", + Protocol: "https", + }, + Concurrency: 5, + }, + expected: Config{ + Platform: "github", + Server: Server{ + Domain: "github.com", + Protocol: "https", + }, + CloneType: "bare", + Concurrency: 5, + }, + }, + { + name: "Merge single token with tokens array", + cfg: Config{ + Token: "single-token", + Tokens: []string{"token1", "token2"}, + }, + expected: Config{ + Tokens: []string{"single-token", "token1", "token2"}, + CloneType: "bare", + Concurrency: 5, + }, + }, + { + name: "Convert single token to tokens array", + cfg: Config{ + Token: "single-token", + }, + expected: Config{ + Tokens: []string{"single-token"}, + CloneType: "bare", + Concurrency: 5, + }, + }, + { + name: "Keep existing values if already set", + cfg: Config{ + Platform: "github", + CloneType: "mirror", + Concurrency: 10, + Server: Server{ + Domain: "custom.github.com", + Protocol: "ssh", + }, + }, + expected: Config{ + Platform: "github", + CloneType: "mirror", + Concurrency: 10, + Server: Server{ + Domain: "custom.github.com", + Protocol: "ssh", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger.InitLogger("fatal") + cfg := tt.cfg + SetSensibleDefaults(&cfg) + + if cfg.Server.Domain != tt.expected.Server.Domain { + t.Errorf("Server Domain = %v, want %v", cfg.Server.Domain, tt.expected.Server.Domain) + } + if cfg.Server.Protocol != tt.expected.Server.Protocol { + t.Errorf("Server Protocol = %v, want %v", cfg.Server.Protocol, tt.expected.Server.Protocol) + } + if cfg.CloneType != tt.expected.CloneType { + t.Errorf("CloneType = %v, want %v", cfg.CloneType, tt.expected.CloneType) + } + if cfg.Concurrency != tt.expected.Concurrency { + t.Errorf("Concurrency = %v, want %v", cfg.Concurrency, tt.expected.Concurrency) + } + if cfg.Token != "" { + t.Error("Token should be cleared after conversion") + } + if len(cfg.Tokens) != len(tt.expected.Tokens) { + t.Errorf("Tokens length = %v, want %v", len(cfg.Tokens), len(tt.expected.Tokens)) + } + for i, token := range tt.expected.Tokens { + if i < len(cfg.Tokens) && cfg.Tokens[i] != token { + t.Errorf("Token at index %d = %v, want %v", i, cfg.Tokens[i], token) + } + } + }) + } +} diff --git a/pkg/config/validate.go b/pkg/config/validate.go index 1d32c6d..1cbe785 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -79,6 +79,10 @@ func ValidateConfig(cfg Config) error { return fmt.Errorf("at least one token must be provided when no raw git URLs are provided. See here: https://github.com/AkashRajpurohit/git-sync/wiki/Configuration") } + if cfg.Platform != "github" && cfg.Platform != "gitlab" && cfg.Platform != "bitbucket" && cfg.Platform != "forgejo" { + return fmt.Errorf("platform can only be `github`, `gitlab`, `bitbucket` or `forgejo` when no raw git URLs are provided") + } + // Server configuration is required for platform-specific sync if cfg.Server.Domain == "" { return fmt.Errorf("server domain cannot be empty when no raw git URLs are provided") diff --git a/pkg/config/validate_test.go b/pkg/config/validate_test.go index b32277e..86d8884 100644 --- a/pkg/config/validate_test.go +++ b/pkg/config/validate_test.go @@ -63,6 +63,7 @@ func TestValidateConfig(t *testing.T) { BackupDir: "test", CloneType: "bare", Concurrency: 5, + Platform: "github", Server: Server{ Domain: "test", Protocol: "https", @@ -78,6 +79,7 @@ func TestValidateConfig(t *testing.T) { BackupDir: "test", CloneType: "bare", Concurrency: 5, + Platform: "github", Server: Server{ Domain: "test", Protocol: "https", @@ -93,6 +95,7 @@ func TestValidateConfig(t *testing.T) { BackupDir: "test", CloneType: "bare", Concurrency: 5, + Platform: "github", Server: Server{ Domain: "test", Protocol: "https", @@ -109,6 +112,7 @@ func TestValidateConfig(t *testing.T) { BackupDir: "test", CloneType: "bare", Concurrency: 5, + Platform: "github", Server: Server{ Domain: "test", Protocol: "https", @@ -123,6 +127,7 @@ func TestValidateConfig(t *testing.T) { BackupDir: "test", CloneType: "bare", Concurrency: 0, + Platform: "github", Server: Server{ Domain: "test", Protocol: "https", @@ -138,6 +143,7 @@ func TestValidateConfig(t *testing.T) { BackupDir: "test", CloneType: "bare", Concurrency: -1, + Platform: "github", Server: Server{ Domain: "test", Protocol: "https", @@ -153,6 +159,7 @@ func TestValidateConfig(t *testing.T) { BackupDir: "test", CloneType: "bare", Concurrency: 10, + Platform: "github", Server: Server{ Domain: "test", Protocol: "https", @@ -219,6 +226,7 @@ func TestValidateConfig(t *testing.T) { CloneType: "bare", Concurrency: 5, Tokens: []string{"token1"}, + Platform: "github", Server: Server{ Domain: "test", Protocol: "https", @@ -233,6 +241,7 @@ func TestValidateConfig(t *testing.T) { Tokens: []string{"token1"}, BackupDir: "test", CloneType: "bare", + Platform: "github", Concurrency: 5, Server: Server{ Protocol: "https", @@ -255,6 +264,17 @@ func TestValidateConfig(t *testing.T) { }, wantErr: true, }, + { + name: "Empty Platform", + cfg: Config{ + Username: "test", + Tokens: []string{"token1"}, + BackupDir: "test", + CloneType: "bare", + Concurrency: 5, + }, + wantErr: true, + }, { name: "Empty Workspace for Bitbucket with No Raw URLs", cfg: Config{ @@ -263,11 +283,11 @@ func TestValidateConfig(t *testing.T) { BackupDir: "test", CloneType: "bare", Concurrency: 5, + Platform: "bitbucket", Server: Server{ Domain: "test", Protocol: "https", }, - Platform: "bitbucket", Workspace: "", }, wantErr: true,