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
4 changes: 4 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ _Avoid_: Workspace Mode, multi-repository mode
The existing default layout where a repository's linked worktrees live next to the main worktree using names like `<repo>-wt-<branch-slug>`.
_Avoid_: Flat mode

**Copy Pattern**:
A user-configured gitignore-style pattern that selects ignored files or ignored directory descendants to copy from a source Repository Worktree into a target Repository Worktree at the same Git-root-relative path.
_Avoid_: Recursive copy entry, exact copy entry

## Example Dialogue

Dev: "I'm working on a feature that touches API, web, and SDK."
Expand Down
56 changes: 56 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ serde_json = "1.0"
serde_yaml = "0.9"
sha2 = "0.10"
toml = "0.8"
globset = "0.4"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ wtk new feature/login
wtk checkout feature/existing
```

By default, `wtk` creates sibling worktree directories named like `<repo>-wt-<branch-slug>`. It also copies ignored local config files that usually need to exist before the worktree is usable, including `.env` files by exact name and ignored descendants under `.agents/`, and it runs `pnpm install` for pnpm repositories. Copy behavior can be customized in repo-local `.wtk/config.toml` or global `~/.wtk/config.toml`.
By default, `wtk` creates sibling worktree directories named like `<repo>-wt-<branch-slug>`. It can copy ignored local config files selected by global `~/.wtk/config.toml` Copy Patterns (for example `**/.env` and `.agents/` from the default template), and it runs `pnpm install` for pnpm repositories. Repo-local `.wtk/config.toml` cannot configure `copy`.

### Move work out after you already started

Expand Down
11 changes: 6 additions & 5 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ wtk checkout feature/existing

By default, `wtk` uses the Sibling Layout: a repository's linked worktrees live next to the main worktree using names like `<repo>-wt-<branch-slug>`.

Commands that create linked worktrees copy configured ignored files from the main worktree into the new worktree at the same Git-root-relative paths. The default recursive file-name list is `.env`, and the default exact-path list is `.agents`. Exact-path directory entries only copy files and symlinks that Git reports as ignored under that directory, so tracked files under `.agents/` are not copied unless you configure them some other way. Recursive matching is still exact-name based, so files such as `.env.local`, `.env.example`, and `.envrc` are not copied unless configured explicitly. When matching ignored files are copied, `wtk` prints one line per file, such as `copied ignored .env: <path>` or `copied ignored file: .agents/local.md`.
Commands that create linked worktrees copy globally configured ignored files from the main worktree into the new worktree at the same Git-root-relative paths. Copy Patterns use gitignore-style syntax except negation (`!`) and only select files or symlinks that Git reports as ignored; tracked files are never copied by Copy Patterns. When matching ignored files are copied, `wtk` prints one concise summary such as `copied 12 ignored files`.

Copy behavior can be configured in either repo-local `.wtk/config.toml` or global `~/.wtk/config.toml`. Repo-local config overrides global config for each configured list. The shape is:
Copy Patterns are configured only in global `~/.wtk/config.toml`. Repo-local `.wtk/config.toml` cannot contain `copy` and fails fast if it does. Without a global `copy` list, `wtk` copies no ignored files by default. The default config template is:

```toml
[copy]
recursive = [".env"]
exact = [".agents"]
copy = [
"**/.env",
".agents/",
]
```

For standalone `wtk new`, success is reported before asynchronous worktree initialization finishes copying ignored files into the new worktree. `wtk` prints info lines when that background initialization starts and where its temporary log file is written.
Expand Down
2 changes: 1 addition & 1 deletion e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This suite runs black-box end-to-end tests for `wtk` with real:

- Git repositories and linked worktrees
- Auxiliary Group configuration, coordinated worktrees, and generated refs
- ignored recursive file copying such as `.env` and `secrets.auto.tfvars`
- ignored file Copy Patterns such as `**/.env` and `**/secrets.auto.tfvars`
- `pnpm install` flows

Run it from the repository root:
Expand Down
41 changes: 32 additions & 9 deletions e2e/test_auxiliary_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,27 @@ def test_auxiliary_group_list_and_remove_manage_config(run_wtk, repo_factory) ->
assert run_wtk("ag", "list", cwd=primary).stdout == "No Auxiliary Groups configured.\n"


def test_auxiliary_group_add_rejects_repo_local_copy(run_wtk, repo_factory) -> None:
primary = repo_factory.init_repo("primary")
api = repo_factory.init_repo("api")
(primary / ".wtk").mkdir()
(primary / ".wtk" / "config.toml").write_text(
'copy = ["**/.env"]\n', encoding="utf-8"
)

result = run_wtk(
"auxiliary-group",
"add",
"backend",
str(api),
cwd=primary,
check=False,
)

result.assert_failure()
assert "Copy Patterns are supported only in global ~/.wtk/config.toml" in result.output


def test_auxiliary_group_remove_rejects_unknown_group(run_wtk, repo_factory) -> None:
primary = repo_factory.init_repo("primary")

Expand Down Expand Up @@ -391,10 +412,6 @@ def test_auxiliary_group_new_dedupes_primary_recursive_and_exact_symlink_copy(
primary,
{
".gitignore": "apps/web/.env\n",
".wtk/config.toml": """
[copy]
exact = ["apps/web/.env"]
""".lstrip(),
"apps/web/keep.txt": "web\n",
},
"configure duplicate coordinated copy",
Expand All @@ -404,6 +421,9 @@ def test_auxiliary_group_new_dedupes_primary_recursive_and_exact_symlink_copy(
shared_env = tmp_path / "shared.env"
shared_env.write_text("WEB=value\n", encoding="utf-8")
os.symlink(shared_env, primary / "apps" / "web" / ".env")
home = tmp_path / "home"
(home / ".wtk").mkdir(parents=True)
(home / ".wtk" / "config.toml").write_text('copy = ["**/.env", "apps/web/.env"]\n', encoding="utf-8")

out = run_wtk(
"new",
Expand All @@ -413,12 +433,14 @@ def test_auxiliary_group_new_dedupes_primary_recursive_and_exact_symlink_copy(
"--from-current",
"--no-clipboard",
cwd=primary,
env={"HOME": str(home)},
).output
primary_linked = linked_worktree_path(primary, "feature/aux-deduped")

assert primary_linked.joinpath("apps/web/.env").is_symlink()
assert primary_linked.joinpath("apps/web/.env").resolve() == shared_env.resolve()
assert out.count("apps/web/.env") == 1
assert "copied 1 ignored files" in out
assert out.count("apps/web/.env") == 0


def test_auxiliary_group_new_rejects_base_with_from_current(run_wtk, repo_factory) -> None:
Expand Down Expand Up @@ -916,9 +938,10 @@ def test_auxiliary_group_add_does_not_persist_inherited_global_copy_config(
(home / ".wtk").mkdir(parents=True)
(home / ".wtk" / "config.toml").write_text(
"""
[copy]
recursive = [".env.local"]
exact = ["specs/change/active"]
copy = [
"**/.env.local",
"specs/change/active",
]
""".lstrip(),
encoding="utf-8",
)
Expand All @@ -928,7 +951,7 @@ def test_auxiliary_group_add_does_not_persist_inherited_global_copy_config(
config_text = (primary / ".wtk" / "config.toml").read_text(encoding="utf-8")
assert "[auxiliary-groups.backend]" in config_text
assert "[auxiliaries.api]" in config_text
assert "[copy]" not in config_text
assert "copy" not in config_text
assert ".env.local" not in config_text
assert "specs/change/active" not in config_text

Expand Down
Loading
Loading