mirror of
https://github.com/taigrr/mg.git
synced 2026-04-01 19:18:42 -07:00
feat(cmd): implement fetch and status commands, update deps to go1.26.1
- Implement fetch command: fetches all registered repos without merging, supports parallel execution via -j/--jobs flag - Implement status command: shows uncommitted changes across all repos with per-file-type counts, supports parallel execution - Update Go to 1.26.1 - Update go-git v5.16.5 -> v5.17.0, go-crypto v1.3.0 -> v1.4.0, go-billy v5.7.0 -> v5.8.0, x/net v0.50.0 -> v0.51.0 - Fix unregister command description (said 'add' instead of 'remove') - Fix goimports formatting in mgconf_test.go
This commit is contained in:
@@ -2,18 +2,90 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
git "github.com/go-git/go-git/v5"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var fetchCmd = &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "fetch all git repos without merging",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("fetch called")
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
type RepoError struct {
|
||||
Error error
|
||||
Repo string
|
||||
}
|
||||
if jobs < 1 {
|
||||
log.Println("jobs must be greater than 0")
|
||||
os.Exit(1)
|
||||
}
|
||||
conf := GetConfig()
|
||||
if len(args) > 0 {
|
||||
log.Println("too many arguments")
|
||||
os.Exit(1)
|
||||
}
|
||||
repoChan := make(chan string, len(conf.Repos))
|
||||
var (
|
||||
errs []RepoError
|
||||
alreadyFetched int
|
||||
mutex sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
wg.Add(len(conf.Repos))
|
||||
for i := 0; i < jobs; i++ {
|
||||
go func() {
|
||||
for repo := range repoChan {
|
||||
log.Printf("attempting fetch: %s\n", repo)
|
||||
r, err := git.PlainOpenWithOptions(repo, &git.PlainOpenOptions{DetectDotGit: true})
|
||||
if err != nil {
|
||||
mutex.Lock()
|
||||
errs = append(errs, RepoError{Error: err, Repo: repo})
|
||||
mutex.Unlock()
|
||||
log.Printf("fetch failed for %s: %v\n", repo, err)
|
||||
wg.Done()
|
||||
continue
|
||||
}
|
||||
err = r.Fetch(&git.FetchOptions{})
|
||||
if err == git.NoErrAlreadyUpToDate {
|
||||
mutex.Lock()
|
||||
alreadyFetched++
|
||||
mutex.Unlock()
|
||||
fmt.Printf("repo %s: already up to date\n", repo)
|
||||
wg.Done()
|
||||
continue
|
||||
} else if err != nil {
|
||||
mutex.Lock()
|
||||
errs = append(errs, RepoError{Error: err, Repo: repo})
|
||||
mutex.Unlock()
|
||||
log.Printf("fetch failed for %s: %v\n", repo, err)
|
||||
wg.Done()
|
||||
continue
|
||||
}
|
||||
fmt.Printf("successfully fetched %s\n", repo)
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
for _, repo := range conf.Repos {
|
||||
repoChan <- repo.Path
|
||||
}
|
||||
close(repoChan)
|
||||
wg.Wait()
|
||||
for _, err := range errs {
|
||||
log.Printf("error fetching %s: %s\n", err.Repo, err.Error)
|
||||
}
|
||||
lenErrs := len(errs)
|
||||
fmt.Println()
|
||||
fmt.Printf("successfully fetched %d/%d repos\n", len(conf.Repos)-lenErrs, len(conf.Repos))
|
||||
fmt.Printf("%d repos already up to date\n", alreadyFetched)
|
||||
fmt.Printf("failed to fetch %d/%d repos\n", lenErrs, len(conf.Repos))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(fetchCmd)
|
||||
fetchCmd.Flags().IntVarP(&jobs, "jobs", "j", 1, "number of jobs to run in parallel")
|
||||
}
|
||||
|
||||
@@ -2,19 +2,156 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
git "github.com/go-git/go-git/v5"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// statusCmd represents the status command
|
||||
type repoStatus struct {
|
||||
Path string
|
||||
Modified int
|
||||
Added int
|
||||
Deleted int
|
||||
Renamed int
|
||||
Copied int
|
||||
Untrack int
|
||||
Clean bool
|
||||
}
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "get the combined git status for all git repos",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("status called")
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
type RepoError struct {
|
||||
Error error
|
||||
Repo string
|
||||
}
|
||||
if jobs < 1 {
|
||||
log.Println("jobs must be greater than 0")
|
||||
os.Exit(1)
|
||||
}
|
||||
conf := GetConfig()
|
||||
if len(args) > 0 {
|
||||
log.Println("too many arguments")
|
||||
os.Exit(1)
|
||||
}
|
||||
repoChan := make(chan string, len(conf.Repos))
|
||||
var (
|
||||
errs []RepoError
|
||||
statuses []repoStatus
|
||||
mutex sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
wg.Add(len(conf.Repos))
|
||||
for i := 0; i < jobs; i++ {
|
||||
go func() {
|
||||
for repo := range repoChan {
|
||||
r, err := git.PlainOpenWithOptions(repo, &git.PlainOpenOptions{DetectDotGit: true})
|
||||
if err != nil {
|
||||
mutex.Lock()
|
||||
errs = append(errs, RepoError{Error: err, Repo: repo})
|
||||
mutex.Unlock()
|
||||
wg.Done()
|
||||
continue
|
||||
}
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
mutex.Lock()
|
||||
errs = append(errs, RepoError{Error: err, Repo: repo})
|
||||
mutex.Unlock()
|
||||
wg.Done()
|
||||
continue
|
||||
}
|
||||
st, err := w.Status()
|
||||
if err != nil {
|
||||
mutex.Lock()
|
||||
errs = append(errs, RepoError{Error: err, Repo: repo})
|
||||
mutex.Unlock()
|
||||
wg.Done()
|
||||
continue
|
||||
}
|
||||
rs := repoStatus{Path: repo, Clean: st.IsClean()}
|
||||
for _, s := range st {
|
||||
code := s.Worktree
|
||||
if code == git.Unmodified {
|
||||
code = s.Staging
|
||||
}
|
||||
switch code {
|
||||
case git.Modified:
|
||||
rs.Modified++
|
||||
case git.Added:
|
||||
rs.Added++
|
||||
case git.Deleted:
|
||||
rs.Deleted++
|
||||
case git.Renamed:
|
||||
rs.Renamed++
|
||||
case git.Copied:
|
||||
rs.Copied++
|
||||
case git.Untracked:
|
||||
rs.Untrack++
|
||||
}
|
||||
}
|
||||
mutex.Lock()
|
||||
statuses = append(statuses, rs)
|
||||
mutex.Unlock()
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
for _, repo := range conf.Repos {
|
||||
repoChan <- repo.Path
|
||||
}
|
||||
close(repoChan)
|
||||
wg.Wait()
|
||||
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].Path < statuses[j].Path
|
||||
})
|
||||
|
||||
dirtyCount := 0
|
||||
for _, rs := range statuses {
|
||||
if rs.Clean {
|
||||
continue
|
||||
}
|
||||
dirtyCount++
|
||||
fmt.Printf("%s:\n", rs.Path)
|
||||
if rs.Modified > 0 {
|
||||
fmt.Printf(" modified: %d\n", rs.Modified)
|
||||
}
|
||||
if rs.Added > 0 {
|
||||
fmt.Printf(" added: %d\n", rs.Added)
|
||||
}
|
||||
if rs.Deleted > 0 {
|
||||
fmt.Printf(" deleted: %d\n", rs.Deleted)
|
||||
}
|
||||
if rs.Renamed > 0 {
|
||||
fmt.Printf(" renamed: %d\n", rs.Renamed)
|
||||
}
|
||||
if rs.Copied > 0 {
|
||||
fmt.Printf(" copied: %d\n", rs.Copied)
|
||||
}
|
||||
if rs.Untrack > 0 {
|
||||
fmt.Printf(" untracked: %d\n", rs.Untrack)
|
||||
}
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
log.Printf("error reading %s: %s\n", err.Repo, err.Error)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("%d/%d repos have uncommitted changes\n", dirtyCount, len(conf.Repos))
|
||||
if len(errs) > 0 {
|
||||
fmt.Printf("failed to read %d/%d repos\n", len(errs), len(conf.Repos))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(statusCmd)
|
||||
statusCmd.Flags().IntVarP(&jobs, "jobs", "j", 1, "number of jobs to run in parallel")
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// unregisterCmd represents the unregister command
|
||||
var unregisterCmd = &cobra.Command{
|
||||
Use: "unregister",
|
||||
Short: "add current path to list of repos",
|
||||
Short: "remove current path from list of repos",
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
conf := GetConfig()
|
||||
path, err := os.Getwd()
|
||||
|
||||
10
go.mod
10
go.mod
@@ -1,21 +1,21 @@
|
||||
module github.com/taigrr/mg
|
||||
|
||||
go 1.25.5
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/go-git/go-git/v5 v5.16.5
|
||||
github.com/go-git/go-git/v5 v5.17.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.7.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.8.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
@@ -27,7 +27,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@@ -3,8 +3,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ=
|
||||
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
@@ -25,12 +25,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
|
||||
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
|
||||
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
|
||||
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
|
||||
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
|
||||
github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
|
||||
github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -85,8 +85,8 @@ golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVo
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
Reference in New Issue
Block a user