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

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

0622f44→main gtrrz-victor·2d ago·4 files·+1,235 added/-3 removed

Add --worktree to entire trail checkout

Changes

4

119 unmodified lines
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

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
}
func TestCheckoutTrailWorktree_CreatesWorktree(t *testing.T) {

}