mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 23:26:39 +00:00
Merge pull request #1118 from arrbee/fix-reset-hard
Fix checkout corner cases, index handling, and reset hard behavior
This commit is contained in:
commit
cfc7b835d6
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.
|
||||
|
@ -9,8 +9,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "indexer.h"
|
||||
#include "strarray.h"
|
||||
#include "diff.h"
|
||||
|
||||
/**
|
||||
* @file git2/checkout.h
|
||||
@ -24,105 +23,113 @@ GIT_BEGIN_DECL
|
||||
/**
|
||||
* Checkout behavior flags
|
||||
*
|
||||
* These flags control what checkout does with files. Pass in a
|
||||
* combination of these values OR'ed together. If you just pass zero
|
||||
* (i.e. no flags), then you are effectively doing a "dry run" where no
|
||||
* files will be modified.
|
||||
* In libgit2, checkout is used to update the working directory and index
|
||||
* to match a target tree. Unlike git checkout, it does not move the HEAD
|
||||
* commit for you - use `git_repository_set_head` or the like to do that.
|
||||
*
|
||||
* Checkout groups the working directory content into 3 classes of files:
|
||||
* (1) files that don't need a change, and files that do need a change
|
||||
* that either (2) we are allowed to modifed or (3) we are not. The flags
|
||||
* you pass in will decide which files we are allowed to modify.
|
||||
* Checkout looks at (up to) four things: the "target" tree you want to
|
||||
* check out, the "baseline" tree of what was checked out previously, the
|
||||
* working directory for actual files, and the index for staged changes.
|
||||
*
|
||||
* By default, checkout is not allowed to modify any files. Anything
|
||||
* needing a change would be considered a conflict.
|
||||
* You give checkout one of four strategies for update:
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_UNMODIFIED means that checkout is allowed to update
|
||||
* any file where the working directory content matches the HEAD
|
||||
* (e.g. either the files match or the file is absent in both places).
|
||||
* - `GIT_CHECKOUT_NONE` is a dry-run strategy that checks for conflicts,
|
||||
* etc., but doesn't make any actual changes.
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_MISSING means checkout can create a missing file
|
||||
* that exists in the index and does not exist in the working directory.
|
||||
* This is usually desirable for initial checkout, etc. Technically, the
|
||||
* missing file differs from the HEAD, which is why this is separate.
|
||||
* - `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_UPDATE_MODIFIED means checkout is allowed to update files
|
||||
* where the working directory does not match the HEAD so long as the file
|
||||
* actually exists in the HEAD. This option implies UPDATE_UNMODIFIED.
|
||||
* In between those are `GIT_CHECKOUT_SAFE` and `GIT_CHECKOUT_SAFE_CREATE`
|
||||
* both of which only make modifications that will not lose changes.
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_UNTRACKED means checkout is allowed to update files
|
||||
* even if there is a working directory version that does not exist in the
|
||||
* HEAD (i.e. the file was independently created in the workdir). This
|
||||
* implies UPDATE_UNMODIFIED | UPDATE_MISSING (but *not* UPDATE_MODIFIED).
|
||||
* | target == baseline | target != baseline |
|
||||
* ---------------------|-----------------------|----------------------|
|
||||
* 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 | |
|
||||
* ---------------------|-----------------------|----------------------|
|
||||
*
|
||||
* The only difference between SAFE and SAFE_CREATE is that SAFE_CREATE
|
||||
* will cause a file to be checked out if it is missing from the working
|
||||
* directory even if it is not modified between the target and baseline.
|
||||
*
|
||||
*
|
||||
* On top of these three basic strategies, there are some modifiers
|
||||
* options that can be applied:
|
||||
* To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
|
||||
* notification callback (see below) that displays information about dirty
|
||||
* files. The default behavior will cancel checkout on conflicts.
|
||||
*
|
||||
* If any files need update but are disallowed by the strategy, normally
|
||||
* checkout calls the conflict callback (if given) and then aborts.
|
||||
* GIT_CHECKOUT_ALLOW_CONFLICTS means it is okay to update the files that
|
||||
* are allowed by the strategy even if there are conflicts. The conflict
|
||||
* callbacks are still made, but non-conflicting files will be updated.
|
||||
* To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE_CREATE` with a
|
||||
* notification callback that cancels the operation if a dirty-but-existing
|
||||
* file is found in the working directory. This core git command isn't
|
||||
* quite "force" but is sensitive about some types of changes.
|
||||
*
|
||||
* Any unmerged entries in the index are automatically considered conflicts.
|
||||
* If you want to proceed anyhow and just skip unmerged entries, you can use
|
||||
* GIT_CHECKOUT_SKIP_UNMERGED which is less dangerous than just allowing all
|
||||
* conflicts. Alternatively, use GIT_CHECKOUT_USE_OURS to proceed and
|
||||
* checkout the stage 2 ("ours") version. GIT_CHECKOUT_USE_THEIRS means to
|
||||
* proceed and use the stage 3 ("theirs") version.
|
||||
* To emulate `git checkout -f`, use `GIT_CHECKOUT_FORCE`.
|
||||
*
|
||||
* GIT_CHECKOUT_UPDATE_ONLY means that update is not allowed to create new
|
||||
* files or delete old ones, only update existing content. With this
|
||||
* flag, files that needs to be created or deleted are not conflicts -
|
||||
* they are just skipped. This also skips typechanges to existing files
|
||||
* (because the old would have to be removed).
|
||||
*
|
||||
* GIT_CHECKOUT_REMOVE_UNTRACKED means that files in the working directory
|
||||
* that are untracked (and not ignored) will be removed altogether. These
|
||||
* untracked files (that do not shadow index entries) are not considered
|
||||
* conflicts and would normally be ignored.
|
||||
* 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
|
||||
* before making any changes and if may decide to abort if there are
|
||||
* conflicts, or you can use the conflict callback to explicitly abort the
|
||||
* action before any updates are made. Despite this, if a second process
|
||||
* is modifying the filesystem while checkout is running, it can't
|
||||
* guarantee that the choices is makes while initially examining the
|
||||
* filesystem are still going to be correct as it applies them.
|
||||
* There are some additional flags to modified the behavior of checkout:
|
||||
*
|
||||
* - GIT_CHECKOUT_ALLOW_CONFLICTS makes SAFE mode apply safe file updates
|
||||
* even if there are conflicts (instead of cancelling the checkout).
|
||||
*
|
||||
* - GIT_CHECKOUT_REMOVE_UNTRACKED means remove untracked files (i.e. not
|
||||
* 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 {
|
||||
GIT_CHECKOUT_DEFAULT = 0, /** default is a dry run, no actual updates */
|
||||
|
||||
/** Allow update of entries where working dir matches HEAD. */
|
||||
GIT_CHECKOUT_UPDATE_UNMODIFIED = (1u << 0),
|
||||
|
||||
/** Allow update of entries where working dir does not have file. */
|
||||
GIT_CHECKOUT_UPDATE_MISSING = (1u << 1),
|
||||
GIT_CHECKOUT_NONE = 0, /** default is a dry run, no actual updates */
|
||||
|
||||
/** Allow safe updates that cannot overwrite uncommited data */
|
||||
GIT_CHECKOUT_SAFE =
|
||||
(GIT_CHECKOUT_UPDATE_UNMODIFIED | GIT_CHECKOUT_UPDATE_MISSING),
|
||||
GIT_CHECKOUT_SAFE = (1u << 0),
|
||||
|
||||
/** Allow update of entries in working dir that are modified from HEAD. */
|
||||
GIT_CHECKOUT_UPDATE_MODIFIED = (1u << 2),
|
||||
|
||||
/** Update existing untracked files that are now present in the index. */
|
||||
GIT_CHECKOUT_UPDATE_UNTRACKED = (1u << 3),
|
||||
/** Allow safe updates plus creation of missing files */
|
||||
GIT_CHECKOUT_SAFE_CREATE = (1u << 1),
|
||||
|
||||
/** Allow all updates to force working directory to look like index */
|
||||
GIT_CHECKOUT_FORCE =
|
||||
(GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED),
|
||||
GIT_CHECKOUT_FORCE = (1u << 2),
|
||||
|
||||
/** Allow checkout to make updates even if conflicts are found */
|
||||
|
||||
/** Allow checkout to make safe updates even if conflicts are found */
|
||||
GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),
|
||||
|
||||
/** Remove untracked files not in index (that are not ignored) */
|
||||
GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),
|
||||
|
||||
/** Remove ignored files not in index */
|
||||
GIT_CHECKOUT_REMOVE_IGNORED = (1u << 6),
|
||||
|
||||
/** Only update existing files, don't create new ones */
|
||||
GIT_CHECKOUT_UPDATE_ONLY = (1u << 6),
|
||||
GIT_CHECKOUT_UPDATE_ONLY = (1u << 7),
|
||||
|
||||
/** Normally checkout updates index entries as it goes; this stops that */
|
||||
GIT_CHECKOUT_DONT_UPDATE_INDEX = (1u << 8),
|
||||
|
||||
/** Don't refresh index/config/etc before doing checkout */
|
||||
GIT_CHECKOUT_NO_REFRESH = (1u << 9),
|
||||
|
||||
/**
|
||||
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
|
||||
@ -142,56 +149,92 @@ typedef enum {
|
||||
|
||||
} git_checkout_strategy_t;
|
||||
|
||||
/**
|
||||
* Checkout notification flags
|
||||
*
|
||||
* Checkout will invoke an options notification callback (`notify_cb`) for
|
||||
* certain cases - you pick which ones via `notify_flags`:
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_CONFLICT invokes checkout on conflicting paths.
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_DIRTY notifies about "dirty" files, i.e. those that
|
||||
* do not need an update but no longer match the baseline. Core git
|
||||
* displays these files when checkout runs, but won't stop the checkout.
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_UPDATED sends notification for any file changed.
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_UNTRACKED notifies about untracked files.
|
||||
*
|
||||
* - GIT_CHECKOUT_NOTIFY_IGNORED notifies about ignored files.
|
||||
*
|
||||
* Returning a non-zero value from this callback will cancel the checkout.
|
||||
* Notification callbacks are made prior to modifying any files on disk.
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_CHECKOUT_NOTIFY_NONE = 0,
|
||||
GIT_CHECKOUT_NOTIFY_CONFLICT = (1u << 0),
|
||||
GIT_CHECKOUT_NOTIFY_DIRTY = (1u << 1),
|
||||
GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2),
|
||||
GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),
|
||||
GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),
|
||||
} 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
|
||||
*
|
||||
* Use zeros to indicate default settings.
|
||||
* This needs to be initialized with the `GIT_CHECKOUT_OPTS_INIT` macro:
|
||||
* Zero out for defaults. Initialize with `GIT_CHECKOUT_OPTS_INIT` macro to
|
||||
* correctly set the `version` field. E.g.
|
||||
*
|
||||
* git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
*/
|
||||
typedef struct git_checkout_opts {
|
||||
unsigned int version;
|
||||
|
||||
unsigned int checkout_strategy; /** default will be a dry run */
|
||||
|
||||
int disable_filters; /** don't apply filters like CRLF conversion */
|
||||
int dir_mode; /** default is 0755 */
|
||||
int file_mode; /** default is 0644 or 0755 as dictated by blob */
|
||||
unsigned int dir_mode; /** default is 0755 */
|
||||
unsigned int file_mode; /** default is 0644 or 0755 as dictated by blob */
|
||||
int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
|
||||
|
||||
/** Optional callback made on files where the index differs from the
|
||||
* working directory but the rules do not allow update. Return a
|
||||
* non-zero value to abort the checkout. All such callbacks will be
|
||||
* made before any changes are made to the working directory.
|
||||
*/
|
||||
int (*conflict_cb)(
|
||||
const char *conflicting_path,
|
||||
const git_oid *index_oid,
|
||||
unsigned int index_mode,
|
||||
unsigned int wd_mode,
|
||||
void *payload);
|
||||
void *conflict_payload;
|
||||
unsigned int notify_flags; /** see `git_checkout_notify_t` above */
|
||||
git_checkout_notify_cb notify_cb;
|
||||
void *notify_payload;
|
||||
|
||||
/* Optional callback to notify the consumer of checkout progress. */
|
||||
void (*progress_cb)(
|
||||
const char *path,
|
||||
size_t completed_steps,
|
||||
size_t total_steps,
|
||||
void *payload);
|
||||
git_checkout_progress_cb progress_cb;
|
||||
void *progress_payload;
|
||||
|
||||
/** When not zeroed out, array of fnmatch patterns specifying which
|
||||
* paths should be taken into account, otherwise all files.
|
||||
*/
|
||||
git_strarray paths;
|
||||
|
||||
git_tree *baseline; /** expected content of workdir, defaults to HEAD */
|
||||
} git_checkout_opts;
|
||||
|
||||
#define GIT_CHECKOUT_OPTS_VERSION 1
|
||||
#define GIT_CHECKOUT_OPTS_INIT {GIT_CHECKOUT_OPTS_VERSION}
|
||||
|
||||
/**
|
||||
* Updates files in the index and the working tree to match the content of the
|
||||
* commit pointed at by HEAD.
|
||||
* Updates files in the index and the working tree to match the content of
|
||||
* the commit pointed at by HEAD.
|
||||
*
|
||||
* @param repo repository to check out (must be non-bare)
|
||||
* @param opts specifies checkout options (may be NULL)
|
||||
|
@ -312,6 +312,17 @@ GIT_EXTERN(const git_index_entry *) git_index_get_bypath(
|
||||
*/
|
||||
GIT_EXTERN(int) git_index_remove(git_index *index, const char *path, int stage);
|
||||
|
||||
/**
|
||||
* Remove all entries from the index under a given directory
|
||||
*
|
||||
* @param index an existing index object
|
||||
* @param dir container directory path
|
||||
* @param stage stage to search
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_index_remove_directory(
|
||||
git_index *index, const char *dir, int stage);
|
||||
|
||||
/**
|
||||
* Add or update an index entry from an in-memory struct
|
||||
*
|
||||
|
@ -504,6 +504,24 @@ GIT_EXTERN(int) git_submodule_status(
|
||||
unsigned int *status,
|
||||
git_submodule *submodule);
|
||||
|
||||
/**
|
||||
* Get the locations of submodule information.
|
||||
*
|
||||
* This is a bit like a very lightweight version of `git_submodule_status`.
|
||||
* It just returns a made of the first four submodule status values (i.e.
|
||||
* the ones like GIT_SUBMODULE_STATUS_IN_HEAD, etc) that tell you where the
|
||||
* submodule data comes from (i.e. the HEAD commit, gitmodules file, etc.).
|
||||
* This can be useful if you want to know if the submodule is present in the
|
||||
* working directory at this point in time, etc.
|
||||
*
|
||||
* @param status Combination of first four `GIT_SUBMODULE_STATUS` flags
|
||||
* @param submodule Submodule for which to get status
|
||||
* @return 0 on success, <0 on error
|
||||
*/
|
||||
GIT_EXTERN(int) git_submodule_location(
|
||||
unsigned int *location_status,
|
||||
git_submodule *submodule);
|
||||
|
||||
/** @} */
|
||||
GIT_END_DECL
|
||||
#endif
|
||||
|
@ -572,9 +572,11 @@ static int collect_attr_files(
|
||||
error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
|
||||
if (!error)
|
||||
error = push_attr_file(repo, files, NULL, dir.ptr);
|
||||
else if (error == GIT_ENOTFOUND)
|
||||
else if (error == GIT_ENOTFOUND) {
|
||||
giterr_clear();
|
||||
error = 0;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (error < 0)
|
||||
|
1502
src/checkout.c
1502
src/checkout.c
File diff suppressed because it is too large
Load Diff
24
src/checkout.h
Normal file
24
src/checkout.h
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2012 the libgit2 contributors
|
||||
*
|
||||
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
#ifndef INCLUDE_checkout_h__
|
||||
#define INCLUDE_checkout_h__
|
||||
|
||||
#include "git2/checkout.h"
|
||||
#include "iterator.h"
|
||||
|
||||
#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
|
||||
|
||||
/**
|
||||
* Update the working directory to match the target iterator. The
|
||||
* expected baseline value can be passed in via the checkout options
|
||||
* or else will default to the HEAD commit.
|
||||
*/
|
||||
extern int git_checkout_iterator(
|
||||
git_iterator *target,
|
||||
git_checkout_opts *opts);
|
||||
|
||||
#endif
|
@ -362,7 +362,7 @@ static bool should_checkout(
|
||||
if (!opts)
|
||||
return false;
|
||||
|
||||
if (opts->checkout_strategy == GIT_CHECKOUT_DEFAULT)
|
||||
if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
|
||||
return false;
|
||||
|
||||
return !git_repository_head_orphan(repo);
|
||||
|
46
src/diff.c
46
src/diff.c
@ -164,6 +164,11 @@ static git_diff_delta *diff_delta__last_for_item(
|
||||
if (git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
|
||||
return delta;
|
||||
break;
|
||||
case GIT_DELTA_UNTRACKED:
|
||||
if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
|
||||
git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
|
||||
return delta;
|
||||
break;
|
||||
case GIT_DELTA_MODIFIED:
|
||||
if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0 ||
|
||||
git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
|
||||
@ -531,14 +536,14 @@ static bool entry_is_prefixed(
|
||||
{
|
||||
size_t pathlen;
|
||||
|
||||
if (!prefix_item || diff->pfxcomp(prefix_item->path, item->path))
|
||||
if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0)
|
||||
return false;
|
||||
|
||||
pathlen = strlen(item->path);
|
||||
pathlen = strlen(prefix_item->path);
|
||||
|
||||
return (item->path[pathlen - 1] == '/' ||
|
||||
prefix_item->path[pathlen] == '\0' ||
|
||||
prefix_item->path[pathlen] == '/');
|
||||
return (prefix_item->path[pathlen - 1] == '/' ||
|
||||
item->path[pathlen] == '\0' ||
|
||||
item->path[pathlen] == '/');
|
||||
}
|
||||
|
||||
static int diff_list_init_from_iterators(
|
||||
@ -584,8 +589,7 @@ int git_diff__from_iterators(
|
||||
|
||||
*diff_ptr = NULL;
|
||||
|
||||
if (!diff ||
|
||||
diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
|
||||
if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
|
||||
goto fail;
|
||||
|
||||
if (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
|
||||
@ -616,7 +620,7 @@ int git_diff__from_iterators(
|
||||
* instead of just generating a DELETE record
|
||||
*/
|
||||
if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
|
||||
entry_is_prefixed(diff, oitem, nitem))
|
||||
entry_is_prefixed(diff, nitem, oitem))
|
||||
{
|
||||
/* this entry has become a tree! convert to TYPECHANGE */
|
||||
git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
|
||||
@ -624,6 +628,17 @@ int git_diff__from_iterators(
|
||||
last->status = GIT_DELTA_TYPECHANGE;
|
||||
last->new_file.mode = GIT_FILEMODE_TREE;
|
||||
}
|
||||
|
||||
/* If new_iter is a workdir iterator, then this situation
|
||||
* will certainly be followed by a series of untracked items.
|
||||
* Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
|
||||
*/
|
||||
if (S_ISDIR(nitem->mode) &&
|
||||
!(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS))
|
||||
{
|
||||
if (git_iterator_advance(new_iter, &nitem) < 0)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (git_iterator_advance(old_iter, &oitem) < 0)
|
||||
@ -635,6 +650,7 @@ int git_diff__from_iterators(
|
||||
*/
|
||||
else if (cmp > 0) {
|
||||
git_delta_t delta_type = GIT_DELTA_UNTRACKED;
|
||||
bool contains_oitem = entry_is_prefixed(diff, oitem, nitem);
|
||||
|
||||
/* check if contained in ignored parent directory */
|
||||
if (git_buf_len(&ignore_prefix) &&
|
||||
@ -646,14 +662,12 @@ int git_diff__from_iterators(
|
||||
* it or if the user requested the contents of untracked
|
||||
* directories and it is not under an ignored directory.
|
||||
*/
|
||||
bool contains_tracked =
|
||||
entry_is_prefixed(diff, nitem, oitem);
|
||||
bool recurse_untracked =
|
||||
(delta_type == GIT_DELTA_UNTRACKED &&
|
||||
(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
|
||||
|
||||
/* do not advance into directories that contain a .git file */
|
||||
if (!contains_tracked && recurse_untracked) {
|
||||
if (!contains_oitem && recurse_untracked) {
|
||||
git_buf *full = NULL;
|
||||
if (git_iterator_current_workdir_path(new_iter, &full) < 0)
|
||||
goto fail;
|
||||
@ -661,7 +675,7 @@ int git_diff__from_iterators(
|
||||
recurse_untracked = false;
|
||||
}
|
||||
|
||||
if (contains_tracked || recurse_untracked) {
|
||||
if (contains_oitem || recurse_untracked) {
|
||||
/* if this directory is ignored, remember it as the
|
||||
* "ignore_prefix" for processing contained items
|
||||
*/
|
||||
@ -707,14 +721,14 @@ int git_diff__from_iterators(
|
||||
goto fail;
|
||||
|
||||
/* if we are generating TYPECHANGE records then check for that
|
||||
* instead of just generating an ADD/UNTRACKED record
|
||||
* instead of just generating an ADDED/UNTRACKED record
|
||||
*/
|
||||
if (delta_type != GIT_DELTA_IGNORED &&
|
||||
(diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
|
||||
entry_is_prefixed(diff, nitem, oitem))
|
||||
contains_oitem)
|
||||
{
|
||||
/* this entry was a tree! convert to TYPECHANGE */
|
||||
git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
|
||||
/* this entry was prefixed with a tree - make TYPECHANGE */
|
||||
git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
|
||||
if (last) {
|
||||
last->status = GIT_DELTA_TYPECHANGE;
|
||||
last->old_file.mode = GIT_FILEMODE_TREE;
|
||||
|
@ -352,6 +352,7 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
|
||||
|
||||
typedef struct {
|
||||
const char *base;
|
||||
size_t baselen;
|
||||
uint32_t flags;
|
||||
int error;
|
||||
} futils__rmdir_data;
|
||||
@ -443,9 +444,13 @@ static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
|
||||
|
||||
static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
|
||||
{
|
||||
int error = p_rmdir(path->ptr);
|
||||
futils__rmdir_data *data = opaque;
|
||||
int error;
|
||||
|
||||
GIT_UNUSED(opaque);
|
||||
if (git_buf_len(path) <= data->baselen)
|
||||
return GIT_ITEROVER;
|
||||
|
||||
error = p_rmdir(git_buf_cstr(path));
|
||||
|
||||
if (error) {
|
||||
int en = errno;
|
||||
@ -457,7 +462,7 @@ static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
|
||||
giterr_clear();
|
||||
error = GIT_ITEROVER;
|
||||
} else {
|
||||
futils__error_cannot_rmdir(path->ptr, NULL);
|
||||
futils__error_cannot_rmdir(git_buf_cstr(path), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -476,6 +481,7 @@ int git_futils_rmdir_r(
|
||||
return -1;
|
||||
|
||||
data.base = base ? base : "";
|
||||
data.baselen = base ? strlen(base) : 0;
|
||||
data.flags = flags;
|
||||
data.error = 0;
|
||||
|
||||
|
41
src/index.c
41
src/index.c
@ -794,6 +794,44 @@ int git_index_remove(git_index *index, const char *path, int stage)
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_index_remove_directory(git_index *index, const char *dir, int stage)
|
||||
{
|
||||
git_buf pfx = GIT_BUF_INIT;
|
||||
int error = 0;
|
||||
size_t pos;
|
||||
git_index_entry *entry;
|
||||
|
||||
if (git_buf_sets(&pfx, dir) < 0 || git_path_to_dir(&pfx) < 0)
|
||||
return -1;
|
||||
|
||||
git_vector_sort(&index->entries);
|
||||
|
||||
pos = git_index__prefix_position(index, pfx.ptr);
|
||||
|
||||
while (1) {
|
||||
entry = git_vector_get(&index->entries, pos);
|
||||
if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0)
|
||||
break;
|
||||
|
||||
if (index_entry_stage(entry) != stage) {
|
||||
++pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
git_tree_cache_invalidate_path(index->tree, entry->path);
|
||||
|
||||
if ((error = git_vector_remove(&index->entries, pos)) < 0)
|
||||
break;
|
||||
index_entry_free(entry);
|
||||
|
||||
/* removed entry at 'pos' so we don't need to increment it */
|
||||
}
|
||||
|
||||
git_buf_free(&pfx);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int index_find(git_index *index, const char *path, int stage)
|
||||
{
|
||||
struct entry_srch_key srch_key;
|
||||
@ -814,7 +852,10 @@ int git_index_find(git_index *index, const char *path)
|
||||
|
||||
if ((pos = git_vector_bsearch2(
|
||||
&index->entries, index->entries_search_path, path)) < 0)
|
||||
{
|
||||
giterr_set(GITERR_INDEX, "Index does not contain %s", path);
|
||||
return pos;
|
||||
}
|
||||
|
||||
/* Since our binary search only looked at path, we may be in the
|
||||
* middle of a list of stages.
|
||||
|
@ -30,8 +30,8 @@
|
||||
(P)->base.start = start ? git__strdup(start) : NULL; \
|
||||
(P)->base.end = end ? git__strdup(end) : NULL; \
|
||||
(P)->base.ignore_case = false; \
|
||||
if ((start && !(P)->base.start) || (end && !(P)->base.end)) \
|
||||
return -1; \
|
||||
if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \
|
||||
git__free(P); return -1; } \
|
||||
} while (0)
|
||||
|
||||
static int iterator__reset_range(
|
||||
@ -988,6 +988,26 @@ fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
git_index *git_iterator_index_get_index(git_iterator *iter)
|
||||
{
|
||||
if (iter->type == GIT_ITERATOR_INDEX)
|
||||
return ((index_iterator *)iter)->index;
|
||||
|
||||
if (iter->type == GIT_ITERATOR_SPOOLANDSORT &&
|
||||
((spoolandsort_callbacks *)iter->cb)->orig_type == GIT_ITERATOR_INDEX)
|
||||
return ((index_iterator *)iter)->index;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
git_iterator_type_t git_iterator_inner_type(git_iterator *iter)
|
||||
{
|
||||
if (iter->type == GIT_ITERATOR_SPOOLANDSORT)
|
||||
return ((spoolandsort_callbacks *)iter->cb)->orig_type;
|
||||
|
||||
return iter->type;
|
||||
}
|
||||
|
||||
int git_iterator_current_tree_entry(
|
||||
git_iterator *iter, const git_tree_entry **tree_entry)
|
||||
{
|
||||
@ -1058,8 +1078,8 @@ int git_iterator_advance_into_directory(
|
||||
|
||||
if (iter->type == GIT_ITERATOR_WORKDIR &&
|
||||
wi->entry.path &&
|
||||
S_ISDIR(wi->entry.mode) &&
|
||||
!S_ISGITLINK(wi->entry.mode))
|
||||
(wi->entry.mode == GIT_FILEMODE_TREE ||
|
||||
wi->entry.mode == GIT_FILEMODE_COMMIT))
|
||||
{
|
||||
if (workdir_iterator__expand_dir(wi) < 0)
|
||||
/* if error loading or if empty, skip the directory. */
|
||||
|
@ -193,4 +193,9 @@ extern int git_iterator_cmp(
|
||||
extern int git_iterator_current_workdir_path(
|
||||
git_iterator *iter, git_buf **path);
|
||||
|
||||
|
||||
extern git_index *git_iterator_index_get_index(git_iterator *iter);
|
||||
|
||||
extern git_iterator_type_t git_iterator_inner_type(git_iterator *iter);
|
||||
|
||||
#endif
|
||||
|
@ -95,11 +95,14 @@ char *git_oid_tostr(char *out, size_t n, const git_oid *oid)
|
||||
{
|
||||
char str[GIT_OID_HEXSZ];
|
||||
|
||||
if (!out || n == 0 || !oid)
|
||||
if (!out || n == 0)
|
||||
return "";
|
||||
|
||||
n--; /* allow room for terminating NUL */
|
||||
|
||||
if (oid == NULL)
|
||||
n = 0;
|
||||
|
||||
if (n > 0) {
|
||||
git_oid_fmt(str, oid);
|
||||
if (n > GIT_OID_HEXSZ)
|
||||
|
51
src/reset.c
51
src/reset.c
@ -62,13 +62,10 @@ int git_reset(
|
||||
git_object *commit = NULL;
|
||||
git_index *index = NULL;
|
||||
git_tree *tree = NULL;
|
||||
int error;
|
||||
int error = 0;
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
assert(repo && target);
|
||||
assert(reset_type == GIT_RESET_SOFT
|
||||
|| reset_type == GIT_RESET_MIXED
|
||||
|| reset_type == GIT_RESET_HARD);
|
||||
|
||||
if (git_object_owner(target) != repo) {
|
||||
giterr_set(GITERR_OBJECT,
|
||||
@ -76,57 +73,51 @@ int git_reset(
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (reset_type != GIT_RESET_SOFT
|
||||
&& (error = git_repository__ensure_not_bare(
|
||||
repo,
|
||||
if (reset_type != GIT_RESET_SOFT &&
|
||||
(error = git_repository__ensure_not_bare(repo,
|
||||
reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_repository_index(&index, repo)) < 0)
|
||||
if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||
|
||||
(error = git_repository_index(&index, repo)) < 0 ||
|
||||
(error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (reset_type == GIT_RESET_SOFT &&
|
||||
(git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE ||
|
||||
git_index_has_conflicts(index))) {
|
||||
giterr_set(GITERR_OBJECT, "%s (soft) while in the middle of a merge.", ERROR_MSG);
|
||||
git_index_has_conflicts(index)))
|
||||
{
|
||||
giterr_set(GITERR_OBJECT, "%s (soft) in the middle of a merge.", ERROR_MSG);
|
||||
error = GIT_EUNMERGED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* move HEAD to the new target */
|
||||
if ((error = update_head(repo, commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (reset_type == GIT_RESET_SOFT) {
|
||||
error = 0;
|
||||
if (reset_type == GIT_RESET_HARD) {
|
||||
/* overwrite working directory with HEAD */
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
|
||||
goto cleanup;
|
||||
if (reset_type > GIT_RESET_SOFT) {
|
||||
/* reset index to the target content */
|
||||
|
||||
if ((error = git_index_read_tree(index, tree)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_index_write(index)) < 0)
|
||||
if ((error = git_repository_index(&index, repo)) < 0 ||
|
||||
(error = git_index_read_tree(index, tree)) < 0 ||
|
||||
(error = git_index_write(index)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_repository_merge_cleanup(repo)) < 0) {
|
||||
giterr_set(GITERR_REPOSITORY, "%s - Failed to clean up merge data.", ERROR_MSG);
|
||||
giterr_set(GITERR_INDEX, "%s - failed to clean up merge data", ERROR_MSG);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (reset_type == GIT_RESET_MIXED) {
|
||||
error = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
error = git_checkout_index(repo, NULL, &opts);
|
||||
|
||||
cleanup:
|
||||
git_object_free(commit);
|
||||
git_index_free(index);
|
||||
|
211
src/stash.c
211
src/stash.c
@ -22,15 +22,6 @@ static int create_error(int error, const char *msg)
|
||||
return error;
|
||||
}
|
||||
|
||||
static int ensure_non_bare_repository(git_repository *repo)
|
||||
{
|
||||
if (!git_repository_is_bare(repo))
|
||||
return 0;
|
||||
|
||||
return create_error(GIT_EBAREREPO,
|
||||
"Stash related operations require a working directory.");
|
||||
}
|
||||
|
||||
static int retrieve_head(git_reference **out, git_repository *repo)
|
||||
{
|
||||
int error = git_repository_head(out, repo);
|
||||
@ -89,23 +80,22 @@ static int retrieve_base_commit_and_message(
|
||||
if ((error = retrieve_head(&head, repo)) < 0)
|
||||
return error;
|
||||
|
||||
error = -1;
|
||||
|
||||
if (strcmp("HEAD", git_reference_name(head)) == 0)
|
||||
git_buf_puts(stash_message, "(no branch): ");
|
||||
error = git_buf_puts(stash_message, "(no branch): ");
|
||||
else
|
||||
git_buf_printf(
|
||||
error = git_buf_printf(
|
||||
stash_message,
|
||||
"%s: ",
|
||||
git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR));
|
||||
|
||||
if (git_commit_lookup(b_commit, repo, git_reference_target(head)) < 0)
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (append_commit_description(stash_message, *b_commit) < 0)
|
||||
if ((error = git_commit_lookup(
|
||||
b_commit, repo, git_reference_target(head))) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = 0;
|
||||
if ((error = append_commit_description(stash_message, *b_commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
git_reference_free(head);
|
||||
@ -114,9 +104,10 @@ cleanup:
|
||||
|
||||
static int build_tree_from_index(git_tree **out, git_index *index)
|
||||
{
|
||||
int error;
|
||||
git_oid i_tree_oid;
|
||||
|
||||
if (git_index_write_tree(&i_tree_oid, index) < 0)
|
||||
if ((error = git_index_write_tree(&i_tree_oid, index)) < 0)
|
||||
return -1;
|
||||
|
||||
return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);
|
||||
@ -132,15 +123,15 @@ static int commit_index(
|
||||
git_tree *i_tree = NULL;
|
||||
git_oid i_commit_oid;
|
||||
git_buf msg = GIT_BUF_INIT;
|
||||
int error = -1;
|
||||
int error;
|
||||
|
||||
if (build_tree_from_index(&i_tree, index) < 0)
|
||||
if ((error = build_tree_from_index(&i_tree, index)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_buf_printf(&msg, "index on %s\n", message) < 0)
|
||||
if ((error = git_buf_printf(&msg, "index on %s\n", message)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_commit_create(
|
||||
if ((error = git_commit_create(
|
||||
&i_commit_oid,
|
||||
git_index_owner(index),
|
||||
NULL,
|
||||
@ -150,7 +141,7 @@ static int commit_index(
|
||||
git_buf_cstr(&msg),
|
||||
i_tree,
|
||||
1,
|
||||
&parent) < 0)
|
||||
&parent)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid);
|
||||
@ -164,6 +155,8 @@ cleanup:
|
||||
struct cb_data {
|
||||
git_index *index;
|
||||
|
||||
int error;
|
||||
|
||||
bool include_changed;
|
||||
bool include_untracked;
|
||||
bool include_ignored;
|
||||
@ -174,52 +167,50 @@ static int update_index_cb(
|
||||
float progress,
|
||||
void *payload)
|
||||
{
|
||||
int pos;
|
||||
struct cb_data *data = (struct cb_data *)payload;
|
||||
const char *add_path = NULL;
|
||||
|
||||
GIT_UNUSED(progress);
|
||||
|
||||
switch (delta->status) {
|
||||
case GIT_DELTA_IGNORED:
|
||||
if (!data->include_ignored)
|
||||
if (data->include_ignored)
|
||||
add_path = delta->new_file.path;
|
||||
break;
|
||||
|
||||
return git_index_add_from_workdir(data->index, delta->new_file.path);
|
||||
|
||||
case GIT_DELTA_UNTRACKED:
|
||||
if (!data->include_untracked)
|
||||
if (data->include_untracked)
|
||||
add_path = delta->new_file.path;
|
||||
break;
|
||||
|
||||
return git_index_add_from_workdir(data->index, delta->new_file.path);
|
||||
|
||||
case GIT_DELTA_ADDED:
|
||||
/* Fall through */
|
||||
case GIT_DELTA_MODIFIED:
|
||||
if (!data->include_changed)
|
||||
if (data->include_changed)
|
||||
add_path = delta->new_file.path;
|
||||
break;
|
||||
|
||||
return git_index_add_from_workdir(data->index, delta->new_file.path);
|
||||
|
||||
case GIT_DELTA_DELETED:
|
||||
if (!data->include_changed)
|
||||
break;
|
||||
|
||||
if ((pos = git_index_find(data->index, delta->new_file.path)) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_index_remove(data->index, delta->new_file.path, 0) < 0)
|
||||
return -1;
|
||||
if (git_index_find(data->index, delta->old_file.path) == 0)
|
||||
data->error = git_index_remove(
|
||||
data->index, delta->old_file.path, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Unimplemented */
|
||||
giterr_set(
|
||||
GITERR_INVALID,
|
||||
"Cannot update index. Unimplemented status kind (%d)",
|
||||
"Cannot update index. Unimplemented status (%d)",
|
||||
delta->status);
|
||||
return -1;
|
||||
data->error = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (add_path != NULL)
|
||||
data->error = git_index_add_from_workdir(data->index, add_path);
|
||||
|
||||
return data->error;
|
||||
}
|
||||
|
||||
static int build_untracked_tree(
|
||||
@ -232,14 +223,15 @@ static int build_untracked_tree(
|
||||
git_diff_list *diff = NULL;
|
||||
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
|
||||
struct cb_data data = {0};
|
||||
int error = -1;
|
||||
int error;
|
||||
|
||||
git_index_clear(index);
|
||||
|
||||
data.index = index;
|
||||
|
||||
if (flags & GIT_STASH_INCLUDE_UNTRACKED) {
|
||||
opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
|
||||
opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED |
|
||||
GIT_DIFF_RECURSE_UNTRACKED_DIRS;
|
||||
data.include_untracked = true;
|
||||
}
|
||||
|
||||
@ -248,19 +240,22 @@ static int build_untracked_tree(
|
||||
data.include_ignored = true;
|
||||
}
|
||||
|
||||
if (git_commit_tree(&i_tree, i_commit) < 0)
|
||||
if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_diff_tree_to_workdir(&diff, git_index_owner(index), i_tree, &opts) < 0)
|
||||
if ((error = git_diff_tree_to_workdir(
|
||||
&diff, git_index_owner(index), i_tree, &opts)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_diff_foreach(diff, update_index_cb, NULL, NULL, &data) < 0)
|
||||
if ((error = git_diff_foreach(
|
||||
diff, update_index_cb, NULL, NULL, &data)) < 0)
|
||||
{
|
||||
if (error == GIT_EUSER)
|
||||
error = data.error;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (build_tree_from_index(tree_out, index) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = 0;
|
||||
error = build_tree_from_index(tree_out, index);
|
||||
|
||||
cleanup:
|
||||
git_diff_list_free(diff);
|
||||
@ -279,15 +274,15 @@ static int commit_untracked(
|
||||
git_tree *u_tree = NULL;
|
||||
git_oid u_commit_oid;
|
||||
git_buf msg = GIT_BUF_INIT;
|
||||
int error = -1;
|
||||
int error;
|
||||
|
||||
if (build_untracked_tree(&u_tree, index, i_commit, flags) < 0)
|
||||
if ((error = build_untracked_tree(&u_tree, index, i_commit, flags)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_buf_printf(&msg, "untracked files on %s\n", message) < 0)
|
||||
if ((error = git_buf_printf(&msg, "untracked files on %s\n", message)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_commit_create(
|
||||
if ((error = git_commit_create(
|
||||
&u_commit_oid,
|
||||
git_index_owner(index),
|
||||
NULL,
|
||||
@ -297,7 +292,7 @@ static int commit_untracked(
|
||||
git_buf_cstr(&msg),
|
||||
u_tree,
|
||||
0,
|
||||
NULL) < 0)
|
||||
NULL)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid);
|
||||
@ -318,35 +313,40 @@ static int build_workdir_tree(
|
||||
git_diff_list *diff = NULL, *diff2 = NULL;
|
||||
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
|
||||
struct cb_data data = {0};
|
||||
int error = -1;
|
||||
int error;
|
||||
|
||||
if (git_commit_tree(&b_tree, b_commit) < 0)
|
||||
if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_diff_tree_to_index(&diff, repo, b_tree, NULL, &opts) < 0)
|
||||
if ((error = git_diff_tree_to_index(&diff, repo, b_tree, NULL, &opts)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_diff_index_to_workdir(&diff2, repo, NULL, &opts) < 0)
|
||||
if ((error = git_diff_index_to_workdir(&diff2, repo, NULL, &opts)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_diff_merge(diff, diff2) < 0)
|
||||
if ((error = git_diff_merge(diff, diff2)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
data.index = index;
|
||||
data.include_changed = true;
|
||||
|
||||
if (git_diff_foreach(diff, update_index_cb, NULL, NULL, &data) < 0)
|
||||
if ((error = git_diff_foreach(
|
||||
diff, update_index_cb, NULL, NULL, &data)) < 0)
|
||||
{
|
||||
if (error == GIT_EUSER)
|
||||
error = data.error;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (build_tree_from_index(tree_out, index) < 0)
|
||||
|
||||
if ((error = build_tree_from_index(tree_out, index)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = 0;
|
||||
|
||||
cleanup:
|
||||
git_diff_list_free(diff);
|
||||
git_diff_list_free(diff2);
|
||||
git_tree_free(b_tree);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -359,25 +359,24 @@ static int commit_worktree(
|
||||
git_commit *b_commit,
|
||||
git_commit *u_commit)
|
||||
{
|
||||
int error = 0;
|
||||
git_tree *w_tree = NULL, *i_tree = NULL;
|
||||
int error = -1;
|
||||
|
||||
const git_commit *parents[] = { NULL, NULL, NULL };
|
||||
|
||||
parents[0] = b_commit;
|
||||
parents[1] = i_commit;
|
||||
parents[2] = u_commit;
|
||||
|
||||
if (git_commit_tree(&i_tree, i_commit) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_index_read_tree(index, i_tree) < 0)
|
||||
if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (build_workdir_tree(&w_tree, index, b_commit) < 0)
|
||||
if ((error = git_index_read_tree(index, i_tree)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_commit_create(
|
||||
if ((error = build_workdir_tree(&w_tree, index, b_commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = git_commit_create(
|
||||
w_commit_oid,
|
||||
git_index_owner(index),
|
||||
NULL,
|
||||
@ -386,10 +385,8 @@ static int commit_worktree(
|
||||
NULL,
|
||||
message,
|
||||
w_tree,
|
||||
u_commit ? 3 : 2, parents) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = 0;
|
||||
u_commit ? 3 : 2,
|
||||
parents);
|
||||
|
||||
cleanup:
|
||||
git_tree_free(i_tree);
|
||||
@ -402,9 +399,11 @@ static int prepare_worktree_commit_message(
|
||||
const char *user_message)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
int error = -1;
|
||||
int error;
|
||||
|
||||
if ((error = git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg))) < 0)
|
||||
return error;
|
||||
|
||||
git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg));
|
||||
git_buf_clear(msg);
|
||||
|
||||
if (!user_message)
|
||||
@ -420,10 +419,11 @@ static int prepare_worktree_commit_message(
|
||||
git_buf_printf(msg, ": %s\n", user_message);
|
||||
}
|
||||
|
||||
error = git_buf_oom(msg) || git_buf_oom(&buf) ? -1 : 0;
|
||||
error = (git_buf_oom(msg) || git_buf_oom(&buf)) ? -1 : 0;
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&buf);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -449,8 +449,6 @@ static int update_reflog(
|
||||
if ((error = git_reflog_write(reflog)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = 0;
|
||||
|
||||
cleanup:
|
||||
git_reference_free(stash);
|
||||
git_reflog_free(reflog);
|
||||
@ -500,8 +498,7 @@ static int reset_index_and_workdir(
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
opts.checkout_strategy =
|
||||
GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED;
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
if (remove_untracked)
|
||||
opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
@ -523,7 +520,7 @@ int git_stash_save(
|
||||
|
||||
assert(out && repo && stasher);
|
||||
|
||||
if ((error = ensure_non_bare_repository(repo)) < 0)
|
||||
if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0)
|
||||
@ -531,47 +528,50 @@ int git_stash_save(
|
||||
|
||||
if ((error = ensure_there_are_changes_to_stash(
|
||||
repo,
|
||||
(flags & GIT_STASH_INCLUDE_UNTRACKED) == GIT_STASH_INCLUDE_UNTRACKED,
|
||||
(flags & GIT_STASH_INCLUDE_IGNORED) == GIT_STASH_INCLUDE_IGNORED)) < 0)
|
||||
(flags & GIT_STASH_INCLUDE_UNTRACKED) != 0,
|
||||
(flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = -1;
|
||||
|
||||
if (git_repository_index(&index, repo) < 0)
|
||||
if ((error = git_repository_index(&index, repo)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (commit_index(&i_commit, index, stasher, git_buf_cstr(&msg), b_commit) < 0)
|
||||
if ((error = commit_index(
|
||||
&i_commit, index, stasher, git_buf_cstr(&msg), b_commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((flags & GIT_STASH_INCLUDE_UNTRACKED || flags & GIT_STASH_INCLUDE_IGNORED)
|
||||
&& commit_untracked(&u_commit, index, stasher, git_buf_cstr(&msg), i_commit, flags) < 0)
|
||||
if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) &&
|
||||
(error = commit_untracked(
|
||||
&u_commit, index, stasher, git_buf_cstr(&msg),
|
||||
i_commit, flags)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (prepare_worktree_commit_message(&msg, message) < 0)
|
||||
if ((error = prepare_worktree_commit_message(&msg, message)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (commit_worktree(out, index, stasher, git_buf_cstr(&msg), i_commit, b_commit, u_commit) < 0)
|
||||
if ((error = commit_worktree(
|
||||
out, index, stasher, git_buf_cstr(&msg),
|
||||
i_commit, b_commit, u_commit)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
git_buf_rtrim(&msg);
|
||||
if (update_reflog(out, repo, stasher, git_buf_cstr(&msg)) < 0)
|
||||
|
||||
if ((error = update_reflog(out, repo, stasher, git_buf_cstr(&msg))) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (reset_index_and_workdir(
|
||||
if ((error = reset_index_and_workdir(
|
||||
repo,
|
||||
((flags & GIT_STASH_KEEP_INDEX) == GIT_STASH_KEEP_INDEX) ?
|
||||
i_commit : b_commit,
|
||||
(flags & GIT_STASH_INCLUDE_UNTRACKED) == GIT_STASH_INCLUDE_UNTRACKED) < 0)
|
||||
((flags & GIT_STASH_KEEP_INDEX) != 0) ? i_commit : b_commit,
|
||||
(flags & GIT_STASH_INCLUDE_UNTRACKED) != 0)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = 0;
|
||||
|
||||
cleanup:
|
||||
|
||||
git_buf_free(&msg);
|
||||
git_commit_free(i_commit);
|
||||
git_commit_free(b_commit);
|
||||
git_commit_free(u_commit);
|
||||
git_index_free(index);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -589,7 +589,6 @@ int git_stash_foreach(
|
||||
error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE);
|
||||
if (error == GIT_ENOTFOUND)
|
||||
return 0;
|
||||
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
@ -605,12 +604,10 @@ int git_stash_foreach(
|
||||
git_reflog_entry_id_new(entry),
|
||||
payload)) {
|
||||
error = GIT_EUSER;
|
||||
goto cleanup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
error = 0;
|
||||
|
||||
cleanup:
|
||||
git_reference_free(stash);
|
||||
git_reflog_free(reflog);
|
||||
|
@ -66,7 +66,7 @@ __KHASH_IMPL(
|
||||
str, static kh_inline, const char *, void *, 1,
|
||||
str_hash_no_trailing_slash, str_equal_no_trailing_slash);
|
||||
|
||||
static int load_submodule_config(git_repository *repo, bool force);
|
||||
static int load_submodule_config(git_repository *repo);
|
||||
static git_config_backend *open_gitmodules(git_repository *, bool, const git_oid *);
|
||||
static int lookup_head_remote(git_buf *url, git_repository *repo);
|
||||
static int submodule_get(git_submodule **, git_repository *, const char *, const char *);
|
||||
@ -106,7 +106,7 @@ int git_submodule_lookup(
|
||||
|
||||
assert(repo && name);
|
||||
|
||||
if ((error = load_submodule_config(repo, false)) < 0)
|
||||
if ((error = load_submodule_config(repo)) < 0)
|
||||
return error;
|
||||
|
||||
pos = git_strmap_lookup_index(repo->submodules, name);
|
||||
@ -148,7 +148,7 @@ int git_submodule_foreach(
|
||||
|
||||
assert(repo && callback);
|
||||
|
||||
if ((error = load_submodule_config(repo, false)) < 0)
|
||||
if ((error = load_submodule_config(repo)) < 0)
|
||||
return error;
|
||||
|
||||
git_strmap_foreach_value(repo->submodules, sm, {
|
||||
@ -708,7 +708,8 @@ int git_submodule_open(
|
||||
int git_submodule_reload_all(git_repository *repo)
|
||||
{
|
||||
assert(repo);
|
||||
return load_submodule_config(repo, true);
|
||||
git_submodule_config_free(repo);
|
||||
return load_submodule_config(repo);
|
||||
}
|
||||
|
||||
int git_submodule_reload(git_submodule *submodule)
|
||||
@ -829,6 +830,20 @@ int git_submodule_status(
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_submodule_location(
|
||||
unsigned int *location_status,
|
||||
git_submodule *submodule)
|
||||
{
|
||||
assert(location_status && submodule);
|
||||
|
||||
*location_status = submodule->flags &
|
||||
(GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX |
|
||||
GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* INTERNAL FUNCTIONS
|
||||
*/
|
||||
@ -1225,14 +1240,14 @@ static git_config_backend *open_gitmodules(
|
||||
return mods;
|
||||
}
|
||||
|
||||
static int load_submodule_config(git_repository *repo, bool force)
|
||||
static int load_submodule_config(git_repository *repo)
|
||||
{
|
||||
int error;
|
||||
git_oid gitmodules_oid;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_config_backend *mods = NULL;
|
||||
|
||||
if (repo->submodules && !force)
|
||||
if (repo->submodules)
|
||||
return 0;
|
||||
|
||||
memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "refs.h"
|
||||
#include "repo/repo_helpers.h"
|
||||
#include "path.h"
|
||||
#include "fileops.h"
|
||||
|
||||
static git_repository *g_repo;
|
||||
|
||||
@ -14,9 +16,48 @@ void test_checkout_head__cleanup(void)
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
void test_checkout_head__checking_out_an_orphaned_head_returns_GIT_EORPHANEDHEAD(void)
|
||||
void test_checkout_head__orphaned_head_returns_GIT_EORPHANEDHEAD(void)
|
||||
{
|
||||
make_head_orphaned(g_repo, NON_EXISTING_HEAD);
|
||||
|
||||
cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL));
|
||||
}
|
||||
|
||||
void test_checkout_head__with_index_only_tree(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
git_index *index;
|
||||
|
||||
/* let's start by getting things into a known state */
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts));
|
||||
|
||||
/* now let's stage some new stuff including a new directory */
|
||||
|
||||
cl_git_pass(git_repository_index(&index, g_repo));
|
||||
|
||||
p_mkdir("testrepo/newdir", 0777);
|
||||
cl_git_mkfile("testrepo/newdir/newfile.txt", "new file\n");
|
||||
|
||||
cl_git_pass(git_index_add_from_workdir(index, "newdir/newfile.txt"));
|
||||
cl_git_pass(git_index_write(index));
|
||||
|
||||
cl_assert(git_path_isfile("testrepo/newdir/newfile.txt"));
|
||||
cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) != NULL);
|
||||
|
||||
git_index_free(index);
|
||||
|
||||
/* okay, so now we have staged this new file; let's see if we can remove */
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts));
|
||||
|
||||
cl_git_pass(git_repository_index(&index, g_repo));
|
||||
cl_git_pass(git_index_read(index)); /* reload if needed */
|
||||
|
||||
cl_assert(!git_path_isfile("testrepo/newdir/newfile.txt"));
|
||||
cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) == NULL);
|
||||
|
||||
git_index_free(index);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include "repository.h"
|
||||
|
||||
static git_repository *g_repo;
|
||||
static git_checkout_opts g_opts;
|
||||
|
||||
static void reset_index_to_treeish(git_object *treeish)
|
||||
{
|
||||
@ -25,9 +24,6 @@ void test_checkout_index__initialize(void)
|
||||
{
|
||||
git_tree *tree;
|
||||
|
||||
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
|
||||
g_repo = cl_git_sandbox_init("testrepo");
|
||||
|
||||
cl_git_pass(git_repository_head_tree(&tree, g_repo));
|
||||
@ -66,7 +62,6 @@ void test_checkout_index__cannot_checkout_a_bare_repository(void)
|
||||
{
|
||||
test_checkout_index__cleanup();
|
||||
|
||||
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
|
||||
g_repo = cl_git_sandbox_init("testrepo.git");
|
||||
|
||||
cl_git_fail(git_checkout_index(g_repo, NULL, NULL));
|
||||
@ -74,11 +69,15 @@ void test_checkout_index__cannot_checkout_a_bare_repository(void)
|
||||
|
||||
void test_checkout_index__can_create_missing_files(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/README", "hey there\n");
|
||||
test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
|
||||
@ -87,30 +86,37 @@ void test_checkout_index__can_create_missing_files(void)
|
||||
|
||||
void test_checkout_index__can_remove_untracked_files(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
git_futils_mkdir("./testrepo/dir/subdir/subsubdir", NULL, 0755, GIT_MKDIR_PATH);
|
||||
cl_git_mkfile("./testrepo/dir/one", "one\n");
|
||||
cl_git_mkfile("./testrepo/dir/subdir/two", "two\n");
|
||||
|
||||
cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir"));
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy =
|
||||
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
cl_assert_equal_i(false, git_path_isdir("./testrepo/dir"));
|
||||
}
|
||||
|
||||
void test_checkout_index__honor_the_specified_pathspecs(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
char *entries[] = { "*.txt" };
|
||||
|
||||
g_opts.paths.strings = entries;
|
||||
g_opts.paths.count = 1;
|
||||
opts.paths.strings = entries;
|
||||
opts.paths.count = 1;
|
||||
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
|
||||
test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
|
||||
@ -134,6 +140,7 @@ static void set_core_autocrlf_to(bool value)
|
||||
|
||||
void test_checkout_index__honor_the_gitattributes_directives(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
const char *attributes =
|
||||
"branch_file.txt text eol=crlf\n"
|
||||
"new.txt text eol=lf\n";
|
||||
@ -141,7 +148,9 @@ void test_checkout_index__honor_the_gitattributes_directives(void)
|
||||
cl_git_mkfile("./testrepo/.gitattributes", attributes);
|
||||
set_core_autocrlf_to(false);
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/README", "hey there\n");
|
||||
test_file_contents("./testrepo/new.txt", "my new file\n");
|
||||
@ -151,12 +160,15 @@ void test_checkout_index__honor_the_gitattributes_directives(void)
|
||||
void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
const char *expected_readme_text = "hey there\r\n";
|
||||
|
||||
cl_git_pass(p_unlink("./testrepo/.gitattributes"));
|
||||
set_core_autocrlf_to(true);
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/README", expected_readme_text);
|
||||
#endif
|
||||
@ -169,9 +181,13 @@ static void set_repo_symlink_handling_cap_to(bool value)
|
||||
|
||||
void test_checkout_index__honor_coresymlinks_setting_set_to_true(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
set_repo_symlink_handling_cap_to(true);
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
|
||||
@ -191,51 +207,63 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void)
|
||||
|
||||
void test_checkout_index__honor_coresymlinks_setting_set_to_false(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
set_repo_symlink_handling_cap_to(false);
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
|
||||
}
|
||||
|
||||
void test_checkout_index__donot_overwrite_modified_file_by_default(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
|
||||
|
||||
/* set this up to not return an error code on conflicts, but it
|
||||
* still will not have permission to overwrite anything...
|
||||
*/
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/new.txt", "This isn't what's stored!");
|
||||
}
|
||||
|
||||
void test_checkout_index__can_overwrite_modified_file(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED;
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/new.txt", "my new file\n");
|
||||
}
|
||||
|
||||
void test_checkout_index__options_disable_filters(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n");
|
||||
|
||||
g_opts.disable_filters = false;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
opts.disable_filters = false;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/new.txt", "my new file\r\n");
|
||||
|
||||
p_unlink("./testrepo/new.txt");
|
||||
|
||||
g_opts.disable_filters = true;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.disable_filters = true;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/new.txt", "my new file\n");
|
||||
}
|
||||
@ -243,6 +271,7 @@ void test_checkout_index__options_disable_filters(void)
|
||||
void test_checkout_index__options_dir_modes(void)
|
||||
{
|
||||
#ifndef GIT_WIN32
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
struct stat st;
|
||||
git_oid oid;
|
||||
git_commit *commit;
|
||||
@ -252,8 +281,10 @@ void test_checkout_index__options_dir_modes(void)
|
||||
|
||||
reset_index_to_treeish((git_object *)commit);
|
||||
|
||||
g_opts.dir_mode = 0701;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
opts.dir_mode = 0701;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
cl_git_pass(p_stat("./testrepo/a", &st));
|
||||
cl_assert_equal_i(st.st_mode & 0777, 0701);
|
||||
@ -269,11 +300,13 @@ void test_checkout_index__options_dir_modes(void)
|
||||
void test_checkout_index__options_override_file_modes(void)
|
||||
{
|
||||
#ifndef GIT_WIN32
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
struct stat st;
|
||||
|
||||
g_opts.file_mode = 0700;
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
opts.file_mode = 0700;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
cl_git_pass(p_stat("./testrepo/new.txt", &st));
|
||||
cl_assert_equal_i(st.st_mode & 0777, 0700);
|
||||
@ -282,42 +315,48 @@ void test_checkout_index__options_override_file_modes(void)
|
||||
|
||||
void test_checkout_index__options_open_flags(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
cl_git_mkfile("./testrepo/new.txt", "hi\n");
|
||||
|
||||
g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND;
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND;
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
|
||||
}
|
||||
|
||||
struct conflict_data {
|
||||
struct notify_data {
|
||||
const char *file;
|
||||
const char *sha;
|
||||
};
|
||||
|
||||
static int conflict_cb(
|
||||
const char *conflict_file,
|
||||
const git_oid *blob_oid,
|
||||
unsigned int index_mode,
|
||||
unsigned int wd_mode,
|
||||
static int test_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)
|
||||
{
|
||||
struct conflict_data *expectations = (struct conflict_data *)payload;
|
||||
struct notify_data *expectations = (struct notify_data *)payload;
|
||||
|
||||
GIT_UNUSED(index_mode);
|
||||
GIT_UNUSED(wd_mode);
|
||||
GIT_UNUSED(workdir);
|
||||
|
||||
cl_assert_equal_s(expectations->file, conflict_file);
|
||||
cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha));
|
||||
cl_assert_equal_i(GIT_CHECKOUT_NOTIFY_CONFLICT, why);
|
||||
cl_assert_equal_s(expectations->file, path);
|
||||
cl_assert_equal_i(0, git_oid_streq(&baseline->oid, expectations->sha));
|
||||
cl_assert_equal_i(0, git_oid_streq(&target->oid, expectations->sha));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_checkout_index__can_notify_of_skipped_files(void)
|
||||
{
|
||||
struct conflict_data data;
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
struct notify_data data;
|
||||
|
||||
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
|
||||
|
||||
@ -330,24 +369,28 @@ void test_checkout_index__can_notify_of_skipped_files(void)
|
||||
data.file = "new.txt";
|
||||
data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
g_opts.conflict_cb = conflict_cb;
|
||||
g_opts.conflict_payload = &data;
|
||||
opts.checkout_strategy =
|
||||
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT;
|
||||
opts.notify_cb = test_checkout_notify_cb;
|
||||
opts.notify_payload = &data;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
}
|
||||
|
||||
static int dont_conflict_cb(
|
||||
const char *conflict_file,
|
||||
const git_oid *blob_oid,
|
||||
unsigned int index_mode,
|
||||
unsigned int wd_mode,
|
||||
static int dont_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)
|
||||
{
|
||||
GIT_UNUSED(conflict_file);
|
||||
GIT_UNUSED(blob_oid);
|
||||
GIT_UNUSED(index_mode);
|
||||
GIT_UNUSED(wd_mode);
|
||||
GIT_UNUSED(why);
|
||||
GIT_UNUSED(path);
|
||||
GIT_UNUSED(baseline);
|
||||
GIT_UNUSED(target);
|
||||
GIT_UNUSED(workdir);
|
||||
GIT_UNUSED(payload);
|
||||
|
||||
cl_assert(false);
|
||||
@ -357,37 +400,45 @@ static int dont_conflict_cb(
|
||||
|
||||
void test_checkout_index__wont_notify_of_expected_line_ending_changes(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
cl_git_pass(p_unlink("./testrepo/.gitattributes"));
|
||||
set_core_autocrlf_to(true);
|
||||
|
||||
cl_git_mkfile("./testrepo/new.txt", "my new file\r\n");
|
||||
|
||||
g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
g_opts.conflict_cb = dont_conflict_cb;
|
||||
g_opts.conflict_payload = NULL;
|
||||
opts.checkout_strategy =
|
||||
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT;
|
||||
opts.notify_cb = dont_notify_cb;
|
||||
opts.notify_payload = NULL;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
}
|
||||
|
||||
static void progress(const char *path, size_t cur, size_t tot, void *payload)
|
||||
static void checkout_progress_counter(
|
||||
const char *path, size_t cur, size_t tot, void *payload)
|
||||
{
|
||||
bool *was_called = (bool*)payload;
|
||||
GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
|
||||
*was_called = true;
|
||||
(*(int *)payload)++;
|
||||
}
|
||||
|
||||
void test_checkout_index__calls_progress_callback(void)
|
||||
{
|
||||
bool was_called = 0;
|
||||
g_opts.progress_cb = progress;
|
||||
g_opts.progress_payload = &was_called;
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
int calls = 0;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
cl_assert_equal_i(was_called, true);
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
opts.progress_cb = checkout_progress_counter;
|
||||
opts.progress_payload = &calls;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
cl_assert(calls > 0);
|
||||
}
|
||||
|
||||
void test_checkout_index__can_overcome_name_clashes(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
git_index *index;
|
||||
|
||||
cl_git_pass(git_repository_index(&index, g_repo));
|
||||
@ -400,7 +451,6 @@ void test_checkout_index__can_overcome_name_clashes(void)
|
||||
cl_git_pass(git_index_add_from_workdir(index, "path0"));
|
||||
cl_git_pass(git_index_add_from_workdir(index, "path1/file1"));
|
||||
|
||||
|
||||
cl_git_pass(p_unlink("./testrepo/path0"));
|
||||
cl_git_pass(git_futils_rmdir_r(
|
||||
"./testrepo/path1", NULL, GIT_RMDIR_REMOVE_FILES));
|
||||
@ -412,14 +462,15 @@ void test_checkout_index__can_overcome_name_clashes(void)
|
||||
cl_assert(git_path_isfile("./testrepo/path1"));
|
||||
cl_assert(git_path_isfile("./testrepo/path0/file0"));
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy =
|
||||
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
cl_git_pass(git_checkout_index(g_repo, index, &opts));
|
||||
|
||||
cl_assert(git_path_isfile("./testrepo/path1"));
|
||||
cl_assert(git_path_isfile("./testrepo/path0/file0"));
|
||||
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
cl_git_pass(git_checkout_index(g_repo, index, &opts));
|
||||
|
||||
cl_assert(git_path_isfile("./testrepo/path0"));
|
||||
cl_assert(git_path_isfile("./testrepo/path1/file1"));
|
||||
@ -429,18 +480,44 @@ void test_checkout_index__can_overcome_name_clashes(void)
|
||||
|
||||
void test_checkout_index__validates_struct_version(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
const git_error *err;
|
||||
|
||||
g_opts.version = 1024;
|
||||
cl_git_fail(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
opts.version = 1024;
|
||||
cl_git_fail(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
err = giterr_last();
|
||||
cl_assert_equal_i(err->klass, GITERR_INVALID);
|
||||
|
||||
g_opts.version = 0;
|
||||
opts.version = 0;
|
||||
giterr_clear();
|
||||
cl_git_fail(git_checkout_index(g_repo, NULL, &g_opts));
|
||||
cl_git_fail(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
err = giterr_last();
|
||||
cl_assert_equal_i(err->klass, GITERR_INVALID);
|
||||
}
|
||||
|
||||
void test_checkout_index__can_update_prefixed_files(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
cl_git_mkfile("./testrepo/READ", "content\n");
|
||||
cl_git_mkfile("./testrepo/README.after", "content\n");
|
||||
cl_git_pass(p_mkdir("./testrepo/branch_file", 0777));
|
||||
cl_git_pass(p_mkdir("./testrepo/branch_file/contained_dir", 0777));
|
||||
cl_git_mkfile("./testrepo/branch_file/contained_file", "content\n");
|
||||
cl_git_pass(p_mkdir("./testrepo/branch_file.txt.after", 0777));
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
|
||||
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
|
||||
|
||||
test_file_contents("./testrepo/README", "hey there\n");
|
||||
test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
|
||||
test_file_contents("./testrepo/new.txt", "my new file\n");
|
||||
|
||||
cl_assert(!git_path_exists("testrepo/READ"));
|
||||
cl_assert(!git_path_exists("testrepo/README.after"));
|
||||
cl_assert(!git_path_exists("testrepo/branch_file"));
|
||||
cl_assert(!git_path_exists("testrepo/branch_file.txt.after"));
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "git2/checkout.h"
|
||||
#include "repository.h"
|
||||
#include "buffer.h"
|
||||
#include "fileops.h"
|
||||
|
||||
static git_repository *g_repo;
|
||||
static git_checkout_opts g_opts;
|
||||
@ -12,7 +14,7 @@ void test_checkout_tree__initialize(void)
|
||||
g_repo = cl_git_sandbox_init("testrepo");
|
||||
|
||||
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
}
|
||||
|
||||
void test_checkout_tree__cleanup(void)
|
||||
@ -48,6 +50,34 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void)
|
||||
cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt"));
|
||||
}
|
||||
|
||||
void test_checkout_tree__can_checkout_and_remove_directory(void)
|
||||
{
|
||||
cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
|
||||
|
||||
/* Checkout brach "subtrees" and update HEAD, so that HEAD matches the
|
||||
* current working tree
|
||||
*/
|
||||
cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees"));
|
||||
cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
|
||||
|
||||
cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees"));
|
||||
|
||||
cl_assert_equal_i(true, git_path_isdir("./testrepo/ab/"));
|
||||
cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt"));
|
||||
cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt"));
|
||||
|
||||
/* Checkout brach "master" and update HEAD, so that HEAD matches the
|
||||
* current working tree
|
||||
*/
|
||||
cl_git_pass(git_revparse_single(&g_object, g_repo, "master"));
|
||||
cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
|
||||
|
||||
cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master"));
|
||||
|
||||
/* This directory should no longer exist */
|
||||
cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
|
||||
}
|
||||
|
||||
void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void)
|
||||
{
|
||||
char *entries[] = { "de/" };
|
||||
@ -85,3 +115,193 @@ void test_checkout_tree__calls_progress_callback(void)
|
||||
|
||||
cl_assert_equal_i(was_called, true);
|
||||
}
|
||||
|
||||
void test_checkout_tree__doesnt_write_unrequested_files_to_worktree(void)
|
||||
{
|
||||
git_oid master_oid;
|
||||
git_oid chomped_oid;
|
||||
git_commit* p_master_commit;
|
||||
git_commit* p_chomped_commit;
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
git_oid_fromstr(&master_oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
|
||||
git_oid_fromstr(&chomped_oid, "e90810b8df3e80c413d903f631643c716887138d");
|
||||
cl_git_pass(git_commit_lookup(&p_master_commit, g_repo, &master_oid));
|
||||
cl_git_pass(git_commit_lookup(&p_chomped_commit, g_repo, &chomped_oid));
|
||||
|
||||
/* GIT_CHECKOUT_NONE should not add any file to the working tree from the
|
||||
* index as it is supposed to be a dry run.
|
||||
*/
|
||||
opts.checkout_strategy = GIT_CHECKOUT_NONE;
|
||||
git_checkout_tree(g_repo, (git_object*)p_chomped_commit, &opts);
|
||||
cl_assert_equal_i(false, git_path_isfile("testrepo/readme.txt"));
|
||||
}
|
||||
|
||||
static void assert_on_branch(git_repository *repo, const char *branch)
|
||||
{
|
||||
git_reference *head;
|
||||
git_buf bname = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE));
|
||||
cl_assert_(git_reference_type(head) == GIT_REF_SYMBOLIC, branch);
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&bname, "refs/heads", branch));
|
||||
cl_assert_equal_s(bname.ptr, git_reference_symbolic_target(head));
|
||||
|
||||
git_reference_free(head);
|
||||
git_buf_free(&bname);
|
||||
}
|
||||
|
||||
void test_checkout_tree__can_switch_branches(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
git_oid oid;
|
||||
git_object *obj = NULL;
|
||||
|
||||
assert_on_branch(g_repo, "master");
|
||||
|
||||
/* do first checkout with FORCE because we don't know if testrepo
|
||||
* base data is clean for a checkout or not
|
||||
*/
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
|
||||
cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
|
||||
|
||||
cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
|
||||
cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir"));
|
||||
|
||||
cl_assert(git_path_isfile("testrepo/README"));
|
||||
cl_assert(git_path_isfile("testrepo/branch_file.txt"));
|
||||
cl_assert(git_path_isfile("testrepo/new.txt"));
|
||||
cl_assert(git_path_isfile("testrepo/a/b.txt"));
|
||||
|
||||
cl_assert(!git_path_isdir("testrepo/ab"));
|
||||
|
||||
assert_on_branch(g_repo, "dir");
|
||||
|
||||
git_object_free(obj);
|
||||
|
||||
/* do second checkout safe because we should be clean after first */
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
|
||||
cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees"));
|
||||
cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
|
||||
|
||||
cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
|
||||
cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees"));
|
||||
|
||||
cl_assert(git_path_isfile("testrepo/README"));
|
||||
cl_assert(git_path_isfile("testrepo/branch_file.txt"));
|
||||
cl_assert(git_path_isfile("testrepo/new.txt"));
|
||||
cl_assert(git_path_isfile("testrepo/ab/4.txt"));
|
||||
cl_assert(git_path_isfile("testrepo/ab/c/3.txt"));
|
||||
cl_assert(git_path_isfile("testrepo/ab/de/2.txt"));
|
||||
cl_assert(git_path_isfile("testrepo/ab/de/fgh/1.txt"));
|
||||
|
||||
cl_assert(!git_path_isdir("testrepo/a"));
|
||||
|
||||
assert_on_branch(g_repo, "subtrees");
|
||||
|
||||
git_object_free(obj);
|
||||
}
|
||||
|
||||
void test_checkout_tree__can_remove_untracked(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
|
||||
cl_git_mkfile("testrepo/untracked_file", "as you wish");
|
||||
cl_assert(git_path_isfile("testrepo/untracked_file"));
|
||||
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts));
|
||||
|
||||
cl_assert(!git_path_isfile("testrepo/untracked_file"));
|
||||
}
|
||||
|
||||
void test_checkout_tree__can_remove_ignored(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
int ignored = 0;
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_IGNORED;
|
||||
|
||||
cl_git_mkfile("testrepo/ignored_file", "as you wish");
|
||||
|
||||
cl_git_pass(git_ignore_add_rule(g_repo, "ignored_file\n"));
|
||||
|
||||
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ignored_file"));
|
||||
cl_assert_equal_i(1, ignored);
|
||||
|
||||
cl_assert(git_path_isfile("testrepo/ignored_file"));
|
||||
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts));
|
||||
|
||||
cl_assert(!git_path_isfile("testrepo/ignored_file"));
|
||||
}
|
||||
|
||||
/* this is essentially the code from git__unescape modified slightly */
|
||||
static void strip_cr_from_buf(git_buf *buf)
|
||||
{
|
||||
char *scan, *pos = buf->ptr;
|
||||
|
||||
for (scan = pos; *scan; pos++, scan++) {
|
||||
if (*scan == '\r')
|
||||
scan++; /* skip '\r' */
|
||||
if (pos != scan)
|
||||
*pos = *scan;
|
||||
}
|
||||
|
||||
*pos = '\0';
|
||||
buf->size = (pos - buf->ptr);
|
||||
}
|
||||
|
||||
void test_checkout_tree__can_update_only(void)
|
||||
{
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
git_oid oid;
|
||||
git_object *obj = NULL;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
|
||||
/* first let's get things into a known state - by checkout out the HEAD */
|
||||
|
||||
assert_on_branch(g_repo, "master");
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts));
|
||||
|
||||
cl_assert(!git_path_isdir("testrepo/a"));
|
||||
|
||||
cl_git_pass(git_futils_readbuffer(&buf, "testrepo/branch_file.txt"));
|
||||
strip_cr_from_buf(&buf);
|
||||
cl_assert_equal_s("hi\nbye!\n", buf.ptr);
|
||||
git_buf_free(&buf);
|
||||
|
||||
/* now checkout branch but with update only */
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
|
||||
|
||||
cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
|
||||
cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
|
||||
|
||||
cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
|
||||
cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir"));
|
||||
|
||||
assert_on_branch(g_repo, "dir");
|
||||
|
||||
/* this normally would have been created (which was tested separately in
|
||||
* the test_checkout_tree__can_switch_branches test), but with
|
||||
* UPDATE_ONLY it will not have been created.
|
||||
*/
|
||||
cl_assert(!git_path_isdir("testrepo/a"));
|
||||
|
||||
/* but this file still should have been updated */
|
||||
cl_git_pass(git_futils_readbuffer(&buf, "testrepo/branch_file.txt"));
|
||||
strip_cr_from_buf(&buf);
|
||||
cl_assert_equal_s("hi\n", buf.ptr);
|
||||
|
||||
git_buf_free(&buf);
|
||||
|
||||
git_object_free(obj);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "git2/checkout.h"
|
||||
#include "path.h"
|
||||
#include "posix.h"
|
||||
#include "fileops.h"
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
|
||||
@ -34,28 +35,97 @@ void test_checkout_typechange__cleanup(void)
|
||||
cl_fixture_cleanup("submod2_target");
|
||||
}
|
||||
|
||||
void test_checkout_typechange__checkout_typechanges(void)
|
||||
static void assert_file_exists(const char *path)
|
||||
{
|
||||
cl_assert_(git_path_isfile(path), path);
|
||||
}
|
||||
|
||||
static void assert_dir_exists(const char *path)
|
||||
{
|
||||
cl_assert_(git_path_isdir(path), path);
|
||||
}
|
||||
|
||||
static void assert_workdir_matches_tree(
|
||||
git_repository *repo, const git_oid *id, const char *root, bool recurse)
|
||||
{
|
||||
git_object *obj;
|
||||
git_tree *tree;
|
||||
size_t i, max_i;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
if (!root)
|
||||
root = git_repository_workdir(repo);
|
||||
cl_assert(root);
|
||||
|
||||
cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJ_ANY));
|
||||
cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJ_TREE));
|
||||
git_object_free(obj);
|
||||
|
||||
max_i = git_tree_entrycount(tree);
|
||||
|
||||
for (i = 0; i < max_i; ++i) {
|
||||
const git_tree_entry *te = git_tree_entry_byindex(tree, i);
|
||||
cl_assert(te);
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, root, git_tree_entry_name(te)));
|
||||
|
||||
switch (git_tree_entry_type(te)) {
|
||||
case GIT_OBJ_COMMIT:
|
||||
assert_dir_exists(path.ptr);
|
||||
break;
|
||||
case GIT_OBJ_TREE:
|
||||
assert_dir_exists(path.ptr);
|
||||
if (recurse)
|
||||
assert_workdir_matches_tree(
|
||||
repo, git_tree_entry_id(te), path.ptr, true);
|
||||
break;
|
||||
case GIT_OBJ_BLOB:
|
||||
switch (git_tree_entry_filemode(te)) {
|
||||
case GIT_FILEMODE_BLOB:
|
||||
case GIT_FILEMODE_BLOB_EXECUTABLE:
|
||||
assert_file_exists(path.ptr);
|
||||
/* because of cross-platform, don't confirm exec bit yet */
|
||||
break;
|
||||
case GIT_FILEMODE_LINK:
|
||||
cl_assert_(git_path_exists(path.ptr), path.ptr);
|
||||
/* because of cross-platform, don't confirm link yet */
|
||||
break;
|
||||
default:
|
||||
cl_assert(false); /* really?! */
|
||||
}
|
||||
break;
|
||||
default:
|
||||
cl_assert(false); /* really?!! */
|
||||
}
|
||||
}
|
||||
|
||||
git_tree_free(tree);
|
||||
git_buf_free(&path);
|
||||
}
|
||||
|
||||
void test_checkout_typechange__checkout_typechanges_safe(void)
|
||||
{
|
||||
int i;
|
||||
git_object *obj;
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
/* if you don't include GIT_CHECKOUT_REMOVE_UNTRACKED then on the final
|
||||
* checkout which is supposed to remove all the files, we will not
|
||||
* actually remove them!
|
||||
*/
|
||||
|
||||
for (i = 0; g_typechange_oids[i] != NULL; ++i) {
|
||||
cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
|
||||
/* fprintf(stderr, "checking out '%s'\n", g_typechange_oids[i]); */
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
|
||||
/* There are bugs in some submodule->tree changes that prevent
|
||||
* SAFE from passing here, even though the following should work:
|
||||
*/
|
||||
/* !i ? GIT_CHECKOUT_FORCE : GIT_CHECKOUT_SAFE; */
|
||||
|
||||
cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
|
||||
|
||||
cl_git_pass(
|
||||
git_repository_set_head_detached(g_repo, git_object_id(obj)));
|
||||
|
||||
assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true);
|
||||
|
||||
git_object_free(obj);
|
||||
|
||||
if (!g_typechange_empty[i]) {
|
||||
@ -75,3 +145,96 @@ void test_checkout_typechange__checkout_typechanges(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int conflicts;
|
||||
int dirty;
|
||||
int updates;
|
||||
int untracked;
|
||||
int ignored;
|
||||
} notify_counts;
|
||||
|
||||
static int notify_counter(
|
||||
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)
|
||||
{
|
||||
notify_counts *cts = payload;
|
||||
|
||||
GIT_UNUSED(path);
|
||||
GIT_UNUSED(baseline);
|
||||
GIT_UNUSED(target);
|
||||
GIT_UNUSED(workdir);
|
||||
|
||||
switch (why) {
|
||||
case GIT_CHECKOUT_NOTIFY_CONFLICT: cts->conflicts++; break;
|
||||
case GIT_CHECKOUT_NOTIFY_DIRTY: cts->dirty++; break;
|
||||
case GIT_CHECKOUT_NOTIFY_UPDATED: cts->updates++; break;
|
||||
case GIT_CHECKOUT_NOTIFY_UNTRACKED: cts->untracked++; break;
|
||||
case GIT_CHECKOUT_NOTIFY_IGNORED: cts->ignored++; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void force_create_file(const char *file)
|
||||
{
|
||||
int error = git_futils_rmdir_r(file, NULL,
|
||||
GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS);
|
||||
cl_assert(!error || error == GIT_ENOTFOUND);
|
||||
cl_git_pass(git_futils_mkpath2file(file, 0777));
|
||||
cl_git_rewritefile(file, "yowza!");
|
||||
}
|
||||
|
||||
void test_checkout_typechange__checkout_with_conflicts(void)
|
||||
{
|
||||
int i;
|
||||
git_object *obj;
|
||||
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
|
||||
notify_counts cts = {0};
|
||||
|
||||
opts.notify_flags =
|
||||
GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_UNTRACKED;
|
||||
opts.notify_cb = notify_counter;
|
||||
opts.notify_payload = &cts;
|
||||
|
||||
for (i = 0; g_typechange_oids[i] != NULL; ++i) {
|
||||
cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
|
||||
|
||||
force_create_file("typechanges/a/blocker");
|
||||
force_create_file("typechanges/b");
|
||||
force_create_file("typechanges/c/sub/sub/file");
|
||||
git_futils_rmdir_r("typechanges/d", NULL, GIT_RMDIR_REMOVE_FILES);
|
||||
p_mkdir("typechanges/d", 0777); /* intentionally empty dir */
|
||||
force_create_file("typechanges/untracked");
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
memset(&cts, 0, sizeof(cts));
|
||||
|
||||
cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
|
||||
cl_assert(cts.conflicts > 0);
|
||||
cl_assert(cts.untracked > 0);
|
||||
|
||||
opts.checkout_strategy =
|
||||
GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
|
||||
memset(&cts, 0, sizeof(cts));
|
||||
|
||||
cl_assert(git_path_exists("typechanges/untracked"));
|
||||
|
||||
cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
|
||||
cl_assert_equal_i(0, cts.conflicts);
|
||||
|
||||
cl_assert(!git_path_exists("typechanges/untracked"));
|
||||
|
||||
cl_git_pass(
|
||||
git_repository_set_head_detached(g_repo, git_object_id(obj)));
|
||||
|
||||
assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true);
|
||||
|
||||
git_object_free(obj);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,16 @@
|
||||
#include "posix.h"
|
||||
#include "path.h"
|
||||
|
||||
void cl_git_report_failure(
|
||||
int error, const char *file, int line, const char *fncall)
|
||||
{
|
||||
char msg[4096];
|
||||
const git_error *last = giterr_last();
|
||||
p_snprintf(msg, 4096, "error %d - %s",
|
||||
error, last ? last->message : "<no message>");
|
||||
clar__assert(0, file, line, fncall, msg, 1);
|
||||
}
|
||||
|
||||
void cl_git_mkfile(const char *filename, const char *content)
|
||||
{
|
||||
int fd;
|
||||
|
@ -6,16 +6,16 @@
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* Special wrapper for `clar_must_pass` that passes
|
||||
* the last library error as the test failure message.
|
||||
* Replace for `clar_must_pass` that passes the last library error as the
|
||||
* test failure message.
|
||||
*
|
||||
* Use this wrapper around all `git_` library calls that
|
||||
* return error codes!
|
||||
* Use this wrapper around all `git_` library calls that return error codes!
|
||||
*/
|
||||
#define cl_git_pass(expr) do { \
|
||||
int _lg2_error; \
|
||||
giterr_clear(); \
|
||||
if ((expr) != 0) \
|
||||
clar__assert(0, __FILE__, __LINE__, "Function call failed: " #expr, giterr_last() ? giterr_last()->message : NULL, 1); \
|
||||
if ((_lg2_error = (expr)) != 0) \
|
||||
cl_git_report_failure(_lg2_error, __FILE__, __LINE__, "Function call failed: " #expr); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
@ -25,6 +25,10 @@
|
||||
*/
|
||||
#define cl_git_fail(expr) cl_must_fail(expr)
|
||||
|
||||
#define cl_git_fail_with(expr, error) cl_assert_equal_i(error,expr)
|
||||
|
||||
void cl_git_report_failure(int, const char *, int, const char *);
|
||||
|
||||
#define cl_assert_equal_sz(sz1,sz2) cl_assert((sz1) == (sz2))
|
||||
|
||||
/*
|
||||
|
@ -290,3 +290,86 @@ void test_index_tests__write_invalid_filename(void)
|
||||
|
||||
cl_fixture_cleanup("read_tree");
|
||||
}
|
||||
|
||||
void test_index_tests__remove_entry(void)
|
||||
{
|
||||
git_repository *repo;
|
||||
git_index *index;
|
||||
|
||||
p_mkdir("index_test", 0770);
|
||||
|
||||
cl_git_pass(git_repository_init(&repo, "index_test", 0));
|
||||
cl_git_pass(git_repository_index(&index, repo));
|
||||
cl_assert(git_index_entrycount(index) == 0);
|
||||
|
||||
cl_git_mkfile("index_test/hello", NULL);
|
||||
cl_git_pass(git_index_add_from_workdir(index, "hello"));
|
||||
cl_git_pass(git_index_write(index));
|
||||
|
||||
cl_git_pass(git_index_read(index)); /* reload */
|
||||
cl_assert(git_index_entrycount(index) == 1);
|
||||
cl_assert(git_index_get_bypath(index, "hello", 0) != NULL);
|
||||
|
||||
cl_git_pass(git_index_remove(index, "hello", 0));
|
||||
cl_git_pass(git_index_write(index));
|
||||
|
||||
cl_git_pass(git_index_read(index)); /* reload */
|
||||
cl_assert(git_index_entrycount(index) == 0);
|
||||
cl_assert(git_index_get_bypath(index, "hello", 0) == NULL);
|
||||
|
||||
git_index_free(index);
|
||||
git_repository_free(repo);
|
||||
cl_fixture_cleanup("index_test");
|
||||
}
|
||||
|
||||
void test_index_tests__remove_directory(void)
|
||||
{
|
||||
git_repository *repo;
|
||||
git_index *index;
|
||||
|
||||
p_mkdir("index_test", 0770);
|
||||
|
||||
cl_git_pass(git_repository_init(&repo, "index_test", 0));
|
||||
cl_git_pass(git_repository_index(&index, repo));
|
||||
cl_assert_equal_i(0, (int)git_index_entrycount(index));
|
||||
|
||||
p_mkdir("index_test/a", 0770);
|
||||
cl_git_mkfile("index_test/a/1.txt", NULL);
|
||||
cl_git_mkfile("index_test/a/2.txt", NULL);
|
||||
cl_git_mkfile("index_test/a/3.txt", NULL);
|
||||
cl_git_mkfile("index_test/b.txt", NULL);
|
||||
|
||||
cl_git_pass(git_index_add_from_workdir(index, "a/1.txt"));
|
||||
cl_git_pass(git_index_add_from_workdir(index, "a/2.txt"));
|
||||
cl_git_pass(git_index_add_from_workdir(index, "a/3.txt"));
|
||||
cl_git_pass(git_index_add_from_workdir(index, "b.txt"));
|
||||
cl_git_pass(git_index_write(index));
|
||||
|
||||
cl_git_pass(git_index_read(index)); /* reload */
|
||||
cl_assert_equal_i(4, (int)git_index_entrycount(index));
|
||||
cl_assert(git_index_get_bypath(index, "a/1.txt", 0) != NULL);
|
||||
cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL);
|
||||
cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL);
|
||||
|
||||
cl_git_pass(git_index_remove(index, "a/1.txt", 0));
|
||||
cl_git_pass(git_index_write(index));
|
||||
|
||||
cl_git_pass(git_index_read(index)); /* reload */
|
||||
cl_assert_equal_i(3, (int)git_index_entrycount(index));
|
||||
cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL);
|
||||
cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL);
|
||||
cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL);
|
||||
|
||||
cl_git_pass(git_index_remove_directory(index, "a", 0));
|
||||
cl_git_pass(git_index_write(index));
|
||||
|
||||
cl_git_pass(git_index_read(index)); /* reload */
|
||||
cl_assert_equal_i(1, (int)git_index_entrycount(index));
|
||||
cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL);
|
||||
cl_assert(git_index_get_bypath(index, "a/2.txt", 0) == NULL);
|
||||
cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL);
|
||||
|
||||
git_index_free(index);
|
||||
git_repository_free(repo);
|
||||
cl_fixture_cleanup("index_test");
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ void test_object_raw_convert__succeed_on_oid_to_string_conversion(void)
|
||||
str = git_oid_tostr(out, 0, &in);
|
||||
cl_assert(str && *str == '\0' && str != out);
|
||||
|
||||
/* NULL oid pointer, returns static empty string */
|
||||
/* NULL oid pointer, sets existing buffer to empty string */
|
||||
str = git_oid_tostr(out, sizeof(out), NULL);
|
||||
cl_assert(str && *str == '\0' && str != out);
|
||||
cl_assert(str && *str == '\0' && str == out);
|
||||
|
||||
/* n == 1, returns out as an empty string */
|
||||
str = git_oid_tostr(out, 1, &in);
|
||||
|
@ -91,7 +91,7 @@ void test_online_clone__can_checkout_a_cloned_repo(void)
|
||||
bool checkout_progress_cb_was_called = false,
|
||||
fetch_progress_cb_was_called = false;
|
||||
|
||||
g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
|
||||
g_options.checkout_opts.progress_cb = &checkout_progress;
|
||||
g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called;
|
||||
g_options.fetch_progress_cb = &fetch_progress;
|
||||
|
@ -54,9 +54,7 @@ void test_reset_hard__resetting_reverts_modified_files(void)
|
||||
static const char *after[4] = {
|
||||
"current_file\n",
|
||||
"modified_file\n",
|
||||
/* wrong value because reset is still slightly incorrect */
|
||||
"staged_new_file\n",
|
||||
/* right value: NULL, */
|
||||
NULL,
|
||||
"staged_changes_modified_file\n"
|
||||
};
|
||||
const char *wd = git_repository_workdir(repo);
|
||||
|
@ -36,15 +36,22 @@ static void push_three_states(void)
|
||||
cl_git_pass(git_repository_index(&index, repo));
|
||||
cl_git_pass(git_index_add_from_workdir(index, "zero.txt"));
|
||||
commit_staged_files(&oid, index, signature);
|
||||
cl_assert(git_path_exists("stash/zero.txt"));
|
||||
|
||||
cl_git_mkfile("stash/one.txt", "content\n");
|
||||
cl_git_pass(git_stash_save(&oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED));
|
||||
cl_assert(!git_path_exists("stash/one.txt"));
|
||||
cl_assert(git_path_exists("stash/zero.txt"));
|
||||
|
||||
cl_git_mkfile("stash/two.txt", "content\n");
|
||||
cl_git_pass(git_stash_save(&oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED));
|
||||
cl_assert(!git_path_exists("stash/two.txt"));
|
||||
cl_assert(git_path_exists("stash/zero.txt"));
|
||||
|
||||
cl_git_mkfile("stash/three.txt", "content\n");
|
||||
cl_git_pass(git_stash_save(&oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED));
|
||||
cl_assert(!git_path_exists("stash/three.txt"));
|
||||
cl_assert(git_path_exists("stash/zero.txt"));
|
||||
|
||||
git_index_free(index);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user