Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions internal/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,11 +303,15 @@ func (s *Syncer) syncWorker(ctx context.Context, jobs <-chan *gh.Repository, res

if s.opts.DryRun {
if s.git.IsGitRepo(localPath) {
fmt.Printf("[DRY RUN] Would update: %s\n", repoName)
if s.opts.Verbose {
fmt.Printf("[DRY RUN] Would update: %s\n", repoName)
}
s.reportProgress(repoName, ProgressUpdated, "dry-run")
results <- syncResult{repoName: repoName, status: ProgressUpdated}
} else {
fmt.Printf("[DRY RUN] Would clone: %s\n", repoName)
if s.opts.Verbose {
fmt.Printf("[DRY RUN] Would clone: %s\n", repoName)
}
s.reportProgress(repoName, ProgressCloned, "dry-run")
results <- syncResult{repoName: repoName, status: ProgressCloned}
}
Expand Down Expand Up @@ -379,11 +383,15 @@ func (s *Syncer) syncSingleRepo(ctx context.Context, repo *gh.Repository, result

if s.opts.DryRun {
if s.git.IsGitRepo(localPath) {
fmt.Printf("[DRY RUN] Would update: %s\n", repoName)
if s.opts.Verbose {
fmt.Printf("[DRY RUN] Would update: %s\n", repoName)
}
s.reportProgress(repoName, ProgressUpdated, "dry-run")
result.Updated = append(result.Updated, repoName)
} else {
fmt.Printf("[DRY RUN] Would clone: %s\n", repoName)
if s.opts.Verbose {
fmt.Printf("[DRY RUN] Would clone: %s\n", repoName)
}
s.reportProgress(repoName, ProgressCloned, "dry-run")
result.Cloned = append(result.Cloned, repoName)
}
Expand All @@ -400,7 +408,9 @@ func (s *Syncer) syncSingleRepo(ctx context.Context, repo *gh.Repository, result
if err != nil {
result.Failed[repoName] = err
s.reportProgress(repoName, ProgressFailed, err.Error())
fmt.Printf("Failed to update %s: %v\n", repoName, err)
if s.opts.Verbose {
fmt.Printf("Failed to update %s: %v\n", repoName, err)
}
} else {
if status == ProgressUpToDate {
result.UpToDate = append(result.UpToDate, repoName)
Expand All @@ -421,7 +431,9 @@ func (s *Syncer) syncSingleRepo(ctx context.Context, repo *gh.Repository, result
if err := s.cloneRepo(ctx, repo, localPath); err != nil {
result.Failed[repoName] = err
s.reportProgress(repoName, ProgressFailed, err.Error())
fmt.Printf("Failed to clone %s: %v\n", repoName, err)
if s.opts.Verbose {
fmt.Printf("Failed to clone %s: %v\n", repoName, err)
}
} else {
result.Cloned = append(result.Cloned, repoName)
s.reportProgress(repoName, ProgressCloned, "")
Expand Down
60 changes: 49 additions & 11 deletions internal/tui/screens/sync_progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package screens
import (
"context"
"fmt"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -76,6 +77,9 @@ type SyncProgressScreen struct {

// Current repo being synced
currentRepo string

// Failed repositories with error messages
failedRepos map[string]string // map[repoName]errorMessage
}

type syncProgressItem struct {
Expand All @@ -97,15 +101,16 @@ func NewSyncProgress(ctx context.Context, app *tui.App) *SyncProgressScreen {
s.Style = tui.GetStyles().Spinner

return &SyncProgressScreen{
ctx: ctx,
app: app,
styles: tui.GetStyles(),
keys: tui.GetKeyMap(),
progress: p,
spinner: s,
width: 80,
height: 24,
loading: true,
ctx: ctx,
app: app,
styles: tui.GetStyles(),
keys: tui.GetKeyMap(),
progress: p,
spinner: s,
width: 80,
height: 24,
loading: true,
failedRepos: make(map[string]string),
}
}

Expand Down Expand Up @@ -295,6 +300,10 @@ func (s *SyncProgressScreen) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
s.collecting = false
s.failed++
s.reposCompleted++
// Store error message if provided
if msg.update.err != nil && msg.update.repoName != "" {
s.failedRepos[msg.update.repoName] = msg.update.err.Error()
}
case "archived":
s.collecting = false
s.archived++
Expand Down Expand Up @@ -473,6 +482,25 @@ func (s *SyncProgressScreen) View() string {
}
if s.failed > 0 {
content.WriteString(fmt.Sprintf(" %s Failed: %d\n", s.styles.Error.Render("●"), s.failed))
// Show detailed error messages for failed repos
if len(s.failedRepos) > 0 {
content.WriteString("\n")
content.WriteString(s.styles.Error.Render("Failed repositories:"))
content.WriteString("\n")

// Sort repo names for consistent display order
repoNames := make([]string, 0, len(s.failedRepos))
for repoName := range s.failedRepos {
repoNames = append(repoNames, repoName)
}
sort.Strings(repoNames)

for _, repoName := range repoNames {
errMsg := s.failedRepos[repoName]
content.WriteString(fmt.Sprintf(" • %s\n", repoName))
content.WriteString(fmt.Sprintf(" %s\n", s.styles.Muted.Render(errMsg)))
}
}
}
if s.archived > 0 {
content.WriteString(fmt.Sprintf(" %s Archived: %d (preserved locally, no longer on remote)\n", s.styles.Info.Render("●"), s.archived))
Expand Down Expand Up @@ -688,6 +716,7 @@ func (s *SyncProgressScreen) runSyncInBackground() {
results := make(chan struct {
status string
idx int
err error
}, len(allRepos))

// Track completed count for progress
Expand Down Expand Up @@ -731,11 +760,18 @@ func (s *SyncProgressScreen) runSyncInBackground() {
result, err := syncer.SyncRepoWithData(s.ctx, repo)

status := "skipped"
var syncErr error
if err != nil {
status = "failed"
syncErr = err
} else if result != nil {
if len(result.Failed) > 0 {
status = "failed"
// Get the error from the Failed map
for _, failErr := range result.Failed {
syncErr = failErr
break // Use the first error
}
} else if len(result.Cloned) > 0 {
status = "cloned"
} else if len(result.Updated) > 0 {
Expand All @@ -754,7 +790,8 @@ func (s *SyncProgressScreen) runSyncInBackground() {
results <- struct {
status string
idx int
}{status: status, idx: idx}
err error
}{status: status, idx: idx, err: syncErr}
}
}()
}
Expand Down Expand Up @@ -794,6 +831,7 @@ func (s *SyncProgressScreen) runSyncInBackground() {
status: res.status,
current: int(completedCount),
total: total,
err: res.err,
}
}

Expand Down Expand Up @@ -866,7 +904,7 @@ type profileSyncProgressUpdate struct {
status string // "collecting", "syncing", "cloned", "updated", "up-to-date", "skipped", "failed", "complete"
current int
total int
err error // Only set when status="complete" and there was an error
err error // Set when status="complete" for overall errors, or status="failed" for individual repo errors
}

type profileSyncProgressMsg struct {
Expand Down