A copier template that scaffolds git projects using the bare repo + worktree pattern described in Git Worktrees Done Right and its followup git-wt: Worktrees Simplified by Ahmed el Gabri.
Instead of switching branches with git checkout, you keep multiple branches checked out
simultaneously as separate directories (worktrees). A bare clone holds all git data, and
each worktree is a full working copy of a branch.
uvx copier copy --trust gh:alltuner/projectuner ~/dev/my-projectThe --trust flag is required because this template runs post-copy tasks (bare clone setup
via mise). Without it, copier will refuse to execute them.
You'll be prompted for:
| Question | Default | Description |
|---|---|---|
git_remote_url |
(empty) | Remote URL to bare-clone. Leave empty to start a fresh repo. |
If you provide a remote URL, the template clones it as a bare repo. If you leave it empty, it initializes a fresh bare repo with an empty initial commit. You can connect it to GitHub afterwards (see Setting up a remote).
my-project/
├── .bare/ # Bare git clone
├── .git # Pointer file to .bare
├── main/ # Worktree for the main branch
├── .mise/
│ └── tasks/ # Project tasks
│ ├── wt-add # Create a new worktree
│ ├── wt-rm # Remove a worktree
│ ├── wt-destroy # Remove worktree + local + remote branch
│ ├── wt-ls # List all worktrees
│ ├── wt-update # Fetch remotes and fast-forward main
│ ├── repo-public # Create a public GitHub repo
│ ├── repo-private # Create a private GitHub repo
│ └── remote-add # Add a remote to an existing repo
├── .mise.toml # Mise configuration
├── _/ # Scratchpad for scripts and notes
├── .claude/ # Agent configuration
├── AGENTS.md # Instructions for AI coding agents
├── CLAUDE.md # Symlink to AGENTS.md
└── .copier-answers.yml
All worktree operations are mise tasks, run from the project root:
# Start a new feature
mise run wt-add my-feature
# Work in it
cd my-feature/
# List all worktrees
mise run wt-ls
# Fetch latest and fast-forward main
mise run wt-update
# Clean up when done (keeps remote branch)
mise run wt-rm my-feature
# Or nuke everything: worktree + local + remote branch
mise run wt-destroy my-featureThe main/ worktree stays pristine as a clean reference for diffing. Never edit files in it
directly.
If you started with a fresh repo (no remote URL), connect it to GitHub:
# Create a new GitHub repo and push (pick one)
mise run repo-private myorg/my-project
mise run repo-public myorg/my-project
# Or add an existing remote
mise run remote-add myorg/my-projectrepo-public and repo-private use gh repo create under the hood, so they'll fail if
the repo already exists (which is what you want).
When the projectuner template gets new features or fixes, pull them into your project:
cd ~/dev/my-project
uvx copier update --trustCopier reads .copier-answers.yml to know which template version you're on, computes a diff
against the latest version, and applies changes. It will prompt you to resolve conflicts if
any of your local changes overlap with template updates.
To update to a specific version:
uvx copier update --trust --vcs-ref=v0.2.0Note:
copier updaterequires.copier-answers.yml, which is created automatically when scaffolding with copier or converting withuvxavailable. If you converted manually (withoutuvx), re-run the conversion script to pick up template changes.
Already have a regular git clone and want to switch to the worktree structure? The
conversion script restructures it in place:
# Run directly from a local copy of this repo
./scripts/convert-to-worktree ~/dev/my-project
# Or pipe it from GitHub (no local clone needed)
bash <(curl -fsSL https://raw.githubusercontent.com/alltuner/projectuner/main/scripts/convert-to-worktree) ~/dev/my-projectWhen uvx is available, the script uses copier under the hood. This gives you
.copier-answers.yml, so future copier update --trust works. When uvx is not available,
it falls back to a manual conversion (no .copier-answers.yml).
The script will:
- Validate the directory is a regular git clone with a clean working tree
- Scaffold the worktree structure via copier (or manually as fallback)
- Preserve all branches, tags, and remote configuration
Requirements: the working tree must be clean (no uncommitted or untracked changes). Commit or stash everything first.
The traditional git workflow (checkout, stash, switch) has a hidden cost: every branch switch tears down your working state and rebuilds it. With worktrees:
- No context switching tax. Each branch is its own directory. Your editor, terminal, and build cache stay intact.
- Parallel work. Run tests on one branch while coding on another.
- AI agent isolation. Spawn a worktree per agent. They can't interfere with each other or your active work.
- Clean diffing. Keep
main/pristine and diff any branch against it without stashing.
The generated AGENTS.md (with CLAUDE.md symlinked to it) teaches AI coding agents how
the worktree structure works: where to run commands, how to create branches, and what lives
outside version control.
| Task | Usage | Description |
|---|---|---|
wt-add |
mise run wt-add <branch> [base] |
Create a new worktree. Fetches remotes first, defaults to branching from main. |
wt-rm |
mise run wt-rm <branch> |
Remove a worktree and its local branch. |
wt-destroy |
mise run wt-destroy <branch> |
Remove a worktree and delete both local and remote branches. |
wt-ls |
mise run wt-ls |
List all active worktrees. |
wt-update |
mise run wt-update |
Fetch all remotes and fast-forward main. |
repo-public |
mise run repo-public <owner/repo> |
Create a public GitHub repo, set up remote, and push main. |
repo-private |
mise run repo-private <owner/repo> |
Create a private GitHub repo, set up remote, and push main. |
remote-add |
mise run remote-add <owner/repo> [name] |
Add an existing GitHub repo as remote and push main. Defaults remote name to origin. |
Files at the project root are outside version control. They're excluded via
.bare/info/exclude (not .gitignore). The setup task pre-populates common patterns.
To add your own, edit .bare/info/exclude directly.
Starship's git_branch module has an ignore_bare_repo option, but
it doesn't work with the gitdir pointer pattern (.git file pointing to .bare/). This is
because Starship uses gitoxide which
doesn't follow gitdir indirection when checking is_bare().
The fix has been merged in gitoxide and is expected in the gitoxide release on 2026-02-22, after which Starship needs to bump its gitoxide dependency.
Based on the workflow from Ahmed el Gabri:
- Git Worktrees Done Right -- the bare repo
- worktree pattern that this template scaffolds.
- git-wt: Worktrees Simplified -- a standalone CLI tool that
wraps the same pattern. Several of our mise tasks (
wt-update,wt-destroy, fetch-before-add) are inspired bygit-wt's approach. If you prefer a single CLI over mise tasks, check out git-wt.