Skip to content

Commit 77cffa3

Browse files
committed
Simplify checkout documentation
This moves a lot of the detailed checkout documentation into a new file (docs/checkout-internals.md) and simplifies the public docs for the checkout API.
1 parent b3fb923 commit 77cffa3

3 files changed

Lines changed: 304 additions & 302 deletions

File tree

docs/checkout-internals.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
Checkout Internals
2+
==================
3+
4+
Checkout has to handle a lot of different cases. It examines the
5+
differences between the target tree, the baseline tree and the working
6+
directory, plus the contents of the index, and groups files into five
7+
categories:
8+
9+
1. UNMODIFIED - Files that match in all places.
10+
2. SAFE - Files where the working directory and the baseline content
11+
match that can be safely updated to the target.
12+
3. DIRTY/MISSING - Files where the working directory differs from the
13+
baseline but there is no conflicting change with the target. One
14+
example is a file that doesn't exist in the working directory - no
15+
data would be lost as a result of writing this file. Which action
16+
will be taken with these files depends on the options you use.
17+
4. CONFLICTS - Files where changes in the working directory conflict
18+
with changes to be applied by the target. If conflicts are found,
19+
they prevent any other modifications from being made (although there
20+
are options to override that and force the update, of course).
21+
5. UNTRACKED/IGNORED - Files in the working directory that are untracked
22+
or ignored (i.e. only in the working directory, not the other places).
23+
24+
Right now, this classification is done via 3 iterators (for the three
25+
trees), with a final lookup in the index. At some point, this may move to
26+
a 4 iterator version to incorporate the index better.
27+
28+
The actual checkout is done in five phases (at least right now).
29+
30+
1. The diff between the baseline and the target tree is used as a base
31+
list of possible updates to be applied.
32+
2. Iterate through the diff and the working directory, building a list of
33+
actions to be taken (and sending notifications about conflicts and
34+
dirty files).
35+
3. Remove any files / directories as needed (because alphabetical
36+
iteration means that an untracked directory will end up sorted *after*
37+
a blob that should be checked out with the same name).
38+
4. Update all blobs.
39+
5. Update all submodules (after 4 in case a new .gitmodules blob was
40+
checked out)
41+
42+
Checkout could be driven either off a target-to-workdir diff or a
43+
baseline-to-target diff. There are pros and cons of each.
44+
45+
Target-to-workdir means the diff includes every file that could be
46+
modified, which simplifies bookkeeping, but the code to constantly refer
47+
back to the baseline gets complicated.
48+
49+
Baseline-to-target has simpler code because the diff defines the action to
50+
take, but needs special handling for untracked and ignored files, if they
51+
need to be removed.
52+
53+
The current checkout implementation is based on a baseline-to-target diff.
54+
55+
56+
Picking Actions
57+
===============
58+
59+
The most interesting aspect of this is phase 2, picking the actions that
60+
should be taken. There are a lot of corner cases, so it may be easier to
61+
start by looking at the rules for a simple 2-iterator diff:
62+
63+
Key
64+
---
65+
- B1,B2,B3 - blobs with different SHAs,
66+
- Bi - ignored blob (WD only)
67+
- T1,T2,T3 - trees with different SHAs,
68+
- Ti - ignored tree (WD only)
69+
- x - nothing
70+
71+
Diff with 2 non-workdir iterators
72+
---------------------------------
73+
74+
Old New
75+
--- ---
76+
0 x x - nothing
77+
1 x B1 - added blob
78+
2 x T1 - added tree
79+
3 B1 x - removed blob
80+
4 B1 B1 - unmodified blob
81+
5 B1 B2 - modified blob
82+
6 B1 T1 - typechange blob -> tree
83+
7 T1 x - removed tree
84+
8 T1 B1 - typechange tree -> blob
85+
9 T1 T1 - unmodified tree
86+
10 T1 T2 - modified tree (implies modified/added/removed blob inside)
87+
88+
89+
Now, let's make the "New" iterator into a working directory iterator, so
90+
we replace "added" items with either untracked or ignored, like this:
91+
92+
Diff with non-work & workdir iterators
93+
--------------------------------------
94+
95+
Old New-WD
96+
--- ------
97+
0 x x - nothing
98+
1 x B1 - untracked blob
99+
2 x Bi - ignored file
100+
3 x T1 - untracked tree
101+
4 x Ti - ignored tree
102+
5 B1 x - removed blob
103+
6 B1 B1 - unmodified blob
104+
7 B1 B2 - modified blob
105+
8 B1 T1 - typechange blob -> tree
106+
9 B1 Ti - removed blob AND ignored tree as separate items
107+
10 T1 x - removed tree
108+
11 T1 B1 - typechange tree -> blob
109+
12 T1 Bi - removed tree AND ignored blob as separate items
110+
13 T1 T1 - unmodified tree
111+
14 T1 T2 - modified tree (implies modified/added/removed blob inside)
112+
113+
Note: if there is a corresponding entry in the old tree, then a working
114+
directory item won't be ignored (i.e. no Bi or Ti for tracked items).
115+
116+
117+
Now, expand this to three iterators: a baseline tree, a target tree, and
118+
an actual working directory tree:
119+
120+
Checkout From 3 Iterators (2 not workdir, 1 workdir)
121+
----------------------------------------------------
122+
123+
(base == old HEAD; target == what to checkout; actual == working dir)
124+
125+
base target actual/workdir
126+
---- ------ ------
127+
0 x x x - nothing
128+
1 x x B1/Bi/T1/Ti - untracked/ignored blob/tree (SAFE)
129+
2+ x B1 x - add blob (SAFE)
130+
3 x B1 B1 - independently added blob (FORCEABLE-2)
131+
4* x B1 B2/Bi/T1/Ti - add blob with content conflict (FORCEABLE-2)
132+
5+ x T1 x - add tree (SAFE)
133+
6* x T1 B1/Bi - add tree with blob conflict (FORCEABLE-2)
134+
7 x T1 T1/i - independently added tree (SAFE+MISSING)
135+
8 B1 x x - independently deleted blob (SAFE+MISSING)
136+
9- B1 x B1 - delete blob (SAFE)
137+
10- B1 x B2 - delete of modified blob (FORCEABLE-1)
138+
11 B1 x T1/Ti - independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!)
139+
12 B1 B1 x - locally deleted blob (DIRTY || SAFE+CREATE)
140+
13+ B1 B2 x - update to deleted blob (SAFE+MISSING)
141+
14 B1 B1 B1 - unmodified file (SAFE)
142+
15 B1 B1 B2 - locally modified file (DIRTY)
143+
16+ B1 B2 B1 - update unmodified blob (SAFE)
144+
17 B1 B2 B2 - independently updated blob (FORCEABLE-1)
145+
18+ B1 B2 B3 - update to modified blob (FORCEABLE-1)
146+
19 B1 B1 T1/Ti - locally deleted blob AND untrack/ign tree (DIRTY)
147+
20* B1 B2 T1/Ti - update to deleted blob AND untrack/ign tree (F-1)
148+
21+ B1 T1 x - add tree with locally deleted blob (SAFE+MISSING)
149+
22* B1 T1 B1 - add tree AND deleted blob (SAFE)
150+
23* B1 T1 B2 - add tree with delete of modified blob (F-1)
151+
24 B1 T1 T1 - add tree with deleted blob (F-1)
152+
25 T1 x x - independently deleted tree (SAFE+MISSING)
153+
26 T1 x B1/Bi - independently deleted tree AND untrack/ign blob (F-1)
154+
27- T1 x T1 - deleted tree (MAYBE SAFE)
155+
28+ T1 B1 x - deleted tree AND added blob (SAFE+MISSING)
156+
29 T1 B1 B1 - independently typechanged tree -> blob (F-1)
157+
30+ T1 B1 B2 - typechange tree->blob with conflicting blob (F-1)
158+
31* T1 B1 T1/T2 - typechange tree->blob (MAYBE SAFE)
159+
32+ T1 T1 x - restore locally deleted tree (SAFE+MISSING)
160+
33 T1 T1 B1/Bi - locally typechange tree->untrack/ign blob (DIRTY)
161+
34 T1 T1 T1/T2 - unmodified tree (MAYBE SAFE)
162+
35+ T1 T2 x - update locally deleted tree (SAFE+MISSING)
163+
36* T1 T2 B1/Bi - update to tree with typechanged tree->blob conflict (F-1)
164+
37 T1 T2 T1/T2/T3 - update to existing tree (MAYBE SAFE)
165+
166+
The number is followed by ' ' if no change is needed or '+' if the case
167+
needs to write to disk or '-' if something must be deleted and '*' if
168+
there should be a delete followed by an write.
169+
170+
There are four tiers of safe cases:
171+
172+
- SAFE == completely safe to update
173+
- SAFE+MISSING == safe except the workdir is missing the expect content
174+
- MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
175+
content, which is unknown at this point
176+
- FORCEABLE == conflict unless FORCE is given
177+
- DIRTY == no conflict but change is not applied unless FORCE
178+
179+
Some slightly unusual circumstances:
180+
181+
8 - parent dir is only deleted when file is, so parent will be left if
182+
empty even though it would be deleted if the file were present
183+
11 - core git does not consider this a conflict but attempts to delete T1
184+
and gives "unable to unlink file" error yet does not skip the rest
185+
of the operation
186+
12 - without FORCE file is left deleted (i.e. not restored) so new wd is
187+
dirty (and warning message "D file" is printed), with FORCE, file is
188+
restored.
189+
24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
190+
combined, but core git considers this a conflict unless forced.
191+
26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
192+
which are ok on their own, but core git treat this as a conflict.
193+
If not forced, this is a conflict. If forced, this actually doesn't
194+
have to write anything and leaves the new blob as an untracked file.
195+
32 - This is the only case where the baseline and target values match
196+
and yet we will still write to the working directory. In all other
197+
cases, if baseline == target, we don't touch the workdir (it is
198+
either already right or is "dirty"). However, since this case also
199+
implies that a ?/B1/x case will exist as well, it can be skipped.
200+
201+
Cases 3, 17, 24, 26, and 29 are all considered conflicts even though
202+
none of them will require making any updates to the working directory.
203+

0 commit comments

Comments
 (0)