Commit bf0e62a2b8b6116ae61dae37f95a3eb840246ec6

nulltoken 2012-10-07T12:50:18

clone: fix cloning of empty repository

diff --git a/src/clone.c b/src/clone.c
index 312a38e..a5f4867 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -22,7 +22,7 @@
 #include "refs.h"
 #include "path.h"
 
-static int create_tracking_branch(
+static int create_branch(
 	git_reference **branch,
 	git_repository *repo,
 	const git_oid *target,
@@ -30,43 +30,74 @@ static int create_tracking_branch(
 {
 	git_object *head_obj = NULL;
 	git_reference *branch_ref;
-	int retcode = GIT_ERROR;
+	int error;
 
 	/* Find the target commit */
-	if (git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY) < 0)
-		return GIT_ERROR;
+	if ((error = git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY)) < 0)
+		return error;
 
 	/* Create the new branch */
-	if (!git_branch_create(&branch_ref, repo, name, head_obj, 0)) {
-		git_config *cfg;
-
-		/* Set up tracking */
-		if (!git_repository_config(&cfg, repo)) {
-			git_buf remote = GIT_BUF_INIT;
-			git_buf merge = GIT_BUF_INIT;
-			git_buf merge_target = GIT_BUF_INIT;
-			if (!git_buf_printf(&remote, "branch.%s.remote", name) &&
-				 !git_buf_printf(&merge, "branch.%s.merge", name) &&
-				 !git_buf_printf(&merge_target, GIT_REFS_HEADS_DIR "%s", name) &&
-				 !git_config_set_string(cfg, git_buf_cstr(&remote), GIT_REMOTE_ORIGIN) &&
-				 !git_config_set_string(cfg, git_buf_cstr(&merge), git_buf_cstr(&merge_target))) {
-				retcode = 0;
-			}
-			git_buf_free(&remote);
-			git_buf_free(&merge);
-			git_buf_free(&merge_target);
-			git_config_free(cfg);
-		}
-	}
+	error = git_branch_create(&branch_ref, repo, name, head_obj, 0);
 
 	git_object_free(head_obj);
 
-	if (!retcode)
+	if (!error)
 		*branch = branch_ref;
 	else
 		git_reference_free(branch_ref);
 
-	return retcode;
+	return error;
+}
+
+static int setup_tracking_config(
+	git_repository *repo,
+	const char *branch_name,
+	const char *remote_name,
+	const char *merge_target)
+{
+	git_config *cfg;
+	git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT;
+	int error = -1;
+
+	if (git_repository_config__weakptr(&cfg, repo) < 0)
+		return -1;
+
+	if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0)
+		goto cleanup;
+
+	if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0)
+		goto cleanup;
+
+	if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0)
+		goto cleanup;
+
+	if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0)
+		goto cleanup;
+
+	error = 0;
+
+cleanup:
+	git_buf_free(&remote_key);
+	git_buf_free(&merge_key);
+	return error;
+}
+
+static int create_tracking_branch(
+	git_reference **branch,
+	git_repository *repo,
+	const git_oid *target,
+	const char *branch_name)
+{
+	int error;
+
+	if ((error = create_branch(branch, repo, target, branch_name)) < 0)
+		return error;
+
+	return setup_tracking_config(
+		repo,
+		branch_name,
+		GIT_REMOTE_ORIGIN,
+		git_reference_name(*branch));
 }
 
 struct head_info {
@@ -117,13 +148,20 @@ static int reference_matches_remote_head(
 	return 0;
 }
 
-static int update_head_to_new_branch(git_repository *repo, const git_oid *target, const char *name)
+static int update_head_to_new_branch(
+	git_repository *repo,
+	const git_oid *target,
+	const char *name)
 {
 	git_reference *tracking_branch;
 	int error;
 
-	if (create_tracking_branch(&tracking_branch, repo, target, name) < 0)
-		return -1;
+	if ((error = create_tracking_branch(
+		&tracking_branch,
+		repo,
+		target,
+		name)) < 0)
+			return error;
 
 	error = git_repository_set_head(repo, git_reference_name(tracking_branch));
 
@@ -139,6 +177,15 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote)
 	struct head_info head_info;
 	git_buf remote_master_name = GIT_BUF_INIT;
 
+	/* Did we just clone an empty repository? */
+	if (remote->refs.length == 0) {
+		return setup_tracking_config(
+			repo,
+			"master",
+			GIT_REMOTE_ORIGIN,
+			GIT_REFS_HEADS_MASTER_FILE);
+	}
+
 	/* Get the remote's HEAD. This is always the first ref in remote->refs. */
 	remote_head = remote->refs.contents[0];
 	git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid);
@@ -244,11 +291,14 @@ static bool path_is_okay(const char *path)
 }
 
 
-static int clone_internal(git_repository **out,
-								  const char *origin_url,
-								  const char *path,
-								  git_indexer_stats *fetch_stats,
-								  int is_bare)
+static int clone_internal(
+	git_repository **out,
+	const char *origin_url,
+	const char *path,
+	git_indexer_stats *fetch_stats,
+	git_indexer_stats *checkout_stats,
+	git_checkout_opts *checkout_opts,
+	int is_bare)
 {
 	int retcode = GIT_ERROR;
 	git_repository *repo = NULL;
@@ -271,6 +321,9 @@ static int clone_internal(git_repository **out,
 		}
 	}
 
+	if (!retcode && !is_bare && !git_repository_head_orphan(repo))
+		retcode = git_checkout_head(*out, checkout_opts, checkout_stats);
+
 	return retcode;
 }
 
@@ -280,7 +333,15 @@ int git_clone_bare(git_repository **out,
 						 git_indexer_stats *fetch_stats)
 {
 	assert(out && origin_url && dest_path);
-	return clone_internal(out, origin_url, dest_path, fetch_stats, 1);
+
+	return clone_internal(
+		out,
+		origin_url,
+		dest_path,
+		fetch_stats,
+		NULL,
+		NULL,
+		1);
 }
 
 
@@ -291,12 +352,14 @@ int git_clone(git_repository **out,
 				  git_indexer_stats *checkout_stats,
 				  git_checkout_opts *checkout_opts)
 {
-	int retcode = GIT_ERROR;
-
 	assert(out && origin_url && workdir_path);
 
-	if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0)))
-		retcode = git_checkout_head(*out, checkout_opts, checkout_stats);
-
-	return retcode;
+	return clone_internal(
+		out,
+		origin_url,
+		workdir_path,
+		fetch_stats,
+		checkout_stats,
+		checkout_opts,
+		0);
 }
diff --git a/tests-clar/clone/clone.c b/tests-clar/clone/clone.c
index 6610943..42ddb8a 100644
--- a/tests-clar/clone/clone.c
+++ b/tests-clar/clone/clone.c
@@ -6,6 +6,7 @@
 #define DO_LOCAL_TEST 0
 #define DO_LIVE_NETWORK_TESTS 0
 #define LIVE_REPO_URL "git://github.com/nulltoken/TestGitRepository"
+#define LIVE_EMPTYREPO_URL "git://github.com/nulltoken/TestEmptyRepository"
 
 
 static git_repository *g_repo;
@@ -152,3 +153,23 @@ void test_clone_clone__fail_with_already_existing_but_non_empty_directory(void)
 	cl_git_mkfile("./foo/bar", "Baz!");
 	cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL));
 }
+
+void test_clone_clone__empty_repository(void)
+{
+#if DO_LIVE_NETWORK_TESTS
+	git_reference *head;
+
+	cl_set_cleanup(&cleanup_repository, "./empty");
+
+	cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./empty", NULL, NULL, NULL));
+
+	cl_assert_equal_i(true, git_repository_is_empty(g_repo));
+	cl_assert_equal_i(true, git_repository_head_orphan(g_repo));
+
+	cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE));
+	cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+	cl_assert_equal_s("refs/heads/master", git_reference_target(head));
+
+	git_reference_free(head);
+#endif
+}