diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/checkout.c | 94 | ||||
-rw-r--r-- | src/diff.c | 78 | ||||
-rw-r--r-- | src/iterator.c | 74 | ||||
-rw-r--r-- | src/iterator.h | 19 | ||||
-rw-r--r-- | src/stash.c | 3 |
5 files changed, 171 insertions, 97 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; |