Commit 04fb12abb24810391fa19af5696eb38629d650df

Patrick Steinhardt 2015-10-27T12:37:51

worktree: implement functions reading HEAD Implement `git_repository_head_for_worktree` and `git_repository_head_detached_for_worktree` for directly accessing a worktree's HEAD without opening it as a `git_repository` first.

diff --git a/include/git2/repository.h b/include/git2/repository.h
index 29eb2da..a396a54 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -346,6 +346,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
  *
  * A repository's HEAD is detached when it points directly to a commit
@@ -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 594ff79..ec869fb 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 03e4390..445005e 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 0000000..5c7595c
--- /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 7758b1b..756cf38 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"