mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-06 21:53:29 +00:00
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.
This commit is contained in:
parent
b3fb9237c2
commit
77cffa31db
203
docs/checkout-internals.md
Normal file
203
docs/checkout-internals.md
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
Checkout Internals
|
||||||
|
==================
|
||||||
|
|
||||||
|
Checkout has to handle a lot of different cases. It examines the
|
||||||
|
differences between the target tree, the baseline tree and the working
|
||||||
|
directory, plus the contents of the index, and groups files into five
|
||||||
|
categories:
|
||||||
|
|
||||||
|
1. UNMODIFIED - Files that match in all places.
|
||||||
|
2. SAFE - Files where the working directory and the baseline content
|
||||||
|
match that can be safely updated to the target.
|
||||||
|
3. DIRTY/MISSING - Files where the working directory differs from the
|
||||||
|
baseline but there is no conflicting change with the target. One
|
||||||
|
example is a file that doesn't exist in the working directory - no
|
||||||
|
data would be lost as a result of writing this file. Which action
|
||||||
|
will be taken with these files depends on the options you use.
|
||||||
|
4. CONFLICTS - Files where changes in the working directory conflict
|
||||||
|
with changes to be applied by the target. If conflicts are found,
|
||||||
|
they prevent any other modifications from being made (although there
|
||||||
|
are options to override that and force the update, of course).
|
||||||
|
5. UNTRACKED/IGNORED - Files in the working directory that are untracked
|
||||||
|
or ignored (i.e. only in the working directory, not the other places).
|
||||||
|
|
||||||
|
Right now, this classification is done via 3 iterators (for the three
|
||||||
|
trees), with a final lookup in the index. At some point, this may move to
|
||||||
|
a 4 iterator version to incorporate the index better.
|
||||||
|
|
||||||
|
The actual checkout is done in five phases (at least right now).
|
||||||
|
|
||||||
|
1. The diff between the baseline and the target tree is used as a base
|
||||||
|
list of possible updates to be applied.
|
||||||
|
2. Iterate through the diff and the working directory, building a list of
|
||||||
|
actions to be taken (and sending notifications about conflicts and
|
||||||
|
dirty files).
|
||||||
|
3. Remove any files / directories as needed (because alphabetical
|
||||||
|
iteration means that an untracked directory will end up sorted *after*
|
||||||
|
a blob that should be checked out with the same name).
|
||||||
|
4. Update all blobs.
|
||||||
|
5. Update all submodules (after 4 in case a new .gitmodules blob was
|
||||||
|
checked out)
|
||||||
|
|
||||||
|
Checkout could be driven either off a target-to-workdir diff or a
|
||||||
|
baseline-to-target diff. There are pros and cons of each.
|
||||||
|
|
||||||
|
Target-to-workdir means the diff includes every file that could be
|
||||||
|
modified, which simplifies bookkeeping, but the code to constantly refer
|
||||||
|
back to the baseline gets complicated.
|
||||||
|
|
||||||
|
Baseline-to-target has simpler code because the diff defines the action to
|
||||||
|
take, but needs special handling for untracked and ignored files, if they
|
||||||
|
need to be removed.
|
||||||
|
|
||||||
|
The current checkout implementation is based on a baseline-to-target diff.
|
||||||
|
|
||||||
|
|
||||||
|
Picking Actions
|
||||||
|
===============
|
||||||
|
|
||||||
|
The most interesting aspect of this is phase 2, picking the actions that
|
||||||
|
should be taken. There are a lot of corner cases, so it may be easier to
|
||||||
|
start by looking at the rules for a simple 2-iterator diff:
|
||||||
|
|
||||||
|
Key
|
||||||
|
---
|
||||||
|
- B1,B2,B3 - blobs with different SHAs,
|
||||||
|
- Bi - ignored blob (WD only)
|
||||||
|
- T1,T2,T3 - trees with different SHAs,
|
||||||
|
- Ti - ignored tree (WD only)
|
||||||
|
- x - nothing
|
||||||
|
|
||||||
|
Diff with 2 non-workdir iterators
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Old New
|
||||||
|
--- ---
|
||||||
|
0 x x - nothing
|
||||||
|
1 x B1 - added blob
|
||||||
|
2 x T1 - added tree
|
||||||
|
3 B1 x - removed blob
|
||||||
|
4 B1 B1 - unmodified blob
|
||||||
|
5 B1 B2 - modified blob
|
||||||
|
6 B1 T1 - typechange blob -> tree
|
||||||
|
7 T1 x - removed tree
|
||||||
|
8 T1 B1 - typechange tree -> blob
|
||||||
|
9 T1 T1 - unmodified tree
|
||||||
|
10 T1 T2 - modified tree (implies modified/added/removed blob inside)
|
||||||
|
|
||||||
|
|
||||||
|
Now, let's make the "New" iterator into a working directory iterator, so
|
||||||
|
we replace "added" items with either untracked or ignored, like this:
|
||||||
|
|
||||||
|
Diff with non-work & workdir iterators
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Old New-WD
|
||||||
|
--- ------
|
||||||
|
0 x x - nothing
|
||||||
|
1 x B1 - untracked blob
|
||||||
|
2 x Bi - ignored file
|
||||||
|
3 x T1 - untracked tree
|
||||||
|
4 x Ti - ignored tree
|
||||||
|
5 B1 x - removed blob
|
||||||
|
6 B1 B1 - unmodified blob
|
||||||
|
7 B1 B2 - modified blob
|
||||||
|
8 B1 T1 - typechange blob -> tree
|
||||||
|
9 B1 Ti - removed blob AND ignored tree as separate items
|
||||||
|
10 T1 x - removed tree
|
||||||
|
11 T1 B1 - typechange tree -> blob
|
||||||
|
12 T1 Bi - removed tree AND ignored blob as separate items
|
||||||
|
13 T1 T1 - unmodified tree
|
||||||
|
14 T1 T2 - modified tree (implies modified/added/removed blob inside)
|
||||||
|
|
||||||
|
Note: if there is a corresponding entry in the old tree, then a working
|
||||||
|
directory item won't be ignored (i.e. no Bi or Ti for tracked items).
|
||||||
|
|
||||||
|
|
||||||
|
Now, expand this to three iterators: a baseline tree, a target tree, and
|
||||||
|
an actual working directory tree:
|
||||||
|
|
||||||
|
Checkout From 3 Iterators (2 not workdir, 1 workdir)
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
(base == old HEAD; target == what to checkout; actual == working dir)
|
||||||
|
|
||||||
|
base target actual/workdir
|
||||||
|
---- ------ ------
|
||||||
|
0 x x x - nothing
|
||||||
|
1 x x B1/Bi/T1/Ti - untracked/ignored blob/tree (SAFE)
|
||||||
|
2+ x B1 x - add blob (SAFE)
|
||||||
|
3 x B1 B1 - independently added blob (FORCEABLE-2)
|
||||||
|
4* x B1 B2/Bi/T1/Ti - add blob with content conflict (FORCEABLE-2)
|
||||||
|
5+ x T1 x - add tree (SAFE)
|
||||||
|
6* x T1 B1/Bi - add tree with blob conflict (FORCEABLE-2)
|
||||||
|
7 x T1 T1/i - independently added tree (SAFE+MISSING)
|
||||||
|
8 B1 x x - independently deleted blob (SAFE+MISSING)
|
||||||
|
9- B1 x B1 - delete blob (SAFE)
|
||||||
|
10- B1 x B2 - delete of modified blob (FORCEABLE-1)
|
||||||
|
11 B1 x T1/Ti - independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!)
|
||||||
|
12 B1 B1 x - locally deleted blob (DIRTY || SAFE+CREATE)
|
||||||
|
13+ B1 B2 x - update to deleted blob (SAFE+MISSING)
|
||||||
|
14 B1 B1 B1 - unmodified file (SAFE)
|
||||||
|
15 B1 B1 B2 - locally modified file (DIRTY)
|
||||||
|
16+ B1 B2 B1 - update unmodified blob (SAFE)
|
||||||
|
17 B1 B2 B2 - independently updated blob (FORCEABLE-1)
|
||||||
|
18+ B1 B2 B3 - update to modified blob (FORCEABLE-1)
|
||||||
|
19 B1 B1 T1/Ti - locally deleted blob AND untrack/ign tree (DIRTY)
|
||||||
|
20* B1 B2 T1/Ti - update to deleted blob AND untrack/ign tree (F-1)
|
||||||
|
21+ B1 T1 x - add tree with locally deleted blob (SAFE+MISSING)
|
||||||
|
22* B1 T1 B1 - add tree AND deleted blob (SAFE)
|
||||||
|
23* B1 T1 B2 - add tree with delete of modified blob (F-1)
|
||||||
|
24 B1 T1 T1 - add tree with deleted blob (F-1)
|
||||||
|
25 T1 x x - independently deleted tree (SAFE+MISSING)
|
||||||
|
26 T1 x B1/Bi - independently deleted tree AND untrack/ign blob (F-1)
|
||||||
|
27- T1 x T1 - deleted tree (MAYBE SAFE)
|
||||||
|
28+ T1 B1 x - deleted tree AND added blob (SAFE+MISSING)
|
||||||
|
29 T1 B1 B1 - independently typechanged tree -> blob (F-1)
|
||||||
|
30+ T1 B1 B2 - typechange tree->blob with conflicting blob (F-1)
|
||||||
|
31* T1 B1 T1/T2 - typechange tree->blob (MAYBE SAFE)
|
||||||
|
32+ T1 T1 x - restore locally deleted tree (SAFE+MISSING)
|
||||||
|
33 T1 T1 B1/Bi - locally typechange tree->untrack/ign blob (DIRTY)
|
||||||
|
34 T1 T1 T1/T2 - unmodified tree (MAYBE SAFE)
|
||||||
|
35+ T1 T2 x - update locally deleted tree (SAFE+MISSING)
|
||||||
|
36* T1 T2 B1/Bi - update to tree with typechanged tree->blob conflict (F-1)
|
||||||
|
37 T1 T2 T1/T2/T3 - update to existing tree (MAYBE SAFE)
|
||||||
|
|
||||||
|
The number is followed by ' ' if no change is needed or '+' if the case
|
||||||
|
needs to write to disk or '-' if something must be deleted and '*' if
|
||||||
|
there should be a delete followed by an write.
|
||||||
|
|
||||||
|
There are four tiers of safe cases:
|
||||||
|
|
||||||
|
- SAFE == completely safe to update
|
||||||
|
- SAFE+MISSING == safe except the workdir is missing the expect content
|
||||||
|
- MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
|
||||||
|
content, which is unknown at this point
|
||||||
|
- FORCEABLE == conflict unless FORCE is given
|
||||||
|
- DIRTY == no conflict but change is not applied unless FORCE
|
||||||
|
|
||||||
|
Some slightly unusual circumstances:
|
||||||
|
|
||||||
|
8 - parent dir is only deleted when file is, so parent will be left if
|
||||||
|
empty even though it would be deleted if the file were present
|
||||||
|
11 - core git does not consider this a conflict but attempts to delete T1
|
||||||
|
and gives "unable to unlink file" error yet does not skip the rest
|
||||||
|
of the operation
|
||||||
|
12 - without FORCE file is left deleted (i.e. not restored) so new wd is
|
||||||
|
dirty (and warning message "D file" is printed), with FORCE, file is
|
||||||
|
restored.
|
||||||
|
24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
|
||||||
|
combined, but core git considers this a conflict unless forced.
|
||||||
|
26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
|
||||||
|
which are ok on their own, but core git treat this as a conflict.
|
||||||
|
If not forced, this is a conflict. If forced, this actually doesn't
|
||||||
|
have to write anything and leaves the new blob as an untracked file.
|
||||||
|
32 - This is the only case where the baseline and target values match
|
||||||
|
and yet we will still write to the working directory. In all other
|
||||||
|
cases, if baseline == target, we don't touch the workdir (it is
|
||||||
|
either already right or is "dirty"). However, since this case also
|
||||||
|
implies that a ?/B1/x case will exist as well, it can be skipped.
|
||||||
|
|
||||||
|
Cases 3, 17, 24, 26, and 29 are all considered conflicts even though
|
||||||
|
none of them will require making any updates to the working directory.
|
||||||
|
|
@ -23,97 +23,82 @@ GIT_BEGIN_DECL
|
|||||||
/**
|
/**
|
||||||
* Checkout behavior flags
|
* Checkout behavior flags
|
||||||
*
|
*
|
||||||
* In libgit2, the function of checkout is to update the working directory
|
* In libgit2, checkout is used to update the working directory and index
|
||||||
* to match a target tree. It does not move the HEAD commit - you do that
|
* to match a target tree. Unlike git checkout, it does not move the HEAD
|
||||||
* separately. To safely perform the update, checkout relies on a baseline
|
* commit for you - use `git_repository_set_head` or the like to do that.
|
||||||
* tree (generally the current HEAD) as a reference for the unmodified
|
|
||||||
* content expected in the working directory.
|
|
||||||
*
|
*
|
||||||
* Checkout examines the differences between the target tree, the baseline
|
* Checkout looks at (up to) four things: the "target" tree you want to
|
||||||
* tree and the working directory, and groups files into five categories:
|
* check out, the "baseline" tree of what was checked out previously, the
|
||||||
|
* working directory for actual files, and the index for staged changes.
|
||||||
*
|
*
|
||||||
* 1. UNMODIFIED - Files that match in all places.
|
* You give checkout one of four strategies for update:
|
||||||
* 2. SAFE - Files where the working directory and the baseline content
|
|
||||||
* match that can be safely updated to the target.
|
|
||||||
* 3. DIRTY/MISSING - Files where the working directory differs from the
|
|
||||||
* baseline but there is no conflicting change with the target. One
|
|
||||||
* example is a file that doesn't exist in the working directory - no
|
|
||||||
* data would be lost as a result of writing this file. Which action
|
|
||||||
* will be taken with these files depends on the options you use.
|
|
||||||
* 4. CONFLICTS - Files where changes in the working directory conflict
|
|
||||||
* with changes to be applied by the target. If conflicts are found,
|
|
||||||
* they prevent any other modifications from being made (although there
|
|
||||||
* are options to override that and force the update, of course).
|
|
||||||
* 5. UNTRACKED/IGNORED - Files in the working directory that are untracked
|
|
||||||
* or ignored (i.e. only in the working directory, not the other places).
|
|
||||||
*
|
*
|
||||||
|
* - `GIT_CHECKOUT_NONE` is a dry-run strategy that checks for conflicts,
|
||||||
|
* etc., but doesn't make any actual changes.
|
||||||
*
|
*
|
||||||
* You control the actions checkout takes with one of four base strategies:
|
* - `GIT_CHECKOUT_FORCE` is at the opposite extreme, taking any action to
|
||||||
|
* make the working directory match the target (including potentially
|
||||||
|
* discarding modified files).
|
||||||
*
|
*
|
||||||
* - `GIT_CHECKOUT_NONE` is the default and applies no changes. It is a dry
|
* In between those are `GIT_CHECKOUT_SAFE` and `GIT_CHECKOUT_SAFE_CREATE`
|
||||||
* run that you can use to find conflicts, etc. if you wish.
|
* both of which only make modifications that will not lose changes.
|
||||||
*
|
*
|
||||||
* - `GIT_CHECKOUT_SAFE` is like `git checkout` and only applies changes
|
* | target == baseline | target != baseline |
|
||||||
* between the baseline and target trees to files in category 2.
|
* ---------------------|-----------------------|----------------------|
|
||||||
|
* workdir == baseline | no action | create, update, or |
|
||||||
|
* | | delete file |
|
||||||
|
* ---------------------|-----------------------|----------------------|
|
||||||
|
* workdir exists and | no action | conflict (notify |
|
||||||
|
* is != baseline | notify dirty MODIFIED | and cancel checkout) |
|
||||||
|
* ---------------------|-----------------------|----------------------|
|
||||||
|
* workdir missing, | create if SAFE_CREATE | create file |
|
||||||
|
* baseline present | notify dirty DELETED | |
|
||||||
|
* ---------------------|-----------------------|----------------------|
|
||||||
*
|
*
|
||||||
* - `GIT_CHECKOUT_SAFE_CREATE` also creates files that are missing from the
|
* The only difference between SAFE and SAFE_CREATE is that SAFE_CREATE
|
||||||
* working directory (category 3), even if there is no change between the
|
* will cause a file to be checked out if it is missing from the working
|
||||||
* baseline and target trees for those files. See notes below on
|
* directory even if it is not modified between the target and baseline.
|
||||||
* emulating `git checkout-index` for some of the subtleties of this.
|
|
||||||
*
|
|
||||||
* - `GIT_CHECKOUT_FORCE` is like `git checkout -f` and will update the
|
|
||||||
* working directory to match the target content regardless of conflicts,
|
|
||||||
* overwriting dirty and conflicting files.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* There are some additional flags to modified the behavior of checkout:
|
|
||||||
*
|
|
||||||
* - GIT_CHECKOUT_ALLOW_CONFLICTS can be added to apply safe file updates
|
|
||||||
* even if there are conflicts. Normally, the entire checkout will be
|
|
||||||
* cancelled if any files are in category 4. With this flag, conflicts
|
|
||||||
* will be skipped (though the notification callback will still be invoked
|
|
||||||
* on the conflicting files if requested).
|
|
||||||
*
|
|
||||||
* - GIT_CHECKOUT_REMOVE_UNTRACKED means that files in the working directory
|
|
||||||
* that are untracked (but not ignored) should be deleted. The are not
|
|
||||||
* considered conflicts and would normally be ignored by checkout.
|
|
||||||
*
|
|
||||||
* - GIT_CHECKOUT_REMOVE_IGNORED means to remove ignored files from the
|
|
||||||
* working directory as well. Obviously, these would normally be ignored.
|
|
||||||
*
|
|
||||||
* - GIT_CHECKOUT_UPDATE_ONLY means to only update the content of files that
|
|
||||||
* already exist. Files will not be created nor deleted. This does not
|
|
||||||
* make adds and deletes into conflicts - it just skips applying those
|
|
||||||
* changes. This will also skip updates to typechanged files (since that
|
|
||||||
* would involve deleting the old and creating the new).
|
|
||||||
*
|
|
||||||
* - Unmerged entries in the index are also considered conflicts. The
|
|
||||||
* GIT_CHECKOUT_SKIP_UNMERGED flag causes us to skip files with unmerged
|
|
||||||
* index entries. You can also use GIT_CHECKOUT_USE_OURS and
|
|
||||||
* GIT_CHECKOUT_USE_THEIRS to proceeed with the checkout using either the
|
|
||||||
* stage 2 ("ours") or stage 3 ("theirs") version of files in the index.
|
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
|
* To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
|
||||||
* notification callback (see below) that displays information about dirty
|
* notification callback (see below) that displays information about dirty
|
||||||
* files (i.e. files that don't need an update but that no longer match the
|
* files. The default behavior will cancel checkout on conflicts.
|
||||||
* baseline content). The default behavior will cancel on conflicts.
|
|
||||||
*
|
*
|
||||||
* To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE_CREATE` with a
|
* To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE_CREATE` with a
|
||||||
* notification callback that cancels the operation if a dirty-but-existing
|
* notification callback that cancels the operation if a dirty-but-existing
|
||||||
* file is found in the working directory. This core git command isn't
|
* file is found in the working directory. This core git command isn't
|
||||||
* quite "force" but is sensitive about some types of changes.
|
* quite "force" but is sensitive about some types of changes.
|
||||||
*
|
*
|
||||||
* To emulate `git checkout -f`, you use `GIT_CHECKOUT_FORCE`.
|
* To emulate `git checkout -f`, use `GIT_CHECKOUT_FORCE`.
|
||||||
|
*
|
||||||
|
* To emulate `git clone` use `GIT_CHECKOUT_SAFE_CREATE` in the options.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Checkout is "semi-atomic" as in it will go through the work to be done
|
* There are some additional flags to modified the behavior of checkout:
|
||||||
* before making any changes and if may decide to abort if there are
|
*
|
||||||
* conflicts, or you can use the notification callback to explicitly abort
|
* - GIT_CHECKOUT_ALLOW_CONFLICTS makes SAFE mode apply safe file updates
|
||||||
* the action before any updates are made. Despite this, if a second
|
* even if there are conflicts (instead of cancelling the checkout).
|
||||||
* process is modifying the filesystem while checkout is running, it can't
|
*
|
||||||
* guarantee that the choices is makes while initially examining the
|
* - GIT_CHECKOUT_REMOVE_UNTRACKED means remove untracked files (i.e. not
|
||||||
* filesystem are still going to be correct as it applies them.
|
* in target, baseline, or index, and not ignored) from the working dir.
|
||||||
|
*
|
||||||
|
* - GIT_CHECKOUT_REMOVE_IGNORED means remove ignored files (that are also
|
||||||
|
* unrtacked) from the working directory as well.
|
||||||
|
*
|
||||||
|
* - GIT_CHECKOUT_UPDATE_ONLY means to only update the content of files that
|
||||||
|
* already exist. Files will not be created nor deleted. This just skips
|
||||||
|
* applying adds, deletes, and typechanges.
|
||||||
|
*
|
||||||
|
* - GIT_CHECKOUT_DONT_UPDATE_INDEX prevents checkout from writing the
|
||||||
|
* updated files' information to the index.
|
||||||
|
*
|
||||||
|
* - Normally, checkout will reload the index and git attributes from disk
|
||||||
|
* before any operations. GIT_CHECKOUT_NO_REFRESH prevents this reload.
|
||||||
|
*
|
||||||
|
* - Unmerged index entries are conflicts. GIT_CHECKOUT_SKIP_UNMERGED skips
|
||||||
|
* files with unmerged index entries instead. GIT_CHECKOUT_USE_OURS and
|
||||||
|
* GIT_CHECKOUT_USE_THEIRS to proceeed with the checkout using either the
|
||||||
|
* stage 2 ("ours") or stage 3 ("theirs") version of files in the index.
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GIT_CHECKOUT_NONE = 0, /** default is a dry run, no actual updates */
|
GIT_CHECKOUT_NONE = 0, /** default is a dry run, no actual updates */
|
||||||
@ -167,45 +152,23 @@ typedef enum {
|
|||||||
/**
|
/**
|
||||||
* Checkout notification flags
|
* Checkout notification flags
|
||||||
*
|
*
|
||||||
* When running a checkout, you can set a notification callback (`notify_cb`)
|
* Checkout will invoke an options notification callback (`notify_cb`) for
|
||||||
* to be invoked for some or all files to be checked out. Which files
|
* certain cases - you pick which ones via `notify_flags`:
|
||||||
* receive a callback depend on the `notify_flags` value which is a
|
|
||||||
* combination of these flags.
|
|
||||||
*
|
*
|
||||||
* - GIT_CHECKOUT_NOTIFY_CONFLICT means that conflicting files that would
|
* - GIT_CHECKOUT_NOTIFY_CONFLICT invokes checkout on conflicting paths.
|
||||||
* prevent the checkout from occurring will receive callbacks. If you
|
|
||||||
* used GIT_CHECKOUT_ALLOW_CONFLICTS, the callbacks are still done, but
|
|
||||||
* the checkout will not be blocked. The callback `status_flags` will
|
|
||||||
* have both index and work tree change bits set (see `git_status_t`).
|
|
||||||
*
|
*
|
||||||
* - GIT_CHECKOUT_NOTIFY_DIRTY means to notify about "dirty" files, i.e.
|
* - GIT_CHECKOUT_NOTIFY_DIRTY notifies about "dirty" files, i.e. those that
|
||||||
* those that do not need to be updated but no longer match the baseline
|
* do not need an update but no longer match the baseline. Core git
|
||||||
* content. Core git displays these files when checkout runs, but does
|
* displays these files when checkout runs, but won't stop the checkout.
|
||||||
* not stop the checkout. For these, `status_flags` will have only work
|
|
||||||
* tree bits set (i.e. GIT_STATUS_WT_MODIFIED, etc).
|
|
||||||
*
|
*
|
||||||
* - GIT_CHECKOUT_NOTIFY_UPDATED sends notification for any file changed by
|
* - GIT_CHECKOUT_NOTIFY_UPDATED sends notification for any file changed.
|
||||||
* the checkout. Callback `status_flags` will have only index bits set.
|
|
||||||
*
|
*
|
||||||
* - GIT_CHECKOUT_NOTIFY_UNTRACKED notifies for all untracked files that
|
* - GIT_CHECKOUT_NOTIFY_UNTRACKED notifies about untracked files.
|
||||||
* are not ignored. Passing GIT_CHECKOUT_REMOVE_UNTRACKED would remove
|
|
||||||
* these files. The `status_flags` will be GIT_STATUS_WT_NEW.
|
|
||||||
*
|
*
|
||||||
* - GIT_CHECKOUT_NOTIFY_IGNORED notifies for the ignored files. Passing
|
* - GIT_CHECKOUT_NOTIFY_IGNORED notifies about ignored files.
|
||||||
* GIT_CHECKOUT_REMOVE_IGNORED will remove these. The `status_flags`
|
|
||||||
* will be to GIT_STATUS_IGNORED.
|
|
||||||
*
|
*
|
||||||
* If you return a non-zero value from the notify callback, the checkout
|
* Returning a non-zero value from this callback will cancel the checkout.
|
||||||
* will be canceled. Notification callbacks are made prior to making any
|
* Notification callbacks are made prior to modifying any files on disk.
|
||||||
* modifications, so returning non-zero will cancel the entire checkout.
|
|
||||||
* If you are do not use GIT_CHECKOUT_ALLOW_CONFLICTS and there are
|
|
||||||
* conflicts, you don't need to explicitly cancel from the callback.
|
|
||||||
* Checkout itself will abort after all files are processed.
|
|
||||||
*
|
|
||||||
* To emulate core git checkout output, use GIT_CHECKOUT_NOTIFY_CONFLICTS
|
|
||||||
* and GIT_CHECKOUT_NOTIFY_DIRTY. Conflicts will have `status_flags` with
|
|
||||||
* changes in both the index and work tree (see the `git_status_t` values).
|
|
||||||
* Dirty files will only have work tree flags set.
|
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GIT_CHECKOUT_NOTIFY_NONE = 0,
|
GIT_CHECKOUT_NOTIFY_NONE = 0,
|
||||||
@ -216,13 +179,27 @@ typedef enum {
|
|||||||
GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),
|
GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),
|
||||||
} git_checkout_notify_t;
|
} git_checkout_notify_t;
|
||||||
|
|
||||||
|
/** Checkout notification callback function */
|
||||||
|
typedef int (*git_checkout_notify_cb)(
|
||||||
|
git_checkout_notify_t why,
|
||||||
|
const char *path,
|
||||||
|
const git_diff_file *baseline,
|
||||||
|
const git_diff_file *target,
|
||||||
|
const git_diff_file *workdir,
|
||||||
|
void *payload);
|
||||||
|
|
||||||
|
/** Checkout progress notification function */
|
||||||
|
typedef void (*git_checkout_progress_cb)(
|
||||||
|
const char *path,
|
||||||
|
size_t completed_steps,
|
||||||
|
size_t total_steps,
|
||||||
|
void *payload);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checkout options structure
|
* Checkout options structure
|
||||||
*
|
*
|
||||||
* Use zeros to indicate default settings.
|
* Zero out for defaults. Initialize with `GIT_CHECKOUT_OPTS_INIT` macro to
|
||||||
*
|
* correctly set the `version` field. E.g.
|
||||||
* This should be initialized with the `GIT_CHECKOUT_OPTS_INIT` macro to
|
|
||||||
* correctly set the `version` field.
|
|
||||||
*
|
*
|
||||||
* git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
* git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||||
*/
|
*/
|
||||||
@ -237,21 +214,11 @@ typedef struct git_checkout_opts {
|
|||||||
int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
|
int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
|
||||||
|
|
||||||
unsigned int notify_flags; /** see `git_checkout_notify_t` above */
|
unsigned int notify_flags; /** see `git_checkout_notify_t` above */
|
||||||
int (*notify_cb)(
|
git_checkout_notify_cb notify_cb;
|
||||||
git_checkout_notify_t why,
|
|
||||||
const char *path,
|
|
||||||
const git_diff_file *baseline,
|
|
||||||
const git_diff_file *target,
|
|
||||||
const git_diff_file *workdir,
|
|
||||||
void *payload);
|
|
||||||
void *notify_payload;
|
void *notify_payload;
|
||||||
|
|
||||||
/* Optional callback to notify the consumer of checkout progress. */
|
/* Optional callback to notify the consumer of checkout progress. */
|
||||||
void (*progress_cb)(
|
git_checkout_progress_cb progress_cb;
|
||||||
const char *path,
|
|
||||||
size_t completed_steps,
|
|
||||||
size_t total_steps,
|
|
||||||
void *payload);
|
|
||||||
void *progress_payload;
|
void *progress_payload;
|
||||||
|
|
||||||
/** When not zeroed out, array of fnmatch patterns specifying which
|
/** When not zeroed out, array of fnmatch patterns specifying which
|
||||||
|
184
src/checkout.c
184
src/checkout.c
@ -24,157 +24,7 @@
|
|||||||
#include "diff.h"
|
#include "diff.h"
|
||||||
#include "pathspec.h"
|
#include "pathspec.h"
|
||||||
|
|
||||||
/* Key
|
/* See docs/checkout-internals.md for more information */
|
||||||
* ===
|
|
||||||
* B1,B2,B3 - blobs with different SHAs,
|
|
||||||
* Bi - ignored blob (WD only)
|
|
||||||
* T1,T2,T3 - trees with different SHAs,
|
|
||||||
* Ti - ignored tree (WD only)
|
|
||||||
* x - nothing
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Diff with 2 non-workdir iterators
|
|
||||||
* =================================
|
|
||||||
* Old New
|
|
||||||
* --- ---
|
|
||||||
* 0 x x - nothing
|
|
||||||
* 1 x B1 - added blob
|
|
||||||
* 2 x T1 - added tree
|
|
||||||
* 3 B1 x - removed blob
|
|
||||||
* 4 B1 B1 - unmodified blob
|
|
||||||
* 5 B1 B2 - modified blob
|
|
||||||
* 6 B1 T1 - typechange blob -> tree
|
|
||||||
* 7 T1 x - removed tree
|
|
||||||
* 8 T1 B1 - typechange tree -> blob
|
|
||||||
* 9 T1 T1 - unmodified tree
|
|
||||||
* 10 T1 T2 - modified tree (implies modified/added/removed blob inside)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Diff with non-work & workdir iterators
|
|
||||||
* ======================================
|
|
||||||
* Old New-WD
|
|
||||||
* --- ------
|
|
||||||
* 0 x x - nothing
|
|
||||||
* 1 x B1 - added blob
|
|
||||||
* 2 x Bi - ignored file
|
|
||||||
* 3 x T1 - added tree
|
|
||||||
* 4 x Ti - ignored tree
|
|
||||||
* 5 B1 x - removed blob
|
|
||||||
* 6 B1 B1 - unmodified blob
|
|
||||||
* 7 B1 B2 - modified blob
|
|
||||||
* 8 B1 T1 - typechange blob -> tree
|
|
||||||
* 9 B1 Ti - removed blob AND ignored tree as separate items
|
|
||||||
* 10 T1 x - removed tree
|
|
||||||
* 11 T1 B1 - typechange tree -> blob
|
|
||||||
* 12 T1 Bi - removed tree AND ignored blob as separate items
|
|
||||||
* 13 T1 T1 - unmodified tree
|
|
||||||
* 14 T1 T2 - modified tree (implies modified/added/removed blob inside)
|
|
||||||
*
|
|
||||||
* If there is a corresponding blob in the old, Bi is irrelevant
|
|
||||||
* If there is a corresponding tree in the old, Ti is irrelevant
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Checkout From 3 Iterators (2 not workdir, 1 workdir)
|
|
||||||
* ====================================================
|
|
||||||
*
|
|
||||||
* (Expect == Old HEAD / Desire == What To Checkout / Actual == Workdir)
|
|
||||||
*
|
|
||||||
* Expect Desire Actual-WD
|
|
||||||
* ------ ------ ------
|
|
||||||
* 0 x x x - nothing
|
|
||||||
* 1 x x B1/Bi/T1/Ti - untracked/ignored blob/tree (SAFE)
|
|
||||||
* 2+ x B1 x - add blob (SAFE)
|
|
||||||
* 3 x B1 B1 - independently added blob (FORCEABLE-2)
|
|
||||||
* 4* x B1 B2/Bi/T1/Ti - add blob with content conflict (FORCEABLE-2)
|
|
||||||
* 5+ x T1 x - add tree (SAFE)
|
|
||||||
* 6* x T1 B1/Bi - add tree with blob conflict (FORCEABLE-2)
|
|
||||||
* 7 x T1 T1/i - independently added tree (SAFE+MISSING)
|
|
||||||
* 8 B1 x x - independently deleted blob (SAFE+MISSING)
|
|
||||||
* 9- B1 x B1 - delete blob (SAFE)
|
|
||||||
* 10- B1 x B2 - delete of modified blob (FORCEABLE-1)
|
|
||||||
* 11 B1 x T1/Ti - independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!)
|
|
||||||
* 12 B1 B1 x - locally deleted blob (DIRTY || SAFE+CREATE)
|
|
||||||
* 13+ B1 B2 x - update to deleted blob (SAFE+MISSING)
|
|
||||||
* 14 B1 B1 B1 - unmodified file (SAFE)
|
|
||||||
* 15 B1 B1 B2 - locally modified file (DIRTY)
|
|
||||||
* 16+ B1 B2 B1 - update unmodified blob (SAFE)
|
|
||||||
* 17 B1 B2 B2 - independently updated blob (FORCEABLE-1)
|
|
||||||
* 18+ B1 B2 B3 - update to modified blob (FORCEABLE-1)
|
|
||||||
* 19 B1 B1 T1/Ti - locally deleted blob AND untrack/ign tree (DIRTY)
|
|
||||||
* 20* B1 B2 T1/Ti - update to deleted blob AND untrack/ign tree (F-1)
|
|
||||||
* 21+ B1 T1 x - add tree with locally deleted blob (SAFE+MISSING)
|
|
||||||
* 22* B1 T1 B1 - add tree AND deleted blob (SAFE)
|
|
||||||
* 23* B1 T1 B2 - add tree with delete of modified blob (F-1)
|
|
||||||
* 24 B1 T1 T1 - add tree with deleted blob (F-1)
|
|
||||||
* 25 T1 x x - independently deleted tree (SAFE+MISSING)
|
|
||||||
* 26 T1 x B1/Bi - independently deleted tree AND untrack/ign blob (F-1)
|
|
||||||
* 27- T1 x T1 - deleted tree (MAYBE SAFE)
|
|
||||||
* 28+ T1 B1 x - deleted tree AND added blob (SAFE+MISSING)
|
|
||||||
* 29 T1 B1 B1 - independently typechanged tree -> blob (F-1)
|
|
||||||
* 30+ T1 B1 B2 - typechange tree->blob with conflicting blob (F-1)
|
|
||||||
* 31* T1 B1 T1/T2 - typechange tree->blob (MAYBE SAFE)
|
|
||||||
* 32+ T1 T1 x - restore locally deleted tree (SAFE+MISSING)
|
|
||||||
* 33 T1 T1 B1/Bi - locally typechange tree->untrack/ign blob (DIRTY)
|
|
||||||
* 34 T1 T1 T1/T2 - unmodified tree (MAYBE SAFE)
|
|
||||||
* 35+ T1 T2 x - update locally deleted tree (SAFE+MISSING)
|
|
||||||
* 36* T1 T2 B1/Bi - update to tree with typechanged tree->blob conflict (F-1)
|
|
||||||
* 37 T1 T2 T1/T2/T3 - update to existing tree (MAYBE SAFE)
|
|
||||||
*
|
|
||||||
* The number will be followed by ' ' if no change is needed or '+' if the
|
|
||||||
* case needs to write to disk or '-' if something must be deleted and '*'
|
|
||||||
* if there should be a delete followed by an write.
|
|
||||||
*
|
|
||||||
* There are four tiers of safe cases:
|
|
||||||
* - SAFE == completely safe to update
|
|
||||||
* - SAFE+MISSING == safe except the workdir is missing the expect content
|
|
||||||
* - MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
|
|
||||||
* content, which is unknown at this point
|
|
||||||
* - FORCEABLE == conflict unless FORCE is given
|
|
||||||
* - DIRTY == no conflict but change is not applied unless FORCE
|
|
||||||
*
|
|
||||||
* Some slightly unusual circumstances:
|
|
||||||
* 8 - parent dir is only deleted when file is, so parent will be left if
|
|
||||||
* empty even though it would be deleted if the file were present
|
|
||||||
* 11 - core git does not consider this a conflict but attempts to delete T1
|
|
||||||
* and gives "unable to unlink file" error yet does not skip the rest
|
|
||||||
* of the operation
|
|
||||||
* 12 - without FORCE file is left deleted (i.e. not restored) so new wd is
|
|
||||||
* dirty (and warning message "D file" is printed), with FORCE, file is
|
|
||||||
* restored.
|
|
||||||
* 24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
|
|
||||||
* combined, but core git considers this a conflict unless forced.
|
|
||||||
* 26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
|
|
||||||
* which are ok on their own, but core git treat this as a conflict.
|
|
||||||
* If not forced, this is a conflict. If forced, this actually doesn't
|
|
||||||
* have to write anything and leaves the new blob as an untracked file.
|
|
||||||
* 32 - This is the only case where the baseline and target values match
|
|
||||||
* and yet we will still write to the working directory. In all other
|
|
||||||
* cases, if baseline == target, we don't touch the workdir (it is
|
|
||||||
* either already right or is "dirty"). However, since this case also
|
|
||||||
* implies that a ?/B1/x case will exist as well, it can be skipped.
|
|
||||||
*
|
|
||||||
* Cases 3, 17, 24, 26, and 29 are all considered conflicts even though
|
|
||||||
* none of them will require making any updates to the working directory.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* expect desire wd
|
|
||||||
* 1 x x T -> ignored dir OR untracked dir OR parent dir
|
|
||||||
* 2 x x I -> ignored file
|
|
||||||
* 3 x x A -> untracked file
|
|
||||||
* 4 x A x -> add from index (no conflict)
|
|
||||||
* 5 x A A -> independently added file
|
|
||||||
* 6 x A B -> add with conflicting file
|
|
||||||
* 7 A x x -> independently deleted file
|
|
||||||
* 8 A x A -> delete from index (no conflict)
|
|
||||||
* 9 A x B -> delete of modified file
|
|
||||||
* 10 A A x -> locally deleted file
|
|
||||||
* 11 A A A -> unmodified file (no conflict)
|
|
||||||
* 12 A A B -> locally modified
|
|
||||||
* 13 A B x -> update of deleted file
|
|
||||||
* 14 A B A -> update of unmodified file (no conflict)
|
|
||||||
* 15 A B B -> independently updated file
|
|
||||||
* 16 A B C -> update of modified file
|
|
||||||
*/
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
CHECKOUT_ACTION__NONE = 0,
|
CHECKOUT_ACTION__NONE = 0,
|
||||||
@ -1317,34 +1167,15 @@ int git_checkout_iterator(
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkout can be driven either off a target-to-workdir diff or a
|
/* Generate baseline-to-target diff which will include an entry for
|
||||||
* baseline-to-target diff. There are pros and cons of each.
|
* every possible update that might need to be made.
|
||||||
*
|
|
||||||
* Target-to-workdir means the diff includes every file that could be
|
|
||||||
* modified, which simplifies bookkeeping, but the code to constantly
|
|
||||||
* refer back to the baseline gets complicated.
|
|
||||||
*
|
|
||||||
* Baseline-to-target has simpler code because the diff defines the
|
|
||||||
* action to take, but needs special handling for untracked and ignored
|
|
||||||
* files, if they need to be removed.
|
|
||||||
*
|
|
||||||
* I've implemented both versions and opted for the second.
|
|
||||||
*/
|
*/
|
||||||
if ((error = git_diff__from_iterators(
|
if ((error = git_diff__from_iterators(
|
||||||
&data.diff, data.repo, baseline, target, &diff_opts)) < 0)
|
&data.diff, data.repo, baseline, target, &diff_opts)) < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
/* In order to detect conflicts prior to performing any operations,
|
/* Loop through diff (and working directory iterator) building a list of
|
||||||
* and in order to deal with some order dependencies, checkout is best
|
* actions to be taken, plus look for conflicts and send notifications.
|
||||||
* performed with up to four passes through the diff.
|
|
||||||
*
|
|
||||||
* 0. Figure out the actions to be taken,
|
|
||||||
* 1. Remove any files / directories as needed (because alphabetical
|
|
||||||
* iteration means that an untracked directory will end up sorted
|
|
||||||
* *after* a blob that should be checked out with the same name),
|
|
||||||
* 2. Then update all blobs,
|
|
||||||
* 3. Then update all submodules in case a new .gitmodules blob was
|
|
||||||
* checked out during pass #2.
|
|
||||||
*/
|
*/
|
||||||
if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0)
|
if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@ -1355,8 +1186,9 @@ int git_checkout_iterator(
|
|||||||
|
|
||||||
report_progress(&data, NULL); /* establish 0 baseline */
|
report_progress(&data, NULL); /* establish 0 baseline */
|
||||||
|
|
||||||
/* TODO: add ability to update index entries while checking out */
|
/* To deal with some order dependencies, perform remaining checkout
|
||||||
|
* in three passes: removes, then update blobs, then update submodules.
|
||||||
|
*/
|
||||||
if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
|
if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
|
||||||
(error = checkout_remove_the_old(actions, &data)) < 0)
|
(error = checkout_remove_the_old(actions, &data)) < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
Loading…
Reference in New Issue
Block a user