tidy trail worktree checkout helpers · Entire

tidy trail worktree checkout helpers

a09618a→main·

pfleidi·1w ago·1 file·+32 added/-18 removed

Review follow-ups: document why session.GetGitCommonDir is unsuitable and why managed-worktree paths are filtered from copy candidates, reuse paths.IsRelativeTraversal for the traversal guard, group the worktree match result into a struct with a found flag, and iterate with SplitSeq instead of allocating split slices.

Sessions

01KX6K36A0T2HW717TVG2H5WGEView transcript

[?
Add trail checkout --worktree SupportClaude Code·2 steps](/content/gh/entireio/cli/session/c5215fe7-28db-4a83-9d2e-482217df292d#timeline-01KX6K36A0T2HW717TVG2H5WGE/index.html)

Changes

1

17 unmodified lines

18
19
20
21
22
23
24
25 unmodified lines

50
51
52
53
54
55
56
57
58
80 unmodified lines

139
140
141
138
142
143
144
145
61 unmodified lines

207
208
209
206
210
211
212
213
13 unmodified lines

227
228
229
226
230
231
232
233
18 unmodified lines

252
253
254
255
256
257
258
259
260
261
4 unmodified lines

266
267
268
261
269
270
271
272
58 unmodified lines

331
332
333
326
334
335
336
337
338
331
332
339
340
341
334
335
342
343
344
345
346
29 unmodified lines

376
377
378
371
372
373
374
379
380
381
382
383
384
385
386
387
388
389
390
391
378
392
393
394
381
395
396
383
397
398
399
400
387
388
401
402
403
404
405

17 unmodified lines

"github.com/entireio/cli/cmd/entire/cli/checkpoint/remote"
    "github.com/entireio/cli/cmd/entire/cli/interactive"
    "github.com/entireio/cli/cmd/entire/cli/paths"
)

const (
25 unmodified lines

// gitCommonDirForTrailWorktree returns the absolute git common dir, which is
// the main repo's .git directory even when run from a linked worktree.
// session.GetGitCommonDir is not reused here because it returns relative
// rev-parse results as-is; this feature needs an absolute path for the
// worktree location and the printed cd hint.
func gitCommonDirForTrailWorktree(ctx context.Context) (string, error) {
    cmd := exec.CommandContext(ctx, "git", "rev-parse", "--git-common-dir")
    output, err := cmd.Output()
80 unmodified lines

if err != nil && !errors.Is(err, os.ErrNotExist) {
        return fmt.Errorf("failed to read %s: %w", path, err)
    }
    for _, line := range strings.Split(string(content), "\n") {
    for line := range strings.SplitSeq(string(content), "\n") {
        if strings.TrimSpace(line) == rule {
            return nil
        }
61 unmodified lines

return nil, fmt.Errorf("failed to read %s: %w", worktreeIncludeFile, err)
    var patterns []string
    for _, raw := range strings.Split(string(data), "\n") {
    for raw := range strings.SplitSeq(string(data), "\n") {
        line := strings.TrimRight(raw, "\r")
        if strings.TrimSpace(line) == "" || strings.HasPrefix(line, "#") {
            continue
        }
13 unmodified lines

return nil, fmt.Errorf("failed to list ignored files: %w", err)
    var files []string
    for _, f := range bytes.Split(output, []byte{0}) {
    for f := range bytes.SplitSeq(output, []byte{0}) {
        if len(f) > 0 {
            files = append(files, string(f))
        }
18 unmodified lines

return included
}

// isManagedTrailWorktreePath excludes paths under .entire/worktrees from the
// copy candidates: once the ignore rule ships, sibling trail worktrees' own
// ignored files (e.g. their .env) appear in `git ls-files --ignored` at the
// main root and would otherwise be copied into every new worktree.
func isManagedTrailWorktreePath(rel string) bool {
    slash := filepath.ToSlash(rel)
    return slash == trailWorktreesRelDir || strings.HasPrefix(slash, trailWorktreesRelDir+"/")
}

return "", false
    }
    clean := filepath.Clean(filepath.FromSlash(rel))
    if clean == "." || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
    if clean == "." || paths.IsRelativeTraversal(clean) {
        return "", false
    }
    return clean, true
}
58 unmodified lines

return fmt.Errorf("failed to find main worktree root: %w", err)
    
    existing, managed, found, err := findWorktreeForBranch(ctx, branch, root)
    match, found, err := findWorktreeForBranch(ctx, branch, root)
    if err != nil {
        return err
    }
    if found {
        if !managed {
            return fmt.Errorf("branch %q is already checked out at %s", branch, existing)
        }
        if !match.managed {
            return fmt.Errorf("branch %q is already checked out at %s", branch, match.path)
        }
        fmt.Fprintf(w, "Worktree already exists at %s\n", existing)
        fmt.Fprintf(w, "cd %s\n", shellQuote(existing))
        fmt.Fprintf(w, "Worktree already exists at %s\n", match.path)
        fmt.Fprintf(w, "cd %s\n", shellQuote(match.path))
        return nil
    }

29 unmodified lines

return nil
}

// findWorktreeForBranch reports whether branch is already checked out in any
// worktree, and whether that worktree is managed under
// <root>/.entire/worktrees.
func findWorktreeForBranch(ctx context.Context, branch, root string) (path string, managed bool, found bool, err error) {
// trailWorktreeMatch describes an existing worktree that has a branch checked
// out; managed means it lives under <root>/.entire/worktrees.
type trailWorktreeMatch struct {
    path    string
    managed bool
}

// findWorktreeForBranch returns the worktree that has branch checked out,
// with found reporting whether any worktree does.
func findWorktreeForBranch(ctx context.Context, branch, root string) (match trailWorktreeMatch, found bool, err error) {
    cmd := exec.CommandContext(ctx, "git", "worktree", "list", "--porcelain")
    output, err := cmd.Output()
    if err != nil {
        return "", false, false, fmt.Errorf("failed to list worktrees: %w", err)
    }
    return trailWorktreeMatch{}, false, fmt.Errorf("failed to list worktrees: %w", err)
    }
    // Empty currentRoot: match any worktree, including the current checkout.
    path, found = parseWorktreeForBranch(string(output), branch, "")
    path, found := parseWorktreeForBranch(string(output), branch, "")
    if !found {
        return "", false, false, nil
    }
    return trailWorktreeMatch{}, false, nil
}
    managedRoot := normalizeWorktreePath(filepath.Join(root, filepath.FromSlash(trailWorktreesRelDir)))
    normalized := normalizeWorktreePath(path)
    managed = normalized == managedRoot || strings.HasPrefix(normalized, managedRoot+string(filepath.Separator))
    return path, managed, true, nil
    managed := normalized == managedRoot || strings.HasPrefix(normalized, managedRoot+string(filepath.Separator))
    return trailWorktreeMatch{path: path, managed: managed}, true, nil
}

// ensureTrailWorktreeBranchAvailable makes sure branch exists locally,