clean up worktree include copy internals · Entire
clean up worktree include copy internals
89e4db0→main·
pfleidi·1w ago·2 files·+37 added/-16 removed
Move the .entire/worktrees exclusion from the pattern matcher into listIgnoredFiles, where the repo scan happens, so matchIncludePatterns is pure gitignore matching. Use the osroot helpers on the copy destination and collapse the three per-error cleanup blocks into one deferred close-and-remove.
Sessions
01KX6P4JQVX3AK3NFWAJ1Z81HXView transcript
[?
Add trail checkout --worktree SupportClaude Code·4 steps](/content/gh/entireio/cli/session/c5215fe7-28db-4a83-9d2e-482217df292d#timeline-01KX6P4JQVX3AK3NFWAJ1Z81HX/index.html)
Changes
2
cmd/entire/cli
Mtrail_checkout_worktree.go+18/-15
Mtrail_checkout_worktree_test.go+19/-1
16 unmodified lines
// listIgnoredFiles returns untracked files ignored by repo ignore rules,
// relative to root.
// relative to root. Paths under .entire/worktrees are excluded: sibling trail
// worktrees' own ignored files (e.g. their .env) appear in the listing at the
// main root and would otherwise be copied into every new worktree.
func listIgnoredFiles(ctx context.Context, root string) ([]string, error) {
cmd := exec.CommandContext(ctx, "git", "ls-files", "--others", "--ignored", "--exclude-standard", "-z")
cmd.Dir = root
}
var files []string
for f := range bytes.SplitSeq(output, []byte{0}) {
if len(f) > 0 {
if len(f) > 0 && !isManagedTrailWorktreePath(string(f)) {
files = append(files, string(f))
}
}
}
included := make([]string, 0, len(files))
for _, file := range files {
rel, ok := cleanRelativeIncludeFile(file)
if !ok || isManagedTrailWorktreePath(rel) || !matcher.Match(strings.Split(filepath.ToSlash(rel), "/"), false) {
if !ok || !matcher.Match(strings.Split(filepath.ToSlash(rel), "/"), false) {
continue
}
}
included = append(included, rel)
}
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+"/")
}
if !openedInfo.Mode().IsRegular() || !os.SameFile(srcInfo, openedInfo) {
return errors.New("source changed while opening")
}
if err := destRoot.MkdirAll(filepath.Dir(rel), 0o750); err != nil {
if err := osroot.MkdirAll(destRoot, filepath.Dir(rel), 0o750); err != nil {
return err //nolint:wrapcheck // mkdir error is sufficient for caller context
}
}
out, err := destRoot.OpenFile(rel, os.O_WRONLY|os.O_CREATE|os.O_EXCL, srcInfo.Mode().Perm())
if err != nil {
return err //nolint:wrapcheck // openfile error is sufficient for caller context
}
copied := false
defer func() {
if !copied {
_ = out.Close()
_ = osroot.Remove(destRoot, rel) //nolint:errcheck // best-effort cleanup after a failed copy
}
}
if _, err := io.Copy(out, in); err != nil {
_ = out.Close()
_ = destRoot.Remove(rel) //nolint:errcheck // best-effort cleanup after a failed copy
return err //nolint:wrapcheck // copy error is sufficient for caller context
}
if err := out.Close(); err != nil {
_ = destRoot.Remove(rel) //nolint:errcheck // best-effort cleanup after a failed copy
return err //nolint:wrapcheck // close error is sufficient for caller context
}
if err := destRoot.Chmod(rel, srcInfo.Mode().Perm()); err != nil {
_ = destRoot.Remove(rel) //nolint:errcheck // best-effort cleanup after a failed copy
return err //nolint:wrapcheck // chmod error is sufficient for caller context
}
copied = true
return nil
}
Mcmd/entire/cli/trail_checkout_worktree.go+18/-15
150 unmodified lines
func TestListIgnoredFiles_ExcludesManagedWorktreePaths(t *testing.T) {
testutil.IsolateGitConfigEnv(t)
repoDir := t.TempDir()
testutil.InitRepo(t, repoDir)
testutil.WriteFile(t, repoDir, ".gitignore", ".env\n.entire/\n")
testutil.WriteFile(t, repoDir, testEnvFile, "SECRET=1\n")
testutil.WriteFile(t, repoDir, ".entire/worktrees/other/.env", "SECRET=2\n")
got, err := listIgnoredFiles(context.Background(), repoDir)
if err != nil {
t.Fatalf("listIgnoredFiles: %v", err)
}
want := []string{testEnvFile}
if !slices.Equal(got, want) {
t.Fatalf("listIgnoredFiles() = %v, want %v", got, want)
}
}
func TestLoadWorktreeIncludePatterns(t *testing.T) {
t.Parallel()
}`