diff options
Diffstat (limited to 'internal/cli')
| -rw-r--r-- | internal/cli/description_cache.go | 38 | ||||
| -rw-r--r-- | internal/cli/description_sync.go | 113 | ||||
| -rw-r--r-- | internal/cli/sync_handlers.go | 142 |
3 files changed, 244 insertions, 49 deletions
diff --git a/internal/cli/description_cache.go b/internal/cli/description_cache.go new file mode 100644 index 0000000..1cfc951 --- /dev/null +++ b/internal/cli/description_cache.go @@ -0,0 +1,38 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +// loadDescriptionCache loads the per-repo canonical description cache +func loadDescriptionCache(workDir string) map[string]string { + cache := make(map[string]string) + cacheFile := filepath.Join(workDir, ".gitsyncer-descriptions-cache.json") + data, err := os.ReadFile(cacheFile) + if err != nil { + return cache + } + if err := json.Unmarshal(data, &cache); err != nil { + fmt.Printf("Warning: Failed to parse descriptions cache: %v\n", err) + return make(map[string]string) + } + fmt.Printf("Loaded descriptions cache with %d entries\n", len(cache)) + return cache +} + +// saveDescriptionCache saves the per-repo canonical description cache +func saveDescriptionCache(workDir string, cache map[string]string) error { + cacheFile := filepath.Join(workDir, ".gitsyncer-descriptions-cache.json") + data, err := json.MarshalIndent(cache, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal descriptions cache: %w", err) + } + if err := os.WriteFile(cacheFile, data, 0644); err != nil { + return fmt.Errorf("failed to write descriptions cache: %w", err) + } + return nil +} + diff --git a/internal/cli/description_sync.go b/internal/cli/description_sync.go new file mode 100644 index 0000000..ae275ce --- /dev/null +++ b/internal/cli/description_sync.go @@ -0,0 +1,113 @@ +package cli + +import ( + "fmt" + "strings" + + "codeberg.org/snonux/gitsyncer/internal/codeberg" + "codeberg.org/snonux/gitsyncer/internal/config" + "codeberg.org/snonux/gitsyncer/internal/github" +) + +// syncRepoDescriptions ensures both platforms have the canonical description +// Precedence: Codeberg > GitHub; if Codeberg empty and GitHub has one, use GitHub. +// knownCBDesc and knownGHDesc can be empty; the function fetches as needed. +func syncRepoDescriptions(cfg *config.Config, dryRun bool, repoName, knownCBDesc, knownGHDesc string, cache map[string]string) { + // Load orgs + ghOrg := cfg.FindGitHubOrg() + cbOrg := cfg.FindCodebergOrg() + + var ghClient *github.Client + var cbClient *codeberg.Client + if ghOrg != nil { + c := github.NewClient(ghOrg.GitHubToken, ghOrg.Name) + ghClient = &c + } + if cbOrg != nil { + c := codeberg.NewClient(cbOrg.Name, cbOrg.CodebergToken) + cbClient = &c + } + + // Get current descriptions (use known if provided) + cbDesc := strings.TrimSpace(knownCBDesc) + ghDesc := strings.TrimSpace(knownGHDesc) + var cbExists, ghExists bool + + if cbDesc == "" && cbClient != nil { + if repo, exists, err := cbClient.GetRepo(repoName); err == nil { + cbExists = exists + if exists { + cbDesc = strings.TrimSpace(repo.Description) + } + } else { + fmt.Printf(" Warning: Codeberg repo lookup failed: %v\n", err) + } + } else if cbClient != nil { + cbExists = true + } + + if ghClient != nil { + if ghDesc == "" || !ghExists { + if repo, exists, err := ghClient.GetRepo(repoName); err == nil { + ghExists = exists + if exists { + ghDesc = strings.TrimSpace(repo.Description) + } + } else { + fmt.Printf(" Warning: GitHub repo lookup failed: %v\n", err) + } + } + } + + // Determine canonical description + canonical := cbDesc + if canonical == "" { + canonical = ghDesc + } + canonical = strings.TrimSpace(canonical) + + // If nothing to sync, bail + if canonical == "" { + return + } + + // Update Codeberg if needed + if cbClient != nil && cbExists { + if cbDesc != canonical { + if dryRun { + fmt.Printf(" [DRY RUN] Would update Codeberg description for %s -> %q\n", repoName, canonical) + } else if cbClient.HasToken() { + if err := cbClient.UpdateRepoDescription(repoName, canonical); err != nil { + fmt.Printf(" Warning: Failed to update Codeberg description: %v\n", err) + } else { + fmt.Printf(" Updated Codeberg description for %s\n", repoName) + } + } else { + fmt.Println(" Warning: No Codeberg token; cannot update description") + } + } + } + + // Update GitHub if needed + if ghClient != nil && ghExists { + if ghDesc != canonical { + if dryRun { + fmt.Printf(" [DRY RUN] Would update GitHub description for %s -> %q\n", repoName, canonical) + } else if ghClient.HasToken() { + if err := ghClient.UpdateRepoDescription(repoName, canonical); err != nil { + fmt.Printf(" Warning: Failed to update GitHub description: %v\n", err) + } else { + fmt.Printf(" Updated GitHub description for %s\n", repoName) + } + } else { + fmt.Println(" Warning: No GitHub token; cannot update description") + } + } + } + + // Update cache + if cache != nil { + cache[repoName] = canonical + } +} + diff --git a/internal/cli/sync_handlers.go b/internal/cli/sync_handlers.go index 1878808..09b993a 100644 --- a/internal/cli/sync_handlers.go +++ b/internal/cli/sync_handlers.go @@ -31,11 +31,17 @@ func HandleSync(cfg *config.Config, flags *Flags) int { syncer := sync.New(cfg, flags.WorkDir) syncer.SetBackupEnabled(flags.Backup) - if err := syncer.SyncRepository(flags.SyncRepo); err != nil { - log.Fatal("Sync failed:", err) - return 1 - } - return 0 + if err := syncer.SyncRepository(flags.SyncRepo); err != nil { + log.Fatal("Sync failed:", err) + return 1 + } + // Also sync descriptions for this single repository + descCache := loadDescriptionCache(flags.WorkDir) + syncRepoDescriptions(cfg, flags.DryRun, flags.SyncRepo, "", "", descCache) + if err := saveDescriptionCache(flags.WorkDir, descCache); err != nil { + fmt.Printf("Warning: Failed to save descriptions cache: %v\n", err) + } + return 0 } // HandleSyncAll handles syncing all configured repositories @@ -65,9 +71,11 @@ func HandleSyncAll(cfg *config.Config, flags *Flags) int { } } - syncer := sync.New(cfg, flags.WorkDir) - syncer.SetBackupEnabled(flags.Backup) - successCount := 0 + syncer := sync.New(cfg, flags.WorkDir) + syncer.SetBackupEnabled(flags.Backup) + successCount := 0 + // Load descriptions cache + descCache := loadDescriptionCache(flags.WorkDir) for i, repo := range cfg.Repositories { fmt.Printf("\n[%d/%d] Syncing %s...\n", i+1, len(cfg.Repositories), repo) @@ -89,14 +97,20 @@ func HandleSyncAll(cfg *config.Config, flags *Flags) int { } } - if err := syncer.SyncRepository(repo); err != nil { - fmt.Printf("ERROR: Failed to sync %s: %v\n", repo, err) - fmt.Printf("Stopping sync due to error.\n") - return 1 - } - successCount++ - } - + if err := syncer.SyncRepository(repo); err != nil { + fmt.Printf("ERROR: Failed to sync %s: %v\n", repo, err) + fmt.Printf("Stopping sync due to error.\n") + return 1 + } + successCount++ + // Sync descriptions after repo sync + syncRepoDescriptions(cfg, flags.DryRun, repo, "", "", descCache) + } + // Save descriptions cache + if err := saveDescriptionCache(flags.WorkDir, descCache); err != nil { + fmt.Printf("Warning: Failed to save descriptions cache: %v\n", err) + } + fmt.Printf("\nSuccessfully synced all %d repositories!\n", successCount) // Print abandoned branches summary @@ -164,8 +178,8 @@ func HandleSyncCodebergPublic(cfg *config.Config, flags *Flags) int { return 0 } - // Show the repositories that will be synced - showReposToSync(repoNames) + // Show the repositories that will be synced + showReposToSync(repoNames) if flags.DryRun { fmt.Printf("\n[DRY RUN] Would sync %d repositories from Codeberg to GitHub\n", len(repoNames)) @@ -177,9 +191,9 @@ func HandleSyncCodebergPublic(cfg *config.Config, flags *Flags) int { } } - if !flags.DryRun { - return syncCodebergRepos(cfg, flags, repos, repoNames) - } + if !flags.DryRun { + return syncCodebergRepos(cfg, flags, repos, repoNames) + } return 0 } @@ -214,8 +228,8 @@ func HandleSyncGitHubPublic(cfg *config.Config, flags *Flags) int { return 0 } - // Show the repositories that will be synced - showReposToSync(repoNames) + // Show the repositories that will be synced + showReposToSync(repoNames) if flags.DryRun { fmt.Printf("\n[DRY RUN] Would sync %d repositories from GitHub to Codeberg\n", len(repoNames)) @@ -225,9 +239,9 @@ func HandleSyncGitHubPublic(cfg *config.Config, flags *Flags) int { return 0 } - if !flags.DryRun { - return syncGitHubRepos(cfg, flags, repos, repoNames) - } + if !flags.DryRun { + return syncGitHubRepos(cfg, flags, repos, repoNames) + } return 0 } @@ -333,7 +347,10 @@ func syncCodebergRepos(cfg *config.Config, flags *Flags, repos []codeberg.Reposi } } - fmt.Printf("\nStarting sync of %d repositories...\n", len(repoNames)) + fmt.Printf("\nStarting sync of %d repositories...\n", len(repoNames)) + + // Load descriptions cache + descCache := loadDescriptionCache(flags.WorkDir) syncer := sync.New(cfg, flags.WorkDir) syncer.SetBackupEnabled(flags.Backup) @@ -345,8 +362,8 @@ func syncCodebergRepos(cfg *config.Config, flags *Flags, repos []codeberg.Reposi repoMap[repo.Name] = repo } - for i, repoName := range repoNames { - fmt.Printf("\n[%d/%d] Syncing %s...\n", i+1, len(repoNames), repoName) + for i, repoName := range repoNames { + fmt.Printf("\n[%d/%d] Syncing %s...\n", i+1, len(repoNames), repoName) // Create GitHub repo if needed if hasGithubClient && flags.CreateGitHubRepos { @@ -363,15 +380,27 @@ func syncCodebergRepos(cfg *config.Config, flags *Flags, repos []codeberg.Reposi } } - if err := syncer.SyncRepository(repoName); err != nil { - fmt.Printf("ERROR: Failed to sync %s: %v\n", repoName, err) - fmt.Printf("Stopping sync due to error.\n") - return 1 - } - successCount++ - } - - fmt.Printf("\n=== Summary ===\n") + if err := syncer.SyncRepository(repoName); err != nil { + fmt.Printf("ERROR: Failed to sync %s: %v\n", repoName, err) + fmt.Printf("Stopping sync due to error.\n") + return 1 + } + successCount++ + + // After syncing, sync descriptions according to precedence + if cbRepo, ok := repoMap[repoName]; ok { + syncRepoDescriptions(cfg, flags.DryRun, repoName, cbRepo.Description, "", descCache) + } else { + syncRepoDescriptions(cfg, flags.DryRun, repoName, "", "", descCache) + } + } + + // Save descriptions cache + if err := saveDescriptionCache(flags.WorkDir, descCache); err != nil { + fmt.Printf("Warning: Failed to save descriptions cache: %v\n", err) + } + + fmt.Printf("\n=== Summary ===\n") fmt.Printf("Successfully synced: %d repositories\n", successCount) // Print abandoned branches summary @@ -426,7 +455,10 @@ func syncGitHubRepos(cfg *config.Config, flags *Flags, repos []github.Repository } } - fmt.Printf("\nStarting sync of %d repositories...\n", len(repoNames)) + fmt.Printf("\nStarting sync of %d repositories...\n", len(repoNames)) + + // Load descriptions cache + descCache := loadDescriptionCache(flags.WorkDir) syncer := sync.New(cfg, flags.WorkDir) syncer.SetBackupEnabled(flags.Backup) @@ -438,8 +470,8 @@ func syncGitHubRepos(cfg *config.Config, flags *Flags, repos []github.Repository repoMap[repo.Name] = repo } - for i, repoName := range repoNames { - fmt.Printf("\n[%d/%d] Syncing %s...\n", i+1, len(repoNames), repoName) + for i, repoName := range repoNames { + fmt.Printf("\n[%d/%d] Syncing %s...\n", i+1, len(repoNames), repoName) // Create Codeberg repo if needed if hasCodebergClient && flags.CreateCodebergRepos { @@ -456,13 +488,25 @@ func syncGitHubRepos(cfg *config.Config, flags *Flags, repos []github.Repository } } - if err := syncer.SyncRepository(repoName); err != nil { - fmt.Printf("ERROR: Failed to sync %s: %v\n", repoName, err) - fmt.Printf("Stopping sync due to error.\n") - return 1 - } - successCount++ - } + if err := syncer.SyncRepository(repoName); err != nil { + fmt.Printf("ERROR: Failed to sync %s: %v\n", repoName, err) + fmt.Printf("Stopping sync due to error.\n") + return 1 + } + successCount++ + + // After syncing, sync descriptions according to precedence + if ghRepo, ok := repoMap[repoName]; ok { + syncRepoDescriptions(cfg, flags.DryRun, repoName, "", ghRepo.Description, descCache) + } else { + syncRepoDescriptions(cfg, flags.DryRun, repoName, "", "", descCache) + } + } + + // Save descriptions cache + if err := saveDescriptionCache(flags.WorkDir, descCache); err != nil { + fmt.Printf("Warning: Failed to save descriptions cache: %v\n", err) + } fmt.Printf("\n=== Summary ===\n") fmt.Printf("Successfully synced: %d repositories\n", successCount) @@ -510,4 +554,4 @@ func ShowFullSyncMessage() { fmt.Println(" - Create missing GitHub repositories") fmt.Println(" - Create missing Codeberg repositories (when implemented)") fmt.Println() -}
\ No newline at end of file +} |
