The usual problem with parallel development is not creating another branch. Git makes that part trivial. The problem is making sure the thing you are testing is actually the branch you just changed, the service is running from the right directory, and your final commit is not dragging unrelated files with it.

I eventually settled on a much cleaner pattern: keep the main repository directory stable, and use git worktree to give every feature branch its own isolated working directory. Once I started doing that consistently, running several features in parallel on the same machine became much less error-prone.

If you are looking for Git parallel branch development, Git worktree workflow, or a practical standard process for isolated branches, that is exactly what this post is about.

Why I stopped relying on repeated branch switching

The core issue is not branch creation. It is contamination.

In a single repository directory, repeated checkout cycles tend to create the same classes of problems:

  • you think you are testing a new feature, but the running service still came from another directory
  • build artifacts from one feature leak into another branch
  • git add . quietly scoops up files that should never be committed
  • bug fixes and feature work start sharing the same messy workspace

That is the part Git worktree solves really well. It does not just help you create a branch faster. It gives each branch a physically separate place to live.

The directory rules I keep fixed

To keep the workflow boring in a good way, I make a few conventions non-negotiable:

  • main repository: /Users/kuangyongjie/charting_library
  • worktree root: /Users/kuangyongjie/worktrees
  • branch format: codex/<feature-name>
  • main branch: master

So the main repository is for stability and merging, while actual feature work happens somewhere like:

/Users/kuangyongjie/worktrees/hotspot-ff

This sounds small, but it changes a lot. Once the path itself tells you where you are, it becomes much harder to confuse the main repo with a feature workspace.

The standard flow is short, but every step matters

First, make sure the worktree root exists:

mkdir -p /Users/kuangyongjie/worktrees

Then create a new worktree and branch from the main repository:

REPO=/Users/kuangyongjie/charting_library
WT=/Users/kuangyongjie/worktrees/<feature-name>
BRANCH=codex/<feature-name>
BASE=master

# If the remote is available, update master first
# git -C "$REPO" pull --rebase central "$BASE"

git -C "$REPO" worktree add "$WT" -b "$BRANCH" "$BASE"
cd "$WT"
git branch --show-current

The expected output is:

codex/<feature-name>

That confirmation matters. From that point on, development, build commands, and local verification should all stay inside "$WT".

Do all development and testing inside the branch directory

Once the worktree exists, I try to be strict about this: build and run only from that branch directory.

For example, if the frontend project lives in a nested folder:

cd "$WT/trendradar-service/trendradar-web"
npm run build
# production obfuscated build
# npm run build:multi-layer

One of the most common false debugging trails is “I changed the code, but the page still looks old.” Quite often the code is fine. The process is just running from somewhere else.

So whenever something looks stale, I check the process cwd first:

lsof -nP -iTCP:<port> | sed -n '1,5p'
ps -p <pid> -o pid,ppid,command
lsof -p <pid> | rg "cwd"

What you want to confirm is that the current working directory actually points to:

/Users/kuangyongjie/worktrees/<feature-name>/...

If it does not, then you are probably testing the wrong branch no matter what Git says.

I almost never use git add . in this workflow

This is one place where I stay deliberately conservative.

With parallel worktrees, I prefer staging only the target files instead of sweeping the whole directory. It is too easy to accidentally commit runtime leftovers, local snapshots, caches, or generated files.

Typical offenders look like this:

  • __pycache__
  • *.db
  • snapshot_*
  • .DS_Store

So my normal pattern is closer to:

cd "$WT"
git add <file1> <file2> <file3>
git status --short
git commit -m "feat/fix: <summary>"

That extra git status --short check is small, but it catches a lot of noise.

Merge back only from the main repository

Once the feature is ready, I do not merge from inside the worktree. I go back to the main repository and merge there:

cd /Users/kuangyongjie/charting_library
git branch --show-current   # must be master

# If the remote is available, update first
# git pull --rebase central master

git merge --no-ff codex/<feature-name>

The important part here is not even --no-ff. It is making sure you really are on the main branch, in the main repository directory, before merging.

If you run into:

MERGE_HEAD exists

that usually means the previous merge never finished cleanly. In that case:

git merge --abort

and then retry the pull/merge sequence.

If you also need to push:

cd /Users/kuangyongjie/charting_library
git push <remote> master

One practical detail here: on your machine, the common remote name is often central, not origin. That is an environment detail, not a Git worktree problem.

When I use fast-forward instead of leaving a merge commit

Sometimes the requirement is slightly different: test in an isolated branch directory, then merge back without creating a merge commit.

That is where a fast-forward style flow makes sense. For example:

cd /Users/kuangyongjie/charting_library
git checkout master
git pull --rebase central master

git worktree add /Users/kuangyongjie/worktrees/hotspot-ff -b codex/hotspot-ff master

Then run the service only from that branch worktree:

cd /Users/kuangyongjie/worktrees/hotspot-ff/trendradar-service
./venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 9544

And verify the process cwd from another terminal:

lsof -nP -iTCP:9544 -sTCP:LISTEN
ps -p <PID> -o pid,ppid,command
lsof -a -p <PID> -d cwd -Fn

That check is worth repeating because “the branch passed testing” means very little if the process never ran from the correct directory.

The rules I now treat as mandatory

If you plan to use worktrees long-term, I would keep at least these rules hard:

  • one feature per worktree directory
  • one task or one conversation per worktree
  • run git branch --show-current before starting work
  • run git status --short before committing
  • merge only from the main master repository

None of these rules are complicated. That is exactly why they work.

Common issues

The local page still shows the old result

Do not assume the code change failed.

First check whether the running process cwd is inside the target worktree. Then verify that you actually rebuilt assets from that branch directory.

fatal: 'origin' does not appear to be a git repository

Usually the remote name is wrong. Run:

git remote -v

and push to the actual remote name. In your environment, that is often central.

MERGE_HEAD exists

That usually means the last merge was left unfinished. Run:

git merge --abort

and restart the merge flow.

I only changed frontend files. Do I need to restart the backend

Usually not. If the static assets were rebuilt and deployed correctly, a cache refresh is often enough.