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
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
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) {
}