{"slug": "auto-copy-files-when-creating-git-worktrees-or-jujutsu-workspaces", "title": "Auto-copy files when creating Git worktrees or Jujutsu workspaces", "summary": "A developer has published a technique to automatically copy ignored files like .env and API keys into new Git worktrees and Jujutsu workspaces, using a post-checkout hook or shell wrapper that leverages `git ls-files --others --ignored --exclude-standard --directory` to enumerate ignored files from the main worktree. The method eliminates manual copying and requires no extra config, with implementations for Git hooks, a `gwt` shell function for Bash/Zsh and Fish, and a Jujutsu wrapper for colocated repositories.", "body_md": "# Auto-copy files when creating Git worktrees or Jujutsu workspaces\n\nWorktrees and workspaces are one of the best ways to work across multiple branches simultaneously — and they’re especially powerful when pairing with AI coding tools like Claude Code or Cursor, where you might spin up a fresh workspace per task or conversation to keep things isolated and clean.\n\nThe friction point: ignored files don’t carry over. Your `.env`\n\n, `.env.local`\n\n, API keys, local config — none of it comes with you. Every new worktree means manually hunting down and copying those files before you can actually get to work.\n\n## The solution: your `.gitignore`\n\nis already the list\n\nThe files that don’t carry over are, by definition, the ones in your `.gitignore`\n\n. So instead of maintaining a separate config file, ask Git directly which ignored files exist in the main worktree:\n\n```\ngit ls-files --others --ignored --exclude-standard --directory\n```\n\nThis lists every untracked file matched by your ignore rules (`.gitignore`\n\n, `.git/info/exclude`\n\n, global excludes). The `--directory`\n\nflag collapses fully-ignored directories like `node_modules/`\n\ninto a single entry, so a `cp -r`\n\ncopies them in one go instead of file-by-file.\n\nNo extra config to commit, and the list stays correct as your `.gitignore`\n\nevolves.\n\n## Setup\n\n### Git — automatic via hook\n\nThis approach runs transparently every time `git worktree add`\n\nis used, with no change to your normal workflow.\n\nCreate `.git/hooks/post-checkout`\n\nwith the following content, then make it executable with `chmod +x .git/hooks/post-checkout`\n\n:\n\n``` bash\n#!/bin/bash\nMAIN_WORKTREE=$(git worktree list | head -1 | awk '{print $1}')\n\n# Only act in linked worktrees, not on regular checkouts in the main one\n[[ \"$(git rev-parse --show-toplevel)\" == \"$MAIN_WORKTREE\" ]] && exit 0\n\ngit -C \"$MAIN_WORKTREE\" ls-files --others --ignored --exclude-standard --directory -z |\n  while IFS= read -r -d '' f; do\n    if [[ -e \"$MAIN_WORKTREE/$f\" && ! -e \"$f\" ]]; then\n      mkdir -p \"$(dirname \"$f\")\"\n      cp -r \"$MAIN_WORKTREE/$f\" \"$f\"\n      echo \"Copied $f\"\n    fi\n  done\n```\n\nNote:Git hooks aren’t shared via the repo, so each contributor needs to add this themselves — or you can use a hook manager like[Lefthook]or[Husky]to distribute them.\n\n### Git — shell wrapper command\n\nIf you prefer an explicit command (or want something more portable across machines without setting up hooks), add a `gwt`\n\nfunction to your `.bashrc`\n\nor `.zshrc`\n\n:\n\n```\ngwt() {\n  git worktree add \"$@\" || return\n  local dest=\"$1\"\n  local main=$(git worktree list | head -1 | awk '{print $1}')\n\n  git -C \"$main\" ls-files --others --ignored --exclude-standard --directory -z |\n    while IFS= read -r -d '' f; do\n      if [[ -e \"$main/$f\" && ! -e \"$dest/$f\" ]]; then\n        mkdir -p \"$dest/$(dirname \"$f\")\"\n        cp -r \"$main/$f\" \"$dest/$f\"\n        echo \"Copied $f\"\n      fi\n    done\n}\n```\n\nUsage is identical to `git worktree add`\n\n, with one rule: the worktree path comes first (Git happily accepts options after it):\n\n```\ngwt ../my-project-feature feature-branch\ngwt ../my-project-feature -b new-branch\n```\n\nFor Fish shell, add to `~/.config/fish/functions/gwt.fish`\n\n:\n\n## View Fish code\n\n```\nfunction gwt\n  git worktree add $argv; or return\n  set dest $argv[1]\n  set main (git worktree list | head -1 | awk '{print $1}')\n\n  git -C $main ls-files --others --ignored --exclude-standard --directory -z | while read -lz f\n    if test -e \"$main/$f\"; and not test -e \"$dest/$f\"\n      mkdir -p \"$dest/\"(dirname $f)\n      cp -r \"$main/$f\" \"$dest/$f\"\n      echo \"Copied $f\"\n    end\n  end\nend\n```\n\n### Jujutsu — shell wrapper command\n\nJujutsu workspaces share the same underlying store, so there’s no hook equivalent. Since Jujutsu reads `.gitignore`\n\nfiles too, the same Git command enumerates the ignored files — this assumes a [colocated repository](https://jj-vcs.github.io/jj/latest/git-compatibility/#co-located-jujutsugit-repos) (created with `jj git init --colocate`\n\nor `jj git clone --colocate`\n\n), which is the common setup.\n\nAdd a `jjws`\n\nfunction to your `.bashrc`\n\nor `.zshrc`\n\n, and run it from your main workspace:\n\n```\njjws() {\n  jj workspace add \"$@\" || return\n  local dest=\"$1\"\n  local main=$(jj root)\n\n  git -C \"$main\" ls-files --others --ignored --exclude-standard --directory -z -- ':(exclude).jj' |\n    while IFS= read -r -d '' f; do\n      if [[ -e \"$main/$f\" && ! -e \"$dest/$f\" ]]; then\n        mkdir -p \"$dest/$(dirname \"$f\")\"\n        cp -r \"$main/$f\" \"$dest/$f\"\n        echo \"Copied $f\"\n      fi\n    done\n}\n```\n\nThe `':(exclude).jj'`\n\npathspec matters: in a colocated repo, Git sees Jujutsu’s internal `.jj/`\n\ndirectory as ignored, and copying it into the new workspace would corrupt it.\n\nUsage mirrors `jj workspace add`\n\n, with the destination path first:\n\n```\njjws ../my-project-feature\n```\n\nFor Fish shell, add to `~/.config/fish/functions/jjws.fish`\n\n:\n\n## View Fish code\n\n```\nfunction jjws\n  jj workspace add $argv; or return\n  set dest $argv[1]\n  set main (jj root)\n\n  git -C $main ls-files --others --ignored --exclude-standard --directory -z -- ':(exclude).jj' | while read -lz f\n    if test -e \"$main/$f\"; and not test -e \"$dest/$f\"\n      mkdir -p \"$dest/\"(dirname $f)\n      cp -r \"$main/$f\" \"$dest/$f\"\n      echo \"Copied $f\"\n    end\n  end\nend\n```\n\n## Tips\n\n**Install with AI** — Ask your AI agent to install this post to your repository or to your shell.\n\n**Skip the heavy stuff** — your ignore list probably includes things you’d rather rebuild than copy, like `node_modules/`\n\nor build output. Filter them out of the pipeline:\n\n```\ngit -C \"$main\" ls-files --others --ignored --exclude-standard --directory -z |\n  grep -zEv '^(node_modules|dist|target)/'\n```\n\n(Or leave `node_modules/`\n\nin — copying it is often faster than reinstalling.)\n\n**Symlink instead of copy** — if the file should stay in sync across all worktrees (e.g. a shared `.env`\n\nwith no per-branch variation), symlink it instead of copying:\n\n```\nln -s \"$main/$f\" \"$dest/$f\"\n```\n\nJust swap the `cp`\n\nfor `ln -s`\n\nin whichever script you’re using.\n\n**Don’t copy if the file already exists** — all examples above skip copying if the destination file is already present, so re-running is safe and won’t clobber local overrides.\n\n### Who am I?\n\nHello! My name is Cretezy and I am a software developer. I write about programming, personal projects, and more.\n\n[See more posts here.](/)", "url": "https://wpnews.pro/news/auto-copy-files-when-creating-git-worktrees-or-jujutsu-workspaces", "canonical_source": "https://cretezy.com/2026/worktree-copy/", "published_at": "2026-08-03 00:02:36.803159+00:00", "updated_at": "2026-08-03 00:02:39.270082+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Git", "Jujutsu", "Claude Code", "Cursor", "Lefthook", "Husky"], "alternates": {"html": "https://wpnews.pro/news/auto-copy-files-when-creating-git-worktrees-or-jujutsu-workspaces", "markdown": "https://wpnews.pro/news/auto-copy-files-when-creating-git-worktrees-or-jujutsu-workspaces.md", "text": "https://wpnews.pro/news/auto-copy-files-when-creating-git-worktrees-or-jujutsu-workspaces.txt", "jsonld": "https://wpnews.pro/news/auto-copy-files-when-creating-git-worktrees-or-jujutsu-workspaces.jsonld"}}