Carla has spent three days on feature/multiple-delete. It is a long branch: it touches app.js, styles.css and the new dialog in ui-components. She has the code half-written, with unfinished functions and the application in a state where it does not even start.

And then the alert arrives: there is a fault in production and a fix has to be released now.

Her current routine is this:

git stash -u
git switch main
git switch -c fix/urgent
# ... fix, test, commit, publish ...
git switch feature/multiple-delete
git stash pop
# ... mentally reconstruct where she was ...

Ten times a day. And every round trip has its toll: the stash with untracked files sometimes gives conflicts when you restore it, the mental context has to be rebuilt, the development server restarts, and since there has been a submodule (lesson 06-05) every branch switch also drags its update along.

Her next idea is to clone the repository twice. It would work, but as we shall see it has a real cost. Git offers something better and fairly little known: several working directories sharing a single object database. It is called git worktree, and with this lesson we close the module.

Contents

  1. What a worktree is
  2. worktree add: the first additional directory
  3. Comparison with cloning twice
  4. The complete set of commands
  5. The key rule: one branch, one worktree
  6. How it looks from the inside
  7. Real use cases
  8. Interaction with stash, submodules and hooks
  9. Closing module 6

  1. What a worktree is

Let us recall the structure from lesson 01-03. A Git repository has two clearly distinct parts:

  • The .git/ directory: the objects, the references, the configuration, the reflog. The real repository.
  • The working tree: the files you see and edit, which are the reflection of one specific commit.

So far we have assumed there is one of each. But it is not obligatory:

A worktree is an additional working directory, with its own HEAD, its own index and its own files, which shares the object database and the references with the original repository.

flowchart TD
    subgraph BD[".git/ — a single database"]
      O["Objects: blobs, trees, commits"]
      R["References: branches, tags, remotes"]
      C["Configuration and reflog"]
    end
    subgraph W1["~/projects/task-manager"]
      H1["HEAD → feature/multiple-delete"]
      I1["Its own index"]
      F1["app.js, styles.css… (half-finished)"]
    end
    subgraph W2["~/projects/task-manager-urgent"]
      H2["HEAD → fix/urgent"]
      I2["Its own index"]
      F2["app.js, styles.css… (clean)"]
    end
    W1 --> BD
    W2 --> BD

What is shared: objects, branches, tags, remotes, git fetch, the repository's configuration, the references' reflog.

What is each worktree's own: HEAD, the index, the files on disk, the state of operations in progress (a half-finished merge, a stopped rebase) and the stash stack.

The original directory is called the main worktree; the added ones, linked worktrees. Functionally they are almost identical.

  1. worktree add: the first additional directory

Carla solves her problem with one command:

cd ~/projects/task-manager
git worktree add ../task-manager-urgent -b fix/urgent main
Preparing worktree (new branch 'fix/urgent')
HEAD is now at c8f2a1e Merge feature/status-filter

What has just happened:

  • The directory ~/projects/task-manager-urgent/ has been created.
  • It contains a complete working copy of the project in main's state.
  • The branch fix/urgent has been created from main and is checked out there.
  • The original directory has not been touched at all: it is still on feature/multiple-delete, with all its half-finished work intact.
git worktree list
/home/carla/projects/task-manager          c8f2a1e [feature/multiple-delete]
/home/carla/projects/task-manager-urgent   c8f2a1e [fix/urgent]

Carla now works in the second directory entirely normally:

cd ../task-manager-urgent
# ... fix app.js ...
git commit -am "Fix deletion when the task has subtasks"
git push -u origin fix/urgent

And when she is finished, she goes back:

cd ../task-manager
git status
On branch feature/multiple-delete
Changes not staged for commit:
	modified:   app.js
	modified:   styles.css

Untracked files:
	new-dialog.js

Exactly as she left it. No stash, no pop, no rebuilding the context: the editor, the development server and the browser window are all still open where they were.

The ways of invoking add:

Command What it does
git worktree add <path> <existing-branch> Opens that branch in the new directory
git worktree add <path> Creates a branch with the directory's name
git worktree add -b <new-branch> <path> [<start-point>] Creates the branch and opens it
git worktree add -B <branch> <path> [<start-point>] Like -b, but resets it if it already exists
git worktree add --detach <path> <commit> Detached HEAD at that commit, with no branch
git worktree add --track -b <branch> <path> origin/<branch> Creates the branch tracking the remote

And a useful one when the branch comes from the remote:

git fetch
git worktree add ../task-manager-review origin/feature/export

Git detects that feature/export exists in origin and automatically creates a local tracking branch (the --guess-remote behaviour, which can be pinned with git config worktree.guessRemote true).

  1. Comparison with cloning twice

The obvious alternative was another git clone. The comparison explains why worktree is better almost always:

git worktree add A second git clone
Disk space Only the working tree's files Files + the whole object database duplicated
Shared objects Yes: a single copy No: two independent copies
Visible branches The same ones in every worktree Each clone has its own
Tags Shared Duplicated and able to drift apart
git fetch A single one updates them all One per clone
Remotes and credentials Shared They have to be configured in each one
Local configuration Shared (with nuances) Independent per clone
A commit in A, visible in B? Immediately Only after push + fetch
Stash Independent per worktree Independent
Hooks Shared (a single .git/hooks) One per clone
Creation time Seconds (there is no transfer) However long the full clone takes
Risk of drifting apart None: it is one single repository Real

The point that is most underestimated is "a commit in A, visible in B?". With two clones, to take a commit from one to the other you have to go through the server. With worktrees, as soon as Carla commits in one directory, that commit already exists for the other: she can run git cherry-pick, git rebase or git log on it immediately, with no network in between.

The space saving, in figures: if .git/ takes up 400 MB (long history, the odd binary) and the working tree 15 MB, an extra clone costs 415 MB and a worktree 15 MB. In large repositories the difference stops being incidental.

There used to be git clone --shared / --reference for sharing objects between clones. It works, but it is fragile: if the reference repository is moved or cleaned up, the dependent clone can end up corrupted. git worktree is the modern, safe solution to the same problem, and the one to use.

  1. The complete set of commands

list

git worktree list
git worktree list --porcelain      # stable format, for scripts
/home/carla/projects/task-manager          c8f2a1e [feature/multiple-delete]
/home/carla/projects/task-manager-urgent   3d8f1a6 [fix/urgent]
/home/carla/projects/task-manager-v1       b7e2c4a (detached HEAD)
/home/carla/projects/task-manager-old      a1b2c3d [experiment] prunable

The prunable marker indicates that the directory no longer exists on disk and its registration can be cleaned up.

remove

The correct way of deleting a worktree:

git worktree remove ../task-manager-urgent

It deletes the directory and its registration. If there are uncommitted changes, it refuses — which is a protection, not a nuisance:

fatal: '../task-manager-urgent' contains modified or untracked files, use --force to delete it
git worktree remove --force ../task-manager-urgent

And be careful: remove does not delete the branch. That is separate:

git branch -d fix/urgent

prune

If you deleted the directory by hand with rm -rf (which works, but leaves the registration behind), prune cleans up the remnants:

git worktree prune
git worktree prune --dry-run -v      # see what it would do without doing it

Git also runs it by itself from time to time, and the grace period is controlled by gc.worktreePruneExpire (three months by default).

move

git worktree move ../task-manager-urgent ~/work/urgent

It moves the directory and updates the internal paths. Moving it with a plain mv breaks the links, so always use this command.

lock and unlock

git worktree lock ../task-manager-usb --reason "It is on the external backup drive"
git worktree unlock ../task-manager-usb

A locked worktree cannot be pruned or moved. It is exactly for the case of a worktree on a removable drive or a network share: when the drive is not mounted, the directory "does not exist" and prune would simply remove it from the registration. lock prevents that, and the reason shows up in git worktree list --porcelain so that people know why.

repair

git worktree repair

It rebuilds the internal links when something has broken them: you have moved directories by hand, you have restored a backup, or you have renamed the main repository.

Summary table:

Command What it does
git worktree add <path> [<branch>] Creates a new worktree
git worktree list Lists them all, with their branch and their commit
git worktree remove <path> Deletes one (with --force if there are changes)
git worktree prune Cleans up registrations of already-deleted worktrees
git worktree move <source> <target> Moves one, updating the paths
git worktree lock/unlock <path> Protects one from prune and move
git worktree repair [<paths>] Repairs broken internal links

  1. The key rule: one branch, one worktree

This is the fundamental restriction, and it has to be understood because it explains half the error messages you will see:

The same branch cannot be checked out in two worktrees at once.

cd ~/projects/task-manager-urgent
git switch feature/multiple-delete
fatal: 'feature/multiple-delete' is already used by worktree at
'/home/carla/projects/task-manager'

And the same when creating one:

git worktree add ../another-one feature/multiple-delete
fatal: 'feature/multiple-delete' is already used by worktree at
'/home/carla/projects/task-manager'

Why the restriction exists. A branch is a pointer that moves on when you commit (lesson 03-01). If two worktrees had the same branch checked out, a commit in one would move the branch under the other's feet: the second would find its HEAD pointing at a commit it had not created, and its working tree would stop corresponding to anything coherent. Git forbids the situation rather than letting it happen.

It is not a limitation, it is a protection. And it has three ways out when you genuinely need the same code twice:

# A) In detached HEAD: with no branch to move, there is no conflict
git worktree add --detach ../review-read-only feature/multiple-delete

# B) At a specific commit, which is the same thing
git worktree add --detach ../version-1.0 v1.0.0

# C) A new branch from the same point
git worktree add -b experiment/another-route ../experiment feature/multiple-delete

Option A is the most used: to read or build a branch you do not need to have it checked out as a branch.

And a practical consequence worth knowing: git branch -d refuses to delete a branch that is checked out in another worktree, and git rebase or git merge cannot operate on it from outside either. To find where it is:

git worktree list | grep branch-name

  1. How it looks from the inside

We pick up lesson 01-04 and the data model again, because the mechanism is elegant and explains everything above.

In the main worktree, .git is a directory:

cd ~/projects/task-manager
ls -ld .git
drwxrwxr-x 9 carla carla 4096 Aug  1 11:23 .git

In a linked worktree, .git is a text file:

cd ~/projects/task-manager-urgent
ls -l .git
cat .git
-rw-rw-r-- 1 carla carla 78 Aug  1 11:25 .git
gitdir: /home/carla/projects/task-manager/.git/worktrees/task-manager-urgent

A single line saying: "my repository is over there". It is exactly the same mechanism used by the submodules from lesson 06-05, where the submodule's .git is a file pointing at the parent's .git/modules/<name>.

And in the main repository:

ls ~/projects/task-manager/.git/worktrees/
task-manager-urgent/  task-manager-v1/
ls ~/projects/task-manager/.git/worktrees/task-manager-urgent/
HEAD  ORIG_HEAD  commondir  gitdir  index  logs/  ORIG_HEAD
File Content
HEAD That worktree's own HEAD
index Its own index (the staging area)
gitdir The absolute path of the working directory it serves
commondir Path to the common .git, where the objects and the refs are
logs/HEAD That worktree's reflog

And there is the complete explanation of the model:

  • HEAD and index are per worktree → each one can be on a different branch and have its own staged changes.
  • objects/ and refs/ are in the commondir → the branches, the tags and the objects are the same for all of them, and that is why a commit in one is instantly visible in the other.

This also makes clear why the restriction in section 5 is unavoidable: there is a single file refs/heads/feature/multiple-delete, and it cannot be the HEAD of two directories committing separately.

There is even a notation for querying another worktree's HEAD:

git rev-parse main@{main-worktree}   # advanced syntax, rarely needed
git worktree list --porcelain        # the usual way

And a note about configuration: by default, .git/config is shared by all the worktrees. If you need one of them to have its own configuration, there is extensions.worktreeConfig:

git config extensions.worktreeConfig true
git config --worktree core.sparseCheckout true

It is an advanced and infrequent case, but it is worth knowing it exists if you come across a config.worktree inside .git/worktrees/<name>/.

  1. Real use cases

Case 1: the hotfix without touching what you have half-finished

Carla's, and the most common. A permanent worktree for emergencies:

git worktree add ../task-manager-hotfix main

When the alert arrives, cd ../task-manager-hotfix, git pull, create the branch, fix it and publish. No stash, no branch switching, no losing the context.

Case 2: comparing two versions while they run

Bruno has to check whether a performance problem existed in version 1.0:

git worktree add --detach ../task-manager-v1 v1.0.0
# Terminal 1
cd ~/projects/task-manager && python3 -m http.server 8000

# Terminal 2
cd ~/projects/task-manager-v1 && python3 -m http.server 8001

Two servers, two browser tabs, both versions running at once. Comparing like that is incomparably more reliable than switching branches back and forth and trying to remember what it was like.

Case 3: building one branch while you work on another

A complete build of task-manager takes four minutes. With a single directory, those four minutes are spent waiting, because any edit spoils the result. With worktrees:

git worktree add ../task-manager-build feature/multiple-delete-rc
cd ../task-manager-build && npm run build &
cd ~/projects/task-manager    # carry on working while it builds

Case 4: reviewing a colleague's branch

Ana has to review Bruno's pull request, but she is halfway through her own work:

git fetch
git worktree add --detach ../review origin/feature/export
cd ../review
# ... run it, read it, test it ...
cd .. && git worktree remove review

It would work without --detach too, but you do not need a local branch in order to review, and this way you avoid accumulating review branches.

Case 5: a long rebase without blocking your work

A git rebase -i with conflicts (lesson 05-02) leaves the repository in an intermediate state. If something urgent comes up, you are trapped: rebase --abort and start again. With a dedicated worktree, the rebase stays paused in its directory and you work in another. Since the state of operations in progress is each worktree's own, they do not interfere.

Case 6: bisect without stopping

git bisect (lesson 06-02) does dozens of checkouts in your directory. If it is a long bisection with builds, you can launch it in a separate worktree:

git worktree add --detach ../task-manager-bisect main
cd ../task-manager-bisect
git bisect start main v1.0.0
git bisect run /tmp/test-delete.sh

Meanwhile, your main directory stays on your branch, untouched. When you are finished, git bisect reset and git worktree remove.

Case 7: documentation on an orphan branch

Some projects publish their documentation on a separate branch (gh-pages and the like). With worktrees:

git worktree add ../task-manager-docs gh-pages

The documentation is edited in its directory and the code in its own, with separate histories and never switching branch.

  1. Interaction with stash, submodules and hooks

With git stash

The stash stack is each worktree's own. A git stash list in the main directory does not show what was stashed in another:

cd ~/projects/task-manager
git stash list
stash@{0}: WIP on feature/multiple-delete: 8d4f2a7 Delegate the list events...
cd ../task-manager-urgent
git stash list
(empty)

Technically, refs/stash is stored per worktree. It is consistent with the model — a stash is half-finished work from one specific tree — but it is surprising the first time.

And the practical conclusion of all this: worktrees do not replace stash, but they greatly reduce the need for it. stash is still the right tool for setting something aside for two minutes within the same branch; the worktree is the right one for working on two branches for days.

Situation Tool
Setting changes aside for two minutes to do a pull git stash
Trying something quickly on the same branch git stash
Working on two branches for hours or days git worktree
Dealing with emergencies without losing the context git worktree
Comparing two versions while they run git worktree
Saving work before switching machine git stash or a WIP branch

With submodules

Worktrees and submodules (lesson 06-05) coexist, but there are two things to know:

  • Submodules do not initialise themselves in a new worktree. After the add, you need:
git worktree add ../task-manager-urgent -b fix/urgent main
cd ../task-manager-urgent
git submodule update --init --recursive
  • The submodules' internal repositories live in the common .git/modules/, so the objects are shared just like the parent's: initialisation does not download anything from the network again, it only does a checkout. It is fast.

Support for worktrees inside submodules has improved a great deal in recent versions of Git, but it is still the territory where the most oddities show up. If something goes out of place, git worktree repair usually sorts it out.

With hooks

Hooks are shared: there is a single .git/hooks/ (or a single core.hooksPath, lesson 06-01) for all the worktrees. An installed pre-commit works in all of them automatically, which is an advantage.

The nuance: a hook that assumes absolute paths or takes a specific directory for granted may get confused. Hooks run with the working directory set to the root of the active worktree, so using relative paths is the right thing to do. And if a hook needs to tell where it is:

git rev-parse --show-toplevel      # root of the current worktree
git rev-parse --git-common-dir     # the shared .git
git rev-parse --git-dir            # this worktree's .git/worktrees/<name>

With performance and maintenance

A brief note, because the full topic belongs to another lesson: worktrees share the object database, so a git gc affects them all and Git takes care not to remove objects referenced from any of them. A worktree that is registered but whose directory no longer exists can, on the other hand, keep objects alive unnecessarily: that is why it is worth running git worktree prune from time to time.

Everything relating to git gc, git maintenance and performance in large repositories is the subject of lesson 08-06: Performance Tips; partial clones, sparse-checkout and the scaling techniques, that of 10-04.

  1. Closing module 6

With this lesson you close the block of tools. Looking back at what has changed in the task-manager team's way of working:

  • Hooks (06-01): automatic checks in pre-commit, commit-msg and pre-push, version-controlled with core.hooksPath. They help against oversight, but what is mandatory gets checked on the server.
  • git bisect (06-02): a binary search that turns 214 commits into 8 tests, automatable with bisect run and its exit codes.
  • git blame (06-03): the history of each line, with -w, -C and --ignore-rev to see through the noise, and git log -L to see the complete evolution.
  • Advanced git log and aliases (06-04): --graph, --first-parent, --simplify-by-decoration, --left-right, coloured formats, shortlog, and the aliases that turn all of that into a single word.
  • Submodules (06-05): a pointer to a commit of another repository, with exact reproducibility in exchange for friction, and compared with subtree, packages and the monorepo.
  • git worktree (06-06): several working directories over a single object database.

And one idea that runs through the whole module: Git's history is not just a record of what happened, it is a queryable database. Who wrote each line, in which commit a behaviour changed, what has been integrated into main this month, which exact version of the library version 1.0 used. All of those questions have an exact answer, and you now know how to ask for it.

What comes next

The team has mastered the tool. Ana, Bruno and Carla know how to build the history, manipulate it with judgement, query it thoroughly and automate checks over it.

What they have not agreed yet is how to work together. And those questions are no longer technical ones:

  • When Bruno finishes a feature, how does he propose it? Does he push straight to main? Does he open a pull request? And what if he has no write access to the repository?
  • When Ana reviews Carla's work, what does she look at, how does she comment and when does she approve? What does a reviewer do beyond repeating what the linter already says?
  • Which branches exist and what is each one for? Is there a develop branch? Release branches? Or does everybody integrate into main several times a day?
  • When is a version released? What has to have been passed before a change reaches production?

There is no single answer: there are different workflows, each with its own logic, its advantages and its type of team. An open source project with hundreds of external contributors cannot work like a team of three people deploying five times a day.

In module 7: Collaboration and Workflow Strategies we shall see forks and pull requests as the mechanism for proposing changes, code reviews and how they are done well, and the three great branching models — Git Flow, GitHub Flow and Trunk Based Development — compared with judgement so that you know which fits which situation. And we shall close with continuous integration: the automatic checks that, this time, nobody can skip with a --no-verify.

We begin with how a change is proposed, in lesson 07-01: Forks and Pull Requests.

Common Mistakes and Tips

Mistake 1: trying to open the same branch in two worktrees. Git prevents it and tells you where it is checked out. Use --detach if you only want to read or build.

Mistake 2: deleting the directory with rm -rf. It works, but it leaves the registration dirty. Use git worktree remove, or git worktree prune afterwards.

Mistake 3: moving the directory with mv. It breaks the internal links. Use git worktree move, or git worktree repair if you have already done it.

Mistake 4: forgetting that remove does not delete the branch. Removing the worktree leaves the branch alive; delete it separately with git branch -d.

Mistake 5: expecting submodules to initialise themselves. You have to run git submodule update --init --recursive in every new worktree.

Mistake 6: looking for a stash in the wrong worktree. The stack is each one's own. If it is not there, look in the other directory.

Mistake 7: creating worktrees inside the repository itself. git worktree add ./temp works, but the directory shows up as untracked content of the main repository. Create them outside, as siblings of the original directory.

Mistake 8: accumulating forgotten worktrees. They take up disk space and keep branches occupied. git worktree list from time to time, and remove the surplus ones.

Tip 1: adopt a naming convention. task-manager, task-manager-hotfix, task-manager-v1. With ../<project>-<purpose> you never get lost.

Tip 2: keep a permanent worktree for emergencies. Always on main, always clean. It is the one that will save your day most often.

Tip 3: create an alias. With what you learned in lesson 06-04:

git config --global alias.wt "worktree list"
git config --global alias.new '!f() { git worktree add "../$(basename "$PWD")-$1" -b "$1"; }; f'

git new urgent-fix creates ../task-manager-urgent-fix with that branch.

Tip 4: --detach for anything read-only. Reviewing, building, comparing or bisecting does not need a local branch, and that way you do not run into the one-branch-per-worktree rule.

Tip 5: one git fetch is enough for all of them. It is a single repository: do not repeat the fetch in each directory.

Tip 6: check your version of Git. worktree has existed since 2.5, but move, remove and repair came later (2.17 and 2.30). On old versions, some operations are manual.

Exercises

Exercise 1: the hotfix flow

  1. Create a repository with main and a feature/long-one branch with uncommitted changes (modified and untracked).
  2. Without doing a stash, create a worktree at ../project-hotfix with a new branch fix/urgent from main.
  3. Commit a fix in the new worktree.
  4. Go back to the original directory and check that your uncommitted changes are still exactly as they were.
  5. Check from the original directory that the hotfix commit already exists (git log fix/urgent), without having done any push or fetch.
  6. Delete the worktree and the branch.

Exercise 2: the one-branch-per-worktree restriction

  1. With the previous repository, try to create a worktree with a branch that is already checked out in another. Note down the error message.
  2. Get the same code into a second directory using --detach.
  3. Check with git worktree list that one shows up with a branch and the other as (detached HEAD).
  4. Try to delete with git branch -d a branch checked out in another worktree and observe what happens.

Exercise 3: the anatomy from the inside

  1. In a linked worktree, check that .git is a file and show its content.
  2. Locate the corresponding directory in the main one's .git/worktrees/ and list its content.
  3. Compare the HEAD of both worktrees.
  4. Do a git stash in one and check that git stash list in the other is empty.
  5. Delete a worktree with rm -rf, check that it still appears in git worktree list (marked as prunable) and clean it up with prune.

Solutions

Solution 1:

mkdir /tmp/practice-worktree && cd /tmp/practice-worktree
git init -q -b main
echo "<html><body></body></html>" > index.html
echo "console.info('start-up');" > app.js
git add . && git commit -q -m "Add the application skeleton"

git switch -qc feature/long-one
echo "// half-finished work, does not build" >> app.js
echo "unfinished draft" > notes.txt

git status --short
 M app.js
?? notes.txt
git worktree add ../project-hotfix -b fix/urgent main
Preparing worktree (new branch 'fix/urgent')
HEAD is now at 4f8a2e6 Add the application skeleton
cd ../project-hotfix
git status --short          # clean
echo "console.info('fix applied');" >> app.js
git commit -qam "Fix the start-up when the container is missing"
git log --oneline
7d3a8f4 Fix the start-up when the container is missing
4f8a2e6 Add the application skeleton
cd /tmp/practice-worktree
git status --short
cat notes.txt
 M app.js
?? notes.txt
unfinished draft

Untouched. No stash, no pop.

git log --oneline fix/urgent
7d3a8f4 Fix the start-up when the container is missing
4f8a2e6 Add the application skeleton

The commit made in the other directory is visible immediately: it is the same object database. With two clones it would have needed push + fetch.

git worktree remove ../project-hotfix
git worktree list
git branch
/tmp/practice-worktree  9c2f7e4 [feature/long-one]
  fix/urgent
* feature/long-one
  main

The branch is still alive: remove does not delete it.

git branch -D fix/urgent

Solution 2:

git worktree add ../other feature/long-one
fatal: 'feature/long-one' is already used by worktree at '/tmp/practice-worktree'
git worktree add --detach ../other feature/long-one
Preparing worktree (detached HEAD 9c2f7e4)
HEAD is now at 9c2f7e4 Add the application skeleton
git worktree list
/tmp/practice-worktree  9c2f7e4 [feature/long-one]
/tmp/other              9c2f7e4 (detached HEAD)

Same commit, same content, no conflict: in ../other there is no branch that could move.

cd /tmp/other
git branch -d feature/long-one
error: Cannot delete branch 'feature/long-one' checked out at '/tmp/practice-worktree'

Git protects the branch checked out in any worktree, not just the current one.

Solution 3:

cd /tmp/other
ls -l .git
cat .git
-rw-rw-r-- 1 carla carla 42 Aug  1 12:10 .git
gitdir: /tmp/practice-worktree/.git/worktrees/other
ls /tmp/practice-worktree/.git/worktrees/other/
HEAD  commondir  gitdir  index  logs  ORIG_HEAD
cat /tmp/practice-worktree/.git/worktrees/other/HEAD
cat /tmp/practice-worktree/.git/HEAD
cat /tmp/practice-worktree/.git/worktrees/other/commondir
9c2f7e4a8b3d5f1e6c9a2b7d4f8c1e5a3b6d9f2c
ref: refs/heads/feature/long-one
../..

The linked worktree has a direct hash (detached), the main one a symbolic reference to its branch, and commondir points at the shared .git where the objects and the refs are.

# 4. The stash is per worktree
cd /tmp/practice-worktree
git stash -u
git stash list
stash@{0}: WIP on feature/long-one: 9c2f7e4 Add the application skeleton
cd /tmp/other
git stash list
(empty)
cd /tmp/practice-worktree && git stash pop     # recover the work
# 5. Deleting by hand and pruning
rm -rf /tmp/other
git worktree list
/tmp/practice-worktree  9c2f7e4 [feature/long-one]
/tmp/other              9c2f7e4 (detached HEAD) prunable

The registration is still there, marked as prunable.

git worktree prune --dry-run -v
git worktree prune
git worktree list
Removing worktrees/other: gitdir file points to non-existent location
/tmp/practice-worktree  9c2f7e4 [feature/long-one]

Clean. With git worktree remove instead of rm -rf, this last step would not have been necessary.

Conclusion

git worktree solves an everyday problem with a simple, well-built idea. The essentials:

  • A worktree is an additional working directory with its own HEAD and index, which shares objects and references with the original repository.
  • Compared with cloning twice: it does not duplicate the object database, it shares branches, tags, remotes and hooks, a single fetch serves them all, and a commit made in one is instantly visible in the other, without going through the server.
  • git worktree add <path> [<branch>] creates it in seconds; -b creates a new branch and --detach opens a commit with no branch.
  • The complete set is add, list, remove, prune, move, lock/unlock and repair. Use remove rather than rm -rf and move rather than mv; lock protects worktrees on removable drives.
  • A branch cannot be checked out in two worktrees at once. It is not a capricious limitation: it stops a commit from moving the branch under another directory's feet. The way out for reading and building is --detach.
  • Inside, the linked worktree's .git is a file with a gitdir: line pointing at .git/worktrees/<name>, where its HEAD, its index and its reflog live; the objects and the refs are in the shared commondir. It is the same mechanism the submodules use.
  • Use cases that earn their place: a hotfix without losing the context, comparing two versions while they run, building one branch while you work on another, reviewing a colleague's branch, and isolating a long rebase or a bisection.
  • The stash is each worktree's own; hooks are shared; submodules have to be initialised in every new worktree.
  • And the relationship with stash: it does not replace it, but it greatly reduces its use. stash for minutes within a branch; worktree for days across several.

With this, module 6 and the course's technical block come to a close. The task-manager team now knows how to build, manipulate, query and automate its history. What it needs now is to reach an agreement: how a change is proposed, how it is reviewed and which branching flow to follow. That is module 7, and it begins in lesson 07-01: Forks and Pull Requests.

Mastering Git: From Beginner to Advanced

Module 1: Introduction to Git

Module 2: Basic Git Operations

Module 3: Branching and Merging

Module 4: Working with Remote Repositories

Module 5: Advanced Git Operations

Module 6: Git Tools and Techniques

Module 7: Collaboration and Workflow Strategies

Module 8: Git Best Practices and Tips

Module 9: Troubleshooting and Debugging

Module 10: Git in the Real World

© Copyright 2026. All rights reserved