Commit 36fd9e30651cf0d6b0ef58452ba2974a3544d4d1

Russell Belfer 2013-06-21T11:20:54

Fix checkout of modified file when missing from wd This fixes the checkout case when a file is modified between the baseline and the target and yet missing in the working directory. The logic for that case appears to have been wrong. This also adds a useful checkout notify callback to the checkout test helpers that will count notifications and also has a debug mode to visualize what checkout thinks that it's doing.

diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index 6798bf3..f49e875 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -183,6 +183,8 @@ typedef enum {
 	GIT_CHECKOUT_NOTIFY_UPDATED   = (1u << 2),
 	GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),
 	GIT_CHECKOUT_NOTIFY_IGNORED   = (1u << 4),
+
+	GIT_CHECKOUT_NOTIFY_ALL       = 0x0FFFFu
 } git_checkout_notify_t;
 
 /** Checkout notification callback function */
diff --git a/src/checkout.c b/src/checkout.c
index ede0be8..065bb50 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -220,9 +220,11 @@ static int checkout_action_no_wd(
 		action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
 		break;
 	case GIT_DELTA_ADDED:    /* case 2 or 28 (and 5 but not really) */
-	case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
 		action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
 		break;
+	case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
+		action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT);
+		break;
 	case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
 		if (delta->new_file.mode == GIT_FILEMODE_TREE)
 			action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
diff --git a/tests-clar/checkout/checkout_helpers.c b/tests-clar/checkout/checkout_helpers.c
index ab93a89..8da024d 100644
--- a/tests-clar/checkout/checkout_helpers.c
+++ b/tests-clar/checkout/checkout_helpers.c
@@ -91,3 +91,98 @@ void check_file_contents_nocr_at_line(
 {
 	check_file_contents_internal(path, expected, true, file, line, msg);
 }
+
+int checkout_count_callback(
+	git_checkout_notify_t why,
+	const char *path,
+	const git_diff_file *baseline,
+	const git_diff_file *target,
+	const git_diff_file *workdir,
+	void *payload)
+{
+	checkout_counts *ct = payload;
+
+	GIT_UNUSED(baseline); GIT_UNUSED(target); GIT_UNUSED(workdir);
+
+	if (why & GIT_CHECKOUT_NOTIFY_CONFLICT) {
+		ct->n_conflicts++;
+
+		if (ct->debug) {
+			if (workdir) {
+				if (baseline) {
+					if (target)
+						fprintf(stderr, "M %s (conflicts with M %s)\n",
+							workdir->path, target->path);
+					else
+						fprintf(stderr, "M %s (conflicts with D %s)\n",
+							workdir->path, baseline->path);
+				} else {
+					if (target)
+						fprintf(stderr, "Existing %s (conflicts with A %s)\n",
+							workdir->path, target->path);
+					else
+						fprintf(stderr, "How can an untracked file be a conflict (%s)\n", workdir->path);
+				}
+			} else {
+				if (baseline) {
+					if (target)
+						fprintf(stderr, "D %s (conflicts with M %s)\n",
+							target->path, baseline->path);
+					else
+						fprintf(stderr, "D %s (conflicts with D %s)\n",
+							baseline->path, baseline->path);
+				} else {
+					if (target)
+						fprintf(stderr, "How can an added file with no workdir be a conflict (%s)\n", target->path);
+					else
+						fprintf(stderr, "How can a nonexistent file be a conflict (%s)\n", path);
+				}
+			}
+		}
+	}
+
+	if (why & GIT_CHECKOUT_NOTIFY_DIRTY) {
+		ct->n_dirty++;
+
+		if (ct->debug) {
+			if (workdir)
+				fprintf(stderr, "M %s\n", workdir->path);
+			else
+				fprintf(stderr, "D %s\n", baseline->path);
+		}
+	}
+
+	if (why & GIT_CHECKOUT_NOTIFY_UPDATED) {
+		ct->n_updates++;
+
+		if (ct->debug) {
+			if (baseline) {
+				if (target)
+					fprintf(stderr, "update: M %s\n", path);
+				else
+					fprintf(stderr, "update: D %s\n", path);
+			} else {
+				if (target)
+					fprintf(stderr, "update: A %s\n", path);
+				else
+					fprintf(stderr, "update: this makes no sense %s\n", path);
+			}
+		}
+	}
+
+	if (why & GIT_CHECKOUT_NOTIFY_UNTRACKED) {
+		ct->n_untracked++;
+
+		if (ct->debug)
+			fprintf(stderr, "? %s\n", path);
+	}
+
+	if (why & GIT_CHECKOUT_NOTIFY_IGNORED) {
+		ct->n_ignored++;
+
+		if (ct->debug)
+			fprintf(stderr, "I %s\n", path);
+	}
+
+	return 0;
+}
diff --git a/tests-clar/checkout/checkout_helpers.h b/tests-clar/checkout/checkout_helpers.h
index 3405380..0e8da31 100644
--- a/tests-clar/checkout/checkout_helpers.h
+++ b/tests-clar/checkout/checkout_helpers.h
@@ -19,3 +19,20 @@ extern void check_file_contents_nocr_at_line(
 
 #define check_file_contents_nocr(PATH,EXP) \
 	check_file_contents_nocr_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH)
+
+typedef struct {
+	int n_conflicts;
+	int n_dirty;
+	int n_updates;
+	int n_untracked;
+	int n_ignored;
+	int debug;
+} checkout_counts;
+
+extern int checkout_count_callback(
+	git_checkout_notify_t why,
+	const char *path,
+	const git_diff_file *baseline,
+	const git_diff_file *target,
+	const git_diff_file *workdir,
+	void *payload);
diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c
index 7a1c797..5835ab0 100644
--- a/tests-clar/checkout/tree.c
+++ b/tests-clar/checkout/tree.c
@@ -448,9 +448,15 @@ void test_checkout_tree__donot_update_deleted_file_by_default(void)
 	git_oid old_id, new_id;
 	git_commit *old_commit = NULL, *new_commit = NULL;
 	git_index *index = NULL;
+	checkout_counts ct;
 
 	opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 
+	memset(&ct, 0, sizeof(ct));
+	opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
+	opts.notify_cb = checkout_count_callback;
+	opts.notify_payload = &ct;
+
 	cl_git_pass(git_repository_index(&index, g_repo));
 
 	cl_git_pass(git_oid_fromstr(&old_id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
@@ -465,8 +471,13 @@ void test_checkout_tree__donot_update_deleted_file_by_default(void)
 
 	cl_git_pass(git_oid_fromstr(&new_id, "099fabac3a9ea935598528c27f866e34089c2eff"));
 	cl_git_pass(git_commit_lookup(&new_commit, g_repo, &new_id));
+
+
 	cl_git_fail(git_checkout_tree(g_repo, (git_object *)new_commit, &opts));
 
+	cl_assert_equal_i(1, ct.n_conflicts);
+	cl_assert_equal_i(1, ct.n_updates);
+
 	git_commit_free(old_commit);
 	git_commit_free(new_commit);
 	git_index_free(index);