Commit c50c58decd92270319bcbdb59e1038e0e2f8f241

Russell Belfer 2013-01-02T17:10:56

Extend tests for checkout with typechanges Test a number of other cases, including intentionally forced conflicts and deeper inspection that trees get created properly. There is a still a bug in checkout because the first test here (i.e. test_checkout_typechange__checkout_typechanges_safe) should be able to pass with GIT_CHECKOUT_SAFE as a strategy, but it will not because of some lingering submodule checkout issues.

diff --git a/tests-clar/checkout/typechange.c b/tests-clar/checkout/typechange.c
index bc7039c..b92cc23 100644
--- a/tests-clar/checkout/typechange.c
+++ b/tests-clar/checkout/typechange.c
@@ -2,6 +2,7 @@
 #include "git2/checkout.h"
 #include "path.h"
 #include "posix.h"
+#include "fileops.h"
 
 static git_repository *g_repo = NULL;
 
@@ -34,24 +35,97 @@ void test_checkout_typechange__cleanup(void)
 	cl_fixture_cleanup("submod2_target");
 }
 
-void test_checkout_typechange__checkout_typechanges(void)
+static void assert_file_exists(const char *path)
+{
+	cl_assert_(git_path_isfile(path), path);
+}
+
+static void assert_dir_exists(const char *path)
+{
+	cl_assert_(git_path_isdir(path), path);
+}
+
+static void assert_workdir_matches_tree(
+	git_repository *repo, const git_oid *id, const char *root, bool recurse)
+{
+	git_object *obj;
+	git_tree *tree;
+	size_t i, max_i;
+	git_buf path = GIT_BUF_INIT;
+
+	if (!root)
+		root = git_repository_workdir(repo);
+	cl_assert(root);
+
+	cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJ_ANY));
+	cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJ_TREE));
+	git_object_free(obj);
+
+	max_i = git_tree_entrycount(tree);
+
+	for (i = 0; i < max_i; ++i) {
+		const git_tree_entry *te = git_tree_entry_byindex(tree, i);
+		cl_assert(te);
+
+		cl_git_pass(git_buf_joinpath(&path, root, git_tree_entry_name(te)));
+
+		switch (git_tree_entry_type(te)) {
+		case GIT_OBJ_COMMIT:
+			assert_dir_exists(path.ptr);
+			break;
+		case GIT_OBJ_TREE:
+			assert_dir_exists(path.ptr);
+			if (recurse)
+				assert_workdir_matches_tree(
+					repo, git_tree_entry_id(te), path.ptr, true);
+			break;
+		case GIT_OBJ_BLOB:
+			switch (git_tree_entry_filemode(te)) {
+			case GIT_FILEMODE_BLOB:
+			case GIT_FILEMODE_BLOB_EXECUTABLE:
+				assert_file_exists(path.ptr);
+				/* because of cross-platform, don't confirm exec bit yet */
+				break;
+			case GIT_FILEMODE_LINK:
+				cl_assert_(git_path_exists(path.ptr), path.ptr);
+				/* because of cross-platform, don't confirm link yet */
+				break;
+			default:
+				cl_assert(false); /* really?! */
+			}
+			break;
+		default:
+			cl_assert(false); /* really?!! */
+		}
+	}
+
+	git_tree_free(tree);
+	git_buf_free(&path);
+}
+
+void test_checkout_typechange__checkout_typechanges_safe(void)
 {
 	int i;
 	git_object *obj;
 	git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
 
-	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
-
 	for (i = 0; g_typechange_oids[i] != NULL; ++i) {
 		cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
 
-		/* fprintf(stderr, "---- checking out '%s' ----\n", g_typechange_oids[i]); */
+		opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+		/* There are bugs in some submodule->tree changes that prevent
+		 * SAFE from passing here, even though the following should work:
+		 */
+		/* !i ? GIT_CHECKOUT_FORCE : GIT_CHECKOUT_SAFE; */
 
 		cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
 
 		cl_git_pass(
 			git_repository_set_head_detached(g_repo, git_object_id(obj)));
 
+		assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true);
+
 		git_object_free(obj);
 
 		if (!g_typechange_empty[i]) {
@@ -71,3 +145,96 @@ void test_checkout_typechange__checkout_typechanges(void)
 		}
 	}
 }
+
+typedef struct {
+	int conflicts;
+	int dirty;
+	int updates;
+	int untracked;
+	int ignored;
+} notify_counts;
+
+static int notify_counter(
+	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)
+{
+	notify_counts *cts = payload;
+
+	GIT_UNUSED(path);
+	GIT_UNUSED(baseline);
+	GIT_UNUSED(target);
+	GIT_UNUSED(workdir);
+
+	switch (why) {
+	case GIT_CHECKOUT_NOTIFY_CONFLICT:  cts->conflicts++; break;
+	case GIT_CHECKOUT_NOTIFY_DIRTY:     cts->dirty++;     break;
+	case GIT_CHECKOUT_NOTIFY_UPDATED:   cts->updates++;   break;
+	case GIT_CHECKOUT_NOTIFY_UNTRACKED: cts->untracked++; break;
+	case GIT_CHECKOUT_NOTIFY_IGNORED:   cts->ignored++;   break;
+	default: break;
+	}
+
+	return 0;
+}
+
+static void force_create_file(const char *file)
+{
+	int error = git_futils_rmdir_r(file, NULL,
+		GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS);
+	cl_assert(!error || error == GIT_ENOTFOUND);
+	cl_git_pass(git_futils_mkpath2file(file, 0777));
+	cl_git_rewritefile(file, "yowza!");
+}
+
+void test_checkout_typechange__checkout_with_conflicts(void)
+{
+	int i;
+	git_object *obj;
+	git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
+	notify_counts cts = {0};
+
+	opts.notify_flags =
+		GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_UNTRACKED;
+	opts.notify_cb = notify_counter;
+	opts.notify_payload = &cts;
+
+	for (i = 0; g_typechange_oids[i] != NULL; ++i) {
+		cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
+
+		force_create_file("typechanges/a/blocker");
+		force_create_file("typechanges/b");
+		force_create_file("typechanges/c/sub/sub/file");
+		git_futils_rmdir_r("typechanges/d", NULL, GIT_RMDIR_REMOVE_FILES);
+		p_mkdir("typechanges/d", 0777); /* intentionally empty dir */
+		force_create_file("typechanges/untracked");
+
+		opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+		memset(&cts, 0, sizeof(cts));
+
+		cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
+		cl_assert(cts.conflicts > 0);
+		cl_assert(cts.untracked > 0);
+
+		opts.checkout_strategy =
+			GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+		memset(&cts, 0, sizeof(cts));
+
+		cl_assert(git_path_exists("typechanges/untracked"));
+
+		cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+		cl_assert_equal_i(0, cts.conflicts);
+
+		cl_assert(!git_path_exists("typechanges/untracked"));
+
+		cl_git_pass(
+			git_repository_set_head_detached(g_repo, git_object_id(obj)));
+
+		assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true);
+
+		git_object_free(obj);
+	}
+}