trail: assert resume repository · Entire

trail: assert resume repository

4f8e8b2→main·

peyton-alt·3w ago·2 files·+116 added/-4 removed

Sessions

6ae8eb6ff73dView transcript

Changes

2

31 unmodified lines  
32  
33  
34  
35  
36  
37  
38  
15 unmodified lines  
54  
55  
56  
57  
58  
59  
60  
1 unmodified line  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
42 unmodified lines  
116  
117  
118  
111  
112  
113  
119  
120  
121  
122  
123  
124  
125  
10 unmodified lines  
136  
137  
138  
139  
140  
141  
142  
14 unmodified lines  
157  
158  
159  
160  
161  
162  
163  
164  
165  
8 unmodified lines  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
19 unmodified lines  
210  
211  
212  
189  
213  
214  
215  
216  
23 unmodified lines  
240  
241  
242  
243  
244  
245  
246  
247  
248  
249  
250  
251  
252  
253  
254  
255  
256  
257  
258  
259  
260  
261  
262  
263  
264  
265  
266  
267  
268  
269  
352 unmodified lines  
622  
623  
624  
625  
626  
627  
628  
629  
630  
631  
632  
633  
634  
635  
636  
34 unmodified lines  
671  
672  
673  
674  
675  
676  
677  
678  
679  
31 unmodified lines

type trailResumeOptions struct {  
    Selector       string  
    ExpectedRepo   string  
    ExpectedBranch string  
    SessionID      string  
    CheckpointID   string  
15 unmodified lines

ID     string `json:\"id,omitempty\"`  
    Number int    `json:\"number,omitempty\"`  
    Title  string `json:\"title,omitempty\"`  
    Repo   string `json:\"repo,omitempty\"`  
    Branch string `json:\"branch\"`  
    Base   string `json:\"base,omitempty\"`  
    Status string `json:\"status,omitempty\"`  
1 unmodified line

URL    string `json:\"url,omitempty\"`  
}

type trailResumeRepository struct {  
    Forge string  
    Owner string  
    Repo  string  
}

type trailResumeSessionContext struct {  
    SessionID    string    `json:\"session_id\"`  
    Agent        string    `json:\"agent,omitempty\"`  
42 unmodified lines

the trail branch. Use --session or --checkpoint to resume an exact session or  
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.`,
Use --repo to assert the GitHub repository for copied commands, and --branch  
with a trail number or id to assert the branch you expect the trail to be  
attached to. If either assertion does not match the current checkout or trail,  
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.ExpectedRepo, "repo", "", "Expected GitHub repository (owner/name); fails if the current checkout points elsewhere")
    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")
14 unmodified lines

if opts.NoResume && (strings.TrimSpace(opts.SessionID) != "" || strings.TrimSpace(opts.CheckpointID) != "") {
        return errors.New("cannot combine --no-resume with --session or --checkpoint")
    }
    if _, err := parseTrailResumeRepoFlag(opts.ExpectedRepo); err != nil {
        return fmt.Errorf("validate --repo: %w", err)
    }
    if checkpointID := strings.TrimSpace(opts.CheckpointID); checkpointID != "" {
        if err := id.Validate(checkpointID); err != nil {
            return fmt.Errorf("validate --checkpoint: %w", err)
        }
8 unmodified lines

if err != nil {
            return err
        }
        targetRepo := trailResumeRepository{Forge: forge, Owner: owner, Repo: repo};
        expectedRepo, err := parseTrailResumeRepoFlag(opts.ExpectedRepo)
        if err != nil {
            return fmt.Errorf("validate --repo: %w", err)
        }
        if err := validateTrailResumeExpectedRepo(targetRepo, expectedRepo); err != nil {
            return err
        }
        if expectedRepo.Repo != "" {
            forge, owner, repo = expectedRepo.Forge, expectedRepo.Owner, expectedRepo.Repo
        }

found, err := resolveTrailBySelector(ctx, client, forge, owner, repo, opts.Selector)
        if err != nil {
19 unmodified lines

fmt.Fprintf(cmd.ErrOrStderr(), "Warning: could not load trail findings: %v\n", findingsErr)
        }

resumeCtx := buildTrailResumeContext(*found, sessions, sessionsUnavailable, findings)
        resumeCtx := buildTrailResumeContextForRepo(*found, sessions, sessionsUnavailable, findings, owner + "/" + repo);
        if opts.JSON {
            return encodeTrailResumeContextJSON(cmd.OutOrStdout(), resumeCtx)
        }
23 unmodified lines

})
}

func parseTrailResumeRepoFlag(value string) (trailResumeRepository, error) {
    value = strings.TrimSpace(value)
    if value == "" {
        return trailResumeRepository{}, nil
    }
    owner, repo, err := parseGitHubURL(value)
    if err != nil {
        return trailResumeRepository{}, err
    }
    return trailResumeRepository{Forge: "gh", Owner: owner, Repo: repo}, nil
}

func validateTrailResumeExpectedRepo(current, expected trailResumeRepository) error {
    if strings.TrimSpace(expected.Repo) == "" {
        return nil
    }
    if current.Forge == expected.Forge &&
        strings.EqualFold(current.Owner, expected.Owner) &&
        strings.EqualFold(current.Repo, expected.Repo) {
        return nil
    }
    return fmt.Errorf("this command targets repository %s/%s, but the current checkout is %s/%s", expected.Owner, expected.Repo, current.Owner, current.Repo)
}

func validateTrailResumeExpectedBranch(found *api.TrailResource, expectedBranch string) error {
    expectedBranch = strings.TrimSpace(expectedBranch)
    if expectedBranch == "" {
352 unmodified lines

}

func buildTrailResumeContext(found api.TrailResource, sessions []trailResumeSessionContext, sessionsUnavailable string, findings trailResumeFindingsContext) trailResumeContext {
    return buildTrailResumeContextForRepo(found, sessions, sessionsUnavailable, findings, "")
}

func buildTrailResumeContextForRepo(found api.TrailResource, sessions []trailResumeSessionContext, sessionsUnavailable string, findings trailResumeFindingsContext, repoFullName string) trailResumeContext {
    trailCtx := trailResumeTrailContext{
        ID:     found.ID,
        Number: found.Number,
        Title:  strings.TrimSpace(found.Title),
        Repo:   strings.TrimSpace(repoFullName),
        Branch: strings.TrimSpace(found.Branch),
        Base:   strings.TrimSpace(found.Base),
        Status: strings.TrimSpace(found.Status),
34 unmodified lines

}
    arg := shellArg(selector)
    resumeCommand := "entire trail resume " + arg
    if repo := strings.TrimSpace(ctx.Trail.Repo); repo != "" {
        resumeCommand += " --repo " + shellArg(repo)
    }
    if branch := strings.TrimSpace(ctx.Trail.Branch); branch != "" {
        resumeCommand += " --branch " + shellArg(branch)
    }

Mcmd/entire/cli/trail_resume_cmd.go+60/-4

63 unmodified lines

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
12 unmodified lines

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
93 unmodified lines

213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242

63 unmodified lines

name: "json no resume accepted",
        opts: trailResumeOptions{JSON: true, NoResume: true},
    },
    {
        name:    "invalid repo assertion",
        opts:    trailResumeOptions{ExpectedRepo: "not a repo"},
        wantErr: "validate --repo",
    },
    {
        name: "repo assertion accepted",
        opts: trailResumeOptions{ExpectedRepo: "entireio/cli"},
    },
}
for _, tc := range cases {

t.Run(tc.name, func(t *testing.T) {
12 unmodified lines

}

func TestValidateTrailResumeExpectedRepo(t *testing.T) {

t.Parallel()

current := trailResumeRepository{Forge: "gh", Owner: "EntireIO", Repo: "CLI"}
    expected := trailResumeRepository{Forge: "gh", Owner: "entireio", Repo: "cli"}
    if err := validateTrailResumeExpectedRepo(current, expected); err != nil {
        t.Fatalf("validateTrailResumeExpectedRepo() matching repo = %v, want nil", err)
    }
    if err := validateTrailResumeExpectedRepo(current, trailResumeRepository{}); err != nil {
        t.Fatalf("validateTrailResumeExpectedRepo() empty expected repo = %v, want nil", err)
    }

err := validateTrailResumeExpectedRepo(current, trailResumeRepository{Forge: "gh", Owner: "entireio", Repo: "entire.io"})
    if err == nil {
        t.Fatal("validateTrailResumeExpectedRepo() mismatch = nil, want error")
    }
    for _, want := range []string{"targets repository entireio/entire.io", "current checkout is EntireIO/CLI"} {
        if !strings.Contains(err.Error(), want) {
            t.Fatalf("error = %q, want it to mention %q", err, want)
        }
    }
}

func TestValidateTrailResumeExpectedBranch(t *testing.T) {

t.Parallel()

93 unmodified lines

}

func TestBuildTrailResumeContextWithRepoIncludesRepoInResumeCommands(t *testing.T) {

t.Parallel()

ctx := buildTrailResumeContextForRepo(api.TrailResource{
        ID:     "trl_1",
        Number: 575,
        Title:  "Add trail resume",
        Branch: "feature/trail-resume",
    }, nil, "", trailResumeFindingsContext{}, "entireio/cli")

wantCommands := []string{
        "entire trail finding 575 --json",
        "entire trail resume 575 --repo entireio/cli --branch feature/trail-resume",
    }
    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) {
    tmpDir := t.TempDir()
    t.Chdir(tmpDir)