mirror of
https://github.com/abhinav/git-spice.git
synced 2026-08-31 07:47:47 +02:00
refac(branch checkout): extract checkout logic into a handler (#740)
This seems like further reinforcement of the handler idea.
The commands that need to use checkout functionality
(and verify branch is restacked after checkout)
now get this for free without `(&branchCheckoutCmd{...}).Run(...)`
and worrying about the N arguments it takes.
This commit is contained in:
@@ -6,15 +6,14 @@ import (
|
||||
"fmt"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.abhg.dev/gs/internal/text"
|
||||
"go.abhg.dev/gs/internal/ui"
|
||||
)
|
||||
|
||||
type bottomCmd struct {
|
||||
checkoutOptions
|
||||
checkout.Options
|
||||
}
|
||||
|
||||
func (*bottomCmd) Help() string {
|
||||
@@ -26,12 +25,10 @@ func (*bottomCmd) Help() string {
|
||||
|
||||
func (cmd *bottomCmd) Run(
|
||||
ctx context.Context,
|
||||
log *silog.Logger,
|
||||
view ui.View,
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
checkoutHandler CheckoutHandler,
|
||||
) error {
|
||||
current, err := wt.CurrentBranch(ctx)
|
||||
if err != nil {
|
||||
@@ -48,8 +45,8 @@ func (cmd *bottomCmd) Run(
|
||||
return fmt.Errorf("find bottom: %w", err)
|
||||
}
|
||||
|
||||
return (&branchCheckoutCmd{
|
||||
checkoutOptions: cmd.checkoutOptions,
|
||||
Branch: bottom,
|
||||
}).Run(ctx, log, view, wt, store, svc, trackHandler)
|
||||
return checkoutHandler.CheckoutBranch(ctx, &checkout.Request{
|
||||
Branch: bottom,
|
||||
Options: &cmd.Options,
|
||||
})
|
||||
}
|
||||
|
||||
+26
-66
@@ -2,25 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/track"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.abhg.dev/gs/internal/text"
|
||||
"go.abhg.dev/gs/internal/ui"
|
||||
)
|
||||
|
||||
type checkoutOptions struct {
|
||||
DryRun bool `short:"n" xor:"detach-or-dry-run" help:"Print the target branch without checking it out"`
|
||||
Detach bool `xor:"detach-or-dry-run" help:"Detach HEAD after checking out"`
|
||||
}
|
||||
|
||||
type branchCheckoutCmd struct {
|
||||
checkoutOptions
|
||||
checkout.Options
|
||||
BranchPromptConfig
|
||||
|
||||
// Allow users to opt out of the "branch not tracked" prompt.
|
||||
@@ -82,68 +74,36 @@ func (cmd *branchCheckoutCmd) AfterApply(
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckoutHandler allows checking out branches.
|
||||
type CheckoutHandler interface {
|
||||
CheckoutBranch(ctx context.Context, req *checkout.Request) error
|
||||
}
|
||||
|
||||
func (cmd *branchCheckoutCmd) Run(
|
||||
ctx context.Context,
|
||||
log *silog.Logger,
|
||||
view ui.View,
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
handler CheckoutHandler,
|
||||
) error {
|
||||
if cmd.Branch != store.Trunk() {
|
||||
if err := svc.VerifyRestacked(ctx, cmd.Branch); err != nil {
|
||||
var restackErr *spice.BranchNeedsRestackError
|
||||
switch {
|
||||
case errors.As(err, &restackErr):
|
||||
log.Warnf("%v: needs to be restacked: run 'gs branch restack --branch=%v'", cmd.Branch, cmd.Branch)
|
||||
case errors.Is(err, state.ErrNotExist):
|
||||
if !ui.Interactive(view) || !cmd.TrackUntrackedPrompt {
|
||||
log.Warnf("%v: branch not tracked: run 'gs branch track'", cmd.Branch)
|
||||
break
|
||||
}
|
||||
|
||||
log.Warnf("%v: branch not tracked", cmd.Branch)
|
||||
shouldTrack := true
|
||||
prompt := ui.NewConfirm().
|
||||
WithValue(&shouldTrack).
|
||||
WithTitle("Do you want to track this branch now?")
|
||||
if err := ui.Run(view, prompt); err != nil {
|
||||
return fmt.Errorf("prompt: %w", err)
|
||||
}
|
||||
|
||||
if shouldTrack {
|
||||
err := trackHandler.AddBranch(ctx, &track.AddBranchRequest{
|
||||
Branch: cmd.Branch,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("track branch: %w", err)
|
||||
}
|
||||
}
|
||||
case errors.Is(err, git.ErrNotExist):
|
||||
return fmt.Errorf("branch %q does not exist", cmd.Branch)
|
||||
default:
|
||||
log.Warnf("error checking branch: %v", err)
|
||||
return handler.CheckoutBranch(ctx, &checkout.Request{
|
||||
Branch: cmd.Branch,
|
||||
Options: &cmd.Options,
|
||||
ShouldTrack: func(branch string) (bool, error) {
|
||||
if !ui.Interactive(view) || !cmd.TrackUntrackedPrompt {
|
||||
log.Warnf("%v: branch not tracked: run 'gs branch track'", branch)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.DryRun {
|
||||
fmt.Println(cmd.Branch)
|
||||
return nil
|
||||
}
|
||||
log.Warnf("%v: branch not tracked", branch)
|
||||
shouldTrack := true
|
||||
prompt := ui.NewConfirm().
|
||||
WithValue(&shouldTrack).
|
||||
WithTitle("Do you want to track this branch now?")
|
||||
if err := ui.Run(view, prompt); err != nil {
|
||||
return false, fmt.Errorf("prompt: %w", err)
|
||||
}
|
||||
|
||||
if cmd.Detach {
|
||||
if err := wt.DetachHead(ctx, cmd.Branch); err != nil {
|
||||
return fmt.Errorf("detach HEAD: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := wt.Checkout(ctx, cmd.Branch); err != nil {
|
||||
return fmt.Errorf("checkout branch: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return shouldTrack, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+3
-4
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
@@ -35,7 +36,7 @@ func (cmd *branchFoldCmd) Run(
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
checkoutHandler CheckoutHandler,
|
||||
) error {
|
||||
if cmd.Branch == "" {
|
||||
currentBranch, err := wt.CurrentBranch(ctx)
|
||||
@@ -131,9 +132,7 @@ func (cmd *branchFoldCmd) Run(
|
||||
}
|
||||
|
||||
// Check out base and delete the branch we are folding.
|
||||
if err := (&branchCheckoutCmd{Branch: b.Base}).Run(
|
||||
ctx, log, view, wt, store, svc, trackHandler,
|
||||
); err != nil {
|
||||
if err := checkoutHandler.CheckoutBranch(ctx, &checkout.Request{Branch: b.Base}); err != nil {
|
||||
return fmt.Errorf("checkout base: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ import (
|
||||
"fmt"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.abhg.dev/gs/internal/text"
|
||||
"go.abhg.dev/gs/internal/ui"
|
||||
)
|
||||
|
||||
type downCmd struct {
|
||||
checkoutOptions
|
||||
checkout.Options
|
||||
|
||||
N int `arg:"" optional:"" help:"Number of branches to move up." default:"1"`
|
||||
}
|
||||
@@ -30,11 +30,10 @@ func (*downCmd) Help() string {
|
||||
func (cmd *downCmd) Run(
|
||||
ctx context.Context,
|
||||
log *silog.Logger,
|
||||
view ui.View,
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
checkoutHandler CheckoutHandler,
|
||||
) error {
|
||||
current, err := wt.CurrentBranch(ctx)
|
||||
if err != nil {
|
||||
@@ -73,8 +72,8 @@ outer:
|
||||
current = below
|
||||
}
|
||||
|
||||
return (&branchCheckoutCmd{
|
||||
checkoutOptions: cmd.checkoutOptions,
|
||||
Branch: below,
|
||||
}).Run(ctx, log, view, wt, store, svc, trackHandler)
|
||||
return checkoutHandler.CheckoutBranch(ctx, &checkout.Request{
|
||||
Branch: below,
|
||||
Options: &cmd.Options,
|
||||
})
|
||||
}
|
||||
|
||||
+4
-5
@@ -7,12 +7,12 @@ import (
|
||||
"slices"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/must"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.abhg.dev/gs/internal/text"
|
||||
"go.abhg.dev/gs/internal/ui"
|
||||
)
|
||||
|
||||
type downstackEditCmd struct {
|
||||
@@ -39,12 +39,11 @@ func (*downstackEditCmd) Help() string {
|
||||
func (cmd *downstackEditCmd) Run(
|
||||
ctx context.Context,
|
||||
log *silog.Logger,
|
||||
view ui.View,
|
||||
repo *git.Repository,
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
checkoutHandler CheckoutHandler,
|
||||
) error {
|
||||
if cmd.Editor == "" {
|
||||
cmd.Editor = gitEditor(ctx, repo)
|
||||
@@ -92,7 +91,7 @@ func (cmd *downstackEditCmd) Run(
|
||||
return fmt.Errorf("edit downstack: %w", err)
|
||||
}
|
||||
|
||||
return (&branchCheckoutCmd{
|
||||
return checkoutHandler.CheckoutBranch(ctx, &checkout.Request{
|
||||
Branch: res.Stack[len(res.Stack)-1],
|
||||
}).Run(ctx, log, view, wt, store, svc, trackHandler)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// Package checkout implements a Handler to change branches in a stack.
|
||||
package checkout
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/track"
|
||||
"go.abhg.dev/gs/internal/must"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination mocks_test.go -package checkout -typed . GitWorktree,TrackHandler,Service,Store
|
||||
|
||||
// Options defines options for checking out a branch.
|
||||
// These turn into command line flags, so be mindful of what you add here.
|
||||
type Options struct {
|
||||
DryRun bool `short:"n" xor:"detach-or-dry-run" help:"Print the target branch without checking it out"`
|
||||
Detach bool `xor:"detach-or-dry-run" help:"Detach HEAD after checking out"`
|
||||
}
|
||||
|
||||
// Store provides access to the git-spice state.
|
||||
type Store interface {
|
||||
// Trunk returns the name of the trunk branch.
|
||||
Trunk() string
|
||||
}
|
||||
|
||||
// GitWorktree allows changing which branch or commit
|
||||
// is checked out in the current working tree.
|
||||
type GitWorktree interface {
|
||||
DetachHead(ctx context.Context, commitish string) error
|
||||
Checkout(ctx context.Context, branch string) error
|
||||
}
|
||||
|
||||
// TrackHandler allows tracking new branches with git-spice.
|
||||
type TrackHandler interface {
|
||||
AddBranch(ctx context.Context, req *track.AddBranchRequest) error
|
||||
}
|
||||
|
||||
// Service provides access to the spice service methods
|
||||
type Service interface {
|
||||
// VerifyRestacked checks if the branch is restacked.
|
||||
VerifyRestacked(ctx context.Context, branch string) error
|
||||
}
|
||||
|
||||
// Handler provides a central place for handling checkout operations.
|
||||
type Handler struct {
|
||||
Stdout io.Writer // required
|
||||
Log *silog.Logger // required
|
||||
Store Store // required
|
||||
Worktree GitWorktree // required
|
||||
Track TrackHandler // required
|
||||
Service Service // required
|
||||
}
|
||||
|
||||
// Request is a request to checkout a branch.
|
||||
type Request struct {
|
||||
// Branch is the name of the branch to checkout.
|
||||
Branch string // required
|
||||
|
||||
// Options are the options for checking out the branch.
|
||||
Options *Options // optional
|
||||
|
||||
// ShouldTrack is called if the branch being checked out is untracked,
|
||||
// and allows the caller to decide if the branch should be tracked.
|
||||
ShouldTrack func(branch string) (bool, error) // optional
|
||||
}
|
||||
|
||||
// CheckoutBranch checks out the specified branch with git-spice,
|
||||
// offering to track it if it's not already tracked.
|
||||
func (h *Handler) CheckoutBranch(ctx context.Context, req *Request) error {
|
||||
branch := req.Branch
|
||||
opts := cmp.Or(req.Options, &Options{})
|
||||
if req.ShouldTrack == nil {
|
||||
req.ShouldTrack = func(string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
must.NotBeBlankf(branch, "branch name must not be blank")
|
||||
must.NotBef(opts.DryRun && opts.Detach, "cannot use both dry-run and detach options")
|
||||
|
||||
log := h.Log
|
||||
if branch != h.Store.Trunk() {
|
||||
if err := h.Service.VerifyRestacked(ctx, branch); err != nil {
|
||||
var restackErr *spice.BranchNeedsRestackError
|
||||
switch {
|
||||
case errors.As(err, &restackErr):
|
||||
log.Warnf("%v: needs to be restacked: run 'gs branch restack --branch=%v'", branch, branch)
|
||||
case errors.Is(err, state.ErrNotExist):
|
||||
shouldTrack, err := req.ShouldTrack(branch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check if branch should be tracked: %w", err)
|
||||
}
|
||||
|
||||
if shouldTrack {
|
||||
err := h.Track.AddBranch(ctx, &track.AddBranchRequest{
|
||||
Branch: branch,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("Error tracking branch", "branch", branch, "error", err)
|
||||
}
|
||||
}
|
||||
case errors.Is(err, git.ErrNotExist):
|
||||
return fmt.Errorf("branch %q does not exist", branch)
|
||||
default:
|
||||
log.Warn("Unable to check if branch is restacked",
|
||||
"branch", branch, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.DryRun {
|
||||
_, _ = fmt.Fprintln(h.Stdout, branch)
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.Detach {
|
||||
if err := h.Worktree.DetachHead(ctx, branch); err != nil {
|
||||
return fmt.Errorf("detach HEAD: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := h.Worktree.Checkout(ctx, branch); err != nil {
|
||||
return fmt.Errorf("checkout branch: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
package checkout
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/track"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestHandler_CheckoutBranch_Trunk(t *testing.T) {
|
||||
mockStore := NewMockStore(gomock.NewController(t))
|
||||
mockStore.
|
||||
EXPECT().
|
||||
Trunk().
|
||||
Return("main").
|
||||
AnyTimes()
|
||||
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: NewMockService(ctrl),
|
||||
}
|
||||
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
Checkout(gomock.Any(), "main").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "main",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("DryRun", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
var stdout bytes.Buffer
|
||||
handler := &Handler{
|
||||
Stdout: &stdout,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: NewMockGitWorktree(ctrl),
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: NewMockService(ctrl),
|
||||
}
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "main",
|
||||
Options: &Options{DryRun: true},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main\n", stdout.String())
|
||||
})
|
||||
|
||||
t.Run("Detach", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: NewMockService(ctrl),
|
||||
}
|
||||
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
DetachHead(gomock.Any(), "main").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "main",
|
||||
Options: &Options{Detach: true},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandler_CheckoutBranch_NonTrunk(t *testing.T) {
|
||||
mockStore := NewMockStore(gomock.NewController(t))
|
||||
mockStore.
|
||||
EXPECT().
|
||||
Trunk().
|
||||
Return("main").
|
||||
AnyTimes()
|
||||
|
||||
t.Run("AlreadyRestacked", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
Checkout(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("DryRun", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockTrack := NewMockTrackHandler(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
var stdout bytes.Buffer
|
||||
handler := &Handler{
|
||||
Stdout: &stdout,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: mockTrack,
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
|
||||
req := &Request{
|
||||
Branch: "feature",
|
||||
Options: &Options{DryRun: true},
|
||||
}
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "feature\n", stdout.String())
|
||||
})
|
||||
|
||||
t.Run("Detach", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
DetachHead(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
Options: &Options{Detach: true},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("NeedsRestack", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
var logBuffer bytes.Buffer
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.New(&logBuffer, nil),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(&spice.BranchNeedsRestackError{
|
||||
Base: "main",
|
||||
BaseHash: git.Hash("abc123"),
|
||||
})
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
Checkout(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, logBuffer.String(), "needs to be restacked")
|
||||
assert.Contains(t, logBuffer.String(), "gs branch restack --branch=feature")
|
||||
})
|
||||
|
||||
t.Run("NotTrackedButShouldTrack", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockTrack := NewMockTrackHandler(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: mockTrack,
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(state.ErrNotExist)
|
||||
mockTrack.
|
||||
EXPECT().
|
||||
AddBranch(gomock.Any(), &track.AddBranchRequest{
|
||||
Branch: "feature",
|
||||
}).
|
||||
Return(nil)
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
Checkout(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
ShouldTrack: func(string) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("NotTrackedNotRequested", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(state.ErrNotExist)
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
Checkout(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("BranchDoesNotExist", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: NewMockGitWorktree(ctrl),
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(git.ErrNotExist)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, `branch "feature" does not exist`)
|
||||
})
|
||||
|
||||
t.Run("OtherVerifyError", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
var logBuffer bytes.Buffer
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.New(&logBuffer, nil),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
unknownError := errors.New("some other error")
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(unknownError)
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
Checkout(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, logBuffer.String(), "Unable to check if branch is restacked")
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandler_CheckoutBranch_EdgeCases(t *testing.T) {
|
||||
mockStore := NewMockStore(gomock.NewController(t))
|
||||
mockStore.
|
||||
EXPECT().
|
||||
Trunk().
|
||||
Return("main").
|
||||
AnyTimes()
|
||||
|
||||
t.Run("ShouldTrackError", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: NewMockGitWorktree(ctrl),
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(state.ErrNotExist)
|
||||
|
||||
shouldTrackError := errors.New("should track error")
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
ShouldTrack: func(string) (bool, error) {
|
||||
return false, shouldTrackError
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "check if branch should be tracked")
|
||||
assert.ErrorIs(t, err, shouldTrackError)
|
||||
})
|
||||
|
||||
t.Run("TrackError", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockTrack := NewMockTrackHandler(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
|
||||
var logBuffer bytes.Buffer
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.New(&logBuffer, nil),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: mockTrack,
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
trackError := errors.New("track error")
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(state.ErrNotExist)
|
||||
mockTrack.
|
||||
EXPECT().
|
||||
AddBranch(gomock.Any(), &track.AddBranchRequest{
|
||||
Branch: "feature",
|
||||
}).
|
||||
Return(trackError)
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
Checkout(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
ShouldTrack: func(string) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, logBuffer.String(), "Error tracking branch")
|
||||
})
|
||||
|
||||
t.Run("CheckoutError", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
checkoutError := errors.New("checkout error")
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
Checkout(gomock.Any(), "feature").
|
||||
Return(checkoutError)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "checkout branch")
|
||||
assert.ErrorIs(t, err, checkoutError)
|
||||
})
|
||||
|
||||
t.Run("DetachError", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockWorktree := NewMockGitWorktree(ctrl)
|
||||
mockService := NewMockService(ctrl)
|
||||
|
||||
handler := &Handler{
|
||||
Stdout: io.Discard,
|
||||
Log: silog.Nop(),
|
||||
Store: mockStore,
|
||||
Worktree: mockWorktree,
|
||||
Track: NewMockTrackHandler(ctrl),
|
||||
Service: mockService,
|
||||
}
|
||||
|
||||
detachError := errors.New("detach error")
|
||||
mockService.
|
||||
EXPECT().
|
||||
VerifyRestacked(gomock.Any(), "feature").
|
||||
Return(nil)
|
||||
mockWorktree.
|
||||
EXPECT().
|
||||
DetachHead(gomock.Any(), "feature").
|
||||
Return(detachError)
|
||||
|
||||
err := handler.CheckoutBranch(t.Context(), &Request{
|
||||
Branch: "feature",
|
||||
Options: &Options{Detach: true},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "detach HEAD")
|
||||
assert.ErrorIs(t, err, detachError)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: go.abhg.dev/gs/internal/handler/checkout (interfaces: GitWorktree,TrackHandler,Service,Store)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination mocks_test.go -package checkout -typed . GitWorktree,TrackHandler,Service,Store
|
||||
//
|
||||
|
||||
// Package checkout is a generated GoMock package.
|
||||
package checkout
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
track "go.abhg.dev/gs/internal/handler/track"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockGitWorktree is a mock of GitWorktree interface.
|
||||
type MockGitWorktree struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockGitWorktreeMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockGitWorktreeMockRecorder is the mock recorder for MockGitWorktree.
|
||||
type MockGitWorktreeMockRecorder struct {
|
||||
mock *MockGitWorktree
|
||||
}
|
||||
|
||||
// NewMockGitWorktree creates a new mock instance.
|
||||
func NewMockGitWorktree(ctrl *gomock.Controller) *MockGitWorktree {
|
||||
mock := &MockGitWorktree{ctrl: ctrl}
|
||||
mock.recorder = &MockGitWorktreeMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockGitWorktree) EXPECT() *MockGitWorktreeMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Checkout mocks base method.
|
||||
func (m *MockGitWorktree) Checkout(ctx context.Context, branch string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Checkout", ctx, branch)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Checkout indicates an expected call of Checkout.
|
||||
func (mr *MockGitWorktreeMockRecorder) Checkout(ctx, branch any) *MockGitWorktreeCheckoutCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Checkout", reflect.TypeOf((*MockGitWorktree)(nil).Checkout), ctx, branch)
|
||||
return &MockGitWorktreeCheckoutCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGitWorktreeCheckoutCall wrap *gomock.Call
|
||||
type MockGitWorktreeCheckoutCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGitWorktreeCheckoutCall) Return(arg0 error) *MockGitWorktreeCheckoutCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGitWorktreeCheckoutCall) Do(f func(context.Context, string) error) *MockGitWorktreeCheckoutCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGitWorktreeCheckoutCall) DoAndReturn(f func(context.Context, string) error) *MockGitWorktreeCheckoutCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DetachHead mocks base method.
|
||||
func (m *MockGitWorktree) DetachHead(ctx context.Context, commitish string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DetachHead", ctx, commitish)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DetachHead indicates an expected call of DetachHead.
|
||||
func (mr *MockGitWorktreeMockRecorder) DetachHead(ctx, commitish any) *MockGitWorktreeDetachHeadCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetachHead", reflect.TypeOf((*MockGitWorktree)(nil).DetachHead), ctx, commitish)
|
||||
return &MockGitWorktreeDetachHeadCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGitWorktreeDetachHeadCall wrap *gomock.Call
|
||||
type MockGitWorktreeDetachHeadCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGitWorktreeDetachHeadCall) Return(arg0 error) *MockGitWorktreeDetachHeadCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGitWorktreeDetachHeadCall) Do(f func(context.Context, string) error) *MockGitWorktreeDetachHeadCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGitWorktreeDetachHeadCall) DoAndReturn(f func(context.Context, string) error) *MockGitWorktreeDetachHeadCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockTrackHandler is a mock of TrackHandler interface.
|
||||
type MockTrackHandler struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockTrackHandlerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockTrackHandlerMockRecorder is the mock recorder for MockTrackHandler.
|
||||
type MockTrackHandlerMockRecorder struct {
|
||||
mock *MockTrackHandler
|
||||
}
|
||||
|
||||
// NewMockTrackHandler creates a new mock instance.
|
||||
func NewMockTrackHandler(ctrl *gomock.Controller) *MockTrackHandler {
|
||||
mock := &MockTrackHandler{ctrl: ctrl}
|
||||
mock.recorder = &MockTrackHandlerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockTrackHandler) EXPECT() *MockTrackHandlerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddBranch mocks base method.
|
||||
func (m *MockTrackHandler) AddBranch(ctx context.Context, req *track.AddBranchRequest) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddBranch", ctx, req)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddBranch indicates an expected call of AddBranch.
|
||||
func (mr *MockTrackHandlerMockRecorder) AddBranch(ctx, req any) *MockTrackHandlerAddBranchCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBranch", reflect.TypeOf((*MockTrackHandler)(nil).AddBranch), ctx, req)
|
||||
return &MockTrackHandlerAddBranchCall{Call: call}
|
||||
}
|
||||
|
||||
// MockTrackHandlerAddBranchCall wrap *gomock.Call
|
||||
type MockTrackHandlerAddBranchCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockTrackHandlerAddBranchCall) Return(arg0 error) *MockTrackHandlerAddBranchCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockTrackHandlerAddBranchCall) Do(f func(context.Context, *track.AddBranchRequest) error) *MockTrackHandlerAddBranchCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockTrackHandlerAddBranchCall) DoAndReturn(f func(context.Context, *track.AddBranchRequest) error) *MockTrackHandlerAddBranchCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockService is a mock of Service interface.
|
||||
type MockService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockServiceMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockServiceMockRecorder is the mock recorder for MockService.
|
||||
type MockServiceMockRecorder struct {
|
||||
mock *MockService
|
||||
}
|
||||
|
||||
// NewMockService creates a new mock instance.
|
||||
func NewMockService(ctrl *gomock.Controller) *MockService {
|
||||
mock := &MockService{ctrl: ctrl}
|
||||
mock.recorder = &MockServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockService) EXPECT() *MockServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// VerifyRestacked mocks base method.
|
||||
func (m *MockService) VerifyRestacked(ctx context.Context, branch string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "VerifyRestacked", ctx, branch)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// VerifyRestacked indicates an expected call of VerifyRestacked.
|
||||
func (mr *MockServiceMockRecorder) VerifyRestacked(ctx, branch any) *MockServiceVerifyRestackedCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyRestacked", reflect.TypeOf((*MockService)(nil).VerifyRestacked), ctx, branch)
|
||||
return &MockServiceVerifyRestackedCall{Call: call}
|
||||
}
|
||||
|
||||
// MockServiceVerifyRestackedCall wrap *gomock.Call
|
||||
type MockServiceVerifyRestackedCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockServiceVerifyRestackedCall) Return(arg0 error) *MockServiceVerifyRestackedCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockServiceVerifyRestackedCall) Do(f func(context.Context, string) error) *MockServiceVerifyRestackedCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockServiceVerifyRestackedCall) DoAndReturn(f func(context.Context, string) error) *MockServiceVerifyRestackedCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockStore is a mock of Store interface.
|
||||
type MockStore struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStoreMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockStoreMockRecorder is the mock recorder for MockStore.
|
||||
type MockStoreMockRecorder struct {
|
||||
mock *MockStore
|
||||
}
|
||||
|
||||
// NewMockStore creates a new mock instance.
|
||||
func NewMockStore(ctrl *gomock.Controller) *MockStore {
|
||||
mock := &MockStore{ctrl: ctrl}
|
||||
mock.recorder = &MockStoreMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockStore) EXPECT() *MockStoreMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Trunk mocks base method.
|
||||
func (m *MockStore) Trunk() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Trunk")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Trunk indicates an expected call of Trunk.
|
||||
func (mr *MockStoreMockRecorder) Trunk() *MockStoreTrunkCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trunk", reflect.TypeOf((*MockStore)(nil).Trunk))
|
||||
return &MockStoreTrunkCall{Call: call}
|
||||
}
|
||||
|
||||
// MockStoreTrunkCall wrap *gomock.Call
|
||||
type MockStoreTrunkCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockStoreTrunkCall) Return(arg0 string) *MockStoreTrunkCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockStoreTrunkCall) Do(f func() string) *MockStoreTrunkCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockStoreTrunkCall) DoAndReturn(f func() string) *MockStoreTrunkCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"go.abhg.dev/gs/internal/forge/github"
|
||||
"go.abhg.dev/gs/internal/forge/gitlab"
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/handler/track"
|
||||
"go.abhg.dev/gs/internal/secret"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
@@ -347,6 +348,22 @@ func (cmd *mainCmd) AfterApply(ctx context.Context, kctx *kong.Context, logger *
|
||||
Service: svc,
|
||||
}, nil
|
||||
}),
|
||||
kctx.BindSingletonProvider(func(
|
||||
log *silog.Logger,
|
||||
store *state.Store,
|
||||
wt *git.Worktree,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
) (CheckoutHandler, error) {
|
||||
return &checkout.Handler{
|
||||
Stdout: kctx.Stdout,
|
||||
Log: log,
|
||||
Store: store,
|
||||
Worktree: wt,
|
||||
Track: trackHandler,
|
||||
Service: svc,
|
||||
}, nil
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.abhg.dev/gs/internal/text"
|
||||
"go.abhg.dev/gs/internal/ui"
|
||||
)
|
||||
|
||||
type stackEditCmd struct {
|
||||
@@ -39,12 +39,11 @@ func (*stackEditCmd) Help() string {
|
||||
func (cmd *stackEditCmd) Run(
|
||||
ctx context.Context,
|
||||
log *silog.Logger,
|
||||
view ui.View,
|
||||
repo *git.Repository,
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
checkoutHandler CheckoutHandler,
|
||||
) error {
|
||||
if cmd.Editor == "" {
|
||||
cmd.Editor = gitEditor(ctx, repo)
|
||||
@@ -97,7 +96,5 @@ func (cmd *stackEditCmd) Run(
|
||||
return fmt.Errorf("edit downstack: %w", err)
|
||||
}
|
||||
|
||||
return (&branchCheckoutCmd{
|
||||
Branch: cmd.Branch,
|
||||
}).Run(ctx, log, view, wt, store, svc, trackHandler)
|
||||
return checkoutHandler.CheckoutBranch(ctx, &checkout.Request{Branch: cmd.Branch})
|
||||
}
|
||||
|
||||
@@ -5,17 +5,17 @@ import (
|
||||
"fmt"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/must"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.abhg.dev/gs/internal/text"
|
||||
"go.abhg.dev/gs/internal/ui"
|
||||
"go.abhg.dev/gs/internal/ui/widget"
|
||||
)
|
||||
|
||||
type topCmd struct {
|
||||
checkoutOptions
|
||||
checkout.Options
|
||||
}
|
||||
|
||||
func (*topCmd) Help() string {
|
||||
@@ -32,9 +32,8 @@ func (cmd *topCmd) Run(
|
||||
log *silog.Logger,
|
||||
view ui.View,
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
checkoutHandler CheckoutHandler,
|
||||
) error {
|
||||
current, err := wt.CurrentBranch(ctx)
|
||||
if err != nil {
|
||||
@@ -81,8 +80,8 @@ func (cmd *topCmd) Run(
|
||||
return nil
|
||||
}
|
||||
|
||||
return (&branchCheckoutCmd{
|
||||
checkoutOptions: cmd.checkoutOptions,
|
||||
Branch: branch,
|
||||
}).Run(ctx, log, view, wt, store, svc, trackHandler)
|
||||
return checkoutHandler.CheckoutBranch(ctx, &checkout.Request{
|
||||
Branch: branch,
|
||||
Options: &cmd.Options,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,29 +3,22 @@ package main
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.abhg.dev/gs/internal/ui"
|
||||
)
|
||||
|
||||
type trunkCmd struct {
|
||||
checkoutOptions
|
||||
checkout.Options
|
||||
}
|
||||
|
||||
func (cmd *trunkCmd) Run(
|
||||
ctx context.Context,
|
||||
log *silog.Logger,
|
||||
view ui.View,
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
checkoutHandler CheckoutHandler,
|
||||
) error {
|
||||
trunk := store.Trunk()
|
||||
return (&branchCheckoutCmd{
|
||||
checkoutOptions: cmd.checkoutOptions,
|
||||
Branch: trunk,
|
||||
}).Run(ctx, log, view, wt, store, svc, trackHandler)
|
||||
return checkoutHandler.CheckoutBranch(ctx, &checkout.Request{
|
||||
Branch: trunk,
|
||||
Options: &cmd.Options,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,16 +5,16 @@ import (
|
||||
"fmt"
|
||||
|
||||
"go.abhg.dev/gs/internal/git"
|
||||
"go.abhg.dev/gs/internal/handler/checkout"
|
||||
"go.abhg.dev/gs/internal/silog"
|
||||
"go.abhg.dev/gs/internal/spice"
|
||||
"go.abhg.dev/gs/internal/spice/state"
|
||||
"go.abhg.dev/gs/internal/text"
|
||||
"go.abhg.dev/gs/internal/ui"
|
||||
"go.abhg.dev/gs/internal/ui/widget"
|
||||
)
|
||||
|
||||
type upCmd struct {
|
||||
checkoutOptions
|
||||
checkout.Options
|
||||
|
||||
N int `arg:"" optional:"" help:"Number of branches to move up." default:"1"`
|
||||
}
|
||||
@@ -33,9 +33,8 @@ func (cmd *upCmd) Run(
|
||||
log *silog.Logger,
|
||||
view ui.View,
|
||||
wt *git.Worktree,
|
||||
store *state.Store,
|
||||
svc *spice.Service,
|
||||
trackHandler TrackHandler,
|
||||
checkoutHandler CheckoutHandler,
|
||||
) error {
|
||||
current, err := wt.CurrentBranch(ctx)
|
||||
if err != nil {
|
||||
@@ -90,8 +89,8 @@ outer:
|
||||
current = branch
|
||||
}
|
||||
|
||||
return (&branchCheckoutCmd{
|
||||
checkoutOptions: cmd.checkoutOptions,
|
||||
Branch: branch,
|
||||
}).Run(ctx, log, view, wt, store, svc, trackHandler)
|
||||
return checkoutHandler.CheckoutBranch(ctx, &checkout.Request{
|
||||
Branch: branch,
|
||||
Options: &cmd.Options,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user