From 24d17de255caa57b01c0c758e6fc81aad493806e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 2 Apr 2014 12:07:27 -0700 Subject: [PATCH 1/5] Make stash and checkout ignore contained repos To emulate git, stash should not remove untracked git repositories inside the parent repo, and checkout's REMOVE_UNTRACKED should also skip over these items. `git stash` actually prints a warning message for these items. That should be possible with a checkout notify callback if you wanted to, although it would require a bit of extra logic as things are at the moment. --- src/checkout.c | 17 +++++++++++++++-- src/stash.c | 3 ++- tests/stash/save.c | 17 ++++++++++++++++- tests/stash/stash_helpers.c | 13 +++++-------- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index 0b385226b..04b2a66b2 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -259,6 +259,17 @@ 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_action_wd_only( checkout_data *data, git_iterator *workdir, @@ -307,11 +318,13 @@ static int checkout_action_wd_only( /* found in index */; else if (git_iterator_current_is_ignored(workdir)) { notify = GIT_CHECKOUT_NOTIFY_IGNORED; - remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0); + 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); + remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) && + wd_item_is_removable(workdir, wd); } error = checkout_notify(data, notify, NULL, wd); 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..5165eeadf 100644 --- a/tests/stash/save.c +++ b/tests/stash/save.c @@ -342,7 +342,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 +354,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..29cb25c5f 100644 --- a/tests/stash/stash_helpers.c +++ b/tests/stash/stash_helpers.c @@ -44,13 +44,10 @@ void assert_status( 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); } From 3c1aa4c110d2d4c8c3d941b0e4ba66357172da2e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 22 Apr 2014 15:23:39 -0700 Subject: [PATCH 2/5] Failing test for stashing a buried ignored file --- tests/stash/save.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/stash/save.c b/tests/stash/save.c index 5165eeadf..7bbd4c8dc 100644 --- a/tests/stash/save.c +++ b/tests/stash/save.c @@ -148,6 +148,19 @@ 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_git_pass(git_stash_save( + &stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + cl_assert(git_path_exists("stash/bundle/vendor/blah")); +} + 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)); From 37da368545b28157e625212e8009ec041cc4a4ea Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 22 Apr 2014 21:51:54 -0700 Subject: [PATCH 3/5] 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. --- src/checkout.c | 85 +++++++++++++++++++++++++------------ src/diff.c | 77 +++------------------------------ src/iterator.c | 68 +++++++++++++++++++++++++++++ src/iterator.h | 8 ++++ tests/stash/save.c | 6 +++ tests/stash/stash_helpers.c | 1 - 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; - error = checkout_notify(data, notify, NULL, wd); + if (remove && wd_item_is_removable(workdir, wd)) + error = checkout_queue_remove(data, wd->path); - if (!error && remove) { - char *path = git_pool_strdup(&data->pool, wd->path); - GITERR_CHECK_ALLOC(path); + 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); - error = git_vector_insert(&data->removes, 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_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)); From 219c89d19d7e18a336faa094b0c29cb7bb0d22c6 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 23 Apr 2014 16:28:45 -0700 Subject: [PATCH 4/5] Treat ignored, empty, and untracked dirs different In the iterator, distinguish between ignores and empty directories so that diff and status can ignore empty directories, but checkout and stash can treat them as untracked items. --- src/checkout.c | 10 +++++----- src/diff.c | 11 ++++++----- src/iterator.c | 17 +++++++++++------ src/iterator.h | 23 +++++++++++++++++------ 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index a412c4c4e..bc976b854 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -336,22 +336,22 @@ static int checkout_action_wd_only( 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); + 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_and_check_ignored( - wditem, &ignored, workdir); + error = git_iterator_advance_over_with_status( + wditem, &untracked_state, workdir); if (error == GIT_ITEROVER) over = true; else if (error < 0) return error; - if (ignored) { + if (untracked_state == GIT_ITERATOR_STATUS_IGNORED) { notify = GIT_CHECKOUT_NOTIFY_IGNORED; remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0); } else { diff --git a/src/diff.c b/src/diff.c index f1c1b0543..18cd52c55 100644 --- a/src/diff.c +++ b/src/diff.c @@ -839,7 +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; + git_iterator_status_t untracked_state; /* attempt to insert record for this directory */ if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0) @@ -851,13 +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 ((error = git_iterator_advance_over_and_check_ignored( - &info->nitem, &ignored, info->new_iter)) < 0 && + if ((error = git_iterator_advance_over_with_status( + &info->nitem, &untracked_state, info->new_iter)) < 0 && error != GIT_ITEROVER) return error; - /* it iteration only found ignored items, update the record */ - if (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 1a24dad10..dfa79977d 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -1529,15 +1529,17 @@ 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 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; - *ignored = false; + *status = GIT_ITERATOR_STATUS_NORMAL; if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) return git_iterator_advance(entryptr, iter); @@ -1548,11 +1550,12 @@ int git_iterator_advance_over_and_check_ignored( if (git_ignore__lookup( &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) wi->is_ignored = true; - *ignored = wi->is_ignored; + if (wi->is_ignored) + *status = GIT_ITERATOR_STATUS_IGNORED; return git_iterator_advance(entryptr, iter); } - *ignored = true; + *status = GIT_ITERATOR_STATUS_EMPTY; base = git__strdup(entry->path); GITERR_CHECK_ALLOC(base); @@ -1577,9 +1580,11 @@ int git_iterator_advance_over_and_check_ignored( /* if we found a non-ignored item, treat parent as untracked */ if (!wi->is_ignored) { - *ignored = false; + *status = GIT_ITERATOR_STATUS_NORMAL; break; } + if (entry && !S_ISDIR(entry->mode)) + *status = GIT_ITERATOR_STATUS_IGNORED; if ((error = git_iterator_advance(&entry, iter)) < 0) break; diff --git a/src/iterator.h b/src/iterator.h index dcedbd530..ba9c1e486 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -258,12 +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); -/* 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. +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_and_check_ignored( - const git_index_entry **entry, bool *ignored, git_iterator *iter); +extern int git_iterator_advance_over_with_status( + const git_index_entry **entry, git_iterator_status_t *status, git_iterator *iter); #endif From a409acefbbadeb607e4d6dde681bff5aed6ae9fc Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 24 Apr 2014 11:59:50 -0700 Subject: [PATCH 5/5] Handle explicitly ignored dir slightly differently When considering status of untracked directories, if we find an explicitly ignored item, even if it is a directory, treat the parent as an IGNORED item. It was accidentally being treated as an EMPTY item because we were not looking into the ignored subdir. --- src/iterator.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/iterator.c b/src/iterator.c index dfa79977d..ef27fa71f 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -1566,25 +1566,26 @@ int git_iterator_advance_over_with_status( &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) wi->is_ignored = true; - if (!wi->is_ignored && S_ISDIR(entry->mode)) { + /* 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; /* treat empty directories as ignored */ + wi->is_ignored = true; /* mark 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) { + } else { + /* we found a non-ignored item, treat parent as untracked */ *status = GIT_ITERATOR_STATUS_NORMAL; break; } - if (entry && !S_ISDIR(entry->mode)) - *status = GIT_ITERATOR_STATUS_IGNORED; if ((error = git_iterator_advance(&entry, iter)) < 0) break;