feat(trail): require explicit branch/selector when --repo is set · Entire
feat(trail): require explicit branch/selector when --repo is set
ea220cd→main·
toothbrush·2w ago·3 files·+49 added/-0 removed
With --repo targeting a repo other than the local clone, the branch-defaulting commands (show, update, delete, watch, finding) fell back to the local checkout's current branch, which could silently resolve the wrong trail when a branch name is shared. Require an explicit --branch or trail selector alongside --repo and fail clearly otherwise.
Also restores a //nolint:ireturn directive in agentimport.go that a prior
local mise run lint --fix pass had stripped (CI lints without --fix).
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
53294056bf83View transcript
?\Investigate CLI Repo and Branch FlagsClaude Code·Opus 4.8[1m]·1 step
Changes
3
cmd/entire/cli
Mtrail_cmd.go+21
Mtrail_repo_flag_test.go+25
Mtrail_review_cmd.go+3
107 unmodified lines
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
33 unmodified lines
159
160
161
162
163
164
165
166
167
845 unmodified lines
1013
1014
1015
1016
1017
1018
1019
1020
1021
335 unmodified lines
1357
1358
1359
1360
1361
1362
1363
1364
1365
107 unmodified lines
return nil
// ensureTrailRepoHasTarget requires an explicit branch or trail selector when
// --repo targets a repository other than the local clone. Without one, the
// branch-defaulting commands fall back to the local checkout's current branch,
// which would silently resolve the wrong trail (a shared branch name) in the
// overridden repo. hint names the acceptable targets for the command.
func ensureTrailRepoHasTarget(cmd *cobra.Command, hasTarget bool, hint string) error {
if trailRepoFlag(cmd) != "" && !hasTarget {
return fmt.Errorf("--repo requires an explicit target: %s", hint)
}
return nil
}
// trailListOptions are the inputs to runTrailListAll. Keeping them on a
// struct avoids a long positional argument list at the two call sites.
type trailListOptions struct {
33 unmodified lines
if selector != "" && trailBranchFlag(cmd) != "" {
return errors.New("pass a trail selector or --branch, not both")
}
if err := ensureTrailRepoHasTarget(cmd, selector != "" || trailBranchFlag(cmd) != "", "pass a trail selector or --branch"); err != nil {
return err
}
return runTrailShow(cmd.Context(), cmd.OutOrStdout(), cmd.ErrOrStderr(), trailInsecureHTTP(cmd), selector, trailRepoFlag(cmd), trailBranchFlag(cmd))
},
}
845 unmodified lines
Short: "Update trail metadata",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if err := ensureTrailRepoHasTarget(cmd, strings.TrimSpace(branch) != "", "pass --branch"); err != nil {
return err
}
return runTrailUpdate(cmd.Context(), cmd.OutOrStdout(), cmd.ErrOrStderr(), trailInsecureHTTP(cmd), trailUpdateInputs{
Status: statusStr,
StatusChanged: cmd.Flags().Changed("status"),
335 unmodified lines
if number > 0 && cmd.Flags().Changed("branch") {
return errors.New("cannot combine a trail <number> with --branch")
}
if err := ensureTrailRepoHasTarget(cmd, number > 0 || strings.TrimSpace(branch) != "", "pass a trail number or --branch"); err != nil {
return err
}
return runTrailDelete(cmd, number, branch, force)
},
}
Mcmd/entire/cli/trail_cmd.go+21
135 unmodified lines
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
135 unmodified lines
}
// --repo must not silently fall back to the local checkout's branch: the
// branch-defaulting commands require an explicit branch or selector alongside it.
func TestTrailRepoRequiresExplicitTarget(t *testing.T) {
t.Parallel()
tests := []struct {
name string
args []string
}{
{name: "show", args: []string{"show", "--repo", "gh/acme/app"}},
{name: "watch", args: []string{"watch", "--repo", "gh/acme/app"}},
{name: "update", args: []string{"update", "--repo", "gh/acme/app"}},
{name: "delete", args: []string{"delete", "--repo", "gh/acme/app"}},
{name: "finding list", args: []string{"finding", "list", "--repo", "gh/acme/app"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := execTrailCmdExpectErr(t, tt.args...)
if err == nil || !strings.Contains(err.Error(), "--repo requires an explicit target") {
t.Fatalf("err = %v, want '--repo requires an explicit target'", err)
}
})
}
}
// Sanity: the persistent --repo flag is registered on the trail root and shows
// up in help, so every read subcommand inherits it.
func TestTrailRepoFlagRegisteredOnRoot(t *testing.T) {