summaryrefslogtreecommitdiff
path: root/src/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/diff.c')
-rw-r--r--src/diff.c203
1 files changed, 143 insertions, 60 deletions
diff --git a/src/diff.c b/src/diff.c
index 58c7eacc6..cea3fdb22 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -698,56 +698,58 @@ static bool entry_is_prefixed(
item->path[pathlen] == '/');
}
-static int handle_unmatched_new_directory(
- git_diff_list *diff, diff_in_progress *info, git_delta_t *delta)
+static int diff_scan_inside_untracked_dir(
+ git_diff_list *diff, diff_in_progress *info, git_delta_t *delta_type)
{
int error = 0;
- const git_index_entry *nitem = info->nitem;
- bool contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
- bool recurse_into_dir =
- (*delta == GIT_DELTA_UNTRACKED &&
- DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
- (*delta == GIT_DELTA_IGNORED &&
- DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
-
- /* do not advance into directories that contain a .git file */
- if (!contains_oitem && recurse_into_dir) {
- git_buf *full = NULL;
- if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
- return -1;
- if (git_path_contains_dir(full, DOT_GIT))
- recurse_into_dir = false;
- }
+ git_buf base = GIT_BUF_INIT;
+ bool is_ignored;
- /* if directory is ignored, remember ignore_prefix */
- if ((contains_oitem || recurse_into_dir) &&
- *delta == GIT_DELTA_UNTRACKED &&
- git_iterator_current_is_ignored(info->new_iter))
- {
- git_buf_sets(&info->ignore_prefix, info->nitem->path);
- *delta = GIT_DELTA_IGNORED;
-
- /* skip recursion if we've just learned this is ignored */
- if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
- recurse_into_dir = false;
- }
+ *delta_type = GIT_DELTA_IGNORED;
+ git_buf_sets(&base, info->nitem->path);
- if (contains_oitem || recurse_into_dir) {
- /* advance into directory */
- error = git_iterator_advance_into(&info->nitem, info->new_iter);
+ /* advance into untracked directory */
+ if ((error = git_iterator_advance_into(&info->nitem, info->new_iter)) < 0) {
- /* if directory is empty, can't advance into it, so skip */
+ /* skip ahead if empty */
if (error == GIT_ENOTFOUND) {
giterr_clear();
error = git_iterator_advance(&info->nitem, info->new_iter);
+ }
- git_buf_clear(&info->ignore_prefix);
+ return error;
+ }
+
+ /* look for actual untracked file */
+ while (!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)) {
+ if ((error = git_iterator_advance_into(
+ &info->nitem, info->new_iter)) < 0)
+ break;
+ continue;
+ }
+
+ /* found a non-ignored item - treat parent dir as untracked */
+ if (!is_ignored) {
+ *delta_type = GIT_DELTA_UNTRACKED;
+ break;
}
- /* return UNMODIFIED to tell caller not to create a new record */
- *delta = GIT_DELTA_UNMODIFIED;
+ if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
+ break;
+ }
+
+ /* finish off scan */
+ while (!diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
+ if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
+ break;
}
+ git_buf_free(&base);
+
return error;
}
@@ -757,36 +759,116 @@ static int handle_unmatched_new_item(
int error = 0;
const git_index_entry *nitem = info->nitem;
git_delta_t delta_type = GIT_DELTA_UNTRACKED;
+ bool contains_oitem;
- /* check if contained in ignored parent directory */
- if (git_buf_len(&info->ignore_prefix) &&
- diff->pfxcomp(nitem->path, git_buf_cstr(&info->ignore_prefix)) == 0)
- delta_type = GIT_DELTA_IGNORED;
+ /* check if this is a prefix of the other side */
+ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
+
+ /* check if this is contained in an ignored parent directory */
+ if (git_buf_len(&info->ignore_prefix)) {
+ if (diff->pfxcomp(nitem->path, git_buf_cstr(&info->ignore_prefix)) == 0)
+ delta_type = GIT_DELTA_IGNORED;
+ else
+ git_buf_clear(&info->ignore_prefix);
+ }
if (S_ISDIR(nitem->mode)) {
- error = handle_unmatched_new_directory(diff, info, &delta_type);
+ bool recurse_into_dir = contains_oitem;
+
+ /* if not already inside an ignored dir, check if this is ignored */
+ if (delta_type != GIT_DELTA_IGNORED &&
+ git_iterator_current_is_ignored(info->new_iter))
+ {
+ delta_type = GIT_DELTA_IGNORED;
+ git_buf_sets(&info->ignore_prefix, nitem->path);
+ }
+
+ /* check if user requests recursion into this type of dir */
+ recurse_into_dir = contains_oitem ||
+ (delta_type == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
+ (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
+
+ /* do not advance into directories that contain a .git file */
+ if (recurse_into_dir) {
+ git_buf *full = NULL;
+ if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
+ return -1;
+ if (full && git_path_contains_dir(full, DOT_GIT))
+ recurse_into_dir = false;
+ }
- if (error || delta_type == GIT_DELTA_UNMODIFIED)
- return error;
+ /* still have to look into untracked directories to match core git -
+ * with no untracked files, directory is treated as ignored
+ */
+ if (!recurse_into_dir &&
+ delta_type == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_FAST_UNTRACKED_DIRS))
+ {
+ git_diff_delta *last;
+
+ /* attempt to insert record for this directory */
+ if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0)
+ return error;
+
+ /* if delta wasn't created (because of rules), just skip ahead */
+ last = diff_delta__last_for_item(diff, nitem);
+ if (!last)
+ 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;
+
+ /* it iteration changed delta type, the update the record */
+ if (delta_type == GIT_DELTA_IGNORED) {
+ last->status = GIT_DELTA_IGNORED;
+
+ /* remove the record if we don't want ignored records */
+ if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
+ git_vector_pop(&diff->deltas);
+ git__free(last);
+ }
+ }
+
+ return 0;
+ }
+
+ /* try to advance into directory if necessary */
+ if (recurse_into_dir) {
+ error = git_iterator_advance_into(&info->nitem, info->new_iter);
+
+ /* if real error or no error, proceed with iteration */
+ if (error != GIT_ENOTFOUND)
+ return error;
+ giterr_clear();
+
+ /* if directory is empty, can't advance into it, so either skip
+ * it or ignore it
+ */
+ if (contains_oitem)
+ return git_iterator_advance(&info->nitem, info->new_iter);
+ delta_type = GIT_DELTA_IGNORED;
+ }
}
- /* In core git, the next two "else if" clauses are effectively
- * reversed -- i.e. when an untracked file contained in an
- * ignored directory is individually ignored, it shows up as an
- * ignored file in the diff list, even though other untracked
- * files in the same directory are skipped completely.
+ /* In core git, the next two checks are effectively reversed --
+ * i.e. when an file contained in an ignored directory is explicitly
+ * ignored, it shows up as an ignored file in the diff list, even though
+ * other untracked files in the same directory are skipped completely.
*
- * To me, this is odd. If the directory is ignored and the file
- * is untracked, we should skip it consistently, regardless of
- * whether it happens to match a pattern in the ignore file.
+ * To me, this seems odd. If the directory is ignored and the file is
+ * untracked, we should skip it consistently, regardless of whether it
+ * happens to match a pattern in the ignore file.
*
- * To match the core git behavior, just reverse the following
- * two "else if" cases so that individual file ignores are
- * checked before container directory exclusions are used to
- * skip the file.
+ * To match the core git behavior, reverse the following two if checks
+ * so that individual file ignores are checked before container
+ * directory exclusions are used to skip the file.
*/
else if (delta_type == GIT_DELTA_IGNORED &&
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
+ /* item contained in ignored directory, so skip over it */
return git_iterator_advance(&info->nitem, info->new_iter);
else if (git_iterator_current_is_ignored(info->new_iter))
@@ -795,15 +877,16 @@ static int handle_unmatched_new_item(
else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
delta_type = GIT_DELTA_ADDED;
+ /* Actually create the record for this item if necessary */
if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0)
return error;
- /* if we are generating TYPECHANGE records then check for that
- * instead of just generating an ADDED/UNTRACKED record
+ /* If user requested TYPECHANGE records, then check for that instead of
+ * just generating an ADDED/UNTRACKED record
*/
if (delta_type != GIT_DELTA_IGNORED &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
- entry_is_prefixed(diff, info->oitem, nitem))
+ contains_oitem)
{
/* this entry was prefixed with a tree - make TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, nitem);