Commit dfbff793b8f39995d3a8744d6b7d75d5cc7201a0

Russell Belfer 2012-10-08T15:14:12

Fix a few diff bugs with directory content There are a few cases where diff should leave directories in the diff list if we want to match core git, such as when the directory contains a .git dir. That feature was lost when I introduced some of the new submodule handling. This restores that and then fixes a couple of related to diff output that are triggered by having diffs with directories in them. Also, this adds a new flag that can be passed to diff if you want diff output to actually include the file content of any untracked files.

diff --git a/include/git2/diff.h b/include/git2/diff.h
index 1f7f8ab..121c403 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -47,7 +47,8 @@ enum {
 	GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9),
 	GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10),
 	GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11),
-	GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12)
+	GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12),
+	GIT_DIFF_INCLUDE_UNTRACKED_CONTENT = (1 << 13),
 };
 
 /**
diff --git a/src/diff.c b/src/diff.c
index 7a0051a..8718e5a 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -669,7 +669,8 @@ static int diff_from_iterators(
 
 			/* check if contained in ignored parent directory */
 			if (git_buf_len(&ignore_prefix) &&
-				ITERATOR_PREFIXCMP(*old_iter, nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
+				ITERATOR_PREFIXCMP(*old_iter, nitem->path,
+					git_buf_cstr(&ignore_prefix)) == 0)
 				delta_type = GIT_DELTA_IGNORED;
 
 			if (S_ISDIR(nitem->mode)) {
@@ -677,10 +678,23 @@ static int diff_from_iterators(
 				 * it or if the user requested the contents of untracked
 				 * directories and it is not under an ignored directory.
 				 */
-				if ((oitem && ITERATOR_PREFIXCMP(*old_iter, oitem->path, nitem->path) == 0) ||
+				bool contains_tracked =
+					(oitem &&
+					 !ITERATOR_PREFIXCMP(*old_iter, oitem->path, nitem->path));
+				bool recurse_untracked =
 					(delta_type == GIT_DELTA_UNTRACKED &&
-					 (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0))
-				{
+					 (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
+
+				/* do not advance into directories that contain a .git file */
+				if (!contains_tracked && recurse_untracked) {
+					git_buf *full = NULL;
+					if (git_iterator_current_workdir_path(new_iter, &full) < 0)
+						goto fail;
+					if (git_path_contains_dir(full, DOT_GIT))
+						recurse_untracked = false;
+				}
+
+				if (contains_tracked || recurse_untracked) {
 					/* if this directory is ignored, remember it as the
 					 * "ignore_prefix" for processing contained items
 					 */
diff --git a/src/diff_output.c b/src/diff_output.c
index f5f6c38..9fee127 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -321,6 +321,9 @@ static int get_workdir_content(
 	if (file->mode == GIT_FILEMODE_COMMIT)
 		return get_workdir_sm_content(ctxt, file, map);
 
+	if (S_ISDIR(file->mode))
+		return 0;
+
 	if (git_buf_joinpath(&path, wd, file->path) < 0)
 		return -1;
 
@@ -535,6 +538,11 @@ static int diff_patch_load(
 		break;
 	case GIT_DELTA_MODIFIED:
 		break;
+	case GIT_DELTA_UNTRACKED:
+		delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA;
+		if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)
+			delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA;
+		break;
 	default:
 		delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA;
 		delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA;
@@ -1070,6 +1078,9 @@ static int print_patch_file(
 
 	GIT_UNUSED(progress);
 
+	if (S_ISDIR(delta->new_file.mode))
+		return 0;
+
 	if (!oldpfx)
 		oldpfx = DIFF_OLD_PREFIX_DEFAULT;
 
@@ -1134,6 +1145,9 @@ static int print_patch_hunk(
 {
 	diff_print_info *pi = data;
 
+	if (S_ISDIR(d->new_file.mode))
+		return 0;
+
 	git_buf_clear(pi->buf);
 	if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0)
 		return -1;
@@ -1158,6 +1172,9 @@ static int print_patch_line(
 {
 	diff_print_info *pi = data;
 
+	if (S_ISDIR(delta->new_file.mode))
+		return 0;
+
 	git_buf_clear(pi->buf);
 
 	if (line_origin == GIT_DIFF_LINE_ADDITION ||
diff --git a/src/iterator.c b/src/iterator.c
index e52554d..267687e 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -905,3 +905,15 @@ int git_iterator_cmp(
 	return ITERATOR_PREFIXCMP(*iter, entry->path, path_prefix);
 }
 
+int git_iterator_current_workdir_path(git_iterator *iter, git_buf **path)
+{
+	workdir_iterator *wi = (workdir_iterator *)iter;
+
+	if (iter->type != GIT_ITERATOR_WORKDIR || !wi->entry.path)
+		*path = NULL;
+	else
+		*path = &wi->path;
+
+	return 0;
+}
+
diff --git a/src/iterator.h b/src/iterator.h
index 11cd218..29c8985 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -10,6 +10,7 @@
 #include "common.h"
 #include "git2/index.h"
 #include "vector.h"
+#include "buffer.h"
 
 #define ITERATOR_PREFIXCMP(ITER, STR, PREFIX)	(((ITER).ignore_case) ? \
 	git__prefixcmp_icase((STR), (PREFIX)) : \
@@ -166,4 +167,11 @@ extern int git_iterator_advance_into_directory(
 extern int git_iterator_cmp(
 	git_iterator *iter, const char *path_prefix);
 
+/**
+ * Get the full path of the current item from a workdir iterator.
+ * This will return NULL for a non-workdir iterator.
+ */
+extern int git_iterator_current_workdir_path(
+	git_iterator *iter, git_buf **path);
+
 #endif