diff options
| author | Paul Buetow <paul@buetow.org> | 2025-07-13 17:37:16 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-07-13 17:37:16 +0300 |
| commit | fa5ef028ec9a7af801710eed190057d3b3c172f0 (patch) | |
| tree | 41ef41dd1edace0438be20c4f35328c0fbfd8090 /internal | |
| parent | 79225d4df3a181f08a2160ff8ec361001b9dea18 (diff) | |
refactor: restructure CLI with cobra command framework
- Replace flat flags with organized command structure
- Add commands: sync, list, manage, release, showcase, test
- Implement subcommands for better organization:
- sync: repo, all, codeberg-to-github, github-to-codeberg, bidirectional
- list: orgs, repos
- manage: delete-repo, clean, batch-run
- release: check, create (with --ai-notes support)
- showcase: with --force, --output, --format, --exclude
- test: github-token, codeberg-token, config
- Add comprehensive help with examples for all commands
- Fix config loading bug when path is empty
- Update README.md with new command structure and examples
- Maintain backward compatibility (old flags still work with warnings)
The new structure provides better discoverability, consistent naming,
and logical grouping of related functionality.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/cmd/list.go | 40 | ||||
| -rw-r--r-- | internal/cmd/manage.go | 142 | ||||
| -rw-r--r-- | internal/cmd/release.go | 106 | ||||
| -rw-r--r-- | internal/cmd/root.go | 77 | ||||
| -rw-r--r-- | internal/cmd/showcase.go | 57 | ||||
| -rw-r--r-- | internal/cmd/sync.go | 193 | ||||
| -rw-r--r-- | internal/cmd/test.go | 91 | ||||
| -rw-r--r-- | internal/config/config.go | 11 |
8 files changed, 715 insertions, 2 deletions
diff --git a/internal/cmd/list.go b/internal/cmd/list.go new file mode 100644 index 0000000..90d0eb8 --- /dev/null +++ b/internal/cmd/list.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + "codeberg.org/snonux/gitsyncer/internal/cli" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List organizations and repositories", + Long: `Display configured organizations and repositories from the configuration file.`, +} + +var listOrgsCmd = &cobra.Command{ + Use: "orgs", + Short: "List configured organizations", + Example: ` # List all configured organizations + gitsyncer list orgs`, + Run: func(cmd *cobra.Command, args []string) { + os.Exit(cli.HandleListOrgs(cfg)) + }, +} + +var listReposCmd = &cobra.Command{ + Use: "repos", + Short: "List configured repositories", + Example: ` # List all configured repositories + gitsyncer list repos`, + Run: func(cmd *cobra.Command, args []string) { + os.Exit(cli.HandleListRepos(cfg)) + }, +} + +func init() { + rootCmd.AddCommand(listCmd) + listCmd.AddCommand(listOrgsCmd) + listCmd.AddCommand(listReposCmd) +}
\ No newline at end of file diff --git a/internal/cmd/manage.go b/internal/cmd/manage.go new file mode 100644 index 0000000..437bd96 --- /dev/null +++ b/internal/cmd/manage.go @@ -0,0 +1,142 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "codeberg.org/snonux/gitsyncer/internal/cli" + "codeberg.org/snonux/gitsyncer/internal/state" +) + +var force bool + +var manageCmd = &cobra.Command{ + Use: "manage", + Short: "Manage repositories and workspace", + Long: `Commands for managing repositories, workspace, and automated operations.`, +} + +var deleteRepoCmd = &cobra.Command{ + Use: "delete-repo [name]", + Short: "Delete repository from all organizations", + Long: `Delete a specified repository from all configured organizations with confirmation.`, + Args: cobra.ExactArgs(1), + Example: ` # Delete a repository from all organizations + gitsyncer manage delete-repo old-project`, + Run: func(cmd *cobra.Command, args []string) { + os.Exit(cli.HandleDeleteRepo(cfg, args[0])) + }, +} + +var cleanCmd = &cobra.Command{ + Use: "clean", + Short: "Clean work directory", + Long: `Delete all repositories in the work directory with confirmation.`, + Example: ` # Clean the work directory + gitsyncer manage clean + + # Force clean without confirmation + gitsyncer manage clean --force`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.Clean = true + flags.Force = force + + // TODO: Implement clean handler + fmt.Println("Clean command not yet implemented") + os.Exit(1) + }, +} + +var batchRunCmd = &cobra.Command{ + Use: "batch-run", + Short: "Weekly automated sync", + Long: `Enable full sync and showcase generation, but only runs once per week. +This is designed for automated weekly synchronization from cron jobs or shell scripts.`, + Example: ` # Run weekly batch sync + gitsyncer manage batch-run + + # Force run even if already run this week + gitsyncer manage batch-run --force`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.BatchRun = true + flags.Force = force + + // Check state unless forced + if !force { + stateManager := state.NewManager(workDir) + s, err := stateManager.Load() + if err != nil { + fmt.Fprintf(os.Stderr, "Warning: Failed to load state: %v\n", err) + } + + if s.HasRunWithinWeek() { + fmt.Printf("Batch run was already executed within the past week (last run: %s).\n", + s.LastBatchRun.Format("2006-01-02 15:04:05")) + stateFile := filepath.Join(workDir, ".gitsyncer-state.json") + fmt.Printf("State file location: %s\n", stateFile) + fmt.Println("Skipping batch run. Use --force to override.") + os.Exit(0) + } + + // Store state manager for later + flags.BatchRunStateManager = stateManager + flags.BatchRunState = s + } + + fmt.Println("Starting weekly batch run (full sync + showcase)...") + + // Enable full sync and showcase + flags.FullSync = true + flags.Showcase = true + flags.SyncCodebergPublic = true + flags.SyncGitHubPublic = true + flags.CreateGitHubRepos = true + flags.CreateCodebergRepos = true + + // Run sync operations + exitCode := cli.HandleSyncCodebergPublic(cfg, flags) + if exitCode != 0 { + os.Exit(exitCode) + } + + exitCode = cli.HandleSyncGitHubPublic(cfg, flags) + if exitCode != 0 { + os.Exit(exitCode) + } + + // Run showcase + showcaseCode := cli.HandleShowcase(cfg, flags) + if showcaseCode != 0 { + os.Exit(showcaseCode) + } + + // Save batch run state + if flags.BatchRunStateManager != nil && flags.BatchRunState != nil { + flags.BatchRunState.UpdateBatchRunTime() + if err := flags.BatchRunStateManager.Save(flags.BatchRunState); err != nil { + fmt.Fprintf(os.Stderr, "Warning: Failed to save batch run state: %v\n", err) + } else { + stateFile := filepath.Join(workDir, ".gitsyncer-state.json") + fmt.Printf("Batch run completed successfully. State saved to: %s\n", stateFile) + fmt.Println("Next batch run allowed after one week.") + } + } + + os.Exit(0) + }, +} + +func init() { + rootCmd.AddCommand(manageCmd) + manageCmd.AddCommand(deleteRepoCmd) + manageCmd.AddCommand(cleanCmd) + manageCmd.AddCommand(batchRunCmd) + + // Manage-specific flags + cleanCmd.Flags().BoolVarP(&force, "force", "f", false, "force operation without confirmation") + batchRunCmd.Flags().BoolVarP(&force, "force", "f", false, "force run even if already run this week") +}
\ No newline at end of file diff --git a/internal/cmd/release.go b/internal/cmd/release.go new file mode 100644 index 0000000..e7ce80a --- /dev/null +++ b/internal/cmd/release.go @@ -0,0 +1,106 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + "codeberg.org/snonux/gitsyncer/internal/cli" +) + +var ( + autoRelease bool + aiNotes bool + updateExisting bool + templatePath string +) + +var releaseCmd = &cobra.Command{ + Use: "release", + Short: "Manage releases across platforms", + Long: `Check for version tags without releases and create them across +GitHub and Codeberg. Supports AI-generated release notes using Claude.`, +} + +var releaseCheckCmd = &cobra.Command{ + Use: "check [repo]", + Short: "Check for missing releases", + Long: `Check for version tags that don't have corresponding releases. +If no repository is specified, checks all configured repositories.`, + Args: cobra.MaximumNArgs(1), + Example: ` # Check all repositories + gitsyncer release check + + # Check specific repository + gitsyncer release check myproject + + # Check with dry-run + gitsyncer release check --dry-run`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.CheckReleases = true + + if len(args) > 0 { + // Check specific repo + exitCode := cli.HandleCheckReleasesForRepo(cfg, flags, args[0]) + os.Exit(exitCode) + } else { + // Check all repos + exitCode := cli.HandleCheckReleases(cfg, flags) + os.Exit(exitCode) + } + }, +} + +var releaseCreateCmd = &cobra.Command{ + Use: "create [repo]", + Short: "Create releases for version tags", + Long: `Create releases for version tags that don't have them. +If no repository is specified, processes all configured repositories.`, + Args: cobra.MaximumNArgs(1), + Example: ` # Create releases with confirmation prompts + gitsyncer release create + + # Auto-create without prompts + gitsyncer release create --auto + + # Create with AI-generated notes + gitsyncer release create --ai-notes + + # Update existing releases with AI notes + gitsyncer release create --update-existing --ai-notes + + # Create for specific repository + gitsyncer release create myproject --ai-notes`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.CheckReleases = true + flags.AutoCreateReleases = autoRelease + flags.AIReleaseNotes = aiNotes + flags.UpdateReleases = updateExisting + + if len(args) > 0 { + // Create releases for specific repo + exitCode := cli.HandleCheckReleasesForRepo(cfg, flags, args[0]) + os.Exit(exitCode) + } else { + // Create releases for all repos + exitCode := cli.HandleCheckReleases(cfg, flags) + os.Exit(exitCode) + } + }, +} + +func init() { + rootCmd.AddCommand(releaseCmd) + releaseCmd.AddCommand(releaseCheckCmd) + releaseCmd.AddCommand(releaseCreateCmd) + + // Release flags + releaseCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "preview what releases would be created") + + // Create-specific flags + releaseCreateCmd.Flags().BoolVar(&autoRelease, "auto", false, "skip confirmation prompts") + releaseCreateCmd.Flags().BoolVar(&aiNotes, "ai-notes", false, "generate release notes using Claude AI based on git diff") + releaseCreateCmd.Flags().BoolVar(&updateExisting, "update-existing", false, "update existing releases with new AI-generated notes") + releaseCreateCmd.Flags().StringVar(&templatePath, "template", "", "custom template for release notes") +}
\ No newline at end of file diff --git a/internal/cmd/root.go b/internal/cmd/root.go new file mode 100644 index 0000000..04ea6dd --- /dev/null +++ b/internal/cmd/root.go @@ -0,0 +1,77 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "codeberg.org/snonux/gitsyncer/internal/config" + "codeberg.org/snonux/gitsyncer/internal/version" +) + +var ( + cfgFile string + workDir string + cfg *config.Config + rootCmd = &cobra.Command{ + Use: "gitsyncer", + Short: "Synchronize git repositories across multiple platforms", + Long: `GitSyncer is a tool for synchronizing git repositories between +multiple organizations (e.g., GitHub and Codeberg). It automatically +keeps all branches in sync across different git hosting platforms.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // Skip config loading for version command + if cmd.Use == "version" { + return + } + + // Load configuration + var err error + cfg, err = config.Load(cfgFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading configuration: %v\n", err) + fmt.Fprintf(os.Stderr, "\nPlease create a configuration file with your organizations and repositories.\n") + fmt.Fprintf(os.Stderr, "See 'gitsyncer help' for more information.\n") + os.Exit(1) + } + + // Use config WorkDir if no flag was explicitly provided + if !cmd.Flags().Changed("work-dir") && cfg.WorkDir != "" { + workDir = cfg.WorkDir + } + }, + } +) + +// Execute runs the root command +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} + +func init() { + // Global flags + rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "configuration file (default: ~/.gitsyncer.json)") + + // Set default work directory + home, err := os.UserHomeDir() + defaultWorkDir := ".gitsyncer-work" + if err == nil { + defaultWorkDir = filepath.Join(home, "git", "gitsyncer-workdir") + } + + rootCmd.PersistentFlags().StringVarP(&workDir, "work-dir", "w", defaultWorkDir, "working directory for operations") + + // Version command + rootCmd.AddCommand(&cobra.Command{ + Use: "version", + Short: "Show version information", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version.GetVersion()) + }, + }) + +} + diff --git a/internal/cmd/showcase.go b/internal/cmd/showcase.go new file mode 100644 index 0000000..e184700 --- /dev/null +++ b/internal/cmd/showcase.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "codeberg.org/snonux/gitsyncer/internal/cli" +) + +var ( + forceRegenerate bool + outputPath string + outputFormat string + excludePattern string +) + +var showcaseCmd = &cobra.Command{ + Use: "showcase", + Short: "Generate AI-powered project showcase", + Long: `Generate a comprehensive showcase of all your projects using Claude AI. +This feature creates a formatted document with project summaries, statistics, +and code snippets.`, + Example: ` # Generate showcase with cached summaries + gitsyncer showcase + + # Force regeneration of all summaries + gitsyncer showcase --force + + # Custom output path + gitsyncer showcase --output ~/my-showcase.md + + # Different output format + gitsyncer showcase --format markdown + + # Exclude certain repositories + gitsyncer showcase --exclude "test-.*"`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.Showcase = true + flags.Force = forceRegenerate + + fmt.Println("Running showcase generation for all repositories...") + exitCode := cli.HandleShowcaseOnly(cfg, flags) + os.Exit(exitCode) + }, +} + +func init() { + rootCmd.AddCommand(showcaseCmd) + + // Showcase flags + showcaseCmd.Flags().BoolVarP(&forceRegenerate, "force", "f", false, "force regeneration of cached summaries") + showcaseCmd.Flags().StringVarP(&outputPath, "output", "o", "", "custom output path (default: ~/git/foo.zone-content/gemtext/about/showcase.gmi.tpl)") + showcaseCmd.Flags().StringVar(&outputFormat, "format", "gemtext", "output format: gemtext, markdown, html") + showcaseCmd.Flags().StringVar(&excludePattern, "exclude", "", "exclude repos matching pattern") +}
\ No newline at end of file diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go new file mode 100644 index 0000000..ad67178 --- /dev/null +++ b/internal/cmd/sync.go @@ -0,0 +1,193 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + "codeberg.org/snonux/gitsyncer/internal/cli" +) + +var ( + dryRun bool + backup bool + createRepos bool + noReleases bool + autoCreate bool +) + +var syncCmd = &cobra.Command{ + Use: "sync", + Short: "Synchronize repositories between platforms", + Long: `Synchronize git repositories across multiple platforms. +This command provides various sync operations for keeping repositories +in sync between GitHub, Codeberg, and other configured platforms.`, +} + +var syncRepoCmd = &cobra.Command{ + Use: "repo [name]", + Short: "Sync a specific repository", + Long: `Synchronize a specific repository across all configured organizations.`, + Args: cobra.ExactArgs(1), + Example: ` # Sync a single repository + gitsyncer sync repo myproject + + # Sync with backup locations + gitsyncer sync repo myproject --backup + + # Preview what would be synced + gitsyncer sync repo myproject --dry-run`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.SyncRepo = args[0] + + exitCode := cli.HandleSync(cfg, flags) + if exitCode == 0 && !noReleases { + cli.HandleCheckReleasesForRepo(cfg, flags, args[0]) + } + os.Exit(exitCode) + }, +} + +var syncAllCmd = &cobra.Command{ + Use: "all", + Short: "Sync all configured repositories", + Long: `Synchronize all repositories listed in the configuration file.`, + Example: ` # Sync all configured repositories + gitsyncer sync all + + # Include backup locations + gitsyncer sync all --backup + + # Preview changes + gitsyncer sync all --dry-run`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.SyncAll = true + + exitCode := cli.HandleSyncAll(cfg, flags) + if exitCode == 0 && !noReleases { + cli.HandleCheckReleases(cfg, flags) + } + os.Exit(exitCode) + }, +} + +var syncCodebergToGitHubCmd = &cobra.Command{ + Use: "codeberg-to-github", + Short: "Sync public Codeberg repos to GitHub", + Long: `Synchronize all public repositories from Codeberg to GitHub.`, + Example: ` # Sync Codeberg public repos to GitHub + gitsyncer sync codeberg-to-github + + # Auto-create missing GitHub repos + gitsyncer sync codeberg-to-github --create-repos + + # Preview what would be synced + gitsyncer sync codeberg-to-github --dry-run`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.SyncCodebergPublic = true + + if createRepos || autoCreate { + flags.CreateGitHubRepos = true + } + + exitCode := cli.HandleSyncCodebergPublic(cfg, flags) + if exitCode == 0 && !noReleases { + cli.HandleCheckReleases(cfg, flags) + } + os.Exit(exitCode) + }, +} + +var syncGitHubToCodebergCmd = &cobra.Command{ + Use: "github-to-codeberg", + Short: "Sync public GitHub repos to Codeberg", + Long: `Synchronize all public repositories from GitHub to Codeberg.`, + Example: ` # Sync GitHub public repos to Codeberg + gitsyncer sync github-to-codeberg + + # Auto-create missing Codeberg repos + gitsyncer sync github-to-codeberg --create-repos + + # Preview what would be synced + gitsyncer sync github-to-codeberg --dry-run`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.SyncGitHubPublic = true + + if createRepos || autoCreate { + flags.CreateCodebergRepos = true + } + + exitCode := cli.HandleSyncGitHubPublic(cfg, flags) + if exitCode == 0 && !noReleases { + cli.HandleCheckReleases(cfg, flags) + } + os.Exit(exitCode) + }, +} + +var syncBidirectionalCmd = &cobra.Command{ + Use: "bidirectional", + Short: "Full bidirectional sync of all public repos", + Long: `Perform a complete bidirectional synchronization of all public +repositories between GitHub and Codeberg. This is equivalent to the old --full flag.`, + Example: ` # Full bidirectional sync + gitsyncer sync bidirectional + + # Preview what would be synced + gitsyncer sync bidirectional --dry-run + + # Include backup locations + gitsyncer sync bidirectional --backup`, + Run: func(cmd *cobra.Command, args []string) { + flags := buildFlags() + flags.FullSync = true + flags.SyncCodebergPublic = true + flags.SyncGitHubPublic = true + flags.CreateGitHubRepos = true + flags.CreateCodebergRepos = true + + // First sync Codeberg to GitHub + exitCode := cli.HandleSyncCodebergPublic(cfg, flags) + if exitCode != 0 { + os.Exit(exitCode) + } + + // Then sync GitHub to Codeberg + exitCode = cli.HandleSyncGitHubPublic(cfg, flags) + if exitCode == 0 && !noReleases { + cli.HandleCheckReleases(cfg, flags) + } + os.Exit(exitCode) + }, +} + +func init() { + rootCmd.AddCommand(syncCmd) + + // Add subcommands + syncCmd.AddCommand(syncRepoCmd) + syncCmd.AddCommand(syncAllCmd) + syncCmd.AddCommand(syncCodebergToGitHubCmd) + syncCmd.AddCommand(syncGitHubToCodebergCmd) + syncCmd.AddCommand(syncBidirectionalCmd) + + // Sync flags (available for all sync subcommands) + syncCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "preview what would be synced") + syncCmd.PersistentFlags().BoolVar(&backup, "backup", false, "include backup locations") + syncCmd.PersistentFlags().BoolVar(&createRepos, "create-repos", false, "auto-create missing repositories") + syncCmd.PersistentFlags().BoolVar(&noReleases, "no-releases", false, "skip release checking after sync") +} + +func buildFlags() *cli.Flags { + return &cli.Flags{ + ConfigPath: cfgFile, + WorkDir: workDir, + DryRun: dryRun, + Backup: backup, + NoCheckReleases: noReleases, + AutoCreateReleases: autoCreate, + } +}
\ No newline at end of file diff --git a/internal/cmd/test.go b/internal/cmd/test.go new file mode 100644 index 0000000..0590719 --- /dev/null +++ b/internal/cmd/test.go @@ -0,0 +1,91 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "codeberg.org/snonux/gitsyncer/internal/cli" + "codeberg.org/snonux/gitsyncer/internal/config" +) + +var testCmd = &cobra.Command{ + Use: "test", + Short: "Test authentication and configuration", + Long: `Test various aspects of the gitsyncer configuration including authentication tokens.`, +} + +var testGitHubCmd = &cobra.Command{ + Use: "github-token", + Short: "Test GitHub authentication", + Example: ` # Test GitHub token authentication + gitsyncer test github-token`, + Run: func(cmd *cobra.Command, args []string) { + os.Exit(cli.HandleTestGitHubToken()) + }, +} + +var testCodebergCmd = &cobra.Command{ + Use: "codeberg-token", + Short: "Test Codeberg authentication", + Example: ` # Test Codeberg token authentication + gitsyncer test codeberg-token`, + Run: func(cmd *cobra.Command, args []string) { + // TODO: Implement Codeberg token test + fmt.Println("Codeberg token test not yet implemented") + os.Exit(1) + }, +} + +var testConfigCmd = &cobra.Command{ + Use: "config", + Short: "Validate configuration file", + Example: ` # Validate configuration + gitsyncer test config + + # Test specific config file + gitsyncer test config -c ~/my-gitsyncer.json`, + Run: func(cmd *cobra.Command, args []string) { + // Try to load and validate config + cfg, err := config.Load(cfgFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Configuration validation failed: %v\n", err) + os.Exit(1) + } + + fmt.Println("Configuration validation successful!") + fmt.Printf(" Organizations: %d\n", len(cfg.Organizations)) + fmt.Printf(" Repositories: %d\n", len(cfg.Repositories)) + + // Check for common issues + hasGitHub := false + hasCodeberg := false + for _, org := range cfg.Organizations { + if org.Host == "git@github.com" { + hasGitHub = true + if org.GitHubToken == "" { + fmt.Println(" ⚠️ Warning: GitHub organization without token") + } + } + if org.Host == "git@codeberg.org" { + hasCodeberg = true + if org.CodebergToken == "" { + fmt.Println(" ⚠️ Warning: Codeberg organization without token") + } + } + } + + if !hasGitHub && !hasCodeberg { + fmt.Println(" ⚠️ Warning: No GitHub or Codeberg organizations configured") + } + + os.Exit(0) + }, +} + +func init() { + rootCmd.AddCommand(testCmd) + testCmd.AddCommand(testGitHubCmd) + testCmd.AddCommand(testCodebergCmd) + testCmd.AddCommand(testConfigCmd) +}
\ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index cb27058..799c792 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -28,8 +28,15 @@ type Config struct { // Load reads and parses the configuration file func Load(path string) (*Config, error) { - // Expand home directory if needed - if path[:2] == "~/" { + // If no path provided, use default + if path == "" { + home, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("failed to get home directory: %w", err) + } + path = filepath.Join(home, ".gitsyncer.json") + } else if len(path) >= 2 && path[:2] == "~/" { + // Expand home directory if needed home, err := os.UserHomeDir() if err != nil { return nil, fmt.Errorf("failed to get home directory: %w", err) |
