|
Packit |
ae9e2a |
Checkout Internals
|
|
Packit |
ae9e2a |
==================
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Checkout has to handle a lot of different cases. It examines the
|
|
Packit |
ae9e2a |
differences between the target tree, the baseline tree and the working
|
|
Packit |
ae9e2a |
directory, plus the contents of the index, and groups files into five
|
|
Packit |
ae9e2a |
categories:
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
1. UNMODIFIED - Files that match in all places.
|
|
Packit |
ae9e2a |
2. SAFE - Files where the working directory and the baseline content
|
|
Packit |
ae9e2a |
match that can be safely updated to the target.
|
|
Packit |
ae9e2a |
3. DIRTY/MISSING - Files where the working directory differs from the
|
|
Packit |
ae9e2a |
baseline but there is no conflicting change with the target. One
|
|
Packit |
ae9e2a |
example is a file that doesn't exist in the working directory - no
|
|
Packit |
ae9e2a |
data would be lost as a result of writing this file. Which action
|
|
Packit |
ae9e2a |
will be taken with these files depends on the options you use.
|
|
Packit |
ae9e2a |
4. CONFLICTS - Files where changes in the working directory conflict
|
|
Packit |
ae9e2a |
with changes to be applied by the target. If conflicts are found,
|
|
Packit |
ae9e2a |
they prevent any other modifications from being made (although there
|
|
Packit |
ae9e2a |
are options to override that and force the update, of course).
|
|
Packit |
ae9e2a |
5. UNTRACKED/IGNORED - Files in the working directory that are untracked
|
|
Packit |
ae9e2a |
or ignored (i.e. only in the working directory, not the other places).
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Right now, this classification is done via 3 iterators (for the three
|
|
Packit |
ae9e2a |
trees), with a final lookup in the index. At some point, this may move to
|
|
Packit |
ae9e2a |
a 4 iterator version to incorporate the index better.
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
The actual checkout is done in five phases (at least right now).
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
1. The diff between the baseline and the target tree is used as a base
|
|
Packit |
ae9e2a |
list of possible updates to be applied.
|
|
Packit |
ae9e2a |
2. Iterate through the diff and the working directory, building a list of
|
|
Packit |
ae9e2a |
actions to be taken (and sending notifications about conflicts and
|
|
Packit |
ae9e2a |
dirty files).
|
|
Packit |
ae9e2a |
3. Remove any files / directories as needed (because alphabetical
|
|
Packit |
ae9e2a |
iteration means that an untracked directory will end up sorted *after*
|
|
Packit |
ae9e2a |
a blob that should be checked out with the same name).
|
|
Packit |
ae9e2a |
4. Update all blobs.
|
|
Packit |
ae9e2a |
5. Update all submodules (after 4 in case a new .gitmodules blob was
|
|
Packit |
ae9e2a |
checked out)
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Checkout could be driven either off a target-to-workdir diff or a
|
|
Packit |
ae9e2a |
baseline-to-target diff. There are pros and cons of each.
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Target-to-workdir means the diff includes every file that could be
|
|
Packit |
ae9e2a |
modified, which simplifies bookkeeping, but the code to constantly refer
|
|
Packit |
ae9e2a |
back to the baseline gets complicated.
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Baseline-to-target has simpler code because the diff defines the action to
|
|
Packit |
ae9e2a |
take, but needs special handling for untracked and ignored files, if they
|
|
Packit |
ae9e2a |
need to be removed.
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
The current checkout implementation is based on a baseline-to-target diff.
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Picking Actions
|
|
Packit |
ae9e2a |
===============
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
The most interesting aspect of this is phase 2, picking the actions that
|
|
Packit |
ae9e2a |
should be taken. There are a lot of corner cases, so it may be easier to
|
|
Packit |
ae9e2a |
start by looking at the rules for a simple 2-iterator diff:
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Key
|
|
Packit |
ae9e2a |
---
|
|
Packit |
ae9e2a |
- B1,B2,B3 - blobs with different SHAs,
|
|
Packit |
ae9e2a |
- Bi - ignored blob (WD only)
|
|
Packit |
ae9e2a |
- T1,T2,T3 - trees with different SHAs,
|
|
Packit |
ae9e2a |
- Ti - ignored tree (WD only)
|
|
Packit |
ae9e2a |
- S1,S2 - submodules with different SHAs
|
|
Packit |
ae9e2a |
- Sd - dirty submodule (WD only)
|
|
Packit |
ae9e2a |
- x - nothing
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Diff with 2 non-workdir iterators
|
|
Packit |
ae9e2a |
---------------------------------
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
| | Old | New | |
|
|
Packit |
ae9e2a |
|----|-----|-----|------------------------------------------------------------|
|
|
Packit |
ae9e2a |
| 0 | x | x | nothing |
|
|
Packit |
ae9e2a |
| 1 | x | B1 | added blob |
|
|
Packit |
ae9e2a |
| 2 | x | T1 | added tree |
|
|
Packit |
ae9e2a |
| 3 | B1 | x | removed blob |
|
|
Packit |
ae9e2a |
| 4 | B1 | B1 | unmodified blob |
|
|
Packit |
ae9e2a |
| 5 | B1 | B2 | modified blob |
|
|
Packit |
ae9e2a |
| 6 | B1 | T1 | typechange blob -> tree |
|
|
Packit |
ae9e2a |
| 7 | T1 | x | removed tree |
|
|
Packit |
ae9e2a |
| 8 | T1 | B1 | typechange tree -> blob |
|
|
Packit |
ae9e2a |
| 9 | T1 | T1 | unmodified tree |
|
|
Packit |
ae9e2a |
| 10 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Now, let's make the "New" iterator into a working directory iterator, so
|
|
Packit |
ae9e2a |
we replace "added" items with either untracked or ignored, like this:
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Diff with non-work & workdir iterators
|
|
Packit |
ae9e2a |
--------------------------------------
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
| | Old | New | |
|
|
Packit |
ae9e2a |
|----|-----|-----|------------------------------------------------------------|
|
|
Packit |
ae9e2a |
| 0 | x | x | nothing |
|
|
Packit |
ae9e2a |
| 1 | x | B1 | untracked blob |
|
|
Packit |
ae9e2a |
| 2 | x | Bi | ignored file |
|
|
Packit |
ae9e2a |
| 3 | x | T1 | untracked tree |
|
|
Packit |
ae9e2a |
| 4 | x | Ti | ignored tree |
|
|
Packit |
ae9e2a |
| 5 | B1 | x | removed blob |
|
|
Packit |
ae9e2a |
| 6 | B1 | B1 | unmodified blob |
|
|
Packit |
ae9e2a |
| 7 | B1 | B2 | modified blob |
|
|
Packit |
ae9e2a |
| 8 | B1 | T1 | typechange blob -> tree |
|
|
Packit |
ae9e2a |
| 9 | B1 | Ti | removed blob AND ignored tree as separate items |
|
|
Packit |
ae9e2a |
| 10 | T1 | x | removed tree |
|
|
Packit |
ae9e2a |
| 11 | T1 | B1 | typechange tree -> blob |
|
|
Packit |
ae9e2a |
| 12 | T1 | Bi | removed tree AND ignored blob as separate items |
|
|
Packit |
ae9e2a |
| 13 | T1 | T1 | unmodified tree |
|
|
Packit |
ae9e2a |
| 14 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Note: if there is a corresponding entry in the old tree, then a working
|
|
Packit |
ae9e2a |
directory item won't be ignored (i.e. no Bi or Ti for tracked items).
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Now, expand this to three iterators: a baseline tree, a target tree, and
|
|
Packit |
ae9e2a |
an actual working directory tree:
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Checkout From 3 Iterators (2 not workdir, 1 workdir)
|
|
Packit |
ae9e2a |
----------------------------------------------------
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
(base == old HEAD; target == what to checkout; actual == working dir)
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
| |base | target | actual/workdir | |
|
|
Packit |
ae9e2a |
|-----|-----|------- |----------------|--------------------------------------------------------------------|
|
|
Packit |
ae9e2a |
| 0 | x | x | x | nothing |
|
|
Packit |
ae9e2a |
| 1 | x | x | B1/Bi/T1/Ti | untracked/ignored blob/tree (SAFE) |
|
|
Packit |
ae9e2a |
| 2+ | x | B1 | x | add blob (SAFE) |
|
|
Packit |
ae9e2a |
| 3 | x | B1 | B1 | independently added blob (FORCEABLE-2) |
|
|
Packit |
ae9e2a |
| 4* | x | B1 | B2/Bi/T1/Ti | add blob with content conflict (FORCEABLE-2) |
|
|
Packit |
ae9e2a |
| 5+ | x | T1 | x | add tree (SAFE) |
|
|
Packit |
ae9e2a |
| 6* | x | T1 | B1/Bi | add tree with blob conflict (FORCEABLE-2) |
|
|
Packit |
ae9e2a |
| 7 | x | T1 | T1/i | independently added tree (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 8 | B1 | x | x | independently deleted blob (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 9- | B1 | x | B1 | delete blob (SAFE) |
|
|
Packit |
ae9e2a |
| 10- | B1 | x | B2 | delete of modified blob (FORCEABLE-1) |
|
|
Packit |
ae9e2a |
| 11 | B1 | x | T1/Ti | independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!) |
|
|
Packit |
ae9e2a |
| 12 | B1 | B1 | x | locally deleted blob (DIRTY || SAFE+CREATE) |
|
|
Packit |
ae9e2a |
| 13+ | B1 | B2 | x | update to deleted blob (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 14 | B1 | B1 | B1 | unmodified file (SAFE) |
|
|
Packit |
ae9e2a |
| 15 | B1 | B1 | B2 | locally modified file (DIRTY) |
|
|
Packit |
ae9e2a |
| 16+ | B1 | B2 | B1 | update unmodified blob (SAFE) |
|
|
Packit |
ae9e2a |
| 17 | B1 | B2 | B2 | independently updated blob (FORCEABLE-1) |
|
|
Packit |
ae9e2a |
| 18+ | B1 | B2 | B3 | update to modified blob (FORCEABLE-1) |
|
|
Packit |
ae9e2a |
| 19 | B1 | B1 | T1/Ti | locally deleted blob AND untrack/ign tree (DIRTY) |
|
|
Packit |
ae9e2a |
| 20* | B1 | B2 | T1/Ti | update to deleted blob AND untrack/ign tree (F-1) |
|
|
Packit |
ae9e2a |
| 21+ | B1 | T1 | x | add tree with locally deleted blob (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 22* | B1 | T1 | B1 | add tree AND deleted blob (SAFE) |
|
|
Packit |
ae9e2a |
| 23* | B1 | T1 | B2 | add tree with delete of modified blob (F-1) |
|
|
Packit |
ae9e2a |
| 24 | B1 | T1 | T1 | add tree with deleted blob (F-1) |
|
|
Packit |
ae9e2a |
| 25 | T1 | x | x | independently deleted tree (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 26 | T1 | x | B1/Bi | independently deleted tree AND untrack/ign blob (F-1) |
|
|
Packit |
ae9e2a |
| 27- | T1 | x | T1 | deleted tree (MAYBE SAFE) |
|
|
Packit |
ae9e2a |
| 28+ | T1 | B1 | x | deleted tree AND added blob (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 29 | T1 | B1 | B1 | independently typechanged tree -> blob (F-1) |
|
|
Packit |
ae9e2a |
| 30+ | T1 | B1 | B2 | typechange tree->blob with conflicting blob (F-1) |
|
|
Packit |
ae9e2a |
| 31* | T1 | B1 | T1/T2 | typechange tree->blob (MAYBE SAFE) |
|
|
Packit |
ae9e2a |
| 32+ | T1 | T1 | x | restore locally deleted tree (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 33 | T1 | T1 | B1/Bi | locally typechange tree->untrack/ign blob (DIRTY) |
|
|
Packit |
ae9e2a |
| 34 | T1 | T1 | T1/T2 | unmodified tree (MAYBE SAFE) |
|
|
Packit |
ae9e2a |
| 35+ | T1 | T2 | x | update locally deleted tree (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 36* | T1 | T2 | B1/Bi | update to tree with typechanged tree->blob conflict (F-1) |
|
|
Packit |
ae9e2a |
| 37 | T1 | T2 | T1/T2/T3 | update to existing tree (MAYBE SAFE) |
|
|
Packit |
ae9e2a |
| 38+ | x | S1 | x | add submodule (SAFE) |
|
|
Packit |
ae9e2a |
| 39 | x | S1 | S1/Sd | independently added submodule (SUBMODULE) |
|
|
Packit |
ae9e2a |
| 40* | x | S1 | B1 | add submodule with blob confilct (FORCEABLE) |
|
|
Packit |
ae9e2a |
| 41* | x | S1 | T1 | add submodule with tree conflict (FORCEABLE) |
|
|
Packit |
ae9e2a |
| 42 | S1 | x | S1/Sd | deleted submodule (SUBMODULE) |
|
|
Packit |
ae9e2a |
| 43 | S1 | x | x | independently deleted submodule (SUBMODULE) |
|
|
Packit |
ae9e2a |
| 44 | S1 | x | B1 | independently deleted submodule with added blob (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 45 | S1 | x | T1 | independently deleted submodule with added tree (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 46 | S1 | S1 | x | locally deleted submodule (SUBMODULE) |
|
|
Packit |
ae9e2a |
| 47+ | S1 | S2 | x | update locally deleted submodule (SAFE) |
|
|
Packit |
ae9e2a |
| 48 | S1 | S1 | S2 | locally updated submodule commit (SUBMODULE) |
|
|
Packit |
ae9e2a |
| 49 | S1 | S2 | S1 | updated submodule commit (SUBMODULE) |
|
|
Packit |
ae9e2a |
| 50+ | S1 | B1 | x | add blob with locally deleted submodule (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 51* | S1 | B1 | S1 | typechange submodule->blob (SAFE) |
|
|
Packit |
ae9e2a |
| 52* | S1 | B1 | Sd | typechange dirty submodule->blob (SAFE!?!?) |
|
|
Packit |
ae9e2a |
| 53+ | S1 | T1 | x | add tree with locally deleted submodule (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 54* | S1 | T1 | S1/Sd | typechange submodule->tree (MAYBE SAFE) |
|
|
Packit |
ae9e2a |
| 55+ | B1 | S1 | x | add submodule with locally deleted blob (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 56* | B1 | S1 | B1 | typechange blob->submodule (SAFE) |
|
|
Packit |
ae9e2a |
| 57+ | T1 | S1 | x | add submodule with locally deleted tree (SAFE+MISSING) |
|
|
Packit |
ae9e2a |
| 58* | T1 | S1 | T1 | typechange tree->submodule (SAFE) |
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
The number is followed by ' ' if no change is needed or '+' if the case
|
|
Packit |
ae9e2a |
needs to write to disk or '-' if something must be deleted and '*' if
|
|
Packit |
ae9e2a |
there should be a delete followed by an write.
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
There are four tiers of safe cases:
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
* SAFE == completely safe to update
|
|
Packit |
ae9e2a |
* SAFE+MISSING == safe except the workdir is missing the expect content
|
|
Packit |
ae9e2a |
* MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
|
|
Packit |
ae9e2a |
content, which is unknown at this point
|
|
Packit |
ae9e2a |
* FORCEABLE == conflict unless FORCE is given
|
|
Packit |
ae9e2a |
* DIRTY == no conflict but change is not applied unless FORCE
|
|
Packit |
ae9e2a |
* SUBMODULE == no conflict and no change is applied unless a deleted
|
|
Packit |
ae9e2a |
submodule dir is empty
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Some slightly unusual circumstances:
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
* 8 - parent dir is only deleted when file is, so parent will be left if
|
|
Packit |
ae9e2a |
empty even though it would be deleted if the file were present
|
|
Packit |
ae9e2a |
* 11 - core git does not consider this a conflict but attempts to delete T1
|
|
Packit |
ae9e2a |
and gives "unable to unlink file" error yet does not skip the rest
|
|
Packit |
ae9e2a |
of the operation
|
|
Packit |
ae9e2a |
* 12 - without FORCE file is left deleted (i.e. not restored) so new wd is
|
|
Packit |
ae9e2a |
dirty (and warning message "D file" is printed), with FORCE, file is
|
|
Packit |
ae9e2a |
restored.
|
|
Packit |
ae9e2a |
* 24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
|
|
Packit |
ae9e2a |
combined, but core git considers this a conflict unless forced.
|
|
Packit |
ae9e2a |
* 26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
|
|
Packit |
ae9e2a |
which are ok on their own, but core git treat this as a conflict.
|
|
Packit |
ae9e2a |
If not forced, this is a conflict. If forced, this actually doesn't
|
|
Packit |
ae9e2a |
have to write anything and leaves the new blob as an untracked file.
|
|
Packit |
ae9e2a |
* 32 - This is the only case where the baseline and target values match
|
|
Packit |
ae9e2a |
and yet we will still write to the working directory. In all other
|
|
Packit |
ae9e2a |
cases, if baseline == target, we don't touch the workdir (it is
|
|
Packit |
ae9e2a |
either already right or is "dirty"). However, since this case also
|
|
Packit |
ae9e2a |
implies that a ?/B1/x case will exist as well, it can be skipped.
|
|
Packit |
ae9e2a |
* 41 - It's not clear how core git distinguishes this case from 39 (mode?).
|
|
Packit |
ae9e2a |
* 52 - Core git makes destructive changes without any warning when the
|
|
Packit |
ae9e2a |
submodule is dirty and the type changes to a blob.
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
Cases 3, 17, 24, 26, and 29 are all considered conflicts even though
|
|
Packit |
ae9e2a |
none of them will require making any updates to the working directory.
|