Commit 44af67a8b6679ac33c3407d45fee042178d97e76

nulltoken 2012-09-15T22:07:45

repository: introduce git_repository_set_head()

diff --git a/include/git2/repository.h b/include/git2/repository.h
index 59a7d2c..025a0a9 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -507,6 +507,28 @@ GIT_EXTERN(int) git_repository_hashfile(
     const char *as_path);
 
 /**
+ * Make the repository HEAD point to the specified reference.
+ *
+ * If the provided reference points to a Tree or a Blob, the HEAD is
+ * unaltered and -1 is returned.
+ *
+ * If the provided reference points to a branch, the HEAD will point
+ * to that branch, staying attached, or become attached if it isn't yet.
+ * If the branch doesn't exist yet, no error will be return. The HEAD
+ * will then be attached to an unborn branch.
+ *
+ * Otherwise, the HEAD will be detached and will directly point to
+ * the Commit.
+ *
+ * @param repo Repository pointer
+ * @param refname Canonical name of the reference the HEAD should point at
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_set_head(
+	git_repository* repo,
+	const char* refname);
+
+/**
  * Make the repository HEAD directly point to the Commit.
  *
  * If the provided committish cannot be found in the repository, the HEAD
diff --git a/src/repository.c b/src/repository.c
index a3e7814..734cab4 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -1442,6 +1442,38 @@ cleanup:
 	return error;
 }
 
+static bool looks_like_a_branch(const char *refname)
+{
+	return git__prefixcmp(refname, GIT_REFS_HEADS_DIR) == 0;
+}
+
+int git_repository_set_head(
+	git_repository* repo,
+	const char* refname)
+{
+	git_reference *ref,
+		*new_head = NULL;
+	int error;
+
+	assert(repo && refname);
+
+	error = git_reference_lookup(&ref, repo, refname);
+	if (error < 0 && error != GIT_ENOTFOUND)
+		return error;
+
+	if (!error) {
+		if (git_reference_is_branch(ref))
+			error = git_reference_create_symbolic(&new_head, repo, GIT_HEAD_FILE, git_reference_name(ref), 1);
+		else
+			error = git_repository_set_head_detached(repo, git_reference_oid(ref));
+	} else if (looks_like_a_branch(refname))
+		error = git_reference_create_symbolic(&new_head, repo, GIT_HEAD_FILE, refname, 1);
+
+	git_reference_free(ref);
+	git_reference_free(new_head);
+	return error;
+}
+
 int git_repository_set_head_detached(
 	git_repository* repo,
 	const git_oid* commitish)
diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c
index 372cdd6..64dec69 100644
--- a/tests-clar/repo/head.c
+++ b/tests-clar/repo/head.c
@@ -51,6 +51,41 @@ void test_repo_head__head_orphan(void)
 	git_reference_free(ref);
 }
 
+void test_repo_head__set_head_Attaches_HEAD_to_un_unborn_branch_when_the_branch_doesnt_exist(void)
+{
+	git_reference *head;
+
+	cl_git_pass(git_repository_set_head(repo, "refs/heads/doesnt/exist/yet"));
+
+	cl_assert_equal_i(false, git_repository_head_detached(repo));
+
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo));
+}
+
+void test_repo_head__set_head_Returns_ENOTFOUND_when_the_reference_doesnt_exist(void)
+{
+	cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head(repo, "refs/tags/doesnt/exist/yet"));
+}
+
+void test_repo_head__set_head_Fails_when_the_reference_points_to_a_non_commitish(void)
+{
+	cl_git_fail(git_repository_set_head(repo, "refs/tags/point_to_blob"));
+}
+
+void test_repo_head__set_head_Attaches_HEAD_when_the_reference_points_to_a_branch(void)
+{
+	git_reference *head;
+
+	cl_git_pass(git_repository_set_head(repo, "refs/heads/br2"));
+
+	cl_assert_equal_i(false, git_repository_head_detached(repo));
+
+	cl_git_pass(git_repository_head(&head, repo));
+	cl_assert_equal_s("refs/heads/br2", git_reference_name(head));
+
+	git_reference_free(head);
+}
+
 static void assert_head_is_correctly_detached(void)
 {
 	git_reference *head;
@@ -66,6 +101,15 @@ static void assert_head_is_correctly_detached(void)
 	git_reference_free(head);
 }
 
+void test_repo_head__set_head_Detaches_HEAD_when_the_reference_doesnt_point_to_a_branch(void)
+{
+	cl_git_pass(git_repository_set_head(repo, "refs/tags/test"));
+
+	cl_assert_equal_i(true, git_repository_head_detached(repo));
+
+	assert_head_is_correctly_detached();
+}
+
 void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void)
 {
 	git_oid oid;