Skip to content

Commit 179fed7

Browse files
committed
checkout: add spice.checkout.verbose config
This adds a new configuration option that controls whether checkout navigation commands print verbose messages when switching branches. The option defaults to true but allows users to opt out. Resolves #717
1 parent 6327126 commit 179fed7

File tree

6 files changed

+193
-1
lines changed

6 files changed

+193
-1
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: Added
2+
body: |
3+
Add 'spice.checkout.verbose' configuration option
4+
to report the branch name when checking out a branch.
5+
time: 2025-07-16T20:22:12.449527-07:00

doc/includes/cli-reference.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ By default, branches are sorted by name.
579579
* `--detach`: Detach HEAD after checking out
580580
* `-u`, `--untracked` ([:material-wrench:{ .middle title="spice.branchCheckout.showUntracked" }](/cli/config.md#spicebranchcheckoutshowuntracked)): Show untracked branches if one isn't supplied
581581

582-
**Configuration**: [spice.branchCheckout.showUntracked](/cli/config.md#spicebranchcheckoutshowuntracked), [spice.branchCheckout.trackUntrackedPrompt](/cli/config.md#spicebranchcheckouttrackuntrackedprompt), [spice.branchPrompt.sort](/cli/config.md#spicebranchpromptsort)
582+
**Configuration**: [spice.branchCheckout.showUntracked](/cli/config.md#spicebranchcheckoutshowuntracked), [spice.branchCheckout.trackUntrackedPrompt](/cli/config.md#spicebranchcheckouttrackuntrackedprompt), [spice.branchPrompt.sort](/cli/config.md#spicebranchpromptsort), [spice.checkout.verbose](/cli/config.md#spicecheckoutverbose)
583583

584584
### gs branch create
585585

@@ -1036,6 +1036,8 @@ Use the -n flag to print the branch without checking it out.
10361036
* `-n`, `--dry-run`: Print the target branch without checking it out
10371037
* `--detach`: Detach HEAD after checking out
10381038

1039+
**Configuration**: [spice.checkout.verbose](/cli/config.md#spicecheckoutverbose)
1040+
10391041
### gs down
10401042

10411043
```
@@ -1058,6 +1060,8 @@ Use the -n flag to print the branch without checking it out.
10581060
* `-n`, `--dry-run`: Print the target branch without checking it out
10591061
* `--detach`: Detach HEAD after checking out
10601062

1063+
**Configuration**: [spice.checkout.verbose](/cli/config.md#spicecheckoutverbose)
1064+
10611065
### gs top
10621066

10631067
```
@@ -1076,6 +1080,8 @@ Use the -n flag to print the branch without checking it out.
10761080
* `-n`, `--dry-run`: Print the target branch without checking it out
10771081
* `--detach`: Detach HEAD after checking out
10781082

1083+
**Configuration**: [spice.checkout.verbose](/cli/config.md#spicecheckoutverbose)
1084+
10791085
### gs bottom
10801086

10811087
```
@@ -1092,6 +1098,8 @@ Use the -n flag to print the branch without checking it out.
10921098
* `-n`, `--dry-run`: Print the target branch without checking it out
10931099
* `--detach`: Detach HEAD after checking out
10941100

1101+
**Configuration**: [spice.checkout.verbose](/cli/config.md#spicecheckoutverbose)
1102+
10951103
### gs trunk
10961104

10971105
```
@@ -1105,6 +1113,8 @@ Move to the trunk branch
11051113
* `-n`, `--dry-run`: Print the target branch without checking it out
11061114
* `--detach`: Detach HEAD after checking out
11071115

1116+
**Configuration**: [spice.checkout.verbose](/cli/config.md#spicecheckoutverbose)
1117+
11081118
## gs version
11091119

11101120
```

doc/src/cli/config.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ Commonly used values are:
112112
- `<name>/`: the committer's name
113113
- `<username>/`: the committer's username
114114

115+
### spice.checkout.verbose
116+
117+
<!-- gs:version unreleased -->
118+
119+
Whether branch navigation commands
120+
($$gs up$$, $$gs down$$, $$gs top$$, $$gs bottom$$,
121+
$$gs trunk$$, $$gs branch checkout$$)
122+
should print a message when switching branches.
123+
124+
**Accepted values:**
125+
126+
- `true` (default)
127+
- `false`
128+
115129
### spice.forge.github.apiUrl
116130

117131
URL at which the GitHub API is available.

internal/handler/checkout/handler.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
type Options struct {
2424
DryRun bool `short:"n" xor:"detach-or-dry-run" help:"Print the target branch without checking it out"`
2525
Detach bool `xor:"detach-or-dry-run" help:"Detach HEAD after checking out"`
26+
27+
Verbose bool `name:"checkout-verbose" hidden:"" default:"true" config:"checkout.verbose"`
2628
}
2729

2830
// Store provides access to the git-spice state.
@@ -133,5 +135,9 @@ func (h *Handler) CheckoutBranch(ctx context.Context, req *Request) error {
133135
return fmt.Errorf("checkout branch: %w", err)
134136
}
135137

138+
if opts.Verbose {
139+
log.Infof("switched to branch: %s", branch)
140+
}
141+
136142
return nil
137143
}

internal/handler/checkout/handler_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,3 +499,89 @@ func TestHandler_CheckoutBranch_EdgeCases(t *testing.T) {
499499
assert.ErrorIs(t, err, detachError)
500500
})
501501
}
502+
503+
func TestHandler_CheckoutBranch_Verbose(t *testing.T) {
504+
mockStore := NewMockStore(gomock.NewController(t))
505+
mockStore.
506+
EXPECT().
507+
Trunk().
508+
Return("main").
509+
AnyTimes()
510+
511+
t.Run("VerboseTrue", func(t *testing.T) {
512+
ctrl := gomock.NewController(t)
513+
mockWorktree := NewMockGitWorktree(ctrl)
514+
515+
var logBuffer bytes.Buffer
516+
handler := &Handler{
517+
Stdout: io.Discard,
518+
Log: silog.New(&logBuffer, nil),
519+
Store: mockStore,
520+
Worktree: mockWorktree,
521+
Track: NewMockTrackHandler(ctrl),
522+
Service: NewMockService(ctrl),
523+
}
524+
525+
mockWorktree.
526+
EXPECT().
527+
Checkout(gomock.Any(), "main").
528+
Return(nil)
529+
530+
err := handler.CheckoutBranch(t.Context(), &Request{
531+
Branch: "main",
532+
Options: &Options{Verbose: true},
533+
})
534+
assert.NoError(t, err)
535+
assert.Contains(t, logBuffer.String(), "switched to branch: main")
536+
})
537+
538+
t.Run("VerboseFalse", func(t *testing.T) {
539+
ctrl := gomock.NewController(t)
540+
mockWorktree := NewMockGitWorktree(ctrl)
541+
542+
var logBuffer bytes.Buffer
543+
handler := &Handler{
544+
Stdout: io.Discard,
545+
Log: silog.New(&logBuffer, nil),
546+
Store: mockStore,
547+
Worktree: mockWorktree,
548+
Track: NewMockTrackHandler(ctrl),
549+
Service: NewMockService(ctrl),
550+
}
551+
552+
mockWorktree.
553+
EXPECT().
554+
Checkout(gomock.Any(), "main").
555+
Return(nil)
556+
557+
err := handler.CheckoutBranch(t.Context(), &Request{
558+
Branch: "main",
559+
Options: &Options{Verbose: false},
560+
})
561+
assert.NoError(t, err)
562+
assert.NotContains(t, logBuffer.String(), "switched to branch")
563+
})
564+
565+
t.Run("DryRunNoVerbose", func(t *testing.T) {
566+
ctrl := gomock.NewController(t)
567+
568+
var logBuffer bytes.Buffer
569+
var stdout bytes.Buffer
570+
handler := &Handler{
571+
Stdout: &stdout,
572+
Log: silog.New(&logBuffer, nil),
573+
Store: mockStore,
574+
Worktree: NewMockGitWorktree(ctrl),
575+
Track: NewMockTrackHandler(ctrl),
576+
Service: NewMockService(ctrl),
577+
}
578+
579+
err := handler.CheckoutBranch(t.Context(), &Request{
580+
Branch: "main",
581+
Options: &Options{DryRun: true, Verbose: true},
582+
})
583+
assert.NoError(t, err)
584+
assert.Equal(t, "main\n", stdout.String())
585+
assert.NotContains(t, logBuffer.String(), "switched to branch")
586+
})
587+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Test checkout verbose messages with default and configured behaviors.
2+
3+
as 'Test <test@example.com>'
4+
at '2025-07-17T21:28:29Z'
5+
6+
# Setup repository
7+
mkdir repo
8+
cd repo
9+
git init
10+
git commit --allow-empty -m 'Initial commit'
11+
gs repo init
12+
13+
# Create a simple stack
14+
gs branch create feature1 -m 'Add feature1'
15+
gs branch create feature2 -m 'Add feature2'
16+
17+
# Test 1: Default behavior - verbose should be true by default, showing messages
18+
gs top
19+
gs down
20+
stderr 'switched to branch: feature1'
21+
22+
gs up
23+
stderr 'switched to branch: feature2'
24+
25+
gs trunk
26+
stderr 'switched to branch: main'
27+
28+
gs top
29+
stderr 'switched to branch: feature2'
30+
31+
gs bottom
32+
stderr 'switched to branch: feature1'
33+
34+
# Test 2: Explicitly set verbose to true
35+
git config spice.checkout.verbose true
36+
37+
gs down
38+
stderr 'switched to branch: main'
39+
40+
gs up
41+
stderr 'switched to branch: feature1'
42+
43+
# Test 3: Opt out of verbose messages
44+
git config spice.checkout.verbose false
45+
46+
gs up
47+
! stderr 'switched to branch'
48+
49+
gs down
50+
! stderr 'switched to branch'
51+
52+
gs top
53+
! stderr 'switched to branch'
54+
55+
gs bottom
56+
! stderr 'switched to branch'
57+
58+
gs trunk
59+
! stderr 'switched to branch'
60+
61+
# Test 4: Dry run should not show checkout message (regardless of config)
62+
git config spice.checkout.verbose true
63+
64+
gs bco feature1
65+
gs up -n
66+
stdout 'feature2'
67+
! stderr 'switched to branch'
68+
69+
gs down -n
70+
stdout 'main'
71+
! stderr 'switched to branch'

0 commit comments

Comments
 (0)