Commit 66142ae03134ef8a45e6bd304c6106a1685c3c99

Russell Belfer 2012-03-22T10:44:36

New status fixes This adds support for roughly-right tracking of submodules (although it does not recurse into submodules to detect internal modifications a la core git), and it adds support for including unmodified files in diff iteration if requested.

diff --git a/include/git2/diff.h b/include/git2/diff.h
index 0e7c02f..cb3ef4e 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -39,7 +39,8 @@ enum {
 	GIT_DIFF_IGNORE_SUBMODULES = (1 << 5),
 	GIT_DIFF_PATIENCE = (1 << 6),
 	GIT_DIFF_INCLUDE_IGNORED = (1 << 7),
-	GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8)
+	GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8),
+	GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9)
 };
 
 /**
diff --git a/include/git2/status.h b/include/git2/status.h
index 3390529..a24d39f 100644
--- a/include/git2/status.h
+++ b/include/git2/status.h
@@ -83,14 +83,14 @@ typedef enum {
  *   the workdir files are included in the status "show" option.
  *   Right now, there is no option to include all files in
  *   directories that are ignored completely.
- * - GIT_STATUS_OPT_EXCLUDE_UNMODIFIED indicates that callback
- *   do not need to be made on unmodified files.
+ * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback
+ *   should be made even on unmodified files.
  * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories
  *   which appear to be submodules should just be skipped over.
  */
 #define GIT_STATUS_OPT_INCLUDE_UNTRACKED  (1 << 0)
 #define GIT_STATUS_OPT_INCLUDE_IGNORED    (1 << 1)
-#define GIT_STATUS_OPT_EXCLUDE_UNMODIFIED (1 << 2)
+#define GIT_STATUS_OPT_INCLUDE_UNMODIFIED (1 << 2)
 #define GIT_STATUS_OPT_EXCLUDE_SUBMODULES (1 << 3)
 
 /**
diff --git a/src/diff.c b/src/diff.c
index 469a6c0..3f8041a 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -132,7 +132,17 @@ static int diff_delta__from_one(
 	git_delta_t   status,
 	const git_index_entry *entry)
 {
-	git_diff_delta *delta = diff_delta__alloc(diff, status, entry->path);
+	git_diff_delta *delta;
+
+	if (status == GIT_DELTA_IGNORED &&
+		(diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+		return 0;
+
+	if (status == GIT_DELTA_UNTRACKED &&
+		(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+		return 0;
+
+	delta = diff_delta__alloc(diff, status, entry->path);
 	GITERR_CHECK_ALLOC(delta);
 
 	/* This fn is just for single-sided diffs */
@@ -168,6 +178,10 @@ static int diff_delta__from_two(
 {
 	git_diff_delta *delta;
 
+	if (status == GIT_DELTA_UNMODIFIED &&
+		(diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+		return 0;
+
 	if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
 		const git_index_entry *temp = old;
 		old = new;
@@ -320,26 +334,30 @@ static int maybe_modified(
 	git_diff_list *diff)
 {
 	git_oid noid, *use_noid = NULL;
+	git_delta_t status = GIT_DELTA_MODIFIED;
 
 	GIT_UNUSED(old);
 
 	/* support "assume unchanged" & "skip worktree" bits */
 	if ((oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) != 0 ||
 		(oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
-		return 0;
+		status = GIT_DELTA_UNMODIFIED;
 
-	if (GIT_MODE_TYPE(oitem->mode) != GIT_MODE_TYPE(nitem->mode)) {
+	/* if basic type of file changed, then split into delete and add */
+	else if (GIT_MODE_TYPE(oitem->mode) != GIT_MODE_TYPE(nitem->mode)) {
 		if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
 			diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
 			return -1;
 		return 0;
 	}
 
-	if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
-		oitem->mode == nitem->mode)
-		return 0;
+	/* if oids and modes match, then file is unmodified */
+	else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
+			oitem->mode == nitem->mode)
+		status = GIT_DELTA_UNMODIFIED;
 
-	if (git_oid_iszero(&nitem->oid) && new->type == GIT_ITERATOR_WORKDIR) {
+	/* if we have a workdir item with an unknown oid, check deeper */
+	else if (git_oid_iszero(&nitem->oid) && new->type == GIT_ITERATOR_WORKDIR) {
 		/* if they files look exactly alike, then we'll assume the same */
 		if (oitem->file_size == nitem->file_size &&
 			oitem->ctime.seconds == nitem->ctime.seconds &&
@@ -348,25 +366,28 @@ static int maybe_modified(
 			oitem->ino == nitem->ino &&
 			oitem->uid == nitem->uid &&
 			oitem->gid == nitem->gid)
-			return 0;
+			status = GIT_DELTA_UNMODIFIED;
+
+		/* TODO? should we do anything special with submodules? */
+		else if (S_ISGITLINK(nitem->mode))
+			status = GIT_DELTA_UNMODIFIED;
 
 		/* TODO: check git attributes so we will not have to read the file
 		 * in if it is marked binary.
 		 */
 
-		if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
+		else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
 			return -1;
 
-		if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
+		else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
 			oitem->mode == nitem->mode)
-			return 0;
+			status = GIT_DELTA_UNMODIFIED;
 
 		/* store calculated oid so we don't have to recalc later */
 		use_noid = &noid;
 	}
 
-	return diff_delta__from_two(
-		diff, GIT_DELTA_MODIFIED, oitem, nitem, use_noid);
+	return diff_delta__from_two(diff, status, oitem, nitem, use_noid);
 }
 
 static int diff_from_iterators(
diff --git a/src/diff_output.c b/src/diff_output.c
index 638cabc..f4c2143 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -314,7 +314,8 @@ int git_diff_foreach(
 		git_blob *old_blob = NULL, *new_blob = NULL;
 		git_map old_data, new_data;
 
-		if (delta->status == GIT_DELTA_UNMODIFIED)
+		if (delta->status == GIT_DELTA_UNMODIFIED &&
+			(diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
 			continue;
 
 		if (delta->status == GIT_DELTA_IGNORED &&
@@ -377,7 +378,8 @@ int git_diff_foreach(
 				 */
 				if (git_oid_cmp(&delta->old.oid, &delta->new.oid) == 0) {
 					delta->status = GIT_DELTA_UNMODIFIED;
-					goto cleanup;
+					if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+						goto cleanup;
 				}
 			}
 		}
diff --git a/src/iterator.c b/src/iterator.c
index cc15b5f..001bee7 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -427,7 +427,12 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
 	/* detect submodules */
 	if (S_ISDIR(wi->entry.mode) &&
 		git_path_contains(&wi->path, DOT_GIT) == true)
+	{
+		size_t len = strlen(wi->entry.path);
+		assert(wi->entry.path[len - 1] == '/');
+		wi->entry.path[len - 1] = '\0';
 		wi->entry.mode = S_IFGITLINK;
+	}
 
 	return 0;
 }
diff --git a/src/status.c b/src/status.c
index eab7c88..0c7a622 100644
--- a/src/status.c
+++ b/src/status.c
@@ -137,7 +137,13 @@ int git_status_foreach_ext(
 	}
 
 	memset(&diffopt, 0, sizeof(diffopt));
-	diffopt.flags = GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+	if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
+		diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
+	if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
+		diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
+	if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
+		diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
+	/* TODO: support EXCLUDE_SUBMODULES flag */
 
 	if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
 		(err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0)
@@ -180,9 +186,9 @@ int git_status_foreach(
 {
 	git_status_options opts;
 
-	opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+	opts.show  = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
 	opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
-		GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
+		GIT_STATUS_OPT_INCLUDE_UNTRACKED;
 
 	return git_status_foreach_ext(repo, &opts, callback, payload);
 }