Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,18 +486,16 @@ strategy = "squash" # squash, rebase, or merge

### Preserve Settings

Automatically copy git-ignored files (`.env`, `.envrc`, etc.) from an existing worktree when checking out a new one with `wt checkout`. Useful for keeping local configuration in sync across worktrees.
Symlink files from the repo root into new worktrees created with `wt checkout`. Useful for keeping local configuration (`.env`, `.envrc`, etc.) in sync across worktrees — edits in any worktree are instantly visible in all others.

```toml
[preserve]
patterns = [".env", ".env.*", ".envrc", "docker-compose.override.yml"]
exclude = ["node_modules", ".cache", "vendor"]
paths = [".env", ".envrc"]
```

- **patterns** — glob patterns matched against file basenames; only git-ignored files are considered
- **exclude** — path segments to skip (any component match excludes the file)
- **paths** — relative paths from the repo root to symlink (e.g., `".env"`, `"config/.env"`)

Files are copied from the worktree on the default branch, e.g. `main` (or the first available worktree). Existing files are never overwritten. Symlinks are skipped. Use `--no-preserve` on `wt checkout` to skip.
Paths that don't exist in the repo root are silently skipped. Existing files in the target worktree are never overwritten. Use `--no-preserve` on `wt checkout` to skip.

### Self-Hosted Instances

Expand Down Expand Up @@ -563,8 +561,7 @@ delete_local_branches = true # replaces global
default = "gitlab" # replaces global

[preserve]
patterns = [".env.local"] # appended to global (deduplicated)
exclude = ["dist"] # appended to global (deduplicated)
paths = [".env.local"] # appended to global (deduplicated)

# Hooks merge by name — add new hooks or override global ones
[hooks.setup]
Expand Down
28 changes: 8 additions & 20 deletions cmd/wt/checkout_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package main

import (
"context"
"errors"
"fmt"
"path/filepath"
"slices"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -208,7 +206,7 @@ func checkoutInRepo(ctx context.Context, repo registry.Repo, branch string, opts
}
}

preserveWorktreeFiles(ctx, gitDir, wtPath, opts.NoPreserve, cfg.Preserve)
preserveWorktreeFiles(ctx, repo.Path, wtPath, opts.NoPreserve, cfg.Preserve)

action := hooks.ActionOpen
if opts.NewBranch {
Expand Down Expand Up @@ -340,29 +338,19 @@ func setUpstreamTracking(ctx context.Context, gitDir, branch string, newBranch,
}
}

// preserveWorktreeFiles copies git-ignored files from an existing worktree to the new one.
func preserveWorktreeFiles(ctx context.Context, gitDir, wtPath string, noPreserve bool, preserveCfg config.PreserveConfig) {
if noPreserve || len(preserveCfg.Patterns) == 0 {
// preserveWorktreeFiles symlinks preserved files from the repo root into the new worktree.
func preserveWorktreeFiles(ctx context.Context, repoPath, wtPath string, noPreserve bool, preserveCfg config.PreserveConfig) {
if noPreserve || len(preserveCfg.Paths) == 0 {
return
}
l := log.FromContext(ctx)

sourceWT, err := preserve.FindSourceWorktree(ctx, gitDir, wtPath)
if err != nil {
if errors.Is(err, preserve.ErrNoSourceWorktree) {
l.Debug("preserve: no source worktree found")
} else {
l.Printf("Warning: preserve: %v\n", err)
}
return
}

copied, err := preserve.PreserveFiles(ctx, preserveCfg, sourceWT, wtPath)
linked, err := preserve.PreserveFiles(ctx, preserveCfg, repoPath, wtPath)
if err != nil {
l.Printf("Warning: preserve files failed: %v\n", err)
} else if len(copied) > 0 {
l.Printf("Preserved %d file(s) from %s\n", len(copied), filepath.Base(sourceWT))
for _, f := range copied {
} else if len(linked) > 0 {
l.Printf("Preserved %d file(s)\n", len(linked))
for _, f := range linked {
l.Debug(" preserved", "file", f)
}
}
Expand Down
Loading
Loading