Skip to content

Instantly share code, notes, and snippets.

@thimslugga
Forked from sellout/git-worktrees.md
Last active August 28, 2026 15:30
Show Gist options
  • Select an option

  • Save thimslugga/0306c9403cd963814f1564ffaf030ecf to your computer and use it in GitHub Desktop.

Select an option

Save thimslugga/0306c9403cd963814f1564ffaf030ecf to your computer and use it in GitHub Desktop.
Git worktree layout

I use git worktrees a lot, and I’ve been annoyed by the repo itself having effectively a privileged worktree that’s always there (and refuses to let any other worktree check out the branch it’s on). And then not having a good place to put my other worktrees. If I clone to $PROJECTS/this-project, where do I put my worktrees?

  • If I put them in the directory, the worktree directories are scattered among all the files in the clone’s working tree (and I seem to remember git having some issues with worktrees inside a working tree), but
  • if I put them next to the directory, then the worktree names need to be prefixed with the repo name (and if there are repos foo and foo-charts, it’s easy to forget that foo-charts isn’t just a worktree of foo.

So, my projects tended to look like this:

$PROJECT_DIR
├── my-project                           # a repo
│   ├── .git
│   └── ...                              # but also a working tree
├── my-project-add-some-feature          # a worktree
│   └── ...
├── my-project-charts                    # a different repo, but with a worktree-looking name
│   ├── .git
│   └── ...                              # which also has its own working tree
├── my-project-charts-refactor-something # a worktree in the different repo
│   └── ...
└── my-project-fix-this-bug              # another worktree in the first repo
    └── ...

But now I have a new script for cloning a repo (abbreviated a bit here), which does this:

CLONE_DIR=$PROJECT_DIR/$REPO/repo
git clone $DOMAIN/$ORGANIZATION/$REPO $CLONE_DIR
cd $CLONE_DIR
git checkout $(git commit-tree $(git hash-object -t tree /dev/null) < /dev/null)

which is what I wish git clone --bare did, but doesn’t quite. Basically, you end up with a repo without a working tree. So there’s no branch locked by the repo, and no privileged working tree.

So then I end up with something like this:

$PROJECT_DIR
├── my-project
│   ├── add-some-feature  # a worktree
│   │   └── ...
│   ├── fix-this-bug      # another worktree
│   │   └── ...
│   └── repo              # the “bare” repo
│       └── .git          # the only thing in the “bare” repo
└── my-project-charts     # a different project with a worktree-looking name
    ├── refactor-something
    │   └── ...
    └── repo
        └── .git

It’s annoying to have that script for cloning, so I’ll probably try to get rid of it. But after the initial clone, I really like working like this.

# Add a worktree (in repo-dir) for an existing local/remote branch (create a local branch if no branch exists).
wt-add branch:
#!/usr/bin/env bash
set -euo pipefail
branch='{{ branch }}'
dir="${branch##*/}"
repo="repo-dir"
target="$repo/$dir"
if [[ -e "$target" ]]; then
echo "Error: target worktree directory already exists: $target" >&2
echo "Branch: $branch" >&2
echo "Derived directory name: $dir" >&2
exit 1
fi
if git -C "$repo" worktree list --porcelain | grep -Fqx "branch refs/heads/$branch"; then
echo "Error: branch is already checked out in another worktree: $branch" >&2
exit 1
fi
git -C "$repo" fetch --prune origin
if git -C "$repo" show-ref --verify --quiet "refs/heads/$branch"; then
echo
echo "Using existing local branch: $branch"
git -C "$repo" worktree add "$dir" "$branch"
elif git -C "$repo" show-ref --verify --quiet "refs/remotes/origin/$branch"; then
echo
echo "Creating local tracking branch from remote: origin/$branch"
git -C "$repo" worktree add --track -b "$branch" "$dir" "origin/$branch"
else
echo
echo "Remote branch origin/$branch does not exist"
echo "Creating new local branch $branch from origin/HEAD"
git -C "$repo" worktree add -b "$branch" "$dir" origin/HEAD
fi
echo "Initializing submodules in $target..."
git -C "$target" submodule update --init
#!/usr/bin/env bash
# Usage: wtree <git url> (in an empty directory)
REPO_URL=$1
SCRIPT_NAME=$(basename "$0")
if [[ -z "$REPO_URL" ]]; then
echo "❌ Error: Missing repository URL."
echo "Usage: $0 <repo-url>"
exit 1
fi
# Check if the current directory is empty but allow the script itself to be present
FILES_IN_DIR=$(ls -A | grep -v "$SCRIPT_NAME")
if [[ ! -z "$FILES_IN_DIR" ]]; then
echo "❌ Error: Current directory is not empty!"
echo "To keep your worktree setup clean, please run this in a fresh folder."
exit 1
fi
echo "🚀 Initializing Pro Git Worktree setup..."
# Clone as bare into a hidden directory
git clone --bare "$REPO_URL" .bare
# Link the root to the bare repository
echo "gitdir: ./.bare" > .git
# Enable remote tracking (The "Remote Awareness" fix)
# This ensures 'git fetch' sees all branches from the server
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
# Fetch everything
git fetch --all
echo "✅ Setup complete!"
echo "Next step: git worktree add main"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment