summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/checkout.h60
-rw-r--r--src/checkout.c1023
-rw-r--r--src/checkout.h21
-rw-r--r--src/diff.c43
-rw-r--r--src/fileops.c18
-rw-r--r--tests-clar/checkout/index.c187
-rw-r--r--tests-clar/checkout/tree.c7
-rw-r--r--tests-clar/checkout/typechange.c3
8 files changed, 823 insertions, 539 deletions
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index 5eedd7bfd..196962bb9 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -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,11 +205,12 @@ typedef enum {
* Dirty files will only have work tree flags set.
*/
typedef enum {
- GIT_CHECKOUT_NOTIFY_CONFLICTS = (1u << 0),
- GIT_CHECKOUT_NOTIFY_DIRTY = (1u << 1),
- GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2),
+ 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_IGNORED = (1u << 4),
} git_checkout_notify_t;
/**
@@ -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;
diff --git a/src/checkout.c b/src/checkout.c
index 8e8c41bd5..5aeb0624c 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -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);
- return 0;
+ 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);
+}
+
+#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_for_delta(
- checkout_diff_data *data,
+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;
- 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;
- break;
- case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
- if (!S_ISDIR(delta->new_file.mode))
- action = safe;
- break;
- case GIT_DELTA_DELETED: /* case 8 or 25 */
- default: /* impossible */ break;
- }
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 12 */
+ 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 = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
+ 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;
}
- /* 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))
- 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;
- }
+ 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 blob (or submodule) */
- else {
- 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))
- {
- if (checkout_notify(
- data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wditem))
- return GIT_EUSER;
+ if (checkout_notify(data, notify, NULL, wd))
+ return GIT_EUSER;
- if (force)
- action = CHECKOUT_ACTION__UPDATE_BLOB;
- }
- break;
- case GIT_DELTA_ADDED: /* case 3, 4 or 6 */
- action = force;
- 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;
- else
- action = safe ?
- CHECKOUT_ACTION__REMOVE : CHECKOUT_ACTION__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;
- else
- action = safe;
- 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;
- else
- action = safe;
- 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;
+ }
+
+ 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, wd))
+ {
+ if (checkout_notify(
+ data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))
+ return GIT_EUSER;
+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE);
}
+ break;
+ case GIT_DELTA_ADDED: /* case 3, 4 or 6 */
+ 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, wd))
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ else
+ 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, wd))
+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ else
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
+ 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 = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
+ 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;
+
+ /* move workdir iterator to follow along with deltas */
+
+ while (1) {
+ if (!wd)
+ return checkout_action_no_wd(data, delta);
+
+ cmp = strcomp(wd->path, delta->old_file.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) {
+ 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;
+ }
+
+ /* 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;
+ }
- while (wditem) {
- bool notify = false;
+ /* 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;
- cmp = data->diff->strcomp(delta->new_file.path, wditem->path);
- if (cmp >= 0)
- break;
+ *wditem_ptr = wd;
+ continue;
+ }
- if (!git_pathspec_match_path(
- pathspec, wditem->path, false, actual->ignore_case))
- notify = false;
+ if (cmp == 0) {
+ /* case 4 */
+ act = checkout_action_with_wd(data, delta, wd);
+ *wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd;
+ return act;
+ }
- else if (S_ISDIR(wditem->mode)) {
- cmp = data->diff->pfxcomp(delta->new_file.path, wditem->path);
+ cmp = pfxcomp(wd->path, delta->old_file.path);
- 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;
- continue;
+ 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;
}
- 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;
+ return checkout_action_with_wd_dir(data, delta, wd);
+ }
- wditem = *wditem_ptr;
- cmp = -1;
+ /* case 6 - wd is after delta */
+ return checkout_action_no_wd(data, delta);
}
- *cmp_out = cmp;
-
- return 0;
+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,71 +992,177 @@ 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 "core.symlinks" is not found anywhere, default to true. */
- if (error == GIT_ENOTFOUND) {
- can_symlink = true;
+ 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. */
+ 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));
+ /* initialize structures and options */
+ error = checkout_data_init(&data, git_iterator_owner(target), opts);
+ if (error < 0)
+ return error;
- 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;
+ diff_opts.flags =
+ 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;
+
+ /* 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;
}
- 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...
+ /* 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, expected, desired, &diff_opts)) < 0)
- goto cleanup;
-
- if ((error = git_buf_puts(&workdir, git_repository_workdir(data.repo))) < 0)
+ &data.diff, data.repo, baseline, target, &diff_opts)) < 0)
goto cleanup;
- data.opts = opts;
- data.pfx = pathspec_pfx;
- data.path = &workdir;
- data.workdir_len = git_buf_len(&workdir);
-
/* In order to detect conflicts prior to performing any operations,
* and in order to deal with some order dependencies, checkout is best
* performed with up to four passes through the diff.
@@ -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;
}
diff --git a/src/checkout.h b/src/checkout.h
index 651b0033f..815abdfed 100644
--- a/src/checkout.h
+++ b/src/checkout.h
@@ -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
diff --git a/src/diff.c b/src/diff.c
index 83e73cd03..042cdf451 100644
--- a/src/diff.c
+++ b/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(
@@ -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;
diff --git a/src/fileops.c b/src/fileops.c
index 7f023bf69..47b47d6c8 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -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;
+
+ if (git_buf_len(path) <= data->baselen)
+ return GIT_ITEROVER;
- GIT_UNUSED(opaque);
+ 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);
}
}
@@ -475,9 +480,10 @@ int git_futils_rmdir_r(
if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
return -1;
- data.base = base ? base : "";
- data.flags = flags;
- data.error = 0;
+ data.base = base ? base : "";
+ data.baselen = base ? strlen(base) : 0;
+ data.flags = flags;
+ data.error = 0;
error = futils__rmdir_recurs_foreach(&data, &fullpath);
diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c
index d42b69e23..b1778a422 100644
--- a/tests-clar/checkout/index.c
+++ b/tests-clar/checkout/index.c
@@ -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);
diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c
index ed46748ae..ab502dd30 100644
--- a/tests-clar/checkout/tree.c
+++ b/tests-clar/checkout/tree.c
@@ -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"));
}
diff --git a/tests-clar/checkout/typechange.c b/tests-clar/checkout/typechange.c
index 85da11570..bc7039caa 100644
--- a/tests-clar/checkout/typechange.c
+++ b/tests-clar/checkout/typechange.c
@@ -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));