Blame docs/checkout-internals.md

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.