Daksh Pareek

Welcome to my personal portfolio website, showcasing my projects and blogs.

How I Work Using Git Worktree?

2025-08-15


Introduction to Pain

Remember when we are in middle of our feature and our peer asks us to make some changes in that feature that we delivered last week. We have to switch the context with git stash ususally or commit half baked feature in between without a full review. Then we switch to that branch and make our fixes and pick our original work again.

I know you can relate to this situation.

Git Worktree - The Solution

Most of us are already familiar with git stash but not git worktree. Git worktree is like having a new fresh git clone of that repository and switching to that specific branch and doing our work. But it is different than having seperate git clone of that repository. We can actually checkout to multiple branches at the same time(without the need of stash/commit) and work on them in parallel(without addressing conflicts because they are not there in my seperate worktree).

Visualizing the Structure

my-project/
├── .bare/                  ← Bare repository (no working files)
│   ├── objects/            ← All commits and history
│   ├── refs/               ← All branches and tags
│   ├── config              ← Repository settings
│   └── worktrees/          ← Links to worktrees
│       ├── main/
│       ├── feature/
│       └── hotfix/
├── .git                     ← Points to .bare directory
├── main/                    ← Worktree #1
│   ├── src/
│   ├── package.json
│   └── .git                ← Points back to .bare
├── feature/                 ← Worktree #2
│   ├── src/
│   ├── package.json
│   └── .git                ← Points back to .bare
└── hotfix/                  ← Worktree #3
    ├── src/
    ├── package.json
    └── .git                ← Points back to .bare

So we just cd into that worktree and do our work.

Keep in mind that

My Project Setup to Leverage Git Worktree

What the problem using git worktree with default approach?

If you create worktrees inside your main repo:

my-project/
├── .git/
├── src/
├── package.json
├── hotfix/          ← Worktree creates subdirectory here
│   ├── .git
│   └── src/
└── feature/         ← Another worktree subdirectory
    ├── .git
    └── src/

My Approach

I use git worktree with a bare repository and this is how my project structure looks like:

my-project/
├── .bare/           ← Single Git database
├── main/            ← Clean worktree
├── hotfix/          ← Clean worktree
└── feature/         ← Clean worktree

But what is bare repository?

A bare repository is a Git repository that contains ONLY the version history and Git data, but NO working files that we can edit.

Regular Repository

my-project/
├── .git/            ← Git database (history, branches, etc.)
├── src/             ← Working files
├── package.json     ← Working files
└── README.md        ← Working files

Bare Repository

my-project.git/      ← Note: .git extension by convention
├── objects/         ← Git database contents (same as .git/objects/)
├── refs/            ← Branch and tag references
├── config          ← Repository configuration
├── HEAD            ← Current branch pointer
└── hooks/          ← Git hooks

You can read more on Git Bare Repositories. I am not going into details of bare repository here. This article is about how I use git worktree.

How I use git worktree with bare repository?

I used to use git worktree commands which are there and then coping my .env files and then installing dependencies in each worktree. But I wanted to automate this process and make it easier for myself.

  1. To solve for coping .env files, I have created a folder called shared-config in my bare repository. I keep my .env files there and when my script creates a new worktree it does a symlinks to those .env files in their place.
if [ -d "apps/core-backend/" ]; then
  ln -sf shared-config/core-backend.env apps/core-backend/.env // Creating a symbolic link to the .env file from shared-config
fi
  1. To solve for installing dependencies, I have created a script that runs npm install in each worktree after creating it.

Below is my complete script that I use to create a new worktree and set it up:

BASE_BRANCH=$1
NEW_BRANCH=$2
WORKTREE_DIR=$3

if [ -z "$BASE_BRANCH" ] || [ -z "$NEW_BRANCH" ] || [ -z "$WORKTREE_DIR" ]; then
    echo "Usage: ./setup-worktree.sh <base-branch> <new-branch> <worktree-dir>"
    echo "Example: ./setup-worktree.sh origin/develop feature/GIV-157 GIV-157"
    exit 1
fi

echo "🚀 Setting up worktree: $WORKTREE_DIR for new branch: $NEW_BRANCH from base: $BASE_BRANCH"

# Check if remote origin is properly configured
if ! git remote get-url origin > /dev/null 2>&1; then
    echo "❌ ERROR: Remote 'origin' is not properly configured"
    echo "   Please run: git remote set-url origin <your-repo-url>"
    echo "   Example: git remote set-url origin https://github.com/your-org/givo-backend.git"
    exit 1
fi

echo "🔄 Fetching latest from remote..."
git fetch origin

# Check if worktree directory already exists
if [ -d "$WORKTREE_DIR" ]; then
    echo "⚠️  Worktree directory '$WORKTREE_DIR' already exists. Skipping creation."
else
    echo "📁 Creating worktree..."
    if ! git worktree add "$WORKTREE_DIR" -b "$NEW_BRANCH" "$BASE_BRANCH"; then
        echo "❌ Failed to create worktree"
        exit 1
    fi
fi

cd "$WORKTREE_DIR" || { echo "❌ Failed to enter worktree directory"; exit 1; }

# 1. Setup .env files (symlinks) if apps directory exists
if [ -d "apps/cms/" ] && [ -d "apps/core-backend/" ]; then
    echo "🔗 Setting up environment files..."
    pushd apps/cms/ > /dev/null
    ln -sf ../../../shared-config/cms.env .env
    popd > /dev/null
    pushd apps/core-backend/ > /dev/null
    ln -sf ../../../shared-config/core-backend.env .env
    popd > /dev/null
else
    echo "⚠️  Apps directory not found, skipping .env setup"
fi

# 2. Install dependencies if package.json exists
if [ -f "package.json" ]; then
    echo "📦 Installing dependencies..."
    pnpm install:all
    # 3. Build shared packages
    echo "🔨 Building shared packages..."
    pnpm build:shared
else
    echo "❌ No package.json found in worktree, skipping dependency installation"
fi

# 4. Check if we are in a detached HEAD state and warn the user
CURRENT_BRANCH=$(git symbolic-ref --short -q HEAD)
if [ -z "$CURRENT_BRANCH" ]; then
    echo -e "\n⚠️  WARNING: You are in a detached HEAD state!"
    echo "   To avoid losing work, create a branch with:"
    echo "     git checkout -b <your-branch-name>"
    echo "   Or, if you meant to work on an existing branch, run:"
    echo "     git checkout <branch-name>"
else
    echo "✅ You are on branch: $CURRENT_BRANCH"
fi

cd ..

echo "✅ Worktree '$WORKTREE_DIR' ready!"
echo "📂 Location: $(pwd)/$WORKTREE_DIR"

Commands I use to create worktree in different scenarios encounter?

./setup-worktree.sh origin/develop feature/GIV-XXX GIV-XXX
./setup-worktree.sh feature/123 feature/GIV-XXX-v1 GIV-XXX-v1