summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/checkout.h155
-rw-r--r--include/git2/errors.h1
-rw-r--r--src/checkout.c466
-rw-r--r--src/fileops.c48
-rw-r--r--src/fileops.h2
-rw-r--r--src/index.c53
-rw-r--r--src/index.h3
-rw-r--r--src/path.c5
-rw-r--r--src/reset.c5
-rw-r--r--src/stash.c4
-rw-r--r--tests-clar/checkout/index.c74
-rw-r--r--tests-clar/checkout/tree.c5
-rw-r--r--tests-clar/checkout/typechange.c13
-rw-r--r--tests-clar/clone/network.c2
14 files changed, 610 insertions, 226 deletions
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index 390d2f215..a9314c2cb 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -25,20 +25,117 @@ GIT_BEGIN_DECL
* Checkout behavior flags
*
* These flags control what checkout does with files. Pass in a
- * combination of these values OR'ed together.
+ * combination of these values OR'ed together. If you just pass zero
+ * (i.e. no flags), then you are effectively doing a "dry run" where no
+ * files will be modified.
+ *
+ * Checkout groups the working directory content into 3 classes of files:
+ * (1) files that don't need a change, and files that do need a change
+ * that either (2) we are allowed to modifed or (3) we are not. The flags
+ * you pass in will decide which files we are allowed to modify.
+ *
+ * By default, checkout is not allowed to modify any files. Anything
+ * needing a change would be considered a conflict.
+ *
+ * GIT_CHECKOUT_UPDATE_UNMODIFIED means that checkout is allowed to update
+ * any file where the working directory content matches the HEAD
+ * (e.g. either the files match or the file is absent in both places).
+ *
+ * GIT_CHECKOUT_UPDATE_MISSING means checkout can create a missing file
+ * that exists in the index and does not exist in the working directory.
+ * This is usually desirable for initial checkout, etc. Technically, the
+ * missing file differs from the HEAD, which is why this is separate.
+ *
+ * GIT_CHECKOUT_UPDATE_MODIFIED means checkout is allowed to update files
+ * where the working directory does not match the HEAD so long as the file
+ * actually exists in the HEAD. This option implies UPDATE_UNMODIFIED.
+ *
+ * GIT_CHECKOUT_UPDATE_UNTRACKED means checkout is allowed to update files
+ * even if there is a working directory version that does not exist in the
+ * HEAD (i.e. the file was independently created in the workdir). This
+ * implies UPDATE_UNMODIFIED | UPDATE_MISSING (but *not* UPDATE_MODIFIED).
+ *
+ *
+ * On top of these three basic strategies, there are some modifiers
+ * options that can be applied:
+ *
+ * If any files need update but are disallowed by the strategy, normally
+ * checkout calls the conflict callback (if given) and then aborts.
+ * GIT_CHECKOUT_ALLOW_CONFLICTS means it is okay to update the files that
+ * are allowed by the strategy even if there are conflicts. The conflict
+ * callbacks are still made, but non-conflicting files will be updated.
+ *
+ * Any unmerged entries in the index are automatically considered conflicts.
+ * If you want to proceed anyhow and just skip unmerged entries, you can use
+ * GIT_CHECKOUT_SKIP_UNMERGED which is less dangerous than just allowing all
+ * conflicts. Alternatively, use GIT_CHECKOUT_USE_OURS to proceed and
+ * checkout the stage 2 ("ours") version. GIT_CHECKOUT_USE_THEIRS means to
+ * proceed and use the stage 3 ("theirs") version.
+ *
+ * GIT_CHECKOUT_UPDATE_ONLY means that update is not allowed to create new
+ * files or delete old ones, only update existing content. With this
+ * flag, files that needs to be created or deleted are not conflicts -
+ * they are just skipped. This also skips typechanges to existing files
+ * (because the old would have to be removed).
+ *
+ * GIT_CHECKOUT_REMOVE_UNTRACKED means that files in the working directory
+ * that are untracked (and not ignored) will be removed altogether. These
+ * untracked files (that do not shadow index entries) are not considered
+ * conflicts and would normally be ignored.
+ *
+ *
+ * Checkout is "semi-atomic" as in it will go through the work to be done
+ * before making any changes and if may decide to abort if there are
+ * conflicts, or you can use the conflict callback to explicitly abort the
+ * action before any updates are made. Despite this, if a second process
+ * is modifying the filesystem while checkout is running, it can't
+ * guarantee that the choices is makes while initially examining the
+ * filesystem are still going to be correct as it applies them.
*/
typedef enum {
- /** Checkout does not update any files in the working directory. */
- GIT_CHECKOUT_DEFAULT = (1 << 0),
+ GIT_CHECKOUT_DEFAULT = 0, /** default is a dry run, no actual updates */
+
+ /** Allow update of entries where working dir matches HEAD. */
+ GIT_CHECKOUT_UPDATE_UNMODIFIED = (1u << 0),
+
+ /** Allow update of entries where working dir does not have file. */
+ GIT_CHECKOUT_UPDATE_MISSING = (1u << 1),
+
+ /** Allow safe updates that cannot overwrite uncommited data */
+ GIT_CHECKOUT_SAFE =
+ (GIT_CHECKOUT_UPDATE_UNMODIFIED | GIT_CHECKOUT_UPDATE_MISSING),
+
+ /** Allow update of entries in working dir that are modified from HEAD. */
+ GIT_CHECKOUT_UPDATE_MODIFIED = (1u << 2),
+
+ /** Update existing untracked files that are now present in the index. */
+ GIT_CHECKOUT_UPDATE_UNTRACKED = (1u << 3),
- /** When a file exists and is modified, replace it with new version. */
- GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1),
+ /** Allow all updates to force working directory to look like index */
+ GIT_CHECKOUT_FORCE =
+ (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED),
- /** When a file does not exist in the working directory, create it. */
- GIT_CHECKOUT_CREATE_MISSING = (1 << 2),
+ /** Allow checkout to make updates even if conflicts are found */
+ GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),
+
+ /** Remove untracked files not in index (that are not ignored) */
+ GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),
+
+ /** Only update existing files, don't create new ones */
+ GIT_CHECKOUT_UPDATE_ONLY = (1u << 6),
+
+ /** Allow checkout to skip unmerged files */
+ GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
+ /** For unmerged files, checkout stage 2 from index */
+ GIT_CHECKOUT_USE_OURS = (1u << 11),
+ /** For unmerged files, checkout stage 3 from index */
+ GIT_CHECKOUT_USE_THEIRS = (1u << 12),
+
+ /** Recursively checkout submodule with same options */
+ GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),
+ /** Recursively checkout submodules only if HEAD moved in super repo */
+ GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17),
- /** If an untracked file in found in the working dir, delete it. */
- GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 3),
} git_checkout_strategy_t;
/**
@@ -47,38 +144,38 @@ typedef enum {
* Use zeros to indicate default settings.
*/
typedef struct git_checkout_opts {
- unsigned int checkout_strategy; /** default: GIT_CHECKOUT_DEFAULT */
+ unsigned int checkout_strategy; /** default will be a dry run */
+
int disable_filters; /** don't apply filters like CRLF conversion */
int dir_mode; /** default is 0755 */
int file_mode; /** default is 0644 or 0755 as dictated by blob */
int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
- /** Optional callback to notify the consumer of files that
- * haven't be checked out because a modified version of them
- * exist in the working directory.
- *
- * When provided, this callback will be invoked when the flag
- * GIT_CHECKOUT_OVERWRITE_MODIFIED isn't part of the checkout strategy.
+ /** Optional callback made on files where the index differs from the
+ * working directory but the rules do not allow update. Return a
+ * non-zero value to abort the checkout. All such callbacks will be
+ * made before any changes are made to the working directory.
*/
- int (* skipped_notify_cb)(
- const char *skipped_file,
- const git_oid *blob_oid,
- int file_mode,
+ int (*conflict_cb)(
+ const char *conflicting_path,
+ const git_oid *index_oid,
+ unsigned int index_mode,
+ unsigned int wd_mode,
void *payload);
- void *notify_payload;
+ void *conflict_payload;
/* Optional callback to notify the consumer of checkout progress. */
- void (* progress_cb)(
- const char *path,
- size_t completed_steps,
- size_t total_steps,
- void *payload);
+ void (*progress_cb)(
+ const char *path,
+ size_t completed_steps,
+ size_t total_steps,
+ void *payload);
void *progress_payload;
- /** When not NULL, array of fnmatch patterns specifying
- * which paths should be taken into account
+ /** When not zeroed out, array of fnmatch patterns specifying which
+ * paths should be taken into account, otherwise all files.
*/
- git_strarray paths;
+ git_strarray paths;
} git_checkout_opts;
/**
diff --git a/include/git2/errors.h b/include/git2/errors.h
index 8bb47f354..45e04578d 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -60,6 +60,7 @@ typedef enum {
GITERR_SUBMODULE,
GITERR_THREAD,
GITERR_STASH,
+ GITERR_CHECKOUT,
} git_error_t;
/**
diff --git a/src/checkout.c b/src/checkout.c
index e4a397cd5..2bad06501 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -22,20 +22,19 @@
#include "filter.h"
#include "blob.h"
#include "diff.h"
+#include "pathspec.h"
-struct checkout_diff_data
-{
+typedef struct {
+ git_repository *repo;
+ git_diff_list *diff;
+ git_checkout_opts *opts;
git_buf *path;
size_t workdir_len;
- git_checkout_opts *checkout_opts;
- git_repository *owner;
bool can_symlink;
- bool found_submodules;
- bool create_submodules;
int error;
size_t total_steps;
size_t completed_steps;
-};
+} checkout_diff_data;
static int buffer_to_file(
git_buf *buffer,
@@ -112,7 +111,8 @@ static int blob_content_to_file(
if (!file_mode)
file_mode = entry_filemode;
- error = buffer_to_file(&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
+ error = buffer_to_file(
+ &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
cleanup:
git_filters_free(&filters);
@@ -123,7 +123,8 @@ cleanup:
return error;
}
-static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink)
+static int blob_content_to_link(
+ git_blob *blob, const char *path, bool can_symlink)
{
git_buf linktarget = GIT_BUF_INIT;
int error;
@@ -142,58 +143,52 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli
}
static int checkout_submodule(
- struct checkout_diff_data *data,
+ checkout_diff_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)
+ return 0;
+
if (git_futils_mkdir(
- file->path, git_repository_workdir(data->owner),
- data->checkout_opts->dir_mode, GIT_MKDIR_PATH) < 0)
+ file->path, git_repository_workdir(data->repo),
+ data->opts->dir_mode, GIT_MKDIR_PATH) < 0)
return -1;
- /* TODO: two cases:
+ /* TODO: Support checkout_strategy options. Two circumstances:
* 1 - submodule already checked out, but we need to move the HEAD
* to the new OID, or
* 2 - submodule not checked out and we should recursively check it out
*
- * Checkout will not execute a pull request on the submodule, but a
- * clone command should probably be able to. Do we need a submodule
- * callback option?
+ * Checkout will not execute a pull on the submodule, but a clone
+ * command should probably be able to. Do we need a submodule callback?
*/
return 0;
}
static void report_progress(
- struct checkout_diff_data *data,
+ checkout_diff_data *data,
const char *path)
{
- if (data->checkout_opts->progress_cb)
- data->checkout_opts->progress_cb(
- path,
- data->completed_steps,
- data->total_steps,
- data->checkout_opts->progress_payload);
+ if (data->opts->progress_cb)
+ data->opts->progress_cb(
+ path, data->completed_steps, data->total_steps,
+ data->opts->progress_payload);
}
static int checkout_blob(
- struct checkout_diff_data *data,
+ checkout_diff_data *data,
const git_diff_file *file)
{
- git_blob *blob;
int error = 0;
+ git_blob *blob;
git_buf_truncate(data->path, data->workdir_len);
- if (git_buf_joinpath(data->path, git_buf_cstr(data->path), file->path) < 0)
+ if (git_buf_puts(data->path, file->path) < 0)
return -1;
- /* If there is a directory where this blob should go, then there is an
- * existing tree that conflicts with this file, but REMOVE_UNTRACKED must
- * not have been passed in. Signal to caller with GIT_ENOTFOUND.
- */
- if (git_path_isdir(git_buf_cstr(data->path)))
- return GIT_ENOTFOUND;
-
- if ((error = git_blob_lookup(&blob, data->owner, &file->oid)) < 0)
+ if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)
return error;
if (S_ISLNK(file->mode))
@@ -201,14 +196,14 @@ static int checkout_blob(
blob, git_buf_cstr(data->path), data->can_symlink);
else
error = blob_content_to_file(
- blob, git_buf_cstr(data->path), file->mode, data->checkout_opts);
+ blob, git_buf_cstr(data->path), file->mode, data->opts);
git_blob_free(blob);
return error;
}
-static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink)
+static int retrieve_symlink_caps(git_repository *repo, bool *can_symlink)
{
git_config *cfg;
int error;
@@ -218,10 +213,7 @@ static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink
error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks");
- /*
- * When no "core.symlinks" entry is found in any of the configuration
- * store (local, global or system), default value is "true".
- */
+ /* If "core.symlinks" is not found anywhere, default to true. */
if (error == GIT_ENOTFOUND) {
*can_symlink = true;
error = 0;
@@ -230,7 +222,8 @@ static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink
return error;
}
-static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *proposed)
+static void normalize_options(
+ git_checkout_opts *normalized, git_checkout_opts *proposed)
{
assert(normalized);
@@ -239,11 +232,16 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *
else
memmove(normalized, proposed, sizeof(git_checkout_opts));
- /* Default options */
- if (!normalized->checkout_strategy)
- normalized->checkout_strategy = GIT_CHECKOUT_DEFAULT;
+ /* implied checkout strategies */
+ if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_MODIFIED) != 0 ||
+ (normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_UNMODIFIED;
+
+ if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_MISSING;
/* opts->disable_filters is false by default */
+
if (!normalized->dir_mode)
normalized->dir_mode = GIT_DIR_MODE;
@@ -254,50 +252,174 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *
enum {
CHECKOUT_ACTION__NONE = 0,
CHECKOUT_ACTION__REMOVE = 1,
- CHECKOUT_ACTION__CREATE_BLOB = 2,
- CHECKOUT_ACTION__CREATE_SUBMODULE = 4,
- CHECKOUT_ACTION__NOTIFY = 8
+ CHECKOUT_ACTION__UPDATE_BLOB = 2,
+ CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
+ CHECKOUT_ACTION__CONFLICT = 8,
+ CHECKOUT_ACTION__MAX = 8
};
-static uint32_t checkout_action_for_delta(
- git_checkout_opts *opts,
- const git_diff_delta *delta)
+static int checkout_confirm_update_blob(
+ checkout_diff_data *data,
+ const git_diff_delta *delta,
+ int action)
{
- uint32_t action = CHECKOUT_ACTION__NONE;
+ int error;
+ unsigned int strat = data->opts->checkout_strategy;
+ struct stat st;
+ bool update_only = ((strat & GIT_CHECKOUT_UPDATE_ONLY) != 0);
+
+ /* for typechange, remove the old item first */
+ if (delta->status == GIT_DELTA_TYPECHANGE) {
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else
+ action |= CHECKOUT_ACTION__REMOVE;
- switch (delta->status) {
- case GIT_DELTA_UNTRACKED:
- if ((opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0)
+ return action;
+ }
+
+ git_buf_truncate(data->path, data->workdir_len);
+ if (git_buf_puts(data->path, delta->new_file.path) < 0)
+ return -1;
+
+ if ((error = p_stat(git_buf_cstr(data->path), &st)) < 0) {
+ if (errno == ENOENT) {
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ } else if (errno == ENOTDIR) {
+ /* File exists where a parent dir needs to go - i.e. untracked
+ * typechange. Ignore if UPDATE_ONLY, remove if allowed.
+ */
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ action |= CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ /* otherwise let error happen when we attempt blob checkout later */
+ }
+ else if (S_ISDIR(st.st_mode)) {
+ /* Directory exists where a blob needs to go - i.e. untracked
+ * typechange. Ignore if UPDATE_ONLY, remove if allowed.
+ */
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
action |= CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+
+ return action;
+}
+
+static int checkout_action_for_delta(
+ checkout_diff_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *head_entry)
+{
+ int action = CHECKOUT_ACTION__NONE;
+ unsigned int strat = data->opts->checkout_strategy;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ if (!head_entry) {
+ /* file independently created in wd, even though not in HEAD */
+ if ((strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ else if (!git_oid_equal(&head_entry->oid, &delta->old_file.oid)) {
+ /* working directory was independently updated to match index */
+ if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
break;
- case GIT_DELTA_TYPECHANGE:
- if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0)
- action |= CHECKOUT_ACTION__REMOVE | CHECKOUT_ACTION__CREATE_BLOB;
- else if (opts->skipped_notify_cb != NULL)
- action = CHECKOUT_ACTION__NOTIFY;
+ case GIT_DELTA_ADDED:
+ /* Impossible. New files should be UNTRACKED or TYPECHANGE */
+ action = CHECKOUT_ACTION__CONFLICT;
break;
case GIT_DELTA_DELETED:
- if ((opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0)
- action |= CHECKOUT_ACTION__CREATE_BLOB;
+ if (head_entry && /* working dir missing, but exists in HEAD */
+ (strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
break;
case GIT_DELTA_MODIFIED:
- if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0)
- action |= CHECKOUT_ACTION__CREATE_BLOB;
- else if (opts->skipped_notify_cb != NULL)
- action = CHECKOUT_ACTION__NOTIFY;
+ case GIT_DELTA_TYPECHANGE:
+ if (!head_entry) {
+ /* working dir was independently updated & does not match index */
+ if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ }
+ else if (git_oid_equal(&head_entry->oid, &delta->new_file.oid))
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
break;
+ case GIT_DELTA_UNTRACKED:
+ if (!head_entry) {
+ if ((strat & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0)
+ action = CHECKOUT_ACTION__REMOVE;
+ }
+ else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) != 0) {
+ action = CHECKOUT_ACTION__REMOVE;
+ } else if ((strat & GIT_CHECKOUT_UPDATE_UNMODIFIED) != 0) {
+ git_oid wd_oid;
+
+ /* if HEAD matches workdir, then remove, else conflict */
+
+ if (git_oid_iszero(&delta->new_file.oid) &&
+ git_diff__oid_for_file(
+ data->repo, delta->new_file.path, delta->new_file.mode,
+ delta->new_file.size, &wd_oid) < 0)
+ action = -1;
+ else if (git_oid_equal(&head_entry->oid, &wd_oid))
+ action = CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
+ } else {
+ /* present in HEAD and workdir, but absent in index */
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ break;
+
+ case GIT_DELTA_IGNORED:
default:
+ /* just skip these files */
break;
}
- if ((action & CHECKOUT_ACTION__CREATE_BLOB) != 0 &&
- S_ISGITLINK(delta->old_file.mode))
- action = (action & ~CHECKOUT_ACTION__CREATE_BLOB) |
- CHECKOUT_ACTION__CREATE_SUBMODULE;
+ if (action > 0 && (action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
+ if (S_ISGITLINK(delta->old_file.mode))
+ action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
+ CHECKOUT_ACTION__UPDATE_SUBMODULE;
+
+ action = checkout_confirm_update_blob(data, delta, action);
+ }
+
+ if (action == CHECKOUT_ACTION__CONFLICT &&
+ data->opts->conflict_cb != NULL &&
+ data->opts->conflict_cb(
+ delta->old_file.path, &delta->old_file.oid,
+ delta->old_file.mode, delta->new_file.mode,
+ data->opts->conflict_payload) != 0)
+ {
+ giterr_clear();
+ action = GIT_EUSER;
+ }
+
+ if (action > 0 && (strat & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ action = (action & ~CHECKOUT_ACTION__REMOVE);
return action;
}
@@ -305,53 +427,119 @@ static uint32_t checkout_action_for_delta(
static int checkout_get_actions(
uint32_t **actions_ptr,
size_t **counts_ptr,
- git_diff_list *diff,
- git_checkout_opts *opts)
+ checkout_diff_data *data)
{
+ int error;
+ git_diff_list *diff = data->diff;
git_diff_delta *delta;
size_t i, *counts;
uint32_t *actions;
+ git_tree *head = NULL;
+ git_iterator *hiter = NULL;
+ char *pfx = git_pathspec_prefix(&data->opts->paths);
+ const git_index_entry *he;
+
+ /* if there is no HEAD, that's okay - we'll make an empty iterator */
+ (void)git_repository_head_tree(&head, data->repo);
+
+ if ((error = git_iterator_for_tree_range(
+ &hiter, data->repo, head, pfx, pfx)) < 0)
+ goto fail;
+
+ if ((diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
+ !hiter->ignore_case &&
+ (error = git_iterator_spoolandsort(
+ &hiter, hiter, diff->entrycmp, true)) < 0)
+ goto fail;
+
+ if ((error = git_iterator_current(hiter, &he)) < 0)
+ goto fail;
+
+ git__free(pfx);
+
+ *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));
+ *actions_ptr = actions = git__calloc(diff->deltas.length, sizeof(uint32_t));
+ if (!counts || !actions) {
+ error = -1;
+ goto fail;
+ }
- *counts_ptr = counts =
- git__calloc(CHECKOUT_ACTION__NOTIFY, sizeof(size_t));
- GITERR_CHECK_ALLOC(counts);
+ git_vector_foreach(&diff->deltas, i, delta) {
+ int cmp = -1, act;
+
+ /* try to track HEAD entries parallel to deltas */
+ while (he) {
+ cmp = S_ISDIR(delta->new_file.mode) ?
+ diff->prefixcmp(he->path, delta->new_file.path) :
+ diff->strcmp(he->path, delta->old_file.path);
+ if (cmp >= 0)
+ break;
+ if (git_iterator_advance(hiter, &he) < 0)
+ he = NULL;
+ }
- *actions_ptr = actions =
- git__calloc(diff->deltas.length, sizeof(uint32_t));
- GITERR_CHECK_ALLOC(actions);
+ act = checkout_action_for_delta(data, delta, !cmp ? he : NULL);
- git_vector_foreach(&diff->deltas, i, delta) {
- actions[i] = checkout_action_for_delta(opts, delta);
+ if (act < 0) {
+ error = act;
+ goto fail;
+ }
- if (actions[i] & CHECKOUT_ACTION__REMOVE)
- counts[CHECKOUT_ACTION__REMOVE]++;
+ if (!cmp && git_iterator_advance(hiter, &he) < 0)
+ he = NULL;
- if (actions[i] & CHECKOUT_ACTION__CREATE_BLOB)
- counts[CHECKOUT_ACTION__CREATE_BLOB]++;
+ actions[i] = act;
- if (actions[i] & CHECKOUT_ACTION__CREATE_SUBMODULE)
- counts[CHECKOUT_ACTION__CREATE_SUBMODULE]++;
+ if (act & CHECKOUT_ACTION__REMOVE)
+ counts[CHECKOUT_ACTION__REMOVE]++;
+ if (act & CHECKOUT_ACTION__UPDATE_BLOB)
+ counts[CHECKOUT_ACTION__UPDATE_BLOB]++;
+ if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++;
+ if (act & CHECKOUT_ACTION__CONFLICT)
+ counts[CHECKOUT_ACTION__CONFLICT]++;
+ }
- if (actions[i] & CHECKOUT_ACTION__NOTIFY)
- counts[CHECKOUT_ACTION__NOTIFY]++;
+ if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
+ (data->opts->checkout_strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
+ {
+ giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",
+ (int)counts[CHECKOUT_ACTION__CONFLICT]);
+ goto fail;
}
+ git_iterator_free(hiter);
return 0;
+
+fail:
+ *counts_ptr = NULL;
+ git__free(counts);
+ *actions_ptr = NULL;
+ git__free(actions);
+
+ git_iterator_free(hiter);
+ git__free(pfx);
+
+ return -1;
}
static int checkout_remove_the_old(
git_diff_list *diff,
unsigned int *actions,
- struct checkout_diff_data *data)
+ checkout_diff_data *data)
{
git_diff_delta *delta;
size_t i;
+ git_buf_truncate(data->path, data->workdir_len);
+
git_vector_foreach(&diff->deltas, i, delta) {
if (actions[i] & CHECKOUT_ACTION__REMOVE) {
int error = git_futils_rmdir_r(
- delta->new_file.path, git_buf_cstr(data->path),
- GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS);
+ delta->new_file.path,
+ git_buf_cstr(data->path), /* here set to work dir root */
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS |
+ GIT_RMDIR_REMOVE_BLOCKERS);
if (error < 0)
return error;
@@ -366,35 +554,20 @@ static int checkout_remove_the_old(
static int checkout_create_the_new(
git_diff_list *diff,
unsigned int *actions,
- struct checkout_diff_data *data)
+ checkout_diff_data *data)
{
git_diff_delta *delta;
size_t i;
git_vector_foreach(&diff->deltas, i, delta) {
- if (actions[i] & CHECKOUT_ACTION__CREATE_BLOB) {
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
int error = checkout_blob(data, &delta->old_file);
-
- /* ENOTFOUND means unable to create the file because of
- * an existing parent dir. Probably flags were given
- * asking to create new blobs without allowing removal
- * of a conflicting tree (or vice versa).
- */
- if (error == GIT_ENOTFOUND)
- giterr_clear();
- else if (error < 0)
+ if (error < 0)
return error;
data->completed_steps++;
report_progress(data, delta->old_file.path);
}
-
- if (actions[i] & CHECKOUT_ACTION__NOTIFY) {
- if (data->checkout_opts->skipped_notify_cb(
- delta->old_file.path, &delta->old_file.oid,
- delta->old_file.mode, data->checkout_opts->notify_payload))
- return GIT_EUSER;
- }
}
return 0;
@@ -403,13 +576,13 @@ static int checkout_create_the_new(
static int checkout_create_submodules(
git_diff_list *diff,
unsigned int *actions,
- struct checkout_diff_data *data)
+ checkout_diff_data *data)
{
git_diff_delta *delta;
size_t i;
git_vector_foreach(&diff->deltas, i, delta) {
- if (actions[i] & CHECKOUT_ACTION__CREATE_SUBMODULE) {
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
int error = checkout_submodule(data, &delta->old_file);
if (error < 0)
return error;
@@ -427,16 +600,13 @@ int git_checkout_index(
git_checkout_opts *opts)
{
git_diff_list *diff = NULL;
-
git_diff_options diff_opts = {0};
git_checkout_opts checkout_opts;
- struct checkout_diff_data data;
+ checkout_diff_data data;
git_buf workdir = GIT_BUF_INIT;
-
uint32_t *actions = NULL;
size_t *counts = NULL;
-
int error;
assert(repo);
@@ -445,9 +615,8 @@ int git_checkout_index(
return error;
diff_opts.flags =
- GIT_DIFF_INCLUDE_UNTRACKED |
- GIT_DIFF_INCLUDE_TYPECHANGE |
- GIT_DIFF_SKIP_BINARY_CHECK;
+ GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_SKIP_BINARY_CHECK;
if (opts && opts->paths.count > 0)
diff_opts.pathspec = opts->paths;
@@ -470,33 +639,35 @@ int git_checkout_index(
* 3. Then checkout all submodules in case a new .gitmodules blob was
* checked out during pass #2.
*/
- if ((error = checkout_get_actions(&actions, &counts, diff, &checkout_opts)) < 0)
- goto cleanup;
memset(&data, 0, sizeof(data));
data.path = &workdir;
data.workdir_len = git_buf_len(&workdir);
- data.checkout_opts = &checkout_opts;
- data.owner = repo;
+ data.repo = repo;
+ data.diff = diff;
+ data.opts = &checkout_opts;
+
+ if ((error = checkout_get_actions(&actions, &counts, &data)) < 0)
+ goto cleanup;
+
data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
- counts[CHECKOUT_ACTION__CREATE_BLOB] +
- counts[CHECKOUT_ACTION__CREATE_SUBMODULE];
+ counts[CHECKOUT_ACTION__UPDATE_BLOB] +
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];
- if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0)
+ if ((error = retrieve_symlink_caps(repo, &data.can_symlink)) < 0)
goto cleanup;
- report_progress(&data, NULL);
+ report_progress(&data, NULL); /* establish 0 baseline */
if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
(error = checkout_remove_the_old(diff, actions, &data)) < 0)
goto cleanup;
- if ((counts[CHECKOUT_ACTION__CREATE_BLOB] +
- counts[CHECKOUT_ACTION__NOTIFY]) > 0 &&
+ if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&
(error = checkout_create_the_new(diff, actions, &data)) < 0)
goto cleanup;
- if (counts[CHECKOUT_ACTION__CREATE_SUBMODULE] > 0 &&
+ if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&
(error = checkout_create_submodules(diff, actions, &data)) < 0)
goto cleanup;
@@ -519,32 +690,28 @@ int git_checkout_tree(
git_object *treeish,
git_checkout_opts *opts)
{
+ int error = 0;
git_index *index = NULL;
git_tree *tree = NULL;
- int error;
-
assert(repo && treeish);
if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
- giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree.");
- return GIT_ERROR;
+ giterr_set(
+ GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");
+ return -1;
}
- if ((error = git_repository_index(&index, repo)) < 0)
- goto cleanup;
+ /* load paths in tree that match pathspec into index */
+ if (!(error = git_repository_index(&index, repo)) &&
+ !(error = git_index_read_tree_match(
+ index, tree, opts ? &opts->paths : NULL)) &&
+ !(error = git_index_write(index)))
+ error = git_checkout_index(repo, opts);
- if ((error = git_index_read_tree(index, tree)) < 0)
- goto cleanup;
-
- if ((error = git_index_write(index)) < 0)
- goto cleanup;
-
- error = git_checkout_index(repo, opts);
-
-cleanup:
git_index_free(index);
git_tree_free(tree);
+
return error;
}
@@ -552,21 +719,16 @@ int git_checkout_head(
git_repository *repo,
git_checkout_opts *opts)
{
- git_reference *head;
int error;
+ git_reference *head = NULL;
git_object *tree = NULL;
assert(repo);
- if ((error = git_repository_head(&head, repo)) < 0)
- return error;
-
- if ((error = git_reference_peel(&tree, head, GIT_OBJ_TREE)) < 0)
- goto cleanup;
+ if (!(error = git_repository_head(&head, repo)) &&
+ !(error = git_reference_peel(&tree, head, GIT_OBJ_TREE)))
+ error = git_checkout_tree(repo, tree, opts);
- error = git_checkout_tree(repo, tree, opts);
-
-cleanup:
git_reference_free(head);
git_object_free(tree);
diff --git a/src/fileops.c b/src/fileops.c
index d2cbd046d..5eebc5057 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -351,6 +351,7 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
}
typedef struct {
+ const char *base;
uint32_t flags;
int error;
} futils__rmdir_data;
@@ -366,11 +367,52 @@ static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
return -1;
}
+static int futils__rm_first_parent(git_buf *path, const char *ceiling)
+{
+ int error = GIT_ENOTFOUND;
+ struct stat st;
+
+ while (error == GIT_ENOTFOUND) {
+ git_buf_rtruncate_at_char(path, '/');
+
+ if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
+ error = 0;
+ else if (p_lstat(path->ptr, &st) == 0) {
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ error = p_unlink(path->ptr);
+ else if (!S_ISDIR(st.st_mode))
+ error = -1; /* fail to remove non-regular file */
+ } else if (errno != ENOTDIR)
+ error = -1;
+ }
+
+ if (error)
+ futils__error_cannot_rmdir(path->ptr, "cannot remove parent");
+
+ return error;
+}
+
static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
+ struct stat st;
futils__rmdir_data *data = opaque;
- if (git_path_isdir(path->ptr) == true) {
+ if ((data->error = p_lstat(path->ptr, &st)) < 0) {
+ if (errno == ENOENT)
+ data->error = 0;
+ else if (errno == ENOTDIR) {
+ /* asked to remove a/b/c/d/e and a/b is a normal file */
+ if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
+ data->error = futils__rm_first_parent(path, data->base);
+ else
+ futils__error_cannot_rmdir(
+ path->ptr, "parent is not directory");
+ }
+ else
+ futils__error_cannot_rmdir(path->ptr, "cannot access");
+ }
+
+ else if (S_ISDIR(st.st_mode)) {
int error = git_path_direach(path, futils__rmdir_recurs_foreach, data);
if (error < 0)
return (error == GIT_EUSER) ? data->error : error;
@@ -393,9 +435,8 @@ static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
futils__error_cannot_rmdir(path->ptr, "cannot be removed");
}
- else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) {
+ else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
data->error = futils__error_cannot_rmdir(path->ptr, "still present");
- }
return data->error;
}
@@ -434,6 +475,7 @@ 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;
diff --git a/src/fileops.h b/src/fileops.h
index 6952c463c..a74f8b758 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -109,6 +109,7 @@ extern int git_futils_mkpath2file(const char *path, const mode_t mode);
* * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error.
* * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base
* if removing this item leaves them empty
+ * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR
*
* The old values translate into the new as follows:
*
@@ -121,6 +122,7 @@ typedef enum {
GIT_RMDIR_REMOVE_FILES = (1 << 0),
GIT_RMDIR_SKIP_NONEMPTY = (1 << 1),
GIT_RMDIR_EMPTY_PARENTS = (1 << 2),
+ GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3),
} git_futils_rmdir_flags;
/**
diff --git a/src/index.c b/src/index.c
index c1b4565a3..0ae1b4479 100644
--- a/src/index.c
+++ b/src/index.c
@@ -13,6 +13,8 @@
#include "tree.h"
#include "tree-cache.h"
#include "hash.h"
+#include "iterator.h"
+#include "pathspec.h"
#include "git2/odb.h"
#include "git2/oid.h"
#include "git2/blob.h"
@@ -1590,3 +1592,54 @@ git_repository *git_index_owner(const git_index *index)
{
return INDEX_OWNER(index);
}
+
+int git_index_read_tree_match(
+ git_index *index, git_tree *tree, git_strarray *strspec)
+{
+#if 0
+ git_iterator *iter = NULL;
+ const git_index_entry *entry;
+ char *pfx = NULL;
+ git_vector pathspec = GIT_VECTOR_INIT;
+ git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;
+#endif
+
+ if (!git_pathspec_is_interesting(strspec))
+ return git_index_read_tree(index, tree);
+
+ return git_index_read_tree(index, tree);
+
+#if 0
+ /* The following loads the matches into the index, but doesn't
+ * erase obsoleted entries (e.g. you load a blob at "a/b" which
+ * should obsolete a blob at "a/b/c/d" since b is no longer a tree)
+ */
+
+ if (git_pathspec_init(&pathspec, strspec, &pathpool) < 0)
+ return -1;
+
+ pfx = git_pathspec_prefix(strspec);
+
+ if ((error = git_iterator_for_tree_range(
+ &iter, INDEX_OWNER(index), tree, pfx, pfx)) < 0 ||
+ (error = git_iterator_current(iter, &entry)) < 0)
+ goto cleanup;
+
+ while (entry != NULL) {
+ if (git_pathspec_match_path(&pathspec, entry->path, false, false) &&
+ (error = git_index_add(index, entry)) < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_advance(iter, &entry)) < 0)
+ goto cleanup;
+ }
+
+cleanup:
+ git_iterator_free(iter);
+ git_pathspec_free(&pathspec);
+ git_pool_clear(&pathpool);
+ git__free(pfx);
+
+ return error;
+#endif
+}
diff --git a/src/index.h b/src/index.h
index 9778a543a..f0dcd64d5 100644
--- a/src/index.h
+++ b/src/index.h
@@ -48,4 +48,7 @@ extern unsigned int git_index__prefix_position(git_index *index, const char *pat
extern int git_index_entry__cmp(const void *a, const void *b);
extern int git_index_entry__cmp_icase(const void *a, const void *b);
+extern int git_index_read_tree_match(
+ git_index *index, git_tree *tree, git_strarray *strspec);
+
#endif
diff --git a/src/path.c b/src/path.c
index 09556bd3f..98351bec3 100644
--- a/src/path.c
+++ b/src/path.c
@@ -382,9 +382,10 @@ int git_path_walk_up(
iter.asize = path->asize;
while (scan >= stop) {
- if ((error = cb(data, &iter)) < 0)
- break;
+ error = cb(data, &iter);
iter.ptr[scan] = oldc;
+ if (error < 0)
+ break;
scan = git_buf_rfind_next(&iter, '/');
if (scan >= 0) {
scan++;
diff --git a/src/reset.c b/src/reset.c
index 7df1c1a57..69a9c4f04 100644
--- a/src/reset.c
+++ b/src/reset.c
@@ -137,10 +137,7 @@ int git_reset(
}
memset(&opts, 0, sizeof(opts));
- opts.checkout_strategy =
- GIT_CHECKOUT_CREATE_MISSING
- | GIT_CHECKOUT_OVERWRITE_MODIFIED
- | GIT_CHECKOUT_REMOVE_UNTRACKED;
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
if (git_checkout_index(repo, &opts) < 0) {
giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG);
diff --git a/src/stash.c b/src/stash.c
index 1d6940e3c..7bff466d1 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -501,8 +501,8 @@ static int reset_index_and_workdir(
memset(&opts, 0, sizeof(git_checkout_opts));
- opts.checkout_strategy =
- GIT_CHECKOUT_CREATE_MISSING | GIT_CHECKOUT_OVERWRITE_MODIFIED;
+ opts.checkout_strategy =
+ GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED;
if (remove_untracked)
opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c
index 18c59a45d..72044d01c 100644
--- a/tests-clar/checkout/index.c
+++ b/tests-clar/checkout/index.c
@@ -26,7 +26,7 @@ void test_checkout_index__initialize(void)
git_tree *tree;
memset(&g_opts, 0, sizeof(g_opts));
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
g_repo = cl_git_sandbox_init("testrepo");
@@ -78,7 +78,6 @@ void test_checkout_index__can_create_missing_files(void)
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_CREATE_MISSING;
cl_git_pass(git_checkout_index(g_repo, &g_opts));
test_file_contents("./testrepo/README", "hey there\n");
@@ -94,7 +93,7 @@ void test_checkout_index__can_remove_untracked_files(void)
cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir"));
- g_opts.checkout_strategy = GIT_CHECKOUT_REMOVE_UNTRACKED;
+ g_opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
cl_git_pass(git_checkout_index(g_repo, &g_opts));
cl_assert_equal_i(false, git_path_isdir("./testrepo/dir"));
@@ -203,7 +202,11 @@ void test_checkout_index__donot_overwrite_modified_file_by_default(void)
{
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
- g_opts.checkout_strategy = 0;
+ /* set this up to not return an error code on conflicts, but it
+ * still will not have permission to overwrite anything...
+ */
+ g_opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS;
+
cl_git_pass(git_checkout_index(g_repo, &g_opts));
test_file_contents("./testrepo/new.txt", "This isn't what's stored!");
@@ -213,7 +216,8 @@ void test_checkout_index__can_overwrite_modified_file(void)
{
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
- g_opts.checkout_strategy = GIT_CHECKOUT_OVERWRITE_MODIFIED;
+ g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED;
+
cl_git_pass(git_checkout_index(g_repo, &g_opts));
test_file_contents("./testrepo/new.txt", "my new file\n");
@@ -282,28 +286,30 @@ void test_checkout_index__options_open_flags(void)
g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND;
- g_opts.checkout_strategy |= GIT_CHECKOUT_OVERWRITE_MODIFIED;
+ g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED;
cl_git_pass(git_checkout_index(g_repo, &g_opts));
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
}
-struct notify_data {
+struct conflict_data {
const char *file;
const char *sha;
};
-static int notify_cb(
- const char *skipped_file,
+static int conflict_cb(
+ const char *conflict_file,
const git_oid *blob_oid,
- int file_mode,
+ unsigned int index_mode,
+ unsigned int wd_mode,
void *payload)
{
- struct notify_data *expectations = (struct notify_data *)payload;
+ struct conflict_data *expectations = (struct conflict_data *)payload;
- GIT_UNUSED(file_mode);
+ GIT_UNUSED(index_mode);
+ GIT_UNUSED(wd_mode);
- cl_assert_equal_s(expectations->file, skipped_file);
+ cl_assert_equal_s(expectations->file, conflict_file);
cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha));
return 0;
@@ -311,7 +317,7 @@ static int notify_cb(
void test_checkout_index__can_notify_of_skipped_files(void)
{
- struct notify_data data;
+ struct conflict_data data;
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
@@ -324,22 +330,24 @@ void test_checkout_index__can_notify_of_skipped_files(void)
data.file = "new.txt";
data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
- g_opts.skipped_notify_cb = notify_cb;
- g_opts.notify_payload = &data;
+ g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
+ g_opts.conflict_cb = conflict_cb;
+ g_opts.conflict_payload = &data;
cl_git_pass(git_checkout_index(g_repo, &g_opts));
}
-static int dont_notify_cb(
- const char *skipped_file,
+static int dont_conflict_cb(
+ const char *conflict_file,
const git_oid *blob_oid,
- int file_mode,
+ unsigned int index_mode,
+ unsigned int wd_mode,
void *payload)
{
- GIT_UNUSED(skipped_file);
+ GIT_UNUSED(conflict_file);
GIT_UNUSED(blob_oid);
- GIT_UNUSED(file_mode);
+ GIT_UNUSED(index_mode);
+ GIT_UNUSED(wd_mode);
GIT_UNUSED(payload);
cl_assert(false);
@@ -354,9 +362,9 @@ void test_checkout_index__wont_notify_of_expected_line_ending_changes(void)
cl_git_mkfile("./testrepo/new.txt", "my new file\r\n");
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
- g_opts.skipped_notify_cb = dont_notify_cb;
- g_opts.notify_payload = NULL;
+ g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
+ g_opts.conflict_cb = dont_conflict_cb;
+ g_opts.conflict_payload = NULL;
cl_git_pass(git_checkout_index(g_repo, &g_opts));
}
@@ -389,8 +397,9 @@ void test_checkout_index__can_overcome_name_clashes(void)
cl_git_pass(p_mkdir("./testrepo/path1", 0777));
cl_git_mkfile("./testrepo/path1/file1", "content\r\n");
- cl_git_pass(git_index_add(index, "path0", 0));
- cl_git_pass(git_index_add(index, "path1/file1", 0));
+ cl_git_pass(git_index_add_from_workdir(index, "path0"));
+ cl_git_pass(git_index_add_from_workdir(index, "path1/file1"));
+
cl_git_pass(p_unlink("./testrepo/path0"));
cl_git_pass(git_futils_rmdir_r(
@@ -400,11 +409,20 @@ void test_checkout_index__can_overcome_name_clashes(void)
cl_git_pass(p_mkdir("./testrepo/path0", 0777));
cl_git_mkfile("./testrepo/path0/file0", "content\r\n");
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ cl_assert(git_path_isfile("./testrepo/path1"));
+ cl_assert(git_path_isfile("./testrepo/path0/file0"));
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
cl_git_pass(git_checkout_index(g_repo, &g_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, &g_opts));
+
+ cl_assert(git_path_isfile("./testrepo/path0"));
+ cl_assert(git_path_isfile("./testrepo/path1/file1"));
+
git_index_free(index);
}
diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c
index c42aefc31..983425324 100644
--- a/tests-clar/checkout/tree.c
+++ b/tests-clar/checkout/tree.c
@@ -12,7 +12,7 @@ void test_checkout_tree__initialize(void)
g_repo = cl_git_sandbox_init("testrepo");
memset(&g_opts, 0, sizeof(g_opts));
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
}
void test_checkout_tree__cleanup(void)
@@ -74,10 +74,13 @@ static void progress(const char *path, size_t cur, size_t tot, void *payload)
void test_checkout_tree__calls_progress_callback(void)
{
bool was_called = 0;
+
g_opts.progress_cb = progress;
g_opts.progress_payload = &was_called;
cl_git_pass(git_revparse_single(&g_object, g_repo, "master"));
+
cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
cl_assert_equal_i(was_called, true);
}
diff --git a/tests-clar/checkout/typechange.c b/tests-clar/checkout/typechange.c
index e86af52ee..cd34885de 100644
--- a/tests-clar/checkout/typechange.c
+++ b/tests-clar/checkout/typechange.c
@@ -40,10 +40,12 @@ void test_checkout_typechange__checkout_typechanges(void)
git_object *obj;
git_checkout_opts opts = {0};
- opts.checkout_strategy =
- GIT_CHECKOUT_REMOVE_UNTRACKED |
- GIT_CHECKOUT_CREATE_MISSING |
- GIT_CHECKOUT_OVERWRITE_MODIFIED;
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ /* if you don't include GIT_CHECKOUT_REMOVE_UNTRACKED then on the final
+ * checkout which is supposed to remove all the files, we will not
+ * actually remove them!
+ */
for (i = 0; g_typechange_oids[i] != NULL; ++i) {
cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
@@ -51,6 +53,9 @@ void test_checkout_typechange__checkout_typechanges(void)
cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+ cl_git_pass(
+ git_repository_set_head_detached(g_repo, git_object_id(obj)));
+
git_object_free(obj);
if (!g_typechange_empty[i]) {
diff --git a/tests-clar/clone/network.c b/tests-clar/clone/network.c
index 19385f77a..68fa8eb6b 100644
--- a/tests-clar/clone/network.c
+++ b/tests-clar/clone/network.c
@@ -113,7 +113,7 @@ void test_clone_network__can_checkout_a_cloned_repo(void)
bool checkout_progress_cb_was_called = false,
fetch_progress_cb_was_called = false;
- opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ opts.checkout_strategy = GIT_CHECKOUT_UPDATE_UNMODIFIED;
opts.progress_cb = &checkout_progress;
opts.progress_payload = &checkout_progress_cb_was_called;