trail: validate resume branch · Entire
trail: validate resume branch
6494868→main·
peyton-alt·3w ago·3 files·+81 added/-15 removed
Sessions
98c72f60a218View transcript
[?
Add Trail Resume SubcommandCodex·GPT-5.5·34 steps](/content/gh/entireio/cli/session/019ef5f3-3472-7f70-82f7-6f0ce46691f4#timeline-98c72f60a218/index.html)
Changes
3
cmd/entire/cli
integration_test
Mtrail_resume_test.go+2/-2
Mtrail_resume_cmd.go+35/-10
Mtrail_resume_cmd_test.go+44/-3
86 unmodified lines
87
88
89
90
91
90
91
92
93
94
86 unmodified lines
checkpointID,
"Create hello method",
"Create goodbye method",
"entire trail resume 321 --session " + firstSession.ID,
"entire trail resume 321 --session " + secondSession.ID,
"entire trail resume 321 --branch feature/test-branch --session " + firstSession.ID,
"entire trail resume 321 --branch feature/test-branch --session " + secondSession.ID,
} {
if !strings.Contains(contextOutput, want) {
t.Fatalf("trail resume --no-resume output missing %q:\n%s", want, contextOutput)
}
Mcmd/entire/cli/integration_test/trail_resume_test.go+2/-2
30 unmodified lines
31
32
33
34
35
36
37
38
39
34
35
36
37
38
39
40
41
42
43
62 unmodified lines
106
107
108
108
109
110
111
112
113
114
115
116
10 unmodified lines
127
128
129
130
131
132
133
36 unmodified lines
170
171
172
173
174
175
176
177
178
37 unmodified lines
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
383 unmodified lines
617
618
619
620
621
622
623
624
625
601
626
627
628
604
629
630
631
607
632
633
634
635
30 unmodified lines
)
type trailResumeOptions struct {
Selector string
SessionID string
CheckpointID string
Force bool
JSON bool
NoResume bool
Selector string
ExpectedBranch string
SessionID string
CheckpointID string
Force bool
JSON bool
NoResume bool
}
type trailResumeContext struct {
62 unmodified lines
between checkpoint sessions on the trail branch when there are multiple.
Non-interactive runs show the same context and resume the latest checkpoint on
the trail branch. Use --session or --checkpoint to resume an exact session or
checkpoint.",
checkpoint.
Use --branch with a trail number or id to assert the branch you expect the trail
to be attached to. If the trail is attached to a different branch, resume stops
before checking anything out.",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
selector, err := parseOptionalTrailSelector(args, opts.Selector)
10 unmodified lines
}
cmd.Flags().StringVar(&opts.Selector, "trail", "", "Trail to resume (number, id, or branch; defaults to the current branch's trail)")
cmd.Flags().StringVar(&opts.ExpectedBranch, "branch", "", "Expected trail branch; fails if the trail is attached to a different branch")
cmd.Flags().StringVar(&opts.SessionID, "session", "", "Resume a specific known local session on the trail branch")
cmd.Flags().StringVar(&opts.CheckpointID, "checkpoint", "", "Resume a specific checkpoint on the trail branch")
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Skip prompts and overwrite existing session logs from checkpoints")
36 unmodified lines
if branch == "" {
return fmt.Errorf("%s has no branch to resume", describeTrailRef(found))
}
if err := validateTrailResumeExpectedBranch(found, opts.ExpectedBranch); err != nil {
return err
}
sessions, sessionErr := resolveTrailResumeSessionContexts(ctx, branch)
sessions, sessionsUnavailable := knownTrailResumeSessionsForContext(sessions, sessionErr)
37 unmodified lines
})
}
func validateTrailResumeExpectedBranch(found *api.TrailResource, expectedBranch string) error {
expectedBranch = strings.TrimSpace(expectedBranch)
if expectedBranch == "" {
return nil
}
actualBranch := strings.TrimSpace(found.Branch)
if actualBranch == expectedBranch {
return nil
}
return fmt.Errorf("%s is attached to branch %q, not expected branch %q", describeTrailRef(found), actualBranch, expectedBranch)
}
func knownTrailResumeSessionsForContext(sessions []trailResumeSessionContext, sessionErr error) ([]trailResumeSessionContext, string) {
if sessionErr != nil {
return nil, sessionErr.Error()
}
383 unmodified lines
return nil
}
arg := shellArg(selector)
resumeCommand := "entire trail resume " + arg
if branch := strings.TrimSpace(ctx.Trail.Branch); branch != "" {
resumeCommand += " --branch " + shellArg(branch)
}
commands := []string{
"entire trail finding " + arg + " --json",
"entire trail resume " + arg,
resumeCommand,
}
if ctx.DefaultResume != nil && ctx.DefaultResume.CheckpointID != "" {
commands = append(commands, "entire trail resume "+arg+" --checkpoint "+shellArg(ctx.DefaultResume.CheckpointID))
commands = append(commands, resumeCommand+" --checkpoint "+shellArg(ctx.DefaultResume.CheckpointID))
}
for _, session := range ctx.Sessions {
commands = append(commands, "entire trail resume "+arg+" --session "+shellArg(session.SessionID))
commands = append(commands, resumeCommand+" --session "+shellArg(session.SessionID))
}
return commands
}
Mcmd/entire/cli/trail_resume_cmd.go+35/-10
81 unmodified lines
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
50 unmodified lines
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
133 unmodified lines
318
319
320
280
321
322
323
324
14 unmodified lines
339
340
341
301
342
343
344
345
49 unmodified lines
395
396
397
357
398
399
400
401
81 unmodified lines
}
}
func TestValidateTrailResumeExpectedBranch(t *testing.T) {
t.Parallel()
trail := &api.TrailResource{
Number: 575,
Title: "Add trail resume",
Branch: "feature/trail-resume",
}
if err := validateTrailResumeExpectedBranch(trail, " feature/trail-resume "); err != nil {
t.Fatalf("validateTrailResumeExpectedBranch() matching branch = %v, want nil", err)
}
if err := validateTrailResumeExpectedBranch(trail, ""); err != nil {
t.Fatalf("validateTrailResumeExpectedBranch() empty expected branch = %v, want nil", err)
}
err := validateTrailResumeExpectedBranch(trail, "feature/other")
if err == nil {
t.Fatal("validateTrailResumeExpectedBranch() mismatch = nil, want error")
}
for _, want := range []string{"trail #575", "feature/trail-resume", "feature/other"} {
if !strings.Contains(err.Error(), want) {
t.Fatalf("error = %q, want it to mention %q", err, want)
}
}
}
func TestKnownTrailResumeSessionsForContextTreatsDiscoveryErrorAsUnavailable(t *testing.T) {
t.Parallel()
if ctx.DefaultResume == nil || ctx.DefaultResume.SessionID != "new-session" {
t.Fatalf("DefaultResume = %#v, want new-session", ctx.DefaultResume)
}
wantCommands := []string{
"entire trail finding 575 --json",
"entire trail resume 575 --branch feature/trail-resume",
"entire trail resume 575 --branch feature/trail-resume --checkpoint aaaaaaaaaaaa",
"entire trail resume 575 --branch feature/trail-resume --session new-session",
"entire trail resume 575 --branch feature/trail-resume --session old-session",
}
if len(ctx.Commands) != len(wantCommands) {
t.Fatalf("commands len = %d, want %d: %#v", len(ctx.Commands), len(wantCommands), ctx.Commands)
}
for i, want := range wantCommands {
if ctx.Commands[i] != want {
t.Fatalf("commands[%d] = %q, want %q", i, ctx.Commands[i], want)
}
}
}
func TestResolveTrailCheckpointSessionsUsesBranchCheckpointMetadata(t *testing.T) {
133 unmodified lines
},
Commands: []string{
"entire trail finding 575 --json",
"entire trail resume 575 --session session-1",
"entire trail resume 575 --branch feature/trail-resume --session session-1",
},
}
14 unmodified lines
"Resume output should show context",
"Commands:",
"entire trail finding 575 --json",
"entire trail resume 575 --session session-1",
"entire trail resume 575 --branch feature/trail-resume --session session-1",
} {
if !strings.Contains(text, want) {
t.Fatalf("context output missing %q:\n%s", want, text)
}
49 unmodified lines
}},
},
DefaultResume: &trailResumeDefaultContext{SessionID: "session-1", CheckpointID: "aaaaaaaaaaaa", Branch: "feature/trail-resume"},
Commands: []string{"entire trail resume 575 --session session-1"},
Commands: []string{"entire trail resume 575 --branch feature/trail-resume --session session-1"},
}
var out bytes.Buffer