Skip to content

Commit b76d15f

Browse files
committed
feat: add quiet mode to list subcommand
The --quiet / -q switch for the `list` subcommand provides scripting friendly output to the list subcommand, outputting only the values from the "PATH" column of the pre-existing output.
1 parent 780af9d commit b76d15f

File tree

2 files changed

+189
-17
lines changed

2 files changed

+189
-17
lines changed

cmd/wtp/list.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,14 @@ func NewListCommand() *cli.Command {
5252
Name: "list",
5353
Usage: "List all worktrees",
5454
Description: "Shows all worktrees with their paths, branches, and HEAD commits.",
55-
Action: listCommand,
55+
Flags: []cli.Flag{
56+
&cli.BoolFlag{
57+
Name: "quiet",
58+
Aliases: []string{"q"},
59+
Usage: "Only display worktree paths",
60+
},
61+
},
62+
Action: listCommand,
5663
}
5764
}
5865

@@ -84,13 +91,16 @@ func listCommand(_ context.Context, cmd *cli.Command) error {
8491
// Load config to get base_dir
8592
cfg, _ := config.LoadConfig(mainRepoPath)
8693

94+
// Get quiet flag
95+
quiet := cmd.Bool("quiet")
96+
8797
// Use CommandExecutor-based implementation
8898
executor := listNewExecutor()
89-
return listCommandWithCommandExecutor(cmd, w, executor, cfg, mainRepoPath)
99+
return listCommandWithCommandExecutor(cmd, w, executor, cfg, mainRepoPath, quiet)
90100
}
91101

92102
func listCommandWithCommandExecutor(
93-
_ *cli.Command, w io.Writer, executor command.Executor, cfg *config.Config, mainRepoPath string,
103+
_ *cli.Command, w io.Writer, executor command.Executor, cfg *config.Config, mainRepoPath string, quiet bool,
94104
) error {
95105
// Get current working directory
96106
cwd, err := listGetwd()
@@ -109,12 +119,18 @@ func listCommandWithCommandExecutor(
109119
worktrees := parseWorktreesFromOutput(result.Results[0].Output)
110120

111121
if len(worktrees) == 0 {
112-
fmt.Fprintln(w, "No worktrees found")
122+
if !quiet {
123+
fmt.Fprintln(w, "No worktrees found")
124+
}
113125
return nil
114126
}
115127

116-
// Display worktrees with relative paths
117-
displayWorktreesRelative(w, worktrees, cwd, cfg, mainRepoPath)
128+
// Display worktrees
129+
if quiet {
130+
displayWorktreesQuiet(w, worktrees, cfg, mainRepoPath)
131+
} else {
132+
displayWorktreesRelative(w, worktrees, cwd, cfg, mainRepoPath)
133+
}
118134
return nil
119135
}
120136

@@ -234,6 +250,27 @@ func truncatePath(path string, maxWidth int) string {
234250
return path[:startLen] + ellipsis + path[len(path)-endLen:]
235251
}
236252

253+
// displayWorktreesQuiet outputs only the worktree names (as shown in PATH column), one per line
254+
func displayWorktreesQuiet(w io.Writer, worktrees []git.Worktree, cfg *config.Config, mainRepoPath string) {
255+
for _, wt := range worktrees {
256+
var pathDisplay string
257+
258+
// Use unified worktree naming function
259+
if cfg != nil {
260+
pathDisplay = getWorktreeNameFromPath(wt.Path, cfg, mainRepoPath, wt.IsMain)
261+
} else {
262+
// Fallback when config can't be loaded
263+
if wt.IsMain {
264+
pathDisplay = "@"
265+
} else {
266+
pathDisplay = filepath.Base(wt.Path)
267+
}
268+
}
269+
270+
fmt.Fprintln(w, pathDisplay)
271+
}
272+
}
273+
237274
// displayWorktreesRelative formats and displays worktree information with relative paths
238275
func displayWorktreesRelative(
239276
w io.Writer, worktrees []git.Worktree, currentPath string, cfg *config.Config, mainRepoPath string,

cmd/wtp/list_test.go

Lines changed: 146 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func TestListCommand_CommandConstruction(t *testing.T) {
111111
cfg := &config.Config{
112112
Defaults: config.Defaults{BaseDir: "../worktrees"},
113113
}
114-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo")
114+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", false)
115115

116116
assert.NoError(t, err)
117117
// Verify the correct git command was executed
@@ -180,7 +180,7 @@ func TestListCommand_Output(t *testing.T) {
180180
cfg := &config.Config{
181181
Defaults: config.Defaults{BaseDir: "../worktrees"},
182182
}
183-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo")
183+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", false)
184184

185185
assert.NoError(t, err)
186186
output := buf.String()
@@ -225,7 +225,7 @@ func TestListCommand_ExecutionError(t *testing.T) {
225225
cfg := &config.Config{
226226
Defaults: config.Defaults{BaseDir: "../worktrees"},
227227
}
228-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo")
228+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", false)
229229

230230
assert.Error(t, err)
231231
assert.Contains(t, err.Error(), "git command failed")
@@ -247,7 +247,7 @@ func TestListCommand_NoWorktrees(t *testing.T) {
247247
cfg := &config.Config{
248248
Defaults: config.Defaults{BaseDir: "../worktrees"},
249249
}
250-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo")
250+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", false)
251251

252252
assert.NoError(t, err)
253253
output := buf.String()
@@ -308,7 +308,7 @@ func TestListCommand_InternationalCharacters(t *testing.T) {
308308
cfg := &config.Config{
309309
Defaults: config.Defaults{BaseDir: "../worktrees"},
310310
}
311-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo")
311+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", false)
312312

313313
assert.NoError(t, err)
314314
output := buf.String()
@@ -376,7 +376,7 @@ func TestListCommand_LongPaths(t *testing.T) {
376376
cfg := &config.Config{
377377
Defaults: config.Defaults{BaseDir: "../worktrees"},
378378
}
379-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo")
379+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", false)
380380

381381
assert.NoError(t, err)
382382
output := buf.String()
@@ -426,7 +426,7 @@ branch refs/heads/feature/test
426426
cfg := &config.Config{
427427
Defaults: config.Defaults{BaseDir: "../worktrees"},
428428
}
429-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo")
429+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", false)
430430

431431
assert.NoError(t, err)
432432
output := buf.String()
@@ -457,7 +457,7 @@ func TestListCommand_HeaderFormatting(t *testing.T) {
457457
cfg := &config.Config{
458458
Defaults: config.Defaults{BaseDir: "../worktrees"},
459459
}
460-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo")
460+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", false)
461461

462462
assert.NoError(t, err)
463463
output := buf.String()
@@ -560,7 +560,7 @@ branch refs/heads/feature/awesome
560560
cfg := &config.Config{
561561
Defaults: config.Defaults{BaseDir: "../worktrees"},
562562
}
563-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/repo")
563+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/repo", false)
564564

565565
assert.NoError(t, err, tt.description)
566566
output := buf.String()
@@ -702,7 +702,7 @@ branch refs/heads/hoge
702702
cfg := &config.Config{
703703
Defaults: config.Defaults{BaseDir: baseDir},
704704
}
705-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, mainRepoPath)
705+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, mainRepoPath, false)
706706

707707
assert.NoError(t, err, tt.description)
708708
output := buf.String()
@@ -786,7 +786,7 @@ branch refs/heads/feature/long-branch-name-that-might-also-be-truncated
786786
cfg := &config.Config{
787787
Defaults: config.Defaults{BaseDir: "../worktrees"},
788788
}
789-
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/repo")
789+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/repo", false)
790790

791791
assert.NoError(t, err, tt.description)
792792
output := buf.String()
@@ -810,3 +810,138 @@ branch refs/heads/feature/long-branch-name-that-might-also-be-truncated
810810
})
811811
}
812812
}
813+
814+
// ===== Quiet Mode Tests =====
815+
816+
func TestListCommand_QuietMode_SingleWorktree(t *testing.T) {
817+
mockExec := &mockListCommandExecutor{
818+
results: []command.Result{
819+
{
820+
Output: "worktree /test/repo\nHEAD abc123\nbranch refs/heads/main\n\n",
821+
Error: nil,
822+
},
823+
},
824+
}
825+
826+
var buf bytes.Buffer
827+
cmd := &cli.Command{}
828+
829+
cfg := &config.Config{
830+
Defaults: config.Defaults{BaseDir: "../worktrees"},
831+
}
832+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", true)
833+
834+
assert.NoError(t, err)
835+
output := buf.String()
836+
837+
// Should only contain the worktree name (@), nothing else
838+
assert.Equal(t, "@\n", output)
839+
// Should not contain headers
840+
assert.NotContains(t, output, "PATH")
841+
assert.NotContains(t, output, "BRANCH")
842+
assert.NotContains(t, output, "HEAD")
843+
}
844+
845+
func TestListCommand_QuietMode_MultipleWorktrees(t *testing.T) {
846+
mockOutput := `worktree /test/repo
847+
HEAD abc123
848+
branch refs/heads/main
849+
850+
worktree /test/repo/.worktrees/feature/test
851+
HEAD def456
852+
branch refs/heads/feature/test
853+
854+
worktree /test/repo/.worktrees/feature/another
855+
HEAD ghi789
856+
branch refs/heads/feature/another
857+
858+
`
859+
860+
mockExec := &mockListCommandExecutor{
861+
results: []command.Result{
862+
{Output: mockOutput, Error: nil},
863+
},
864+
}
865+
866+
var buf bytes.Buffer
867+
cmd := &cli.Command{}
868+
869+
cfg := &config.Config{
870+
Defaults: config.Defaults{BaseDir: ".worktrees"},
871+
}
872+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", true)
873+
874+
assert.NoError(t, err)
875+
output := buf.String()
876+
877+
// Should contain all three worktree names, one per line
878+
expectedOutput := "@\nfeature/test\nfeature/another\n"
879+
assert.Equal(t, expectedOutput, output)
880+
881+
// Should not contain headers or formatting
882+
assert.NotContains(t, output, "PATH")
883+
assert.NotContains(t, output, "BRANCH")
884+
assert.NotContains(t, output, "HEAD")
885+
assert.NotContains(t, output, "----")
886+
}
887+
888+
func TestListCommand_QuietMode_NoWorktrees(t *testing.T) {
889+
mockExec := &mockListCommandExecutor{
890+
results: []command.Result{
891+
{
892+
Output: "",
893+
Error: nil,
894+
},
895+
},
896+
}
897+
898+
var buf bytes.Buffer
899+
cmd := &cli.Command{}
900+
901+
cfg := &config.Config{
902+
Defaults: config.Defaults{BaseDir: "../worktrees"},
903+
}
904+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", true)
905+
906+
assert.NoError(t, err)
907+
output := buf.String()
908+
// Should produce no output in quiet mode when there are no worktrees
909+
assert.Equal(t, "", output)
910+
assert.NotContains(t, output, "No worktrees found")
911+
}
912+
913+
func TestListCommand_QuietMode_DetachedHead(t *testing.T) {
914+
mockOutput := `worktree /test/repo
915+
HEAD abc123
916+
branch refs/heads/main
917+
918+
worktree /test/repo/.worktrees/detached
919+
HEAD def456
920+
detached
921+
922+
`
923+
924+
mockExec := &mockListCommandExecutor{
925+
results: []command.Result{
926+
{Output: mockOutput, Error: nil},
927+
},
928+
}
929+
930+
var buf bytes.Buffer
931+
cmd := &cli.Command{}
932+
933+
cfg := &config.Config{
934+
Defaults: config.Defaults{BaseDir: ".worktrees"},
935+
}
936+
err := listCommandWithCommandExecutor(cmd, &buf, mockExec, cfg, "/test/repo", true)
937+
938+
assert.NoError(t, err)
939+
output := buf.String()
940+
941+
// Should only contain worktree names, not branch state information
942+
expectedOutput := "@\ndetached\n"
943+
assert.Equal(t, expectedOutput, output)
944+
assert.NotContains(t, output, "detached HEAD")
945+
assert.NotContains(t, output, "BRANCH")
946+
assert.NotContains(t, output, "HEAD")
947+
}

0 commit comments

Comments
 (0)