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
1 change: 1 addition & 0 deletions client/clienter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Clienter interface {
CloneRepos(ctx context.Context, dir string) ([]*clientctx.Repository, error)
CommitRepos(ctx context.Context, dirs []string, args ...string) error
DiffRepos(ctx context.Context, repoDirs []string, cfg *repos.DiffConfig) error
FetchRepos(ctx context.Context, repoDirs []string, args ...string) error
GetBranchAndTagNames(ctx context.Context, dirs []string) ([]string, error)
GetBranchNames(ctx context.Context, dirs []string) ([]string, error)
GetDirs(ctx context.Context, dir string) ([]string, error)
Expand Down
80 changes: 80 additions & 0 deletions client/repos/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package repos

import (
"bytes"
"context"
"errors"
"fmt"
"os/exec"
"strings"

ctxhelper "github.com/gomicro/align/client/context"
"github.com/gosuri/uiprogress"
)

func (r *Repos) FetchRepos(ctx context.Context, dirs []string, args ...string) error {
count := len(dirs)
args = append([]string{"fetch"}, args...)

verbose := ctxhelper.Verbose(ctx)

var bar *uiprogress.Bar
currRepo := ""

if verbose {
r.scrb.BeginDescribe("Command")
defer r.scrb.EndDescribe()

r.scrb.Print(fmt.Sprintf("git %s", strings.Join(args, " ")))

r.scrb.BeginDescribe("directories")
defer r.scrb.EndDescribe()
} else {
bar = uiprogress.AddBar(count).
AppendCompleted().
PrependElapsed().
PrependFunc(func(b *uiprogress.Bar) string {
return fmt.Sprintf("Fetching (%d/%d)", b.Current(), count)
}).
AppendFunc(func(b *uiprogress.Bar) string {
return currRepo
})
}

var errs []error

for _, dir := range dirs {
currRepo = fmt.Sprintf("\nCurrent Repo: %v", dir)

out := &bytes.Buffer{}
errout := &bytes.Buffer{}

cmd := exec.CommandContext(ctx, "git", args...)
cmd.Stdout = out
cmd.Stderr = errout
cmd.Dir = dir

err := cmd.Run()
if verbose {
r.scrb.BeginDescribe(dir)
if err != nil {
r.scrb.Error(err)
r.scrb.PrintLines(errout)
} else {
r.scrb.PrintLines(out)
}

r.scrb.EndDescribe()
} else {
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w: %s", dir, err, strings.TrimSpace(errout.String())))
}

bar.Incr()
}
}

currRepo = ""

return errors.Join(errs...)
}
6 changes: 6 additions & 0 deletions client/testclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ func (c *TestClient) CloneRepos(ctx context.Context, baseDir string) ([]*clientc
return nil, c.Errors["CloneRepos"]
}

func (c *TestClient) FetchRepos(ctx context.Context, repoDirs []string, args ...string) error {
c.CommandsCalled = append(c.CommandsCalled, "FetchRepos")

return c.Errors["FetchRepos"]
}

func (c *TestClient) GetBranchNames(ctx context.Context, dirs []string) ([]string, error) {
c.CommandsCalled = append(c.CommandsCalled, "GetBranchNames")

Expand Down
83 changes: 83 additions & 0 deletions cmd/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cmd

import (
"context"
"fmt"

ctxhelper "github.com/gomicro/align/client/context"
"github.com/gosuri/uiprogress"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func init() {
RootCmd.AddCommand(fetchCmd)

fetchCmd.Flags().StringVar(&dir, "dir", ".", "directory to fetch repos in")
fetchCmd.Flags().BoolVar(&tags, "tags", false, "fetch all tags")
}

var fetchCmd = &cobra.Command{
Use: "fetch [remote]",
Short: "Fetch from remotes across all repos in a directory",
Long: `Fetch from remotes across all repos in a directory without merging into the working tree.`,
Args: cobra.MaximumNArgs(1),
ValidArgsFunction: fetchCmdValidArgsFunc,
PersistentPreRun: setupClient,
RunE: fetchFunc,
}

func fetchCmdValidArgsFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) >= 1 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

setupClient(cmd, args)

fetchDir, err := cmd.Flags().GetString("dir")
if err != nil {
fetchDir = "."
}

ctx := context.Background()

repoDirs, err := clt.GetDirs(ctx, fetchDir)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}

names, err := clt.GetRemoteNames(ctx, repoDirs)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}

return names, cobra.ShellCompDirectiveNoFileComp
}

func fetchFunc(cmd *cobra.Command, args []string) error {
verbose := viper.GetBool("verbose")
ctx := ctxhelper.WithVerbose(context.Background(), verbose)

if !verbose {
uiprogress.Start()
defer uiprogress.Stop()
}

repoDirs, err := clt.GetDirs(ctx, dir)
if err != nil {
cmd.SilenceUsage = true
return fmt.Errorf("get dirs: %w", err)
}

if tags {
args = append(args, "--tags")
}

err = clt.FetchRepos(ctx, repoDirs, args...)
if err != nil {
cmd.SilenceUsage = true
return fmt.Errorf("fetch repos: %w", err)
}

return nil
}
57 changes: 57 additions & 0 deletions cmd/fetch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
"errors"
"testing"

"github.com/gomicro/align/client/testclient"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)

func TestFetch(t *testing.T) {
viper.Set("verbose", true)
t.Cleanup(func() { viper.Set("verbose", false) })

t.Run("calls expected commands", func(t *testing.T) {
tc := testclient.New()
clt = tc

err := fetchFunc(fetchCmd, []string{})
assert.NoError(t, err)

tc.AssertCommandsCalled(t, "GetDirs", "FetchRepos")
})

t.Run("calls expected commands with remote arg", func(t *testing.T) {
tc := testclient.New()
clt = tc

err := fetchFunc(fetchCmd, []string{"origin"})
assert.NoError(t, err)

tc.AssertCommandsCalled(t, "GetDirs", "FetchRepos")
})

t.Run("returns error on get dirs failure", func(t *testing.T) {
tc := testclient.New()
tc.Errors["GetDirs"] = errors.New("some dirs error")
clt = tc

err := fetchFunc(fetchCmd, []string{})
assert.ErrorContains(t, err, "get dirs")

tc.AssertCommandsCalled(t, "GetDirs")
})

t.Run("returns error on fetch repos failure", func(t *testing.T) {
tc := testclient.New()
tc.Errors["FetchRepos"] = errors.New("some fetch error")
clt = tc

err := fetchFunc(fetchCmd, []string{})
assert.ErrorContains(t, err, "fetch repos")

tc.AssertCommandsCalled(t, "GetDirs", "FetchRepos")
})
}
Loading