diff --git a/include/git2/repository.h b/include/git2/repository.h index 29eb2da49..a396a5409 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -345,6 +345,17 @@ GIT_EXTERN(int) git_repository_init_ext( */ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); +/** + * Retrieve the referenced HEAD for the worktree + * + * @param out pointer to the reference which will be retrieved + * @param repo a repository object + * @param name name of the worktree to retrieve HEAD for + * @return 0 when successful, error-code otherwise + */ +GIT_EXTERN(int) git_repository_head_for_worktree(git_reference **out, git_repository *repo, + const char *name); + /** * Check if a repository's HEAD is detached * @@ -357,6 +368,20 @@ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); */ GIT_EXTERN(int) git_repository_head_detached(git_repository *repo); +/* + * Check if a worktree's HEAD is detached + * + * A worktree's HEAD is detached when it points directly to a + * commit instead of a branch. + * + * @param repo a repository object + * @param name name of the worktree to retrieve HEAD for + * @return 1 if HEAD is detached, 0 if its not; error code if + * there was an error + */ +GIT_EXTERN(int) git_repository_head_detached_for_worktree(git_repository *repo, + const char *name); + /** * Check if the current branch is unborn * diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 594ff795b..ec869fb59 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -77,7 +77,7 @@ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); */ GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path); -/* +/** * Lock worktree if not already locked * * Lock a worktree, optionally specifying a reason why the linked diff --git a/src/repository.c b/src/repository.c index 03e43909b..445005e96 100644 --- a/src/repository.c +++ b/src/repository.c @@ -2032,6 +2032,49 @@ int git_repository_head_detached(git_repository *repo) return exists; } +static int read_worktree_head(git_buf *out, git_repository *repo, const char *name) +{ + git_buf path = GIT_BUF_INIT; + int err; + + assert(out && repo && name); + + git_buf_clear(out); + + if ((err = git_buf_printf(&path, "%s/worktrees/%s/HEAD", repo->commondir, name)) < 0) + goto out; + if (!git_path_exists(path.ptr)) + { + err = -1; + goto out; + } + + if ((err = git_futils_readbuffer(out, path.ptr)) < 0) + goto out; + git_buf_rtrim(out); + +out: + git_buf_free(&path); + + return err; +} + +int git_repository_head_detached_for_worktree(git_repository *repo, const char *name) +{ + git_buf buf = GIT_BUF_INIT; + int ret; + + assert(repo && name); + + if (read_worktree_head(&buf, repo, name) < 0) + return -1; + + ret = git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) != 0; + git_buf_free(&buf); + + return ret; +} + int git_repository_head(git_reference **head_out, git_repository *repo) { git_reference *head; @@ -2051,6 +2094,48 @@ int git_repository_head(git_reference **head_out, git_repository *repo) return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error; } +int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name) +{ + git_buf buf = GIT_BUF_INIT; + git_reference *head; + int err; + + assert(out && repo && name); + + *out = NULL; + + if (git_repository_head_detached_for_worktree(repo, name)) + return -1; + if ((err = read_worktree_head(&buf, repo, name)) < 0) + goto out; + + /* We can only resolve symbolic references */ + if (git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) + { + err = -1; + goto out; + } + git_buf_consume(&buf, buf.ptr + strlen(GIT_SYMREF)); + + if ((err = git_reference_lookup(&head, repo, buf.ptr)) < 0) + goto out; + if (git_reference_type(head) == GIT_REF_OID) + { + *out = head; + err = 0; + goto out; + } + + err = git_reference_lookup_resolved( + out, repo, git_reference_symbolic_target(head), -1); + git_reference_free(head); + +out: + git_buf_free(&buf); + + return err; +} + int git_repository_head_unborn(git_repository *repo) { git_reference *ref = NULL; diff --git a/tests/worktree/repository.c b/tests/worktree/repository.c new file mode 100644 index 000000000..5c7595c64 --- /dev/null +++ b/tests/worktree/repository.c @@ -0,0 +1,63 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" +#include "submodule/submodule_helpers.h" + +#include "repository.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_repository__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_repository__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_repository__head(void) +{ + git_reference *ref, *head; + + cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree")); + cl_git_pass(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree")); + cl_assert(git_reference_cmp(ref, head) == 0); + + git_reference_free(ref); + git_reference_free(head); +} + +void test_worktree_repository__head_fails_for_invalid_worktree(void) +{ + git_reference *head = NULL; + + cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "invalid")); + cl_assert(head == NULL); +} + +void test_worktree_repository__head_detached(void) +{ + git_reference *ref, *head; + + cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree")); + cl_git_pass(git_repository_set_head_detached(fixture.worktree, &ref->target.oid)); + + cl_assert(git_repository_head_detached(fixture.worktree)); + cl_assert(git_repository_head_detached_for_worktree(fixture.repo, "testrepo-worktree")); + cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree")); + + git_reference_free(ref); +} + +void test_worktree_repository__head_detached_fails_for_invalid_worktree(void) +{ + git_reference *head = NULL; + + cl_git_fail(git_repository_head_detached_for_worktree(fixture.repo, "invalid")); + cl_assert(head == NULL); +} diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 7758b1b1c..756cf387b 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "worktree_helpers.h" +#include "checkout.h" #include "repository.h" #include "worktree.h"