Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ddev-worktree

A global DDEV add-on that clones an existing DDEV project (files + database) into a new directory. If the project is a git repo, it uses git worktree to create a linked worktree; otherwise it copies files with rsync.

The new directory gets its own DDEV project name via config.worktree.local.yaml, so both instances can run simultaneously without collision.

Installation

ddev add-on install elabx/ddev-worktrees

Or install from a local clone:

ddev add-on install /path/to/ddev-worktree

Once installed, the ddev worktree and ddev worktree-remove commands are available in every DDEV project.

Quick Start

cd ~/projects/my-site

# Create a worktree for a new branch
ddev worktree feature-login

# Both projects now run side-by-side
ddev list
# my-site          running
# my-site-feature-login  running

# Done with the worktree? Clean up
ddev worktree-remove feature-login

Usage

ddev worktree

ddev worktree <branch-or-name> [--profile=NAME] [--no-db] [--no-start] [--force]

Creates a clone of the current DDEV project in a sibling directory.

What it does:

  1. Exports the database from the source project
  2. Creates the target directory via git worktree add (or rsync if not a git repo)
  3. Copies .ddev/ if it's gitignored
  4. Copies any files specified in the profile's copy list
  5. Creates .ddev/config.worktree.local.yaml with a unique project name
  6. Starts DDEV and imports the database
  7. Runs any post_create commands from the profile

Flags:

Flag Description
--profile=NAME Which profile from .ddev/worktree-hooks.yaml to use
--no-db Skip database export/import
--no-start Create files only — don't start DDEV or run post_create
--force, -f Overwrite an existing target directory/project

Target directory: ../<source-dirname>-<sanitized-name> Target project name: <source-project>-<sanitized-name>

Branch names are sanitized for use as directory names (e.g., feature/foo becomes feature-foo).

ddev worktree-remove

ddev worktree-remove <name> [--list]

List clones:

ddev worktree-remove --list

Remove a clone:

ddev worktree-remove feature-login

This stops the DDEV project, removes the git worktree (or deletes the directory), and cleans up. It refuses to remove directories that don't have a config.worktree.local.yaml as a safety measure (the older config.worktree.yaml is still accepted, so worktrees created before v1.1.0 remain removable).

Configuration: .ddev/worktree-hooks.yaml

Create this file in your project's .ddev/ directory to configure what gets copied and what commands run after clone creation. Commit it to git so your team shares the same setup.

# .ddev/worktree-hooks.yaml
default_profile: full

profiles:
  full:
    copy:
      - site/assets
      - .env
    post_create:
      - composer install
      - npm install

  minimal:
    copy:
      - .env
    post_create:
      - composer install

  frontend:
    copy:
      - .env
    post_create:
      - npm install

Fields:

Field Description
default_profile Which profile to use when --profile is not specified
profiles.<name>.copy Files/directories to copy from source to target (relative to project root). Use this for things not in git: uploads, .env, etc.
profiles.<name>.post_create Shell commands to run in the target directory after DDEV is started

If no .ddev/worktree-hooks.yaml exists, the command works fine — it just skips the copy and post_create steps.

Profile Examples

Note: Only the ProcessWire profile has been tested. The other profiles are untested examples — adapt them to your project's needs.

ProcessWire:

default_profile: full

profiles:
  full:
    copy:
      - site/assets
      - .env
    post_create:
      - composer install

  minimal:
    copy:
      - .env
    post_create:
      - composer install

  frontend:
    copy:
      - .env
      - site/assets
    post_create:
      - npm install

Drupal:

default_profile: full

profiles:
  full:
    copy:
      - sites/default/files
      - .env
    post_create:
      - composer install
      - ddev drush cr

Laravel:

default_profile: full

profiles:
  full:
    copy:
      - storage/app
      - .env
    post_create:
      - composer install
      - npm install
      - ddev artisan key:generate

WordPress:

default_profile: full

profiles:
  full:
    copy:
      - wp-content/uploads
      - .env
    post_create:
      - composer install

How It Works

Project Isolation

The clone gets a config.worktree.local.yaml with override_config: true and a unique name field. This overrides the project name from config.yaml without modifying any git-tracked files. Both projects can run simultaneously with their own containers, databases, and URLs.

Git Worktree Behavior

When the source project is a git repo:

  • Existing local branchgit worktree add <path> <branch>
  • Remote branch only — creates a local tracking branch
  • No such branch — creates a new branch from HEAD

Non-Git Projects

If there's no git repo, the command uses rsync to copy all files, excluding DDEV runtime artifacts.

Keeping git clean

The generated config is named config.worktree.local.yaml, and the .local. is deliberate: DDEV's own generated .ddev/.gitignore already ignores /config.*.local.y*ml. The file stays out of git status without this add-on writing to .gitignore, .git/info/exclude, or any other git-owned file.

One wrinkle: .ddev/.gitignore is itself untracked, so a fresh worktree starts without it and the config is briefly visible to git status. The first ddev start regenerates the ignore file and it disappears. With --no-start, it stays visible until you start the project.

Database transfer

With a database export (the default), the dump is written to a private temp directory created with mktemp -d under $TMPDIR (falling back to /tmp), and removed by an EXIT/INT/TERM trap — so an interrupted or failed run leaves nothing behind, and concurrent runs never collide.

Troubleshooting

mktemp: mkstemp failed on /tmp/ddev-worktree-db-XXXXXX.sql.gz: File exists

Affects v1.1.0 and earlier on macOS. Those versions built the dump path with the X placeholders mid-string (ddev-worktree-db-XXXXXX.sql.gz). BSD mktemp only substitutes a trailing run of Xs, so the name was used literally — the same fixed path on every run. There was also no cleanup trap, so any failure between export and import left the file behind, and every later run then failed on it.

Unblock an affected machine:

rm -f /tmp/ddev-worktree-db-*.sql.gz

Then upgrade to v1.1.1 or later, which uses a temp directory plus a cleanup trap. Note that reinstalling or updating the add-on overwrites ~/.ddev/commands/host/worktree, so patch the repo rather than the installed copy.

Dependencies

  • DDEV >= v1.24.0
  • yq (recommended) — for full YAML parsing of .ddev/worktree-hooks.yaml. Install via brew install yq or see yq docs. Without yq, the command falls back to basic grep/awk parsing with a warning.
  • rsync — for file copying (pre-installed on macOS and most Linux distributions)

Uninstall

ddev add-on remove ddev-worktree

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages