diff --git a/cmd/push.go b/cmd/push.go index 2676c5f..de64aff 100644 --- a/cmd/push.go +++ b/cmd/push.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + "sync" + "sync/atomic" "time" "github.com/fatih/color" @@ -35,13 +37,12 @@ var pushCmd = &cobra.Command{ if err != nil { return err } - pushed := 0 + + // Serial filter pass — cheap disk reads, avoids concurrent isModified races. + var jobs []*issue.Issue for _, iss := range issues { if iss.Number == 0 { - if err := pushOne(root, iss); err != nil { - return err - } - pushed++ + jobs = append(jobs, iss) continue } mod, err := isModified(root, iss) @@ -49,19 +50,41 @@ var pushCmd = &cobra.Command{ return err } if mod { + jobs = append(jobs, iss) + } + } + + if len(jobs) == 0 { + _, err := color.New(color.FgHiBlack).Println("Nothing to push.") + return err + } + + const concurrency = 10 + sem := make(chan struct{}, concurrency) + var wg sync.WaitGroup + errCh := make(chan error, len(jobs)) + var pushed atomic.Int32 + + for _, iss := range jobs { + wg.Add(1) + sem <- struct{}{} + go func(iss *issue.Issue) { + defer wg.Done() + defer func() { <-sem }() if err := pushOne(root, iss); err != nil { - return err + errCh <- err + return } - pushed++ - } + pushed.Add(1) + }(iss) } - if pushed == 0 { - if _, err := color.New(color.FgHiBlack).Println("Nothing to push."); err != nil { - return err - } - } else { - fmt.Printf("%s %d issue(s)\n", color.GreenString("Pushed"), pushed) + wg.Wait() + close(errCh) + if err := <-errCh; err != nil { + return err } + + fmt.Printf("%s %d issue(s)\n", color.GreenString("Pushed"), pushed.Load()) return nil }, } diff --git a/cmd/sync.go b/cmd/sync.go index 3999fb7..4406b98 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -22,23 +22,43 @@ var syncCmd = &cobra.Command{ return err } - // Step 1: push any new T-numbered issues + // Step 1: push any new T-numbered issues in parallel local, err := loadAllLocal(root) if err != nil { return err } - newCount := 0 + + var newIssues []*issue.Issue for _, iss := range local { if iss.Number == 0 { - fmt.Printf("Pushing new issue: %s\n", color.CyanString(iss.Title)) - if err := pushOne(root, iss); err != nil { - return err - } - newCount++ + newIssues = append(newIssues, iss) } } - if newCount > 0 { - fmt.Printf("%s %d new issue(s)\n\n", color.GreenString("Pushed"), newCount) + + if len(newIssues) > 0 { + const concurrency = 10 + sem := make(chan struct{}, concurrency) + var wg sync.WaitGroup + errCh := make(chan error, len(newIssues)) + + for _, iss := range newIssues { + wg.Add(1) + sem <- struct{}{} + go func(iss *issue.Issue) { + defer wg.Done() + defer func() { <-sem }() + fmt.Printf("Pushing new issue: %s\n", color.CyanString(iss.Title)) + if err := pushOne(root, iss); err != nil { + errCh <- err + } + }(iss) + } + wg.Wait() + close(errCh) + if err := <-errCh; err != nil { + return err + } + fmt.Printf("%s %d new issue(s)\n\n", color.GreenString("Pushed"), len(newIssues)) } // Reload — T-issues now have real numbers