diff --git a/include/git2/repository.h b/include/git2/repository.h index a536c1398..e68e0548f 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -506,6 +506,25 @@ GIT_EXTERN(int) git_repository_hashfile( git_otype type, const char *as_path); +/** + * Detach the HEAD. + * + * If the HEAD is already detached and points to a Commit, 0 is returned. + * + * If the HEAD is already detached and points to a Tag, the HEAD is + * updated into making it point to the peeled Commit, and 0 is returned. + * + * If the HEAD is already detached and points to a non commitish, the HEAD is + * unaletered, and -1 is returned. + * + * Otherwise, the HEAD will be detached and point to the peeled Commit. + * + * @param repo Repository pointer + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_detach_head( + git_repository* repo); + /** @} */ GIT_END_DECL #endif diff --git a/src/repository.c b/src/repository.c index 20a623a85..def96816f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1442,3 +1442,27 @@ cleanup: return error; } +int git_repository_detach_head( + git_repository* repo) +{ + git_reference *old_head = NULL, + *new_head = NULL; + git_object *object = NULL; + int error = -1; + + assert(repo); + + if (git_repository_head(&old_head, repo) < 0) + return -1; + + if (git_object_lookup(&object, repo, git_reference_oid(old_head), GIT_OBJ_COMMIT) < 0) + goto cleanup; + + error = git_reference_create_oid(&new_head, repo, GIT_HEAD_FILE, git_reference_oid(old_head), 1); + +cleanup: + git_object_free(object); + git_reference_free(old_head); + git_reference_free(new_head); + return error; +} diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c index eb1332aff..74d2a1c88 100644 --- a/tests-clar/repo/head.c +++ b/tests-clar/repo/head.c @@ -50,3 +50,38 @@ void test_repo_head__head_orphan(void) git_reference_free(ref); } + +static void assert_head_is_correctly_detached(void) +{ + git_reference *head; + git_object *commit; + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + + cl_git_pass(git_object_lookup(&commit, repo, git_reference_oid(head), GIT_OBJ_COMMIT)); + + git_object_free(commit); + git_reference_free(head); +} + +void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_detach_head(repo)); + + assert_head_is_correctly_detached(); +} + +void test_repo_head__detach_head_Fails_if_HEAD_and_point_to_a_non_commitish(void) +{ + git_reference *head; + + cl_git_pass(git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, "refs/tags/point_to_blob", 1)); + + cl_git_fail(git_repository_detach_head(repo)); + + git_reference_free(head); +}