summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVicent Marti <vicent@github.com>2014-04-25 02:04:12 -0700
committerVicent Marti <vicent@github.com>2014-04-25 02:04:12 -0700
commit2ad51b81d2d735c50549f16c308d61c0a5ed990c (patch)
tree206483ccc5c59eafe9f7036faaf8ecf26ea40de7
parentaf9eeac93929bdf10c30e60f34717959d83b6bd3 (diff)
parenta409acefbbadeb607e4d6dde681bff5aed6ae9fc (diff)
downloadlibgit2-2ad51b81d2d735c50549f16c308d61c0a5ed990c.tar.gz
Merge pull request #2241 from libgit2/rb/stash-skip-submodules
Improve stash and checkout for ignored + untracked items
-rw-r--r--src/checkout.c94
-rw-r--r--src/diff.c78
-rw-r--r--src/iterator.c74
-rw-r--r--src/iterator.h19
-rw-r--r--src/stash.c3
-rw-r--r--tests/stash/save.c36
-rw-r--r--tests/stash/stash_helpers.c14
7 files changed, 211 insertions, 107 deletions
diff --git a/src/checkout.c b/src/checkout.c
index 0b385226b..bc976b854 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;
@@ -259,21 +260,41 @@ static int checkout_action_no_wd(
return checkout_action_common(action, data, delta, NULL);
}
+static bool wd_item_is_removable(git_iterator *iter, const git_index_entry *wd)
+{
+ git_buf *full = NULL;
+
+ if (wd->mode != GIT_FILEMODE_TREE)
+ return true;
+ if (git_iterator_current_workdir_path(&full, iter) < 0)
+ return true;
+ 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) {
@@ -303,24 +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);
- }
- else {
- notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;
- remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0);
- }
+ 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);
+
+ if (!error)
+ error = git_iterator_advance(wditem, workdir);
+ } else {
+ /* untracked or ignored - can't know which until we advance through */
+ bool over = false, removable = wd_item_is_removable(workdir, wd);
+ git_iterator_status_t untracked_state;
+
+ /* 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_iterator_advance_over_with_status(
+ wditem, &untracked_state, workdir);
+ if (error == GIT_ITEROVER)
+ over = true;
+ else if (error < 0)
+ return error;
- error = checkout_notify(data, notify, NULL, wd);
+ if (untracked_state == GIT_ITERATOR_STATUS_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 && remove) {
- char *path = git_pool_strdup(&data->pool, wd->path);
- GITERR_CHECK_ALLOC(path);
+ if ((error = checkout_notify(data, notify, NULL, &saved_wd)) != 0)
+ return error;
- error = git_vector_insert(&data->removes, path);
+ if (remove && removable)
+ error = checkout_queue_remove(data, saved_wd.path);
+
+ if (!error && over) /* restore ITEROVER if needed */
+ error = GIT_ITEROVER;
}
return error;
@@ -554,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;
}
@@ -619,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;
@@ -1853,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 fd881c6f6..4b6fbe25a 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;
+ git_iterator_status_t untracked_state;
/* attempt to insert record for this directory */
if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
@@ -916,11 +851,14 @@ 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_with_status(
+ &info->nitem, &untracked_state, info->new_iter)) < 0 &&
+ error != GIT_ITEROVER)
+ return error;
- /* it iteration changed delta type, the update the record */
- if (delta_type == GIT_DELTA_IGNORED) {
+ /* if we found nothing or just ignored items, update the record */
+ if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
+ untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
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..ef27fa71f 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -1528,3 +1528,77 @@ int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)
return 0;
}
+
+int git_iterator_advance_over_with_status(
+ const git_index_entry **entryptr,
+ git_iterator_status_t *status,
+ git_iterator *iter)
+{
+ int error = 0;
+ workdir_iterator *wi = (workdir_iterator *)iter;
+ char *base = NULL;
+ const git_index_entry *entry;
+
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+
+ 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;
+ if (wi->is_ignored)
+ *status = GIT_ITERATOR_STATUS_IGNORED;
+ return git_iterator_advance(entryptr, iter);
+ }
+
+ *status = GIT_ITERATOR_STATUS_EMPTY;
+
+ 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 we found an explicitly ignored item, then update from
+ * EMPTY to IGNORED
+ */
+ if (wi->is_ignored)
+ *status = GIT_ITERATOR_STATUS_IGNORED;
+ else if (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; /* mark empty directories as ignored */
+ } else
+ break; /* real error, stop here */
+ } else {
+ /* we found a non-ignored item, treat parent as untracked */
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+ 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..ba9c1e486 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -258,4 +258,23 @@ 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);
+typedef enum {
+ GIT_ITERATOR_STATUS_NORMAL = 0,
+ GIT_ITERATOR_STATUS_IGNORED = 1,
+ GIT_ITERATOR_STATUS_EMPTY = 2
+} git_iterator_status_t;
+
+/* Advance over a directory and check if it contains no files or just
+ * ignored files.
+ *
+ * In a tree or the index, all directories will contain files, but in the
+ * working directory it is possible to have an empty directory tree or a
+ * tree that only contains ignored files. Many Git operations treat these
+ * cases specially. This advances over a directory (presumably an
+ * untracked directory) but checks during the scan if there are any files
+ * and any non-ignored files.
+ */
+extern int git_iterator_advance_over_with_status(
+ const git_index_entry **entry, git_iterator_status_t *status, git_iterator *iter);
+
#endif
diff --git a/src/stash.c b/src/stash.c
index d20e29b80..86e0a627c 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -178,7 +178,8 @@ static int stash_update_index_from_diff(
break;
case GIT_DELTA_UNTRACKED:
- if (data->include_untracked)
+ if (data->include_untracked &&
+ delta->new_file.mode != GIT_FILEMODE_TREE)
add_path = delta->new_file.path;
break;
diff --git a/tests/stash/save.c b/tests/stash/save.c
index f06c1fb71..87c6d7e0f 100644
--- a/tests/stash/save.c
+++ b/tests/stash/save.c
@@ -148,6 +148,25 @@ void test_stash_save__can_include_untracked_files(void)
assert_blob_oid("refs/stash^3:just.ignore", NULL);
}
+void test_stash_save__untracked_skips_ignored(void)
+{
+ cl_git_append2file("stash/.gitignore", "bundle/vendor/\n");
+ cl_must_pass(p_mkdir("stash/bundle", 0777));
+ 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)
{
cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED));
@@ -342,7 +361,7 @@ void test_stash_save__can_stage_normal_then_stage_untracked(void)
void test_stash_save__including_untracked_without_any_untracked_file_creates_an_empty_tree(void)
{
- cl_git_pass(p_unlink("stash/when"));
+ cl_must_pass(p_unlink("stash/when"));
assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED);
assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED);
@@ -354,3 +373,18 @@ void test_stash_save__including_untracked_without_any_untracked_file_creates_an_
assert_object_oid("stash^3^{tree}", EMPTY_TREE, GIT_OBJ_TREE);
}
+
+void test_stash_save__skip_submodules(void)
+{
+ git_repository *untracked_repo;
+ cl_git_pass(git_repository_init(&untracked_repo, "stash/untracked_repo", false));
+ cl_git_mkfile("stash/untracked_repo/content", "stuff");
+ git_repository_free(untracked_repo);
+
+ assert_status(repo, "untracked_repo/", GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_stash_save(
+ &stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+
+ assert_status(repo, "untracked_repo/", GIT_STATUS_WT_NEW);
+}
diff --git a/tests/stash/stash_helpers.c b/tests/stash/stash_helpers.c
index 8b7d685f8..ff683eced 100644
--- a/tests/stash/stash_helpers.c
+++ b/tests/stash/stash_helpers.c
@@ -42,15 +42,11 @@ void assert_status(
int status_flags)
{
unsigned int status;
- int error;
- error = git_status_file(&status, repo, path);
-
- if (status_flags < 0) {
- cl_assert_equal_i(status_flags, error);
- return;
+ if (status_flags < 0)
+ cl_assert_equal_i(status_flags, git_status_file(&status, repo, path));
+ else {
+ cl_git_pass(git_status_file(&status, repo, path));
+ cl_assert_equal_i((unsigned int)status_flags, status);
}
-
- cl_assert_equal_i(0, error);
- cl_assert_equal_i((unsigned int)status_flags, status);
}