summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2012-11-08 17:05:07 -0800
committerRussell Belfer <rb@github.com>2012-11-09 13:52:07 -0800
commitad9a921b92a964a0f28a5f0d59079cde5a0ada1e (patch)
tree218caac93e8acd2686521974ce77d165386c4e4b /src
parent55cbd05b18960e761a4d237ce5f1ff06455da98d (diff)
downloadlibgit2-ad9a921b92a964a0f28a5f0d59079cde5a0ada1e.tar.gz
Rework checkout with new strategy options
This is a major reworking of checkout strategy options. The checkout code is now sensitive to the contents of the HEAD tree and the new options allow you to update the working tree so that it will match the index content only when it previously matched the contents of the HEAD. This allows you to, for example, to distinguish between removing files that are in the HEAD but not in the index, vs just removing all untracked files. Because of various corner cases that arise, etc., this required some additional capabilities in rmdir and other utility functions. This includes the beginnings of an implementation of code to read a partial tree into the index based on a pathspec, but that is not enabled because of the possibility of creating conflicting index entries.
Diffstat (limited to 'src')
-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
8 files changed, 423 insertions, 163 deletions
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;