Commit 7e5c8a5b by Russell Belfer

More checkout improvements

This flips checkout back to be driven off the changes between
the baseline and the target trees.  This reinstates the complex
code for tracking the contents of the working directory, but
overall, I think the resulting logic is easier to follow.
parent cf208031
......@@ -9,8 +9,7 @@
#include "common.h"
#include "types.h"
#include "indexer.h"
#include "strarray.h"
#include "diff.h"
/**
* @file git2/checkout.h
......@@ -25,27 +24,28 @@ GIT_BEGIN_DECL
* Checkout behavior flags
*
* In libgit2, the function of checkout is to update the working directory
* to match a target tree given an expected baseline tree. It does not move
* the HEAD commit - you do that separately. Typically the expected tree is
* the (to-be-moved) HEAD commit.
* to match a target tree. It does not move the HEAD commit - you do that
* separately. To safely perform the update, checkout relies on a baseline
* tree (generally the current HEAD) as a reference for the unmodified
* content expected in the working directory.
*
* Checkout examines the differences between the target and expected trees
* plus the current working directory and groups files into five categories:
* Checkout examines the differences between the target tree, the baseline
* tree and the working directory, and groups files into five categories:
*
* 1. UNMODIFIED - Files that match in all places.
* 2. SAFE - Files where the working directory and the expect content match
* that can be safely updated to the target.
* 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
* expected content but there is no conflicting change with the target
* tree. An example is a file that doesn't exist in the working
* directory - no data would be lost as a result of writing this file.
* The action to take with these files depends on the options you elect.
* 4. CONFLICTS - Files where changes in the working directory conflicts
* 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.
* or ignored (i.e. only in the working directory, not the other places).
*
*
* You control the actions checkout takes with one of four base strategies:
......@@ -54,11 +54,11 @@ GIT_BEGIN_DECL
* run that you can use to find conflicts, etc. if you wish.
*
* - `GIT_CHECKOUT_SAFE` is like `git checkout` and only applies changes
* between the expected and target trees to files in category 2.
* between the baseline and target trees to files in category 2.
*
* - `GIT_CHECKOUT_SAFE_CREATE` also creates files that are missing from the
* working directory (category 3), even if there is no change between the
* expected and target trees for those files. See notes below on
* baseline and target trees for those files. See notes below on
* emulating `git checkout-index` for some of the subtleties of this.
*
* - `GIT_CHECKOUT_FORCE` is like `git checkout -f` and will update the
......@@ -97,7 +97,7 @@ GIT_BEGIN_DECL
* To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
* notification callback (see below) that displays information about dirty
* files (i.e. files that don't need an update but that no longer match the
* expected content). The default behavior will cancel on conflicts.
* baseline content). The default behavior will cancel on conflicts.
*
* To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE_CREATE` with a
* notification callback that cancels the operation if a dirty-but-existing
......@@ -140,6 +140,9 @@ typedef enum {
/** Only update existing files, don't create new ones */
GIT_CHECKOUT_UPDATE_ONLY = (1u << 7),
/** Don't refresh index/config/etc before doing checkout */
GIT_CHECKOUT_NO_REFRESH = (1u << 8),
/**
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
*/
......@@ -166,14 +169,14 @@ typedef enum {
* receive a callback depend on the `notify_flags` value which is a
* combination of these flags.
*
* - GIT_CHECKOUT_NOTIFY_CONFLICTS means that conflicting files that would
* - GIT_CHECKOUT_NOTIFY_CONFLICT means that conflicting files that would
* prevent the checkout from occurring will receive callbacks. If you
* used GIT_CHECKOUT_ALLOW_CONFLICTS, the callbacks are still done, but
* the checkout will not be blocked. The callback `status_flags` will
* have both index and work tree change bits set (see `git_status_t`).
*
* - GIT_CHECKOUT_NOTIFY_DIRTY means to notify about "dirty" files, i.e.
* those that do not need to be updated but no longer match the expected
* those that do not need to be updated but no longer match the baseline
* content. Core git displays these files when checkout runs, but does
* not stop the checkout. For these, `status_flags` will have only work
* tree bits set (i.e. GIT_STATUS_WT_MODIFIED, etc).
......@@ -202,7 +205,8 @@ typedef enum {
* Dirty files will only have work tree flags set.
*/
typedef enum {
GIT_CHECKOUT_NOTIFY_CONFLICTS = (1u << 0),
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),
......@@ -231,11 +235,11 @@ typedef struct git_checkout_opts {
unsigned int notify_flags; /** see `git_checkout_notify_t` above */
int (*notify_cb)(
git_checkout_notify_t why,
const char *path,
unsigned int status_flags, /** combo of git_status_t values */
const git_oid *index_oid,
unsigned int checkout_mode,
unsigned int workdir_mode,
const git_diff_file *baseline,
const git_diff_file *target,
const git_diff_file *workdir,
void *payload);
void *notify_payload;
......
......@@ -126,7 +126,7 @@
* 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) expected
* - 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
......@@ -146,9 +146,9 @@
* 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 expected and desired values match
* 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 expected == desired, we don't touch the workdir (it is
* 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.
*
......@@ -182,271 +182,460 @@ enum {
CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
CHECKOUT_ACTION__CONFLICT = 8,
CHECKOUT_ACTION__MAX = 8,
CHECKOUT_ACTION__REMOVE_EMPTY = 16,
CHECKOUT_ACTION__DEFER_REMOVE = 16,
CHECKOUT_ACTION__REMOVE_AND_UPDATE =
(CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE),
};
typedef struct {
git_repository *repo;
git_diff_list *diff;
git_checkout_opts *opts;
const char *pfx;
git_buf *path;
git_checkout_opts opts;
bool opts_free_baseline;
char *pfx;
git_iterator *baseline;
git_pool pool;
git_vector removes;
git_buf path;
size_t workdir_len;
bool can_symlink;
int error;
unsigned int strategy;
int can_symlink;
size_t total_steps;
size_t completed_steps;
} checkout_diff_data;
} checkout_data;
static int checkout_notify(
checkout_diff_data *data,
checkout_data *data,
git_checkout_notify_t why,
const git_diff_delta *delta,
const git_index_entry *wditem)
const git_index_entry *baseitem)
{
GIT_UNUSED(data);
GIT_UNUSED(why);
GIT_UNUSED(delta);
GIT_UNUSED(wditem);
git_diff_file basefile;
const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL;
if (!data->opts.notify_cb)
return 0;
if ((why & data->opts.notify_flags) == 0)
return 0;
if (baseitem) {
memset(&basefile, 0, sizeof(basefile));
git_oid_cpy(&basefile.oid, &baseitem->oid);
basefile.path = baseitem->path;
basefile.size = baseitem->file_size;
basefile.flags = GIT_DIFF_FILE_VALID_OID;
basefile.mode = baseitem->mode;
baseline = &basefile;
}
if ((why & GIT_CHECKOUT__NOTIFY_CONFLICT_TREE) != 0) {
/* baseitem is a blob that conflicts with a tree in the workdir */
} else {
switch (delta->status) {
case GIT_DELTA_UNMODIFIED:
case GIT_DELTA_MODIFIED:
case GIT_DELTA_TYPECHANGE:
default:
target = &delta->old_file;
workdir = &delta->new_file;
break;
case GIT_DELTA_ADDED:
case GIT_DELTA_IGNORED:
case GIT_DELTA_UNTRACKED:
workdir = &delta->new_file;
break;
case GIT_DELTA_DELETED:
target = &delta->old_file;
break;
}
}
return data->opts.notify_cb(
why, delta->old_file.path,
baseline, target, workdir,
data->opts.notify_payload);
}
static bool checkout_is_workdir_modified(
checkout_diff_data *data,
const git_diff_file *item,
const git_index_entry *wditem)
checkout_data *data,
const git_diff_file *wditem,
const git_index_entry *baseitem)
{
git_oid oid;
if (item->size != wditem->file_size)
if (wditem->size != baseitem->file_size)
return true;
if (git_diff__oid_for_file(
data->repo, wditem->path, wditem->mode,
wditem->file_size, &oid) < 0)
data->repo, wditem->path, wditem->mode, wditem->size, &oid) < 0)
return false;
return (git_oid_cmp(&item->oid, &oid) != 0);
return (git_oid_cmp(&baseitem->oid, &oid) != 0);
}
static int checkout_action_for_delta(
checkout_diff_data *data,
#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \
((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO)
static const char *checkout_action_name_debug(int act)
{
if (act & CHECKOUT_ACTION__CONFLICT)
return "CONFLICT";
if (act & CHECKOUT_ACTION__REMOVE) {
if (act & CHECKOUT_ACTION__UPDATE_BLOB)
return "REMOVE+UPDATE";
if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
return "REMOVE+UPDATE SUB";
return "REMOVE";
}
if (act & CHECKOUT_ACTION__DEFER_REMOVE) {
if (act & CHECKOUT_ACTION__UPDATE_BLOB)
return "UPDATE (WITH REMOVE)";
if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
return "UPDATE SUB (WITH REMOVE)";
return "DEFERRED REMOVE";
}
if (act & CHECKOUT_ACTION__UPDATE_BLOB)
return "UPDATE";
if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
return "UPDATE SUB";
assert(act == 0);
return "NONE";
}
static int checkout_action_common(
checkout_data *data,
int action,
const git_diff_delta *delta,
const git_index_entry *wditem)
const git_index_entry *wd)
{
git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
if (action <= 0)
return action;
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
action = (action & ~CHECKOUT_ACTION__REMOVE);
if ((action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
if (S_ISGITLINK(delta->new_file.mode))
action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
CHECKOUT_ACTION__UPDATE_SUBMODULE;
notify = GIT_CHECKOUT_NOTIFY_UPDATED;
}
if ((action & CHECKOUT_ACTION__CONFLICT) != 0)
notify = GIT_CHECKOUT_NOTIFY_CONFLICT;
if (notify != GIT_CHECKOUT_NOTIFY_NONE &&
checkout_notify(data, notify, delta, wd) != 0)
return GIT_EUSER;
return action;
}
static int checkout_action_no_wd(
checkout_data *data,
const git_diff_delta *delta)
{
int action = CHECKOUT_ACTION__NONE;
unsigned int strat = data->opts->checkout_strategy;
int safe = ((strat & GIT_CHECKOUT_SAFE) != 0) ?
CHECKOUT_ACTION__UPDATE_BLOB : CHECKOUT_ACTION__NONE;
int force = ((strat & GIT_CHECKOUT_FORCE) != 0) ?
CHECKOUT_ACTION__UPDATE_BLOB : CHECKOUT_ACTION__CONFLICT;
/* nothing in workdir, so this is pretty easy */
if (!wditem) {
switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 12 */
if ((strat & GIT_CHECKOUT_SAFE_CREATE) != 0)
action = CHECKOUT_ACTION__UPDATE_BLOB;
if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL))
return GIT_EUSER;
action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
break;
case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
action = safe;
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
break;
case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
if (!S_ISDIR(delta->new_file.mode))
action = safe;
if (delta->new_file.mode == GIT_FILEMODE_TREE)
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
break;
case GIT_DELTA_DELETED: /* case 8 or 25 */
default: /* impossible */ break;
default: /* impossible */
break;
}
return checkout_action_common(data, action, delta, NULL);
}
static int checkout_action_wd_only(
checkout_data *data,
git_iterator *workdir,
const git_index_entry *wd,
git_vector *pathspec)
{
bool ignored, remove;
git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
if (!git_pathspec_match_path(
pathspec, wd->path, false, workdir->ignore_case))
return 0;
ignored = git_iterator_current_is_ignored(workdir);
if (ignored) {
notify = GIT_CHECKOUT_NOTIFY_IGNORED;
remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0);
} else {
notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;
remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0);
}
/* workdir has a directory where this entry should be */
else if (S_ISDIR(wditem->mode)) {
switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */
if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL) ||
checkout_notify(
data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wditem))
if (checkout_notify(data, notify, NULL, wd))
return GIT_EUSER;
break;
case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */
case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */
if (!S_ISDIR(delta->new_file.mode))
action = force;
break;
case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */
if (!S_ISDIR(delta->old_file.mode) &&
checkout_notify(
data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wditem))
return GIT_EUSER;
break;
case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */
/* For typechange to dir, dir is already created so no action */
/* For typechange to blob, remove dir and add blob, but it is
* not safe to remove dir if it contains modified files.
* However, safely removing child files will remove the parent
* directory if is it left empty, so we only need to remove dir
* if it is already empty and has no children to remove.
*/
if (S_ISDIR(delta->old_file.mode)) {
action = safe;
if (action != 0)
action |= CHECKOUT_ACTION__REMOVE |
CHECKOUT_ACTION__REMOVE_EMPTY;
}
break;
default: /* impossible */ break;
}
if (remove) {
char *path = git_pool_strdup(&data->pool, wd->path);
GITERR_CHECK_ALLOC(path);
if (git_vector_insert(&data->removes, path) < 0)
return -1;
}
/* workdir has a blob (or submodule) */
else {
return 0;
}
static int checkout_action_with_wd(
checkout_data *data,
const git_diff_delta *delta,
const git_index_entry *wd)
{
int action = CHECKOUT_ACTION__NONE;
switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
if (S_ISDIR(delta->old_file.mode) ||
checkout_is_workdir_modified(data, &delta->old_file, wditem))
checkout_is_workdir_modified(data, &delta->old_file, wd))
{
if (checkout_notify(
data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wditem))
data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))
return GIT_EUSER;
if (force)
action = CHECKOUT_ACTION__UPDATE_BLOB;
action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE);
}
break;
case GIT_DELTA_ADDED: /* case 3, 4 or 6 */
action = force;
action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
break;
case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */
if (checkout_is_workdir_modified(data, &delta->old_file, wditem))
action = force ?
CHECKOUT_ACTION__REMOVE : CHECKOUT_ACTION__CONFLICT;
if (checkout_is_workdir_modified(data, &delta->old_file, wd))
action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
else
action = safe ?
CHECKOUT_ACTION__REMOVE : CHECKOUT_ACTION__NONE;
action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
break;
case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */
if (checkout_is_workdir_modified(data, &delta->old_file, wditem))
action = force;
if (checkout_is_workdir_modified(data, &delta->old_file, wd))
action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
else
action = safe;
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
break;
case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
if (S_ISDIR(delta->old_file.mode) ||
checkout_is_workdir_modified(data, &delta->old_file, wditem))
action = force;
if (delta->new_file.mode == GIT_FILEMODE_TREE)
action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
else if (checkout_is_workdir_modified(data, &delta->old_file, wd))
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
else
action = safe;
action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
break;
default: /* impossible */
break;
default: /* impossible */ break;
}
}
if (action > 0 && (strat & GIT_CHECKOUT_UPDATE_ONLY) != 0)
action = (action & ~CHECKOUT_ACTION__REMOVE);
return checkout_action_common(data, action, delta, wd);
}
if (action > 0 && (action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
if (S_ISGITLINK(delta->new_file.mode))
action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
CHECKOUT_ACTION__UPDATE_SUBMODULE;
static int checkout_action_with_wd_blocker(
checkout_data *data,
const git_diff_delta *delta,
const git_index_entry *wd)
{
int action = CHECKOUT_ACTION__NONE;
if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_UPDATED, delta, wditem))
switch (delta->status) {
case GIT_DELTA_UNMODIFIED:
/* should show delta as dirty / deleted */
if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))
return GIT_EUSER;
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE);
break;
case GIT_DELTA_ADDED:
case GIT_DELTA_MODIFIED:
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
break;
case GIT_DELTA_DELETED:
action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
break;
case GIT_DELTA_TYPECHANGE:
/* not 100% certain about this... */
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
break;
default: /* impossible */
break;
}
if ((action & CHECKOUT_ACTION__CONFLICT) != 0) {
if (checkout_notify(
data, GIT_CHECKOUT_NOTIFY_CONFLICTS, delta, wditem))
return checkout_action_common(data, action, delta, wd);
}
static int checkout_action_with_wd_dir(
checkout_data *data,
const git_diff_delta *delta,
const git_index_entry *wd)
{
int action = CHECKOUT_ACTION__NONE;
switch (delta->status) {
case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */
if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL) ||
checkout_notify(
data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd))
return GIT_EUSER;
break;
case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */
case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */
if (delta->new_file.mode != GIT_FILEMODE_TREE)
action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
break;
case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */
if (delta->old_file.mode != GIT_FILEMODE_TREE &&
checkout_notify(
data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd))
return GIT_EUSER;
break;
case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */
/* For typechange to dir, dir is already created so no action */
/* For typechange to blob, remove dir and add blob, but it is
* not safe to remove dir if it contains modified files.
* However, safely removing child files will remove the parent
* directory if is it left empty, so we can defer removing the
* dir and it will succeed if no children are left.
*/
if (delta->old_file.mode == GIT_FILEMODE_TREE) {
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
if (action != CHECKOUT_ACTION__NONE)
action |= CHECKOUT_ACTION__DEFER_REMOVE;
}
break;
default: /* impossible */
break;
}
return action;
return checkout_action_common(data, action, delta, wd);
}
static int checkout_track_wd(
int *cmp_out,
const git_index_entry **wditem_ptr,
checkout_diff_data *data,
git_iterator *actual,
static int checkout_action(
checkout_data *data,
git_diff_delta *delta,
git_iterator *workdir,
const git_index_entry **wditem_ptr,
git_vector *pathspec)
{
int cmp = -1;
const git_index_entry *wditem = *wditem_ptr;
const git_index_entry *wd = *wditem_ptr;
int cmp = -1, act;
int (*strcomp)(const char *, const char *) = data->diff->strcomp;
int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp;
while (wditem) {
bool notify = false;
/* move workdir iterator to follow along with deltas */
cmp = data->diff->strcomp(delta->new_file.path, wditem->path);
if (cmp >= 0)
break;
while (1) {
if (!wd)
return checkout_action_no_wd(data, delta);
if (!git_pathspec_match_path(
pathspec, wditem->path, false, actual->ignore_case))
notify = false;
cmp = strcomp(wd->path, delta->old_file.path);
else if (S_ISDIR(wditem->mode)) {
cmp = data->diff->pfxcomp(delta->new_file.path, wditem->path);
/* 1. wd before delta ("a/a" before "a/b")
* 2. wd prefixes delta & should expand ("a/" before "a/b")
* 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c")
* 4. wd equals delta ("a/b" and "a/b")
* 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b")
* 6. wd after delta ("a/c" after "a/b")
*/
if (cmp < 0)
notify = true; /* notify untracked/ignored tree */
else if (!cmp) {
/* workdir is prefix of current, so dive in and continue */
if (git_iterator_advance_into_directory(actual, &wditem) < 0)
return -1;
if (cmp < 0) {
cmp = pfxcomp(delta->old_file.path, wd->path);
if (cmp == 0) {
if (wd->mode == GIT_FILEMODE_TREE) {
/* case 2 - descend in wd */
if (git_iterator_advance_into_directory(workdir, &wd) < 0)
goto fail;
continue;
}
else /* how can the wditem->path be < 0 but a prefix be > 0 */
assert(false);
} else
notify = true; /* notify untracked/ignored blob */
if (notify && checkout_notify(
data, git_iterator_current_is_ignored(actual) ?
GIT_CHECKOUT_NOTIFY_IGNORED : GIT_CHECKOUT_NOTIFY_UNTRACKED,
NULL, wditem))
return GIT_EUSER;
if (git_iterator_advance(actual, wditem_ptr) < 0)
break;
/* case 3 - wd contains non-dir where dir expected */
act = checkout_action_with_wd_blocker(data, delta, wd);
*wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd;
return act;
}
/* case 1 - handle wd item (if it matches pathspec) */
if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0 ||
git_iterator_advance(workdir, &wd) < 0)
goto fail;
wditem = *wditem_ptr;
cmp = -1;
*wditem_ptr = wd;
continue;
}
*cmp_out = cmp;
if (cmp == 0) {
/* case 4 */
act = checkout_action_with_wd(data, delta, wd);
*wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd;
return act;
}
return 0;
cmp = pfxcomp(wd->path, delta->old_file.path);
if (cmp == 0) { /* case 5 */
if (delta->status == GIT_DELTA_TYPECHANGE &&
(delta->new_file.mode == GIT_FILEMODE_TREE ||
delta->new_file.mode == GIT_FILEMODE_COMMIT ||
delta->old_file.mode == GIT_FILEMODE_TREE ||
delta->old_file.mode == GIT_FILEMODE_COMMIT))
{
act = checkout_action_with_wd(data, delta, wd);
*wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd;
return act;
}
return checkout_action_with_wd_dir(data, delta, wd);
}
/* case 6 - wd is after delta */
return checkout_action_no_wd(data, delta);
}
fail:
*wditem_ptr = NULL;
return -1;
}
static int checkout_get_actions(
uint32_t **actions_ptr,
size_t **counts_ptr,
checkout_diff_data *data)
checkout_data *data,
git_iterator *workdir)
{
int error = 0;
git_iterator *actual = NULL;
const git_index_entry *wditem;
git_vector pathspec = GIT_VECTOR_INIT, *deltas;
git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;
git_diff_delta *delta;
size_t i, *counts = NULL;
uint32_t *actions = NULL;
bool allow_conflicts =
((data->opts->checkout_strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0);
if (data->opts->paths.count > 0 &&
git_pathspec_init(&pathspec, &data->opts->paths, &pathpool) < 0)
if (data->opts.paths.count > 0 &&
git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0)
return -1;
if ((error = git_iterator_for_workdir_range(
&actual, data->repo, data->pfx, data->pfx)) < 0 ||
(error = git_iterator_current(actual, &wditem)) < 0)
if ((error = git_iterator_current(workdir, &wditem)) < 0)
goto fail;
deltas = &data->diff->deltas;
......@@ -460,23 +649,13 @@ static int checkout_get_actions(
}
git_vector_foreach(deltas, i, delta) {
int cmp = -1, act;
int act = checkout_action(data, delta, workdir, &wditem, &pathspec);
/* move workdir iterator to follow along with deltas */
if (wditem != NULL &&
(error = checkout_track_wd(
&cmp, &wditem, data, actual, delta, &pathspec)) < 0)
goto fail;
act = checkout_action_for_delta(data, delta, !cmp ? wditem : NULL);
if (act < 0) {
error = act;
goto fail;
}
if (!cmp && git_iterator_advance(actual, &wditem) < 0)
wditem = NULL;
actions[i] = act;
if (act & CHECKOUT_ACTION__REMOVE)
......@@ -489,14 +668,17 @@ static int checkout_get_actions(
counts[CHECKOUT_ACTION__CONFLICT]++;
}
if (counts[CHECKOUT_ACTION__CONFLICT] > 0 && !allow_conflicts) {
counts[CHECKOUT_ACTION__REMOVE] += data->removes.length;
if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
(data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
{
giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",
(int)counts[CHECKOUT_ACTION__CONFLICT]);
error = -1;
goto fail;
}
git_iterator_free(actual);
git_pathspec_free(&pathspec);
git_pool_clear(&pathpool);
......@@ -508,7 +690,6 @@ fail:
*actions_ptr = NULL;
git__free(actions);
git_iterator_free(actual);
git_pathspec_free(&pathspec);
git_pool_clear(&pathpool);
......@@ -603,7 +784,7 @@ cleanup:
}
static int blob_content_to_link(
git_blob *blob, const char *path, bool can_symlink)
git_blob *blob, const char *path, int can_symlink)
{
git_buf linktarget = GIT_BUF_INIT;
int error;
......@@ -622,16 +803,16 @@ static int blob_content_to_link(
}
static int checkout_submodule(
checkout_diff_data *data,
checkout_data *data,
const git_diff_file *file)
{
/* Until submodules are supported, UPDATE_ONLY means do nothing here */
if ((data->opts->checkout_strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
return 0;
if (git_futils_mkdir(
file->path, git_repository_workdir(data->repo),
data->opts->dir_mode, GIT_MKDIR_PATH) < 0)
data->opts.dir_mode, GIT_MKDIR_PATH) < 0)
return -1;
/* TODO: Support checkout_strategy options. Two circumstances:
......@@ -647,24 +828,24 @@ static int checkout_submodule(
}
static void report_progress(
checkout_diff_data *data,
checkout_data *data,
const char *path)
{
if (data->opts->progress_cb)
data->opts->progress_cb(
if (data->opts.progress_cb)
data->opts.progress_cb(
path, data->completed_steps, data->total_steps,
data->opts->progress_payload);
data->opts.progress_payload);
}
static int checkout_blob(
checkout_diff_data *data,
checkout_data *data,
const git_diff_file *file)
{
int error = 0;
git_blob *blob;
git_buf_truncate(data->path, data->workdir_len);
if (git_buf_puts(data->path, file->path) < 0)
git_buf_truncate(&data->path, data->workdir_len);
if (git_buf_puts(&data->path, file->path) < 0)
return -1;
if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)
......@@ -672,42 +853,45 @@ static int checkout_blob(
if (S_ISLNK(file->mode))
error = blob_content_to_link(
blob, git_buf_cstr(data->path), data->can_symlink);
blob, git_buf_cstr(&data->path), data->can_symlink);
else
error = blob_content_to_file(
blob, git_buf_cstr(data->path), file->mode, data->opts);
blob, git_buf_cstr(&data->path), file->mode, &data->opts);
git_blob_free(blob);
/* if we try to create the blob and an existing directory blocks it from
* being written, then there must have been a typechange conflict in a
* parent directory - suppress the error and try to continue.
*/
if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 &&
(error == GIT_ENOTFOUND || error == GIT_EEXISTS))
{
giterr_clear();
error = 0;
}
return error;
}
static int checkout_remove_the_old(
unsigned int *actions,
checkout_diff_data *data)
checkout_data *data)
{
int error = 0;
git_diff_delta *delta;
const char *str;
size_t i;
const char *workdir = git_buf_cstr(data->path);
const char *workdir = git_buf_cstr(&data->path);
uint32_t flg = GIT_RMDIR_EMPTY_PARENTS |
GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
git_buf_truncate(data->path, data->workdir_len);
git_buf_truncate(&data->path, data->workdir_len);
git_vector_foreach(&data->diff->deltas, i, delta) {
if (actions[i] & CHECKOUT_ACTION__REMOVE) {
uint32_t flg = GIT_RMDIR_EMPTY_PARENTS;
bool empty_only =
((actions[i] & CHECKOUT_ACTION__REMOVE_EMPTY) != 0);
if (!empty_only)
flg |= GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
error = git_futils_rmdir_r(delta->old_file.path, workdir, flg);
/* ignore error if empty_only, because that just means we lacked
* info to do the right thing when the action was picked.
*/
if (error < 0 && !empty_only)
if (error < 0)
return error;
data->completed_steps++;
......@@ -715,19 +899,57 @@ static int checkout_remove_the_old(
}
}
git_vector_foreach(&data->removes, i, str) {
error = git_futils_rmdir_r(str, workdir, flg);
if (error < 0)
return error;
data->completed_steps++;
report_progress(data, str);
}
return 0;
}
static int checkout_deferred_remove(git_repository *repo, const char *path)
{
#if 0
int error = git_futils_rmdir_r(
path, git_repository_workdir(repo), GIT_RMDIR_EMPTY_PARENTS);
if (error == GIT_ENOTFOUND) {
error = 0;
giterr_clear();
}
return error;
#else
GIT_UNUSED(repo);
GIT_UNUSED(path);
return 0;
#endif
}
static int checkout_create_the_new(
unsigned int *actions,
checkout_diff_data *data)
checkout_data *data)
{
int error = 0;
git_diff_delta *delta;
size_t i;
git_vector_foreach(&data->diff->deltas, i, delta) {
if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
/* this had a blocker directory that should only be removed iff
* all of the contents of the directory were safely removed
*/
if ((error = checkout_deferred_remove(
data->repo, delta->old_file.path)) < 0)
return error;
}
if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
int error = checkout_blob(data, &delta->new_file);
error = checkout_blob(data, &delta->new_file);
if (error < 0)
return error;
......@@ -741,12 +963,22 @@ static int checkout_create_the_new(
static int checkout_create_submodules(
unsigned int *actions,
checkout_diff_data *data)
checkout_data *data)
{
int error = 0;
git_diff_delta *delta;
size_t i;
git_vector_foreach(&data->diff->deltas, i, delta) {
if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
/* this has a blocker directory that should only be removed iff
* all of the contents of the directory were safely removed
*/
if ((error = checkout_deferred_remove(
data->repo, delta->old_file.path)) < 0)
return error;
}
if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
int error = checkout_submodule(data, &delta->new_file);
if (error < 0)
......@@ -760,70 +992,176 @@ static int checkout_create_submodules(
return 0;
}
static int retrieve_symlink_caps(git_repository *repo, bool *out)
static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
{
int error = 0;
git_reference *ref = NULL;
git_object *head;
if (!(error = git_repository_head(&ref, repo)) &&
!(error = git_reference_peel(&head, ref, GIT_OBJ_TREE)))
*out = (git_tree *)head;
git_reference_free(ref);
return error;
}
static void checkout_data_clear(checkout_data *data)
{
if (data->opts_free_baseline) {
git_tree_free(data->opts.baseline);
data->opts.baseline = NULL;
}
git_vector_free(&data->removes);
git_pool_clear(&data->pool);
git__free(data->pfx);
data->pfx = NULL;
git_buf_free(&data->path);
}
static int checkout_data_init(
checkout_data *data,
git_repository *repo,
git_checkout_opts *proposed)
{
int error = 0;
git_config *cfg;
int error, can_symlink = 0;
if (git_repository_config__weakptr(&cfg, repo) < 0)
memset(data, 0, sizeof(*data));
if (!repo) {
giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing");
return -1;
}
error = git_config_get_bool(&can_symlink, cfg, "core.symlinks");
if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
return error;
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
return error;
data->repo = repo;
GITERR_CHECK_VERSION(
proposed, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts");
if (!proposed)
GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTS_VERSION);
else
memmove(&data->opts, proposed, sizeof(git_checkout_opts));
/* if you are forcing, definitely allow safe updates */
if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE;
if ((data->opts.checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0)
data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
data->strategy = data->opts.checkout_strategy;
/* opts->disable_filters is false by default */
if (!data->opts.dir_mode)
data->opts.dir_mode = GIT_DIR_MODE;
if (!data->opts.file_open_flags)
data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
data->pfx = git_pathspec_prefix(&data->opts.paths);
error = git_config_get_bool(&data->can_symlink, cfg, "core.symlinks");
if (error < 0) {
if (error != GIT_ENOTFOUND)
goto cleanup;
/* If "core.symlinks" is not found anywhere, default to true. */
if (error == GIT_ENOTFOUND) {
can_symlink = true;
data->can_symlink = true;
giterr_clear();
error = 0;
}
*out = can_symlink;
if (!data->opts.baseline) {
data->opts_free_baseline = true;
if ((error = checkout_lookup_head_tree(&data->opts.baseline, repo)) < 0)
goto cleanup;
}
if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
(error = git_pool_init(&data->pool, 1, 0)) < 0 ||
(error = git_buf_puts(&data->path, git_repository_workdir(repo))) < 0)
goto cleanup;
data->workdir_len = git_buf_len(&data->path);
cleanup:
if (error < 0)
checkout_data_clear(data);
return error;
}
int git_checkout__from_iterators(
git_iterator *desired,
git_iterator *expected,
git_checkout_opts *opts,
const char *pathspec_pfx)
int git_checkout_iterator(
git_iterator *target,
git_checkout_opts *opts)
{
int error = 0;
checkout_diff_data data;
git_iterator *baseline = NULL, *workdir = NULL;
checkout_data data = {0};
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
git_buf workdir = GIT_BUF_INIT;
uint32_t *actions = NULL;
size_t *counts = NULL;
memset(&data, 0, sizeof(data));
data.repo = git_iterator_owner(desired);
if (!data.repo) data.repo = git_iterator_owner(expected);
if (!data.repo) {
giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing");
return -1;
}
/* initialize structures and options */
error = checkout_data_init(&data, git_iterator_owner(target), opts);
if (error < 0)
return error;
diff_opts.flags =
GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED |
GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_SKIP_BINARY_CHECK;
if (opts->paths.count > 0)
diff_opts.pathspec = opts->paths;
/* By analyzing the cases above, it becomes clear that checkout can work
* off the diff between the desired and expected trees, instead of using
* a work dir diff. This should make things somewhat faster...
*/
if ((error = git_diff__from_iterators(
&data.diff, data.repo, expected, desired, &diff_opts)) < 0)
GIT_DIFF_INCLUDE_UNMODIFIED |
GIT_DIFF_INCLUDE_UNTRACKED |
GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */
GIT_DIFF_INCLUDE_IGNORED |
GIT_DIFF_INCLUDE_TYPECHANGE |
GIT_DIFF_INCLUDE_TYPECHANGE_TREES |
GIT_DIFF_SKIP_BINARY_CHECK;
if (data.opts.paths.count > 0)
diff_opts.pathspec = data.opts.paths;
/* set up iterators */
if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||
(error = git_iterator_for_workdir_range(
&workdir, data.repo, data.pfx, data.pfx)) < 0 ||
(error = git_iterator_for_tree_range(
&baseline, data.opts.baseline, data.pfx, data.pfx)) < 0)
goto cleanup;
if ((error = git_buf_puts(&workdir, git_repository_workdir(data.repo))) < 0)
/* Handle case insensitivity for baseline if necessary */
if (workdir->ignore_case && !baseline->ignore_case) {
if ((error = git_iterator_spoolandsort(
&baseline, baseline, git_index_entry__cmp_icase, true)) < 0)
goto cleanup;
}
data.opts = opts;
data.pfx = pathspec_pfx;
data.path = &workdir;
data.workdir_len = git_buf_len(&workdir);
/* Checkout can 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.
*
* I've implemented both versions and opted for the second.
*/
if ((error = git_diff__from_iterators(
&data.diff, data.repo, baseline, target, &diff_opts)) < 0)
goto cleanup;
/* In order to detect conflicts prior to performing any operations,
* and in order to deal with some order dependencies, checkout is best
......@@ -837,16 +1175,13 @@ int git_checkout__from_iterators(
* 3. Then update all submodules in case a new .gitmodules blob was
* checked out during pass #2.
*/
if ((error = checkout_get_actions(&actions, &counts, &data)) < 0)
if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0)
goto cleanup;
data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
counts[CHECKOUT_ACTION__UPDATE_BLOB] +
counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];
if ((error = retrieve_symlink_caps(data.repo, &data.can_symlink)) < 0)
goto cleanup;
report_progress(&data, NULL); /* establish 0 baseline */
/* TODO: add ability to update index entries while checking out */
......@@ -870,107 +1205,35 @@ cleanup:
giterr_clear();
git_diff_list_free(data.diff);
git_buf_free(&workdir);
git_iterator_free(workdir);
git_iterator_free(data.baseline);
git__free(actions);
git__free(counts);
checkout_data_clear(&data);
return error;
}
static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
{
int error = 0;
git_reference *ref = NULL;
git_object *head;
if (!(error = git_repository_head(&ref, repo)) &&
!(error = git_reference_peel(&head, ref, GIT_OBJ_TREE)))
*out = (git_tree *)head;
git_reference_free(ref);
return error;
}
static int checkout_normalize_opts(
git_checkout_opts *normalized,
char **pfx,
git_repository *repo,
git_checkout_opts *proposed)
{
assert(normalized);
GITERR_CHECK_VERSION(
proposed, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts");
if (!proposed)
GIT_INIT_STRUCTURE(normalized, GIT_CHECKOUT_OPTS_VERSION);
else
memmove(normalized, proposed, sizeof(git_checkout_opts));
/* if you are forcing, definitely allow safe updates */
if ((normalized->checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
normalized->checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE;
if ((normalized->checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0)
normalized->checkout_strategy |= GIT_CHECKOUT_SAFE;
/* opts->disable_filters is false by default */
if (!normalized->dir_mode)
normalized->dir_mode = GIT_DIR_MODE;
if (!normalized->file_open_flags)
normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
if (pfx)
*pfx = git_pathspec_prefix(&normalized->paths);
if (!normalized->baseline) {
normalized->checkout_strategy |= GIT_CHECKOUT__FREE_BASELINE;
return checkout_lookup_head_tree(&normalized->baseline, repo);
}
return 0;
}
static void checkout_cleanup_opts(git_checkout_opts *opts)
{
if ((opts->checkout_strategy & GIT_CHECKOUT__FREE_BASELINE) != 0)
git_tree_free(opts->baseline);
}
int git_checkout_index(
git_repository *repo,
git_index *index,
git_checkout_opts *opts)
{
int error;
git_checkout_opts co_opts;
git_iterator *base_i, *index_i;
char *pfx;
assert(repo);
GITERR_CHECK_VERSION(opts, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts");
git_iterator *index_i;
if ((error = git_repository__ensure_not_bare(repo, "checkout index")) < 0)
return error;
if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
return error;
GIT_REFCOUNT_INC(index);
if (!(error = checkout_normalize_opts(&co_opts, &pfx, repo, opts)) &&
!(error = git_iterator_for_tree_range(
&base_i, co_opts.baseline, pfx, pfx)) &&
!(error = git_iterator_for_index_range(&index_i, index, pfx, pfx)))
error = git_checkout__from_iterators(index_i, base_i, &co_opts, pfx);
if (!(error = git_iterator_for_index(&index_i, index)))
error = git_checkout_iterator(index_i, opts);
git__free(pfx);
git_iterator_free(index_i);
git_iterator_free(base_i);
checkout_cleanup_opts(&co_opts);
git_index_free(index);
return error;
}
......@@ -981,12 +1244,8 @@ int git_checkout_tree(
git_checkout_opts *opts)
{
int error;
git_checkout_opts co_opts;
git_tree *tree;
git_iterator *tree_i, *base_i;
char *pfx;
assert(repo);
git_tree *tree = NULL;
git_iterator *tree_i = NULL;
if ((error = git_repository__ensure_not_bare(repo, "checkout tree")) < 0)
return error;
......@@ -997,17 +1256,11 @@ int git_checkout_tree(
return -1;
}
if (!(error = checkout_normalize_opts(&co_opts, &pfx, repo, opts)) &&
!(error = git_iterator_for_tree_range(
&base_i, co_opts.baseline, pfx, pfx)) &&
!(error = git_iterator_for_tree_range(&tree_i, tree, pfx, pfx)))
error = git_checkout__from_iterators(tree_i, base_i, &co_opts, pfx);
if (!(error = git_iterator_for_tree(&tree_i, tree)))
error = git_checkout_iterator(tree_i, opts);
git__free(pfx);
git_iterator_free(tree_i);
git_iterator_free(base_i);
git_tree_free(tree);
checkout_cleanup_opts(&co_opts);
return error;
}
......@@ -1017,30 +1270,18 @@ int git_checkout_head(
git_checkout_opts *opts)
{
int error;
git_checkout_opts co_opts;
git_tree *head;
git_iterator *i1, *i2;
char *pfx;
assert(repo);
git_tree *head = NULL;
git_iterator *head_i = NULL;
if ((error = git_repository__ensure_not_bare(repo, "checkout head")) < 0)
return error;
if ((error = checkout_lookup_head_tree(&head, repo)) < 0)
return error;
if (!(error = checkout_normalize_opts(&co_opts, &pfx, repo, opts)) &&
!(error = git_iterator_for_tree_range(
&i1, co_opts.baseline, pfx, pfx)) &&
!(error = git_iterator_for_tree_range(&i2, head, pfx, pfx)))
error = git_checkout__from_iterators(i1, i2, &co_opts, pfx);
if (!(error = checkout_lookup_head_tree(&head, repo)) &&
!(error = git_iterator_for_tree(&head_i, head)))
error = git_checkout_iterator(head_i, opts);
git__free(pfx);
git_iterator_free(i1);
git_iterator_free(i2);
git_iterator_free(head_i);
git_tree_free(head);
checkout_cleanup_opts(&co_opts);
return error;
}
......@@ -10,22 +10,15 @@
#include "git2/checkout.h"
#include "iterator.h"
#define GIT_CHECKOUT__FREE_BASELINE (1u << 24)
#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
/**
* Given a working directory which is expected to match the contents
* of iterator "expected", this will make the directory match the
* contents of "desired" according to the rules in the checkout "opts".
*
* Because the iterators for the desired and expected values were already
* created when this is invoked, if the checkout opts `paths` is in play,
* then presumably the pathspec_pfx was already computed, so it should be
* passed in to prevent reallocation.
* 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__from_iterators(
git_iterator *desired,
git_iterator *expected,
git_checkout_opts *opts,
const char *pathspec_pfx);
extern int git_checkout_iterator(
git_iterator *target,
git_checkout_opts *opts);
#endif
......@@ -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(
......@@ -616,7 +621,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 +629,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 +651,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 +663,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 +676,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 +722,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;
......
......@@ -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,8 +24,6 @@ void test_checkout_index__initialize(void)
{
git_tree *tree;
GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION);
g_repo = cl_git_sandbox_init("testrepo");
cl_git_pass(git_repository_head_tree(&tree, g_repo));
......@@ -65,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));
......@@ -73,13 +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"));
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
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/README", "hey there\n");
test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
......@@ -88,34 +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 =
opts.checkout_strategy =
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED;
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
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"));
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
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");
......@@ -139,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";
......@@ -146,9 +148,9 @@ void test_checkout_index__honor_the_gitattributes_directives(void)
cl_git_mkfile("./testrepo/.gitattributes", attributes);
set_core_autocrlf_to(false);
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
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/README", "hey there\n");
test_file_contents("./testrepo/new.txt", "my new file\n");
......@@ -158,14 +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);
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
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/README", expected_readme_text);
#endif
......@@ -178,11 +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);
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
#ifdef GIT_WIN32
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
......@@ -202,55 +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);
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
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/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_SAFE | 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_FORCE;
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.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
g_opts.disable_filters = false;
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
opts.disable_filters = false;
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\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");
}
......@@ -258,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;
......@@ -267,10 +281,10 @@ void test_checkout_index__options_dir_modes(void)
reset_index_to_treeish((git_object *)commit);
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
g_opts.dir_mode = 0701;
opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
opts.dir_mode = 0701;
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/a", &st));
cl_assert_equal_i(st.st_mode & 0777, 0701);
......@@ -286,12 +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.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
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);
......@@ -300,13 +315,15 @@ 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.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
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_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, NULL, &opts));
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
}
......@@ -316,28 +333,29 @@ struct notify_data {
const char *sha;
};
static int notify_cb(
const char *file,
unsigned int status,
const git_oid *blob_oid,
unsigned int checkout_mode,
unsigned int workdir_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 notify_data *expectations = (struct notify_data *)payload;
GIT_UNUSED(checkout_mode);
GIT_UNUSED(workdir_mode);
GIT_UNUSED(status);
GIT_UNUSED(workdir);
cl_assert_equal_s(expectations->file, 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)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
struct notify_data data;
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
......@@ -351,28 +369,28 @@ void test_checkout_index__can_notify_of_skipped_files(void)
data.file = "new.txt";
data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
g_opts.checkout_strategy =
opts.checkout_strategy =
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
g_opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICTS;
g_opts.notify_cb = notify_cb;
g_opts.notify_payload = &data;
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_notify_cb(
const char *file,
unsigned int status,
const git_oid *blob_oid,
unsigned int checkout_mode,
unsigned int workdir_mode,
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(file);
GIT_UNUSED(status);
GIT_UNUSED(blob_oid);
GIT_UNUSED(checkout_mode);
GIT_UNUSED(workdir_mode);
GIT_UNUSED(why);
GIT_UNUSED(path);
GIT_UNUSED(baseline);
GIT_UNUSED(target);
GIT_UNUSED(workdir);
GIT_UNUSED(payload);
cl_assert(false);
......@@ -382,18 +400,20 @@ static int dont_notify_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 =
opts.checkout_strategy =
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
g_opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICTS;
g_opts.notify_cb = dont_notify_cb;
g_opts.notify_payload = NULL;
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 checkout_progress_counter(
......@@ -405,18 +425,20 @@ static void checkout_progress_counter(
void test_checkout_index__calls_progress_callback(void)
{
git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
int calls = 0;
g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
g_opts.progress_cb = checkout_progress_counter;
g_opts.progress_payload = &calls;
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, &g_opts));
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));
......@@ -440,15 +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 =
opts.checkout_strategy =
GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
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"));
......@@ -458,17 +480,18 @@ 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);
......
......@@ -126,9 +126,10 @@ void test_checkout_tree__doesnt_write_unrequested_files_to_worktree(void)
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));
/* A GIT_CHECKOUT_DEFAULT checkout is not allowed to add any file to the
* working tree from the index as it is supposed to be a dry run. */
opts.checkout_strategy = GIT_CHECKOUT_DEFAULT;
/* 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"));
}
......@@ -44,7 +44,8 @@ void test_checkout_typechange__checkout_typechanges(void)
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]); */
/* fprintf(stderr, "---- checking out '%s' ----\n", g_typechange_oids[i]); */
cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment