Skip to content

Commit 3ccaa0b

Browse files
authored
Merge pull request #51 from gomicro/add-stash
add in stash and subcommands
2 parents 961d267 + 1d323f0 commit 3ccaa0b

12 files changed

Lines changed: 466 additions & 0 deletions

File tree

client/clienter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type Clienter interface {
3131
Remove(ctx context.Context, dirs []string, name string) error
3232
SetURLs(ctx context.Context, repoDirs []string, name, baseURL string) error
3333
StageFiles(ctx context.Context, dirs []string, args ...string) error
34+
StashRepos(ctx context.Context, dirs []string, args ...string) error
3435
StatusRepos(ctx context.Context, dirs []string, ignoreEmpty bool, args ...string) error
3536
TagRepos(ctx context.Context, repoDirs []string, args ...string) error
3637
}

client/repos/stash.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package repos
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"fmt"
8+
"os/exec"
9+
"strings"
10+
11+
ctxhelper "github.com/gomicro/align/client/context"
12+
"github.com/gosuri/uiprogress"
13+
)
14+
15+
func (r *Repos) StashRepos(ctx context.Context, dirs []string, args ...string) error {
16+
count := len(dirs)
17+
args = append([]string{"stash"}, args...)
18+
19+
verbose := ctxhelper.Verbose(ctx)
20+
21+
var bar *uiprogress.Bar
22+
currRepo := ""
23+
24+
if verbose {
25+
r.scrb.BeginDescribe("Command")
26+
defer r.scrb.EndDescribe()
27+
28+
r.scrb.Print(fmt.Sprintf("git %s", strings.Join(args, " ")))
29+
30+
r.scrb.BeginDescribe("directories")
31+
defer r.scrb.EndDescribe()
32+
} else {
33+
bar = uiprogress.AddBar(count).
34+
AppendCompleted().
35+
PrependElapsed().
36+
PrependFunc(func(b *uiprogress.Bar) string {
37+
return fmt.Sprintf("Stashing (%d/%d)", b.Current(), count)
38+
}).
39+
AppendFunc(func(b *uiprogress.Bar) string {
40+
return currRepo
41+
})
42+
}
43+
44+
var errs []error
45+
46+
for _, dir := range dirs {
47+
currRepo = fmt.Sprintf("\nCurrent Repo: %v", dir)
48+
49+
out := &bytes.Buffer{}
50+
errout := &bytes.Buffer{}
51+
52+
cmd := exec.CommandContext(ctx, "git", args...)
53+
cmd.Stdout = out
54+
cmd.Stderr = errout
55+
cmd.Dir = dir
56+
57+
err := cmd.Run()
58+
if verbose {
59+
r.scrb.BeginDescribe(dir)
60+
if err != nil {
61+
r.scrb.Error(err)
62+
r.scrb.PrintLines(errout)
63+
} else {
64+
r.scrb.PrintLines(out)
65+
}
66+
67+
r.scrb.EndDescribe()
68+
} else {
69+
if err != nil {
70+
errs = append(errs, fmt.Errorf("%s: %w: %s", dir, err, strings.TrimSpace(errout.String())))
71+
}
72+
73+
bar.Incr()
74+
}
75+
}
76+
77+
currRepo = ""
78+
79+
return errors.Join(errs...)
80+
}

client/testclient/client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ func (c *TestClient) Remove(ctx context.Context, dirs []string, name string) err
133133
return c.Errors["Remove"]
134134
}
135135

136+
func (c *TestClient) StashRepos(ctx context.Context, repoDirs []string, args ...string) error {
137+
c.CommandsCalled = append(c.CommandsCalled, "StashRepos")
138+
139+
return c.Errors["StashRepos"]
140+
}
141+
136142
func (c *TestClient) StatusRepos(ctx context.Context, dirs []string, ignoreEmpty bool, args ...string) error {
137143
c.CommandsCalled = append(c.CommandsCalled, "StatusRepos")
138144

cmd/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/gomicro/align/client"
88
cfgCmd "github.com/gomicro/align/cmd/config"
99
"github.com/gomicro/align/cmd/remote"
10+
"github.com/gomicro/align/cmd/stash"
1011
"github.com/gomicro/align/config"
1112
"github.com/spf13/cobra"
1213
"github.com/spf13/viper"
@@ -21,6 +22,7 @@ func init() {
2122

2223
RootCmd.AddCommand(cfgCmd.ConfigCmd)
2324
RootCmd.AddCommand(remote.RemoteCmd)
25+
RootCmd.AddCommand(stash.StashCmd)
2426

2527
RootCmd.PersistentFlags().BoolP("verbose", "v", false, "show more verbose output")
2628

cmd/stash/drop.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package stash
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
ctxhelper "github.com/gomicro/align/client/context"
8+
"github.com/gosuri/uiprogress"
9+
"github.com/spf13/cobra"
10+
"github.com/spf13/viper"
11+
)
12+
13+
func init() {
14+
StashCmd.AddCommand(dropCmd)
15+
}
16+
17+
var dropCmd = &cobra.Command{
18+
Use: "drop",
19+
Short: "Drop the most recent stash entry across all repos",
20+
Long: "Remove the most recent stash entry from the stash list across all repos.",
21+
RunE: dropFunc,
22+
}
23+
24+
func dropFunc(cmd *cobra.Command, args []string) error {
25+
verbose := viper.GetBool("verbose")
26+
ctx := ctxhelper.WithVerbose(context.Background(), verbose)
27+
28+
if !verbose {
29+
uiprogress.Start()
30+
defer uiprogress.Stop()
31+
}
32+
33+
repoDirs, err := clt.GetDirs(ctx, dir)
34+
if err != nil {
35+
cmd.SilenceUsage = true
36+
return fmt.Errorf("get dirs: %w", err)
37+
}
38+
39+
err = clt.StashRepos(ctx, repoDirs, "drop")
40+
if err != nil {
41+
cmd.SilenceUsage = true
42+
return fmt.Errorf("stash drop: %w", err)
43+
}
44+
45+
return nil
46+
}

cmd/stash/drop_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package stash
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/gomicro/align/client/testclient"
8+
"github.com/spf13/viper"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestStashDrop(t *testing.T) {
13+
viper.Set("verbose", true)
14+
t.Cleanup(func() { viper.Set("verbose", false) })
15+
16+
t.Run("calls expected commands", func(t *testing.T) {
17+
tc := testclient.New()
18+
clt = tc
19+
20+
err := dropFunc(dropCmd, []string{})
21+
assert.NoError(t, err)
22+
23+
tc.AssertCommandsCalled(t, "GetDirs", "StashRepos")
24+
})
25+
26+
t.Run("returns error on get dirs failure", func(t *testing.T) {
27+
tc := testclient.New()
28+
tc.Errors["GetDirs"] = errors.New("some dirs error")
29+
clt = tc
30+
31+
err := dropFunc(dropCmd, []string{})
32+
assert.ErrorContains(t, err, "get dirs")
33+
34+
tc.AssertCommandsCalled(t, "GetDirs")
35+
})
36+
37+
t.Run("returns error on stash drop failure", func(t *testing.T) {
38+
tc := testclient.New()
39+
tc.Errors["StashRepos"] = errors.New("some stash drop error")
40+
clt = tc
41+
42+
err := dropFunc(dropCmd, []string{})
43+
assert.ErrorContains(t, err, "stash drop")
44+
45+
tc.AssertCommandsCalled(t, "GetDirs", "StashRepos")
46+
})
47+
}

cmd/stash/list.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package stash
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
ctxhelper "github.com/gomicro/align/client/context"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func init() {
12+
StashCmd.AddCommand(listCmd)
13+
}
14+
15+
var listCmd = &cobra.Command{
16+
Use: "list",
17+
Short: "List stash entries across all repos",
18+
Long: "List stash entries across all repos in a directory.",
19+
RunE: listFunc,
20+
}
21+
22+
func listFunc(cmd *cobra.Command, args []string) error {
23+
// list output is always verbose — force it so scribe prints each repo's output
24+
ctx := ctxhelper.WithVerbose(context.Background(), true)
25+
26+
repoDirs, err := clt.GetDirs(ctx, dir)
27+
if err != nil {
28+
cmd.SilenceUsage = true
29+
return fmt.Errorf("get dirs: %w", err)
30+
}
31+
32+
err = clt.StashRepos(ctx, repoDirs, "list")
33+
if err != nil {
34+
cmd.SilenceUsage = true
35+
return fmt.Errorf("stash list: %w", err)
36+
}
37+
38+
return nil
39+
}

cmd/stash/list_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package stash
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/gomicro/align/client/testclient"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestStashList(t *testing.T) {
12+
t.Run("calls expected commands", func(t *testing.T) {
13+
})
14+
15+
t.Run("returns error on get dirs failure", func(t *testing.T) {
16+
tc := testclient.New()
17+
tc.Errors["GetDirs"] = errors.New("some dirs error")
18+
clt = tc
19+
20+
err := listFunc(listCmd, []string{})
21+
assert.ErrorContains(t, err, "get dirs")
22+
23+
tc.AssertCommandsCalled(t, "GetDirs")
24+
})
25+
26+
t.Run("returns error on stash list failure", func(t *testing.T) {
27+
tc := testclient.New()
28+
tc.Errors["StashRepos"] = errors.New("some stash list error")
29+
clt = tc
30+
31+
err := listFunc(listCmd, []string{})
32+
assert.ErrorContains(t, err, "stash list")
33+
34+
tc.AssertCommandsCalled(t, "GetDirs", "StashRepos")
35+
})
36+
}

cmd/stash/pop.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package stash
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
ctxhelper "github.com/gomicro/align/client/context"
8+
"github.com/gosuri/uiprogress"
9+
"github.com/spf13/cobra"
10+
"github.com/spf13/viper"
11+
)
12+
13+
func init() {
14+
StashCmd.AddCommand(popCmd)
15+
}
16+
17+
var popCmd = &cobra.Command{
18+
Use: "pop",
19+
Short: "Apply the most recent stash across all repos",
20+
Long: "Apply the most recent stash entry and remove it from the stash list across all repos.",
21+
RunE: popFunc,
22+
}
23+
24+
func popFunc(cmd *cobra.Command, args []string) error {
25+
verbose := viper.GetBool("verbose")
26+
ctx := ctxhelper.WithVerbose(context.Background(), verbose)
27+
28+
if !verbose {
29+
uiprogress.Start()
30+
defer uiprogress.Stop()
31+
}
32+
33+
repoDirs, err := clt.GetDirs(ctx, dir)
34+
if err != nil {
35+
cmd.SilenceUsage = true
36+
return fmt.Errorf("get dirs: %w", err)
37+
}
38+
39+
err = clt.StashRepos(ctx, repoDirs, "pop")
40+
if err != nil {
41+
cmd.SilenceUsage = true
42+
return fmt.Errorf("stash pop: %w", err)
43+
}
44+
45+
return nil
46+
}

cmd/stash/pop_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package stash
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/gomicro/align/client/testclient"
8+
"github.com/spf13/viper"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestStashPop(t *testing.T) {
13+
viper.Set("verbose", true)
14+
t.Cleanup(func() { viper.Set("verbose", false) })
15+
16+
t.Run("calls expected commands", func(t *testing.T) {
17+
tc := testclient.New()
18+
clt = tc
19+
20+
err := popFunc(popCmd, []string{})
21+
assert.NoError(t, err)
22+
23+
tc.AssertCommandsCalled(t, "GetDirs", "StashRepos")
24+
})
25+
26+
t.Run("returns error on get dirs failure", func(t *testing.T) {
27+
tc := testclient.New()
28+
tc.Errors["GetDirs"] = errors.New("some dirs error")
29+
clt = tc
30+
31+
err := popFunc(popCmd, []string{})
32+
assert.ErrorContains(t, err, "get dirs")
33+
34+
tc.AssertCommandsCalled(t, "GetDirs")
35+
})
36+
37+
t.Run("returns error on stash pop failure", func(t *testing.T) {
38+
tc := testclient.New()
39+
tc.Errors["StashRepos"] = errors.New("some stash pop error")
40+
clt = tc
41+
42+
err := popFunc(popCmd, []string{})
43+
assert.ErrorContains(t, err, "stash pop")
44+
45+
tc.AssertCommandsCalled(t, "GetDirs", "StashRepos")
46+
})
47+
}

0 commit comments

Comments
 (0)