Skip to content

Commit

Permalink
feat: 🚀 support for raw git urls added
Browse files Browse the repository at this point in the history
  • Loading branch information
AkashRajpurohit committed Dec 4, 2024
1 parent afc5518 commit b3f9643
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 61 deletions.
72 changes: 52 additions & 20 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/AkashRajpurohit/git-sync/pkg/github"
"github.com/AkashRajpurohit/git-sync/pkg/gitlab"
"github.com/AkashRajpurohit/git-sync/pkg/logger"
"github.com/AkashRajpurohit/git-sync/pkg/raw"
ch "github.com/robfig/cron/v3"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -69,30 +70,51 @@ var rootCmd = &cobra.Command{
// Create backup directory if it doesn't exist
os.MkdirAll(cfg.BackupDir, os.ModePerm)

var client client.Client

switch cfg.Platform {
case "github":
client = github.NewGitHubClient(cfg.Token)
case "gitlab":
client = gitlab.NewGitlabClient(cfg.Server, cfg.Token)
case "bitbucket":
client = bitbucket.NewBitbucketClient(cfg.Username, cfg.Token)
case "forgejo":
client = forgejo.NewForgejoClient(cfg.Server, cfg.Token)
default:
logger.Fatalf("Platform %s not supported", cfg.Platform)
var platformClient client.Client
var hasRawURLs bool = len(cfg.RawGitURLs) > 0

// Only initialize platform client if raw URLs are not provided or if both are needed
if !hasRawURLs || (cfg.Username != "" && cfg.Token != "") {
switch cfg.Platform {
case "github":
platformClient = github.NewGitHubClient(cfg.Token)
case "gitlab":
platformClient = gitlab.NewGitlabClient(cfg.Server, cfg.Token)
case "bitbucket":
platformClient = bitbucket.NewBitbucketClient(cfg.Username, cfg.Token)
case "forgejo":
platformClient = forgejo.NewForgejoClient(cfg.Server, cfg.Token)
default:
if !hasRawURLs {
logger.Fatalf("Platform %s not supported", cfg.Platform)
}
}
}

logger.Info("Valid config found ✅")
logger.Infof("Using Platform: %s", cfg.Platform)
if platformClient != nil {
logger.Infof("Using Platform: %s", cfg.Platform)
}
if hasRawURLs {
logger.Infof("Found %d raw git URLs to sync", len(cfg.RawGitURLs))
}

if cfg.Cron != "" {
c := ch.New()
_, err := c.AddFunc(cfg.Cron, func() {
err := client.Sync(cfg)
if err != nil {
logger.Fatalf("Error syncing repositories: %s", err)
// First sync platform repositories if configured
if platformClient != nil {
if err := platformClient.Sync(cfg); err != nil {
logger.Errorf("Error syncing platform repositories: %s", err)
}
}

// Then sync raw git URLs if any
if hasRawURLs {
rawClient := raw.NewRawClient()
if err := rawClient.Sync(cfg); err != nil {
logger.Errorf("Error syncing raw repositories: %s", err)
}
}
})

Expand All @@ -106,9 +128,19 @@ var rootCmd = &cobra.Command{
// Wait indefinitely
select {}
} else {
err = client.Sync(cfg)
if err != nil {
logger.Fatalf("Error syncing repositories: %s", err)
// First sync platform repositories if configured
if platformClient != nil {
if err := platformClient.Sync(cfg); err != nil {
logger.Errorf("Error syncing platform repositories: %s", err)
}
}

// Then sync raw git URLs if any
if hasRawURLs {
rawClient := raw.NewRawClient()
if err := rawClient.Sync(cfg); err != nil {
logger.Errorf("Error syncing raw repositories: %s", err)
}
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Config struct {
Workspace string `mapstructure:"workspace"`
Cron string `mapstructure:"cron"`
CloneType string `mapstructure:"clone_type"`
RawGitURLs []string `mapstructure:"raw_git_urls"`
}

func expandPath(path string) string {
Expand Down Expand Up @@ -107,6 +108,7 @@ func SaveConfig(config Config, cfgFile string) error {
viper.Set("workspace", config.Workspace)
viper.Set("cron", config.Cron)
viper.Set("clone_type", config.CloneType)
viper.Set("raw_git_urls", config.RawGitURLs)

return viper.WriteConfig()
}
Expand All @@ -130,5 +132,6 @@ func GetInitialConfig() Config {
Cron: "",
BackupDir: GetBackupDir(""),
CloneType: "bare",
RawGitURLs: []string{},
}
}
78 changes: 62 additions & 16 deletions pkg/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,91 @@ package config

import (
"fmt"
"net/url"
"strings"

"github.com/robfig/cron/v3"
)

func ValidateConfig(cfg Config) error {
if cfg.Username == "" {
return fmt.Errorf("username cannot be empty")
// validateGitURL validates if the provided URL is a valid git repository URL
func validateGitURL(rawURL string) error {
// Handle SSH URLs (git@github.com:user/repo.git)
if strings.HasPrefix(rawURL, "git@") {
parts := strings.Split(rawURL, ":")
if len(parts) != 2 || !strings.HasSuffix(parts[1], ".git") {
return fmt.Errorf("invalid SSH git URL format: %s", rawURL)
}
return nil
}

if cfg.Token == "" {
return fmt.Errorf("token cannot be empty. See here: https://github.com/AkashRajpurohit/git-sync/wiki/Configuration")
// Handle HTTPS URLs
u, err := url.Parse(rawURL)
if err != nil {
return fmt.Errorf("invalid git URL: %s", rawURL)
}

if cfg.BackupDir == "" {
return fmt.Errorf("backup directory cannot be empty")
if u.Scheme != "http" && u.Scheme != "https" {
return fmt.Errorf("git URL must use http or https protocol: %s", rawURL)
}

if cfg.Server.Domain == "" {
return fmt.Errorf("server domain cannot be empty")
if !strings.HasSuffix(u.Path, ".git") {
return fmt.Errorf("git URL must end with .git: %s", rawURL)
}

if cfg.Server.Protocol != "https" && cfg.Server.Protocol != "http" {
return fmt.Errorf("server protocol can only be http or https")
return nil
}

func ValidateConfig(cfg Config) error {
// Validate backup directory (required for all cases)
if cfg.BackupDir == "" {
return fmt.Errorf("backup directory cannot be empty")
}

if cfg.Platform == "bitbucket" && cfg.Workspace == "" {
return fmt.Errorf("workspace cannot be empty for bitbucket")
// Validate clone type (required for all cases)
if cfg.CloneType != "bare" && cfg.CloneType != "full" && cfg.CloneType != "mirror" && cfg.CloneType != "shallow" {
return fmt.Errorf("clone_type can only be `bare`, `full`, `mirror` or `shallow`")
}

// Validate cron if provided
if cfg.Cron != "" {
_, err := cron.ParseStandard(cfg.Cron)
if err != nil {
return fmt.Errorf("invalid cron expression %s", cfg.Cron)
}
}

// validate that clone_type can only be `bare`, `full`, `mirror` or `shallow`
if cfg.CloneType != "bare" && cfg.CloneType != "full" && cfg.CloneType != "mirror" && cfg.CloneType != "shallow" {
return fmt.Errorf("clone_type can only be `bare`, `full`, `mirror` or `shallow`")
// Validate raw git URLs if provided
for _, url := range cfg.RawGitURLs {
if err := validateGitURL(url); err != nil {
return err
}
}

// If there are no raw git URLs, validate platform-specific configuration
if len(cfg.RawGitURLs) == 0 {
// Username is required for platform-specific sync
if cfg.Username == "" {
return fmt.Errorf("username cannot be empty when no raw git URLs are provided")
}

// Token is required for platform-specific sync
if cfg.Token == "" {
return fmt.Errorf("token cannot be empty when no raw git URLs are provided. See here: https://github.com/AkashRajpurohit/git-sync/wiki/Configuration")
}

// 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")
}

if cfg.Server.Protocol != "https" && cfg.Server.Protocol != "http" {
return fmt.Errorf("server protocol can only be http or https")
}

// Workspace is required only for Bitbucket
if cfg.Platform == "bitbucket" && cfg.Workspace == "" {
return fmt.Errorf("workspace cannot be empty for bitbucket")
}
}

return nil
Expand Down
Loading

0 comments on commit b3f9643

Please sign in to comment.