# Merge pull request \#1709 from entireio/feat/entire-trail-checkout-worktree

`0622f44`→[main](/content/gh/entireio/cli/commits/main/index.html)
gtrrz-victor·2d ago·4 files·+1,235 added/-3 removed

Add --worktree to entire trail checkout

## Changes

4

- cmd/entire/cli

- Mtrail_checkout_cmd_test.go+13
  - Atrail_checkout_worktree.go+525
  - Atrail_checkout_worktree_test.go+675
  - Mtrail_cmd.go+22/-3

```
119 unmodified lines

```

```go
func TestTrailCheckoutHasWorktreeFlag(t *testing.T) {

t.Parallel()

cmd := newTrailCheckoutCmd()
	flag := cmd.Flags().Lookup("worktree")
	if flag == nil {
		t.Fatal("worktree flag not registered")
	}
	if flag.Value.Type() != "bool" {
		t.Fatalf("worktree flag type = %q, want bool", flag.Value.Type())
	}
}
```

Mcmd/entire/cli/trail_checkout_worktree.go+525

```go
package cli

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

"github.com/go-git/go-git/v6/plumbing/format/gitignore"
)

const (
	trailWorktreesRelDir      = ".entire/worktrees"
	trailWorktreeFallbackName = "branch"
)

func defaultTrailWorktreePath(repoRoot, branch string, trailNumber int) string {
	name := fmt.Sprintf("trail-%d-%s", trailNumber, sanitizeTrailWorktreeName(branch))
	return filepath.Join(repoRoot, filepath.FromSlash(trailWorktreesRelDir), name)
}

func sanitizeTrailWorktreeName(branch string) string {
	name := strings.Map(func(r rune) rune {
		if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '_' || r == '.' {
			return r
		}
		return '-'
	}, strings.TrimSpace(branch))
	name = strings.Trim(name, "-.")
	if name == "" {
		return trailWorktreeFallbackName
	}
	return name
}

// gitCommonDirForTrailWorktree returns the absolute git common dir, which is
// the main repo's .git directory even when run from a linked worktree.
func gitCommonDirForTrailWorktree(ctx context.Context) (string, error) {
	cmd := exec.CommandContext(ctx, "git", "rev-parse", "--git-common-dir")
	output, err := cmd.Output()
	if err != nil {
		return "", gitOutputError("failed to get git common dir", err)
	}
	gitDir := strings.TrimSpace(string(output))
	return filepath.Clean(gitDir), nil
}

func trailWorktreeBaseRoot(ctx context.Context) (string, error) {
	gitDir, err := gitCommonDirForTrailWorktree(ctx)
	if err != nil {
		return "", err
	}
	if filepath.Base(gitDir) != ".git" {
		return "", fmt.Errorf("git common dir %q is not a .git directory", gitDir)
	}
	return filepath.Dir(gitDir), nil
}

// ensureTrailWorktreeIgnoreRule appends the .entire/worktrees/ rule to an
// existing repo-root .gitignore when the directory isn't already ignored.
func ensureTrailWorktreeIgnoreRule(ctx context.Context, w io.Writer, root string) error {
	check := exec.CommandContext(ctx, "git", "check-ignore", "-q", trailWorktreesRelDir+"/")
	check.Dir = root
	err := check.Run()
	if err == nil {
		return nil
	}
	var exitErr *exec.ExitError
	if !errors.As(err, &exitErr) || exitErr.ExitCode() != 1 {
		return fmt.Errorf("failed to check ignore status of %s: %w", trailWorktreesRelDir, err)
	}

appended, err := appendIgnoreRule(filepath.Join(root, ".gitignore"))
	if err != nil {
		return err
	}
	if appended {
		fmt.Fprintln(w, "Added .entire/worktrees/ to .gitignore — commit it to keep the rule.")
	}
	return nil
}

// appendIgnoreRule adds the worktree ignore rule to .gitignore if not already present.
func appendIgnoreRule(path string) (bool, error) {
	const rule = trailWorktreesRelDir + "/"
	content, err := os.ReadFile(path) 
	if errors.Is(err, os.ErrNotExist) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("failed to read %s: %w", path, err)
	}
	for _, line := range strings.Split(string(content), "\n") {
		if strings.TrimSpace(line) == rule {
			return false, nil
		}
	}
	updated := string(content) + rule + "\n"
	if err := os.WriteFile(path, []byte(updated), 0o600); err != nil {
		return false, fmt.Errorf("failed to update %s: %w", path, err)
	}
	return true, nil
}
```

```go
func TestCheckoutTrailWorktree_CreatesWorktree(t *testing.T) {

}
```
