summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2014-04-22 21:51:54 -0700
committerRussell Belfer <rb@github.com>2014-04-22 21:51:54 -0700
commit37da368545b28157e625212e8009ec041cc4a4ea (patch)
tree9fac6b09448e10224191507e764263374ed36083
parent3c1aa4c110d2d4c8c3d941b0e4ba66357172da2e (diff)
downloadlibgit2-37da368545b28157e625212e8009ec041cc4a4ea.tar.gz
Make checkout match diff for untracked/ignored dir
When diff finds an untracked directory, it emulates Git behavior by looking inside the directory to see if there are any untracked items inside it. If there are only ignored items inside the dir, then diff considers it ignored, even if there is no direct ignore rule for it. Checkout was not copying this behavior - when it found an untracked directory, it just treated it as untracked. Unfortunately, when combined with GIT_CHECKOUT_REMOVE_UNTRACKED, this made is seem that checkout (and stash, which uses checkout) was removing ignored items when you had only asked it to remove untracked ones. This commit moves the logic for advancing past an untracked dir while scanning for non-ignored items into an iterator helper fn, and uses that for both diff and checkout.
-rw-r--r--src/checkout.c85
-rw-r--r--src/diff.c77
-rw-r--r--src/iterator.c68
-rw-r--r--src/iterator.h8
-rw-r--r--tests/stash/save.c6
-rw-r--r--tests/stash/stash_helpers.c1
6 files changed, 146 insertions, 99 deletions
diff --git a/src/checkout.c b/src/checkout.c
index 04b2a66b2..a412c4c4e 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -56,6 +56,7 @@ typedef struct {
git_vector conflicts;
git_buf path;
size_t workdir_len;
+ git_buf tmp;
unsigned int strategy;
int can_symlink;
bool reload_submodules;
@@ -270,21 +271,30 @@ static bool wd_item_is_removable(git_iterator *iter, const git_index_entry *wd)
return !full || !git_path_contains(full, DOT_GIT);
}
+static int checkout_queue_remove(checkout_data *data, const char *path)
+{
+ char *copy = git_pool_strdup(&data->pool, path);
+ GITERR_CHECK_ALLOC(copy);
+ return git_vector_insert(&data->removes, copy);
+}
+
+/* note that this advances the iterator over the wd item */
static int checkout_action_wd_only(
checkout_data *data,
git_iterator *workdir,
- const git_index_entry *wd,
+ const git_index_entry **wditem,
git_vector *pathspec)
{
int error = 0;
bool remove = false;
git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+ const git_index_entry *wd = *wditem;
if (!git_pathspec__match(
pathspec, wd->path,
(data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
git_iterator_ignore_case(workdir), NULL, NULL))
- return 0;
+ return git_iterator_advance(wditem, workdir);
/* check if item is tracked in the index but not in the checkout diff */
if (data->index != NULL) {
@@ -314,26 +324,49 @@ static int checkout_action_wd_only(
}
}
- if (notify != GIT_CHECKOUT_NOTIFY_NONE)
- /* found in index */;
- else if (git_iterator_current_is_ignored(workdir)) {
- notify = GIT_CHECKOUT_NOTIFY_IGNORED;
- remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0) &&
- wd_item_is_removable(workdir, wd);
- }
- else {
- notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;
- remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) &&
- wd_item_is_removable(workdir, wd);
- }
+ if (notify != GIT_CHECKOUT_NOTIFY_NONE) {
+ /* if we found something in the index, notify and advance */
+ if ((error = checkout_notify(data, notify, NULL, wd)) != 0)
+ return error;
+
+ if (remove && wd_item_is_removable(workdir, wd))
+ error = checkout_queue_remove(data, wd->path);
- error = checkout_notify(data, notify, NULL, wd);
+ if (!error)
+ error = git_iterator_advance(wditem, workdir);
+ } else {
+ /* untracked or ignored - can't know which until we advance through */
+ bool ignored, over = false;
+ bool removable = wd_item_is_removable(workdir, wd);
- if (!error && remove) {
- char *path = git_pool_strdup(&data->pool, wd->path);
- GITERR_CHECK_ALLOC(path);
+ /* copy the entry for issuing notification callback later */
+ git_index_entry saved_wd = *wd;
+ git_buf_sets(&data->tmp, wd->path);
+ saved_wd.path = data->tmp.ptr;
- error = git_vector_insert(&data->removes, path);
+ error = git_iterator_advance_over_and_check_ignored(
+ wditem, &ignored, workdir);
+ if (error == GIT_ITEROVER)
+ over = true;
+ else if (error < 0)
+ return error;
+
+ 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);
+ }
+
+ if ((error = checkout_notify(data, notify, NULL, &saved_wd)) != 0)
+ return error;
+
+ if (remove && removable)
+ error = checkout_queue_remove(data, saved_wd.path);
+
+ if (!error && over) /* restore ITEROVER if needed */
+ error = GIT_ITEROVER;
}
return error;
@@ -567,11 +600,8 @@ static int checkout_action(
}
/* case 1 - handle wd item (if it matches pathspec) */
- error = checkout_action_wd_only(data, workdir, wd, pathspec);
- if (error)
- goto done;
- if ((error = git_iterator_advance(wditem, workdir)) < 0 &&
- error != GIT_ITEROVER)
+ error = checkout_action_wd_only(data, workdir, wditem, pathspec);
+ if (error && error != GIT_ITEROVER)
goto done;
continue;
}
@@ -632,10 +662,8 @@ static int checkout_remaining_wd_items(
{
int error = 0;
- while (wd && !error) {
- if (!(error = checkout_action_wd_only(data, workdir, wd, spec)))
- error = git_iterator_advance(&wd, workdir);
- }
+ while (wd && !error)
+ error = checkout_action_wd_only(data, workdir, &wd, spec);
if (error == GIT_ITEROVER)
error = 0;
@@ -1866,6 +1894,7 @@ static void checkout_data_clear(checkout_data *data)
data->pfx = NULL;
git_buf_free(&data->path);
+ git_buf_free(&data->tmp);
git_index_free(data->index);
data->index = NULL;
diff --git a/src/diff.c b/src/diff.c
index 0d1aed4ad..f1c1b0543 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -784,72 +784,6 @@ static bool entry_is_prefixed(
item->path[pathlen] == '/');
}
-static int diff_scan_inside_untracked_dir(
- git_diff *diff, diff_in_progress *info, git_delta_t *delta_type)
-{
- int error = 0;
- git_buf base = GIT_BUF_INIT;
- bool is_ignored;
-
- *delta_type = GIT_DELTA_IGNORED;
- git_buf_sets(&base, info->nitem->path);
-
- /* advance into untracked directory */
- if ((error = git_iterator_advance_into(&info->nitem, info->new_iter)) < 0) {
-
- /* skip ahead if empty */
- if (error == GIT_ENOTFOUND) {
- giterr_clear();
- error = git_iterator_advance(&info->nitem, info->new_iter);
- }
-
- goto done;
- }
-
- /* look for actual untracked file */
- while (info->nitem != NULL &&
- !diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
- is_ignored = git_iterator_current_is_ignored(info->new_iter);
-
- /* need to recurse into non-ignored directories */
- if (!is_ignored && S_ISDIR(info->nitem->mode)) {
- error = git_iterator_advance_into(&info->nitem, info->new_iter);
-
- if (!error)
- continue;
- else if (error == GIT_ENOTFOUND) {
- error = 0;
- is_ignored = true; /* treat empty as ignored */
- } else
- break; /* real error, must stop */
- }
-
- /* found a non-ignored item - treat parent dir as untracked */
- if (!is_ignored) {
- *delta_type = GIT_DELTA_UNTRACKED;
- break;
- }
-
- if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
- break;
- }
-
- /* finish off scan */
- while (info->nitem != NULL &&
- !diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
- if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
- break;
- }
-
-done:
- git_buf_free(&base);
-
- if (error == GIT_ITEROVER)
- error = 0;
-
- return error;
-}
-
static int handle_unmatched_new_item(
git_diff *diff, diff_in_progress *info)
{
@@ -905,6 +839,7 @@ static int handle_unmatched_new_item(
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS))
{
git_diff_delta *last;
+ bool ignored;
/* attempt to insert record for this directory */
if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
@@ -916,11 +851,13 @@ static int handle_unmatched_new_item(
return git_iterator_advance(&info->nitem, info->new_iter);
/* iterate into dir looking for an actual untracked file */
- if (diff_scan_inside_untracked_dir(diff, info, &delta_type) < 0)
- return -1;
+ if ((error = git_iterator_advance_over_and_check_ignored(
+ &info->nitem, &ignored, info->new_iter)) < 0 &&
+ error != GIT_ITEROVER)
+ return error;
- /* it iteration changed delta type, the update the record */
- if (delta_type == GIT_DELTA_IGNORED) {
+ /* it iteration only found ignored items, update the record */
+ if (ignored) {
last->status = GIT_DELTA_IGNORED;
/* remove the record if we don't want ignored records */
diff --git a/src/iterator.c b/src/iterator.c
index 63c14f962..1a24dad10 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -1528,3 +1528,71 @@ int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)
return 0;
}
+
+int git_iterator_advance_over_and_check_ignored(
+ const git_index_entry **entryptr, bool *ignored, git_iterator *iter)
+{
+ int error = 0;
+ workdir_iterator *wi = (workdir_iterator *)iter;
+ char *base = NULL;
+ const git_index_entry *entry;
+
+ *ignored = false;
+
+ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return git_iterator_advance(entryptr, iter);
+ if ((error = git_iterator_current(&entry, iter)) < 0)
+ return error;
+
+ if (!S_ISDIR(entry->mode)) {
+ if (git_ignore__lookup(
+ &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0)
+ wi->is_ignored = true;
+ *ignored = wi->is_ignored;
+ return git_iterator_advance(entryptr, iter);
+ }
+
+ *ignored = true;
+
+ base = git__strdup(entry->path);
+ GITERR_CHECK_ALLOC(base);
+
+ /* scan inside directory looking for a non-ignored item */
+ while (entry && !iter->prefixcomp(entry->path, base)) {
+ if (git_ignore__lookup(
+ &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0)
+ wi->is_ignored = true;
+
+ if (!wi->is_ignored && S_ISDIR(entry->mode)) {
+ error = git_iterator_advance_into(&entry, iter);
+
+ if (!error)
+ continue;
+ else if (error == GIT_ENOTFOUND) {
+ error = 0;
+ wi->is_ignored = true; /* treat empty directories as ignored */
+ } else
+ break; /* real error, stop here */
+ }
+
+ /* if we found a non-ignored item, treat parent as untracked */
+ if (!wi->is_ignored) {
+ *ignored = false;
+ break;
+ }
+
+ if ((error = git_iterator_advance(&entry, iter)) < 0)
+ break;
+ }
+
+ /* wrap up scan back to base directory */
+ while (entry && !iter->prefixcomp(entry->path, base))
+ if ((error = git_iterator_advance(&entry, iter)) < 0)
+ break;
+
+ *entryptr = entry;
+ git__free(base);
+
+ return error;
+}
+
diff --git a/src/iterator.h b/src/iterator.h
index 751e139d0..dcedbd530 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -258,4 +258,12 @@ extern int git_iterator_current_workdir_path(
/* Return index pointer if index iterator, else NULL */
extern git_index *git_iterator_get_index(git_iterator *iter);
+/* Special type of advance that can be called when looking at a tree in
+ * the working directory that leaves the iterator on the next item after
+ * the tree, but also scans the tree contents looking for any items that
+ * are not ignored.
+ */
+extern int git_iterator_advance_over_and_check_ignored(
+ const git_index_entry **entry, bool *ignored, git_iterator *iter);
+
#endif
diff --git a/tests/stash/save.c b/tests/stash/save.c
index 7bbd4c8dc..87c6d7e0f 100644
--- a/tests/stash/save.c
+++ b/tests/stash/save.c
@@ -155,10 +155,16 @@ void test_stash_save__untracked_skips_ignored(void)
cl_must_pass(p_mkdir("stash/bundle/vendor", 0777));
cl_git_mkfile("stash/bundle/vendor/blah", "contents\n");
+ cl_assert(git_path_exists("stash/when")); /* untracked */
+ cl_assert(git_path_exists("stash/just.ignore")); /* ignored */
+ cl_assert(git_path_exists("stash/bundle/vendor/blah")); /* ignored */
+
cl_git_pass(git_stash_save(
&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+ cl_assert(!git_path_exists("stash/when"));
cl_assert(git_path_exists("stash/bundle/vendor/blah"));
+ cl_assert(git_path_exists("stash/just.ignore"));
}
void test_stash_save__can_include_untracked_and_ignored_files(void)
diff --git a/tests/stash/stash_helpers.c b/tests/stash/stash_helpers.c
index 29cb25c5f..ff683eced 100644
--- a/tests/stash/stash_helpers.c
+++ b/tests/stash/stash_helpers.c
@@ -42,7 +42,6 @@ void assert_status(
int status_flags)
{
unsigned int status;
- int error;
if (status_flags < 0)
cl_assert_equal_i(status_flags, git_status_file(&status, repo, path));