Skip to content

Commit a92a9c2

Browse files
brabojclaude
andcommitted
docs: add bare repo motivation with before/after diagrams
Chapter 2 now explains why bare repos exist using an Alice-and-Bob scenario: direct push desynchronizes the working tree, bare repo in the middle decouples developers. ASCII diagrams inline, draw.io sources in assets/drawio/ (PNG export pending). Recipe trimmed to cross-reference the chapter for the full explanation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5a73524 commit a92a9c2

4 files changed

Lines changed: 170 additions & 33 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<mxfile host="Electron">
2+
<diagram id="bare-after" name="After — bare repo as hub">
3+
<mxGraphModel dx="1094" dy="700" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="780" pageHeight="320" math="0" shadow="0">
4+
<root>
5+
<mxCell id="0" />
6+
<mxCell id="1" parent="0" />
7+
<mxCell id="bg-card" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#D3D1C7;strokeWidth=0.5;shadow=0;arcSize=5;" value="" vertex="1">
8+
<mxGeometry height="300" width="760" x="10" y="10" as="geometry" />
9+
</mxCell>
10+
<mxCell id="zone-alice" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FAF4E8;strokeColor=#D3D1C7;strokeWidth=0.5;shadow=0;arcSize=5;" value="" vertex="1">
11+
<mxGeometry height="250" width="210" x="30" y="30" as="geometry" />
12+
</mxCell>
13+
<mxCell id="label-alice" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;fontSize=12;fontStyle=1;fontColor=#2C2C2A;fontFamily=Helvetica;" value="Alice (non-bare)" vertex="1">
14+
<mxGeometry height="20" width="150" x="40" y="35" as="geometry" />
15+
</mxCell>
16+
<mxCell id="box-alice-wt" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DBEAFE;strokeColor=#2563EB;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#1E40AF;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value="Working&lt;br&gt;Tree" vertex="1">
17+
<mxGeometry height="55" width="170" x="50" y="70" as="geometry" />
18+
</mxCell>
19+
<mxCell id="box-alice-git" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DCFCE7;strokeColor=#16A34A;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#15803D;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value=".git/" vertex="1">
20+
<mxGeometry height="55" width="170" x="50" y="145" as="geometry" />
21+
</mxCell>
22+
<mxCell id="zone-bare" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#EAF1F5;strokeColor=#D3D1C7;strokeWidth=0.5;shadow=0;arcSize=5;" value="" vertex="1">
23+
<mxGeometry height="250" width="200" x="290" y="30" as="geometry" />
24+
</mxCell>
25+
<mxCell id="label-bare" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;fontSize=12;fontStyle=1;fontColor=#2C2C2A;fontFamily=Helvetica;" value="Shared hub (bare)" vertex="1">
26+
<mxGeometry height="20" width="160" x="300" y="35" as="geometry" />
27+
</mxCell>
28+
<mxCell id="box-bare" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#EDE9FE;strokeColor=#7C3AED;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#5B21B6;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value="objects + refs&lt;br&gt;&lt;i style=&quot;font-weight:normal;color:#5F5E5A&quot;&gt;no working tree&lt;/i&gt;" vertex="1">
29+
<mxGeometry height="55" width="160" x="310" y="120" as="geometry" />
30+
</mxCell>
31+
<mxCell id="zone-bob" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FAF4E8;strokeColor=#D3D1C7;strokeWidth=0.5;shadow=0;arcSize=5;" value="" vertex="1">
32+
<mxGeometry height="250" width="210" x="540" y="30" as="geometry" />
33+
</mxCell>
34+
<mxCell id="label-bob" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;fontSize=12;fontStyle=1;fontColor=#2C2C2A;fontFamily=Helvetica;" value="Bob (non-bare)" vertex="1">
35+
<mxGeometry height="20" width="150" x="550" y="35" as="geometry" />
36+
</mxCell>
37+
<mxCell id="box-bob-wt" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DBEAFE;strokeColor=#2563EB;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#1E40AF;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value="Working&lt;br&gt;Tree" vertex="1">
38+
<mxGeometry height="55" width="170" x="560" y="70" as="geometry" />
39+
</mxCell>
40+
<mxCell id="box-bob-git" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DCFCE7;strokeColor=#16A34A;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#15803D;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value=".git/" vertex="1">
41+
<mxGeometry height="55" width="170" x="560" y="145" as="geometry" />
42+
</mxCell>
43+
<mxCell id="arr-bob-push" edge="1" parent="1" source="box-bob-git" style="endArrow=classic;html=1;strokeColor=#444441;strokeWidth=1;endFill=1;endSize=6;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" target="box-bare" vertex="1">
44+
<mxGeometry relative="1" as="geometry" />
45+
</mxCell>
46+
<mxCell id="label-bob-push" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=bottom;fontSize=11;fontStyle=0;fontColor=#2C2C2A;fontFamily=Courier New;" value="push" vertex="1">
47+
<mxGeometry height="18" width="50" x="490" y="162" as="geometry" />
48+
</mxCell>
49+
<mxCell id="arr-alice-pull" edge="1" parent="1" source="box-bare" style="endArrow=classic;html=1;strokeColor=#444441;strokeWidth=1;endFill=1;endSize=6;exitX=0;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" target="box-alice-git" vertex="1">
50+
<mxGeometry relative="1" as="geometry" />
51+
</mxCell>
52+
<mxCell id="label-alice-pull" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=top;fontSize=11;fontStyle=0;fontColor=#2C2C2A;fontFamily=Courier New;" value="pull" vertex="1">
53+
<mxGeometry height="18" width="50" x="240" y="132" as="geometry" />
54+
</mxCell>
55+
<mxCell id="label-alice-ready" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;fontSize=11;fontStyle=2;fontColor=#5F5E5A;fontFamily=Helvetica;" value="(when ready)" vertex="1">
56+
<mxGeometry height="18" width="80" x="235" y="148" as="geometry" />
57+
</mxCell>
58+
</root>
59+
</mxGraphModel>
60+
</diagram>
61+
</mxfile>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<mxfile host="Electron">
2+
<diagram id="bare-before" name="Before — direct push">
3+
<mxGraphModel dx="1094" dy="700" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="680" pageHeight="320" math="0" shadow="0">
4+
<root>
5+
<mxCell id="0" />
6+
<mxCell id="1" parent="0" />
7+
<mxCell id="bg-card" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#D3D1C7;strokeWidth=0.5;shadow=0;arcSize=5;" value="" vertex="1">
8+
<mxGeometry height="300" width="660" x="10" y="10" as="geometry" />
9+
</mxCell>
10+
<mxCell id="zone-alice" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FAF4E8;strokeColor=#D3D1C7;strokeWidth=0.5;shadow=0;arcSize=5;" value="" vertex="1">
11+
<mxGeometry height="250" width="270" x="30" y="30" as="geometry" />
12+
</mxCell>
13+
<mxCell id="label-alice" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;fontSize=12;fontStyle=1;fontColor=#2C2C2A;fontFamily=Helvetica;" value="Alice (non-bare)" vertex="1">
14+
<mxGeometry height="20" width="150" x="40" y="35" as="geometry" />
15+
</mxCell>
16+
<mxCell id="box-alice-wt" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DBEAFE;strokeColor=#2563EB;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#1E40AF;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value="Working Tree&lt;br&gt;&lt;i style=&quot;font-weight:normal;color:#5F5E5A&quot;&gt;files from commit A&lt;br&gt;+ uncommitted edits&lt;/i&gt;" vertex="1">
17+
<mxGeometry height="70" width="230" x="50" y="70" as="geometry" />
18+
</mxCell>
19+
<mxCell id="box-alice-git" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DCFCE7;strokeColor=#16A34A;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#15803D;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value=".git/&lt;br&gt;&lt;i style=&quot;font-weight:normal;color:#5F5E5A&quot;&gt;main → commit B&lt;br&gt;(moved by push)&lt;/i&gt;" vertex="1">
20+
<mxGeometry height="70" width="230" x="50" y="160" as="geometry" />
21+
</mxCell>
22+
<mxCell id="zone-bob" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FAF4E8;strokeColor=#D3D1C7;strokeWidth=0.5;shadow=0;arcSize=5;" value="" vertex="1">
23+
<mxGeometry height="250" width="270" x="380" y="30" as="geometry" />
24+
</mxCell>
25+
<mxCell id="label-bob" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;fontSize=12;fontStyle=1;fontColor=#2C2C2A;fontFamily=Helvetica;" value="Bob (non-bare)" vertex="1">
26+
<mxGeometry height="20" width="150" x="390" y="35" as="geometry" />
27+
</mxCell>
28+
<mxCell id="box-bob-wt" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DBEAFE;strokeColor=#2563EB;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#1E40AF;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value="Working Tree" vertex="1">
29+
<mxGeometry height="70" width="230" x="400" y="70" as="geometry" />
30+
</mxCell>
31+
<mxCell id="box-bob-git" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DCFCE7;strokeColor=#16A34A;strokeWidth=0.5;shadow=0;fontSize=12;fontStyle=1;fontColor=#15803D;fontFamily=Helvetica;verticalAlign=middle;align=center;arcSize=10;" value=".git/&lt;br&gt;&lt;i style=&quot;font-weight:normal;color:#5F5E5A&quot;&gt;main → commit B&lt;/i&gt;" vertex="1">
32+
<mxGeometry height="70" width="230" x="400" y="160" as="geometry" />
33+
</mxCell>
34+
<mxCell id="arr-push" edge="1" parent="1" source="box-bob-git" style="endArrow=classic;html=1;strokeColor=#A32D2D;strokeWidth=1.5;endFill=1;endSize=6;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" target="box-alice-git" vertex="1">
35+
<mxGeometry relative="1" as="geometry" />
36+
</mxCell>
37+
<mxCell id="label-push" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=bottom;fontSize=11;fontStyle=0;fontColor=#A32D2D;fontFamily=Courier New;" value="git push" vertex="1">
38+
<mxGeometry height="18" width="70" x="305" y="178" as="geometry" />
39+
</mxCell>
40+
<mxCell id="icon-desync" parent="1" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;fontSize=14;fontStyle=1;fontColor=#A32D2D;fontFamily=Helvetica;" value="OUT OF SYNC" vertex="1">
41+
<mxGeometry height="20" width="120" x="105" y="148" as="geometry" />
42+
</mxCell>
43+
</root>
44+
</mxGraphModel>
45+
</diagram>
46+
</mxfile>

chapters/02-building-blocks.md

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,64 @@ The `.git` folder contains the same structure as a bare repository.
8888
The difference is that a non-bare repository also has a working tree
8989
next to it — the place where you do your actual work.
9090

91-
A bare repository is designed to be a shared hub. Because it has no
92-
working tree, nobody edits files in it directly — it only receives
93-
changes sent from other repositories. A non-bare repository is the
94-
opposite: it is where you do your day-to-day work — editing files,
95-
recording snapshots, and browsing history.
91+
### Why bare repositories exist
92+
93+
Imagine two developers — Alice and Bob — working on the same project
94+
over a local network. Each has a non-bare repository with a working
95+
tree. There is no central server — Bob has Alice's repository set as
96+
his remote and sends changes directly into her `.git/` directory.
97+
98+
```text
99+
Alice (non-bare) Bob (non-bare)
100+
┌──────────────────┐ ┌──────────────────┐
101+
│ Working Tree │ │ Working Tree │
102+
│ files from A │ │ │
103+
│ + uncommitted │ │ │
104+
├──────────────────┤ ├──────────────────┤
105+
│ .git/ │ git push │ .git/ │
106+
│ main → B ───────│◄────────────│ main → B │
107+
│ (moved by push) │ │ │
108+
└──────────────────┘ └──────────────────┘
109+
⚠ OUT OF SYNC
110+
branch says B, files say A
111+
```
112+
113+
The problem: when Bob sends his changes, Git updates the branch
114+
inside Alice's `.git/` to point to Bob's latest commit. But Alice's
115+
working tree is **not** updated — her files still reflect the old
116+
commit, plus whatever edits she has in progress. Alice's branch and
117+
her working tree are now out of sync. If she commits at this point,
118+
she unknowingly **reverts Bob's changes** — because her files do not
119+
contain them.
120+
121+
Git prevents this by refusing to accept changes sent to a non-bare
122+
repository that has the target branch checked out.
123+
124+
The solution is to place a **bare repository** between the two
125+
developers. A bare repository has no working tree — it holds only
126+
objects and references. Because nobody edits files in it, updating a
127+
branch is always safe.
128+
129+
```text
130+
Alice (non-bare) Shared hub (bare) Bob (non-bare)
131+
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
132+
│ Working Tree │ │ │ │ Working Tree │
133+
│ │ │ objects + refs │ │ │
134+
├──────────────────┤ │ no working tree │ ├──────────────────┤
135+
│ .git/ │ │ │ │ .git/ │
136+
│ │◄───│ │◄───│ │
137+
└──────────────────┘ └──────────────────┘ └──────────────────┘
138+
pull push
139+
(when ready)
140+
```
141+
142+
With a bare repository in the middle, each developer works
143+
independently. Bob sends his changes to the bare repository whenever
144+
he is ready. Alice commits her own work first, then downloads Bob's
145+
changes from the bare repository when **she** is ready. No one
146+
reaches into anyone else's working tree. This is exactly how
147+
hosting services like GitHub and GitLab work — every repository on
148+
the server is bare.
96149

97150
## 3. Object Model
98151

chapters/recipes/bare-repositories.md

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -117,34 +117,11 @@ $ git push /tmp/dev-alice main
117117
remote: error: refusing to update checked out branch: refs/heads/main
118118
```
119119

120-
To understand why, remember that a push only updates the branch
121-
reference inside `.git/` — it does **not** touch the working tree.
122-
These are two separate operations, and only the first one happens.
123-
124-
Imagine Alice and Bob working on the same network with **no bare
125-
repository in between** — no GitHub, no central server. Bob has
126-
Alice's non-bare repository configured as his remote, so he pushes
127-
directly into her `.git/` directory.
128-
129-
1. Alice checks out `main`. Her working tree has the files from
130-
commit `A`.
131-
2. Alice edits `report.txt` but has not committed yet.
132-
3. Bob runs `git push alice-machine main`, which writes his new
133-
commit `B` directly into Alice's `.git/` and updates her
134-
reference `.git/refs/heads/main` to point to `B` — but Alice's
135-
working tree is not updated. Her files on disk are still from
136-
commit `A`, plus her uncommitted edits.
137-
4. Alice's repository is now in a broken state:
138-
- `HEAD``main` → commit `B` (moved by the push)
139-
- Working tree → files from commit `A` + uncommitted edits (untouched)
140-
5. If Alice runs `git status`, Git compares her working tree against
141-
commit `B`. Every file Bob changed shows up as a difference —
142-
mixed in with Alice's real edits. She cannot tell which changes
143-
are hers and which are artifacts of the branch moving under her.
144-
145-
Git prevents step 3 entirely. A bare repository avoids this problem
146-
because there is no working tree — updating the branch reference is
147-
always safe when there are no files to desynchronize.
120+
Git refuses because a push updates the branch reference but not the
121+
working tree — this would leave the two out of sync and could cause
122+
the recipient to unknowingly revert the pushed changes on their next
123+
commit. For a full walkthrough of the problem, see
124+
[Building Blocks — Why bare repositories exist](../02-building-blocks.md#why-bare-repositories-exist).
148125

149126
If you need to accept pushes on a non-bare repository (rare), you
150127
can enable it:

0 commit comments

Comments
 (0)