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

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