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
cmd/entire/cli
Mtrail_checkout_worktree.go+32/-18
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,