---
title: 'Multiple Working Trees'
source: 'https://academia.sh/en/courses/advanced-git/multiple-working-trees'
course: 'Advanced Git'
language: en
updated: '2026-08-17T18:10:42+00:00'
license: 'CC BY-SA 4.0'
---

# Multiple Working Trees

An extra working tree shares the object store and copies 0 objects: in a thousand-commit history, the clone scheme writes 22560 objects for four branches while the tree scheme stays at 5640; 11466 of the 16920 gained objects are binary.

The previous lesson dropped clone cost to a third but did not eliminate the cost: the table
still said **clone**, and the clone was paid again every time it was done. This carries an
assumption with it too — the assumption that working on two branches of the same repository
together requires a second copy.

The assumption is not true. A repository has two components, and they can be duplicated
separately: an **object store** and a **working tree**. A clone copies both at once. This
lesson's question is: if the object store is left single and the working tree is
duplicated, how many objects go uncopied compared to a clone per branch?

## One Object Store, Multiple Trees

A **worktree** is a repository commit written to the file system. **Multiple working
trees** is the scheme that lets more than one working tree be set up bound to the same
object store. An extra tree is opened as a directory; inside it, a small file pointing not
to a repository directory but to the main repository is placed, and from that moment the
tree reads its objects from the main repository.

```text
# taught commands — example dump, not executed

git worktree add ../tree-report report
git worktree add --detach ../tree-review <commit-id>
git worktree list
git worktree remove ../tree-report
git worktree prune
```

`add` opens the new directory and checks out the given branch there. `--detach` checks out
not a branch but a commit directly; the tree stands in a detached-HEAD state, and this is
the state wanted for a temporary inspection. `list` reports every tree and which branch each
one is on. `remove` removes a tree, and `prune` cleans the administrative record of trees
whose directory was deleted by hand.

One constraint has to be known from the start: **a branch can be checked out in only one
tree at a time.** Trying to check the same branch out in a second tree is refused. The
constraint is not arbitrary — if two trees wrote commits to the same branch, which end the
branch showed would depend on the tree, and it would lose reference uniqueness.

## What Gets Shared

The distinction is built in one sentence: **what belongs to history is shared, what belongs
to work is specific to the tree.**

The shared side is the object store and the references. A commit written in one tree drops
into the store as an object and becomes **reachable** from the other trees from that moment
— no transfer is required. Branches, tags, and the reflog are shared too.

The tree-specific side is the working state: the `HEAD` reference, the staging area, and the
files in the working area. Every tree keeps its own `HEAD`, so it can stand on a separate
branch; every tree keeps its own staging area, so a change staged in one does not show up in
another. An uncommitted change stays inside the tree and is not shared.

Configuration stands between the two. The repository's configuration file is shared, and a
change made in one tree affects them all; making a setting specific to just one tree is an
option that has to be separately turned on. Repository attributes are shared too — that
file, the subject of the next lesson, is a tracked file and belongs to history, so it applies
the same rule across all trees.

This distinction directly gives the measurement's setup: an extra tree **copies no object**,
it only writes an administrative record and checks files out to the working area. A clone,
by contrast, copies the entire object store.

## The Measurement's Assumptions

- **LR14** — The measurement uses the shared setup's four scales. The same work, on the same
  number of branches, is done under two separate schemes; the produced commit and object set
  is the same in both.
- **LR15** — In the clone scheme, a separate clone is opened per branch and each clone
  copies the **entire** object store; partial clone and shallow clone are not used in this
  measurement.
- **LR16** — In the tree scheme, the object store is single; an extra tree copies **0
  objects**. The only thing it writes is **3** administrative records per tree, and these
  records are not objects, they are counted separately.
- **LR17** — The files checked out to the working area are the same in both schemes; four
  branches materialize four file sets in both schemes. This is why it is outside the
  measurement.
- **LR18** — "Binary uncopied" is the share of the gained objects coming from binary bodies;
  this is the same count as the previous lesson's object-type distinction.
- **LR19** — The unit of the first and second parts is **touched object**. The unit of the
  third part is **step**, and a step is a single fetch that moves one branch's tip from one
  copy to another.

## Measurement

```python
"""Multiple working trees: what object sharing gains over a clone per branch.

Part 1 - four trees, four scales: clone-scheme and tree-scheme objects.
Part 2 - gain as tree count grows (1000-commit history).
Part 3 - access from one tree to a commit written in another: unit step.
"""
SEED = 20260814
FILES = ("metrics.py", "report.py", "identity.py", "config.py", "document.md")
SCALES = (50, 200, 1000, 4000)
TREES = (1, 2, 4, 8)
ADMIN_RECORD = 3   # admin record written per extra tree; copies no object


def rng(seed):
    d = seed % 2147483646 + 1

    def draw(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return draw


def history(n, seed=SEED):
    """n-commit linear history; each commit touches one file."""
    draw, commits = rng(seed), []
    for i in range(n):
        file = FILES[draw(5)]
        binary = draw(11) == 0
        commits.append({"no": i + 1, "file": file, "binary": binary,
                         "objects": 2 + (40 if binary else 0)})
    return commits


def clone_cost(t, include_binary=True):
    return sum(x["objects"] for x in t if include_binary or not x["binary"])


def clone_scheme(t, trees):
    """One clone per branch: each clone copies its own object store."""
    return trees * clone_cost(t)


def tree_scheme(t, trees):
    """Multiple working trees: one object store, extra trees copy no object."""
    return clone_cost(t)


def access_step(trees, scheme):
    """Access from a commit written in one tree to the other trees: fetch step."""
    return 0 if scheme == "tree" else trees - 1


print("commits  clone objects  4-clone objects  4-tree objects  gained objects  binary uncopied")
for n in SCALES:
    t = history(n)
    k, a = clone_scheme(t, 4), tree_scheme(t, 4)
    binary = 3 * (clone_cost(t) - clone_cost(t, False))
    print(f"{n:6d} {clone_cost(t):13d} {k:15d} {a:14d} {k - a:15d} {binary:16d}")
print()
t = history(1000)
print("trees  clone-scheme objects  tree-scheme objects  gained objects  objects per extra tree  admin records")
for trees in TREES:
    k, a = clone_scheme(t, trees), tree_scheme(t, trees)
    extra = (k - clone_cost(t)) // (trees - 1) if trees > 1 else 0
    print(f"{trees:4d} {k:20d} {a:20d} {k - a:15d} {extra:22d} "
          f"{(trees - 1) * ADMIN_RECORD:13d}")
print()
print("trees  fetch steps in clone scheme  fetch steps in tree scheme")
for trees in TREES:
    print(f"{trees:4d} {access_step(trees, 'clone'):27d} "
          f"{access_step(trees, 'tree'):27d}")
print()
print(f"single object store {clone_cost(t)} objects; four trees share the same store, "
      f"objects copied by the extra trees {tree_scheme(t, 4) - clone_cost(t)}")
print(f"if the same work were done with four clones, objects copied "
      f"{clone_scheme(t, 4) - clone_cost(t)}")
```

```
commits  clone objects  4-clone objects  4-tree objects  gained objects  binary uncopied
    50           380            1520            380            1140              882
   200           920            3680            920            2760             1638
  1000          5640           22560           5640           16920            11466
  4000         22600           90400          22600           67800            45990

trees  clone-scheme objects  tree-scheme objects  gained objects  objects per extra tree  admin records
   1                 5640                 5640               0                      0             0
   2                11280                 5640            5640                   5640             3
   4                22560                 5640           16920                   5640             9
   8                45120                 5640           39480                   5640            21

trees  fetch steps in clone scheme  fetch steps in tree scheme
   1                           0                           0
   2                           1                           0
   4                           3                           0
   8                           7                           0

single object store 5640 objects; four trees share the same store, objects copied by the extra trees 0
if the same work were done with four clones, objects copied 16920
```

## What Sharing Gains

The first table's unit is **touched object**. In a thousand-commit history, working on four
branches writes **22560** objects under the clone scheme; under the tree scheme, the total
stays at **5640**. Gained objects: **16920** — the equivalent of three full clones. At four
thousand commits, the same computation gives **67800** objects.

Where the gain comes from is written in the last column. Of the **16920** objects gained at
a thousand commits, **11466** come from binary bodies; the previous lesson's **0.6777**
share drops in here exactly the same way. The more binary assets a repository has, the more
opening a second clone costs — and that entire cost is **never paid** with a shared object
store.

The pattern is the same at small scales too, but the magnitudes can mislead. At fifty
commits, the gain is **1140** objects; next to four thousand commits' **67800**, this looks
small, but the ratio between the two schemes is identical in every row. The gain is directly
the product `(trees − 1) × clone`; scale only grows the multiplier, it does not change the
relationship. The smallest measurable difference at this scale is **1/50**, and no number in
the table rests on a claim below that resolution.

The second table gives the same thing by tree count, and the middle column stays fixed: two
trees, four trees, eight trees — all **5640**. The object copied per extra tree is **0** in
every row; the **5640** on the right is what the clone scheme pays per extra copy. The only
thing paid under the tree scheme is the administrative record at the far right: **21**
records for eight trees. These are not objects and do not enter history.

The metric that follows from this is one sentence: **working-tree count does not grow the
object store.** Under the clone scheme, cost grows linearly with tree count; under the tree
scheme, it is fixed. This is the one pattern among the costs measured in this course that is
**entirely** independent of scale: bisect grew slowly with scale, the rewrite ratio was
fixed but its total grew; here, an extra copy's object cost is **zero** at every scale and
every tree count.

## Visibility and the Fetch Step

The third table's unit is **step**, and what it measures is not objects but access.

Under the clone scheme, a commit written to a branch stands in that clone's object store.
For the other copies to access it, a fetch is required; in a four-copy scheme, a commit
reaching all of them is **3** steps, in an eight-copy one, **7**. Under the tree scheme,
this number is **0** in every row — the commit is already in the shared object store, and
the other tree sees it the moment it is written.

This is the scheme's second gain, independent of object count, and it matters more in
practice. A fix written in one tree can be tested immediately in another; cherry-pick,
comparison, and merge operations happen between trees **without going over the network**.
Under the clone scheme, the same work requires a round trip to a remote repository.

The limit comes from the same place. A shared object store means a shared failure surface: a
garbage collection done in one tree concerns every tree, and a repository corrupted in one
tree affects all of them at once. Under the clone scheme, copies are isolated from each
other, and this isolation is the one thing bought in exchange for the **16920** objects
paid.

## A Third Scheme: Switching in a Single Tree

The measurement compared two schemes, but the third one is the most common in practice: a
single working tree and switching between branches. This scheme copies **0** extra objects
— same as the tree scheme — and requires **0** fetch steps. Looked at by the object unit, it
is the cheapest scheme.

Its cost is paid somewhere else. Every switch rewrites the files in the working area; if
there is an uncommitted change, the switch is either blocked or the change is carried along.
The Branching and Collaboration course counted this case in detail: which change gets
carried, which is blocked, and which is left behind was measured there and is not repeated
here. The stash is the standard way to get past this obstacle — it sets an uncommitted
change aside and reopens it after the switch.

What multiple working trees solves is exactly this: **there is no switch.** Two branches are
open at the same time, in two separate directories; a test can run in one while an edit is
made in the other. The stash is not needed, because an uncommitted change does not need to
give up its place.

In exchange, every tree materializes its own working state, and this is not limited to
files. Dependency directories, build outputs, and caches are recreated per tree; these are
outside the measurement because they do not belong to history, but they really do take up
room on disk, and in a four-tree scheme they are produced four times. The **16920** gained
in the object unit does not cover this cost. The choice between the two schemes therefore
does not come down to a single number: the measured object gain is real and large, the
unmeasured working state grows with tree count.

## Removing a Tree and Recovery

When a working tree is removed, objects are not removed — the objects did not belong to the
tree. The only thing that can be lost is what was **uncommitted in that tree**: the changes
in the working area and the staging area's contents. The `remove` command refuses if there
is an uncommitted change in the tree; deleting the directory by hand asks nothing. The
difference between these two paths is whether there is a warning.

The second loss path is quieter. A commit written in a tree in a detached-HEAD state is not
bound to any branch; once the tree is removed, that commit becomes unreachable and drops
once the garbage-collection window closes. **The recovery path:** the reflog is shared, so
the commit's tip can be read from another tree and bound to a branch before the window
closes. The cheaper path is opening that tree not with `--detach` but with a branch name — a
branch is a reference, and a reference is independent of the tree's lifetime.

For a tree standing on a removable medium or a temporarily mounted directory, there is a
separate safeguard: the tree is **locked**. A locked tree's administrative record is not
cleaned up even when the directory is unreachable; otherwise the cleanup operation would
ignore the tree, and the branch in that tree would become checkable out somewhere else. The
lock does not prevent loss, it prevents a **misdiagnosis**.

The third is the administrative record itself: the record of a tree whose directory was
deleted by hand stays in the repository and keeps preventing that branch from being checked
out in another tree. `prune` cleans these records and touches no object.

## Summary

- A repository has two components: an **object store** and a **working tree**. A clone
  copies both; multiple working trees duplicates only the second.
- The object store, branches, tags, and reflog are shared; `HEAD`, the staging area, and the
  working area are tree-specific. A branch can be checked out in only one tree at a time.
- In a thousand-commit history, the clone scheme writes **22560** objects for four
  branches, the tree scheme stays at **5640**; **11466** of the **16920** gained objects
  come from binary bodies.
- The object copied per extra tree is **0** at every scale; the only thing paid under the
  tree scheme is **3** administrative records per tree, and the object store does not grow
  with tree count.
- In the step unit: a commit reaching every copy requires **3** and **7** fetches under the
  clone scheme, **0** under the tree scheme; what is lost in exchange is the isolation
  between copies.

## Next Step

The measurement left one assumption out of scope: the assumption that the files checked out
to the working area are the same under both schemes. This said that **how** files are
written is not a choice — but there is a transformation layer applied to a file during
checkout, and the blob in the object store and the file in the working area do not have to
be identical to each other. The next lesson measures that layer: when line-ending
conversion is not configured, which commit, in which file, writes a new object while
actually changing nothing at all?
