Skip to content

Commit 04fb12a

Browse files
committed
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.
1 parent f0cfc34 commit 04fb12a

File tree

5 files changed

+175
-1
lines changed

5 files changed

+175
-1
lines changed

include/git2/repository.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,17 @@ GIT_EXTERN(int) git_repository_init_ext(
345345
*/
346346
GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
347347

348+
/**
349+
* Retrieve the referenced HEAD for the worktree
350+
*
351+
* @param out pointer to the reference which will be retrieved
352+
* @param repo a repository object
353+
* @param name name of the worktree to retrieve HEAD for
354+
* @return 0 when successful, error-code otherwise
355+
*/
356+
GIT_EXTERN(int) git_repository_head_for_worktree(git_reference **out, git_repository *repo,
357+
const char *name);
358+
348359
/**
349360
* Check if a repository's HEAD is detached
350361
*
@@ -357,6 +368,20 @@ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
357368
*/
358369
GIT_EXTERN(int) git_repository_head_detached(git_repository *repo);
359370

371+
/*
372+
* Check if a worktree's HEAD is detached
373+
*
374+
* A worktree's HEAD is detached when it points directly to a
375+
* commit instead of a branch.
376+
*
377+
* @param repo a repository object
378+
* @param name name of the worktree to retrieve HEAD for
379+
* @return 1 if HEAD is detached, 0 if its not; error code if
380+
* there was an error
381+
*/
382+
GIT_EXTERN(int) git_repository_head_detached_for_worktree(git_repository *repo,
383+
const char *name);
384+
360385
/**
361386
* Check if the current branch is unborn
362387
*

include/git2/worktree.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt);
7777
*/
7878
GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path);
7979

80-
/*
80+
/**
8181
* Lock worktree if not already locked
8282
*
8383
* Lock a worktree, optionally specifying a reason why the linked

src/repository.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2032,6 +2032,49 @@ int git_repository_head_detached(git_repository *repo)
20322032
return exists;
20332033
}
20342034

2035+
static int read_worktree_head(git_buf *out, git_repository *repo, const char *name)
2036+
{
2037+
git_buf path = GIT_BUF_INIT;
2038+
int err;
2039+
2040+
assert(out && repo && name);
2041+
2042+
git_buf_clear(out);
2043+
2044+
if ((err = git_buf_printf(&path, "%s/worktrees/%s/HEAD", repo->commondir, name)) < 0)
2045+
goto out;
2046+
if (!git_path_exists(path.ptr))
2047+
{
2048+
err = -1;
2049+
goto out;
2050+
}
2051+
2052+
if ((err = git_futils_readbuffer(out, path.ptr)) < 0)
2053+
goto out;
2054+
git_buf_rtrim(out);
2055+
2056+
out:
2057+
git_buf_free(&path);
2058+
2059+
return err;
2060+
}
2061+
2062+
int git_repository_head_detached_for_worktree(git_repository *repo, const char *name)
2063+
{
2064+
git_buf buf = GIT_BUF_INIT;
2065+
int ret;
2066+
2067+
assert(repo && name);
2068+
2069+
if (read_worktree_head(&buf, repo, name) < 0)
2070+
return -1;
2071+
2072+
ret = git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) != 0;
2073+
git_buf_free(&buf);
2074+
2075+
return ret;
2076+
}
2077+
20352078
int git_repository_head(git_reference **head_out, git_repository *repo)
20362079
{
20372080
git_reference *head;
@@ -2051,6 +2094,48 @@ int git_repository_head(git_reference **head_out, git_repository *repo)
20512094
return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error;
20522095
}
20532096

2097+
int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name)
2098+
{
2099+
git_buf buf = GIT_BUF_INIT;
2100+
git_reference *head;
2101+
int err;
2102+
2103+
assert(out && repo && name);
2104+
2105+
*out = NULL;
2106+
2107+
if (git_repository_head_detached_for_worktree(repo, name))
2108+
return -1;
2109+
if ((err = read_worktree_head(&buf, repo, name)) < 0)
2110+
goto out;
2111+
2112+
/* We can only resolve symbolic references */
2113+
if (git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)))
2114+
{
2115+
err = -1;
2116+
goto out;
2117+
}
2118+
git_buf_consume(&buf, buf.ptr + strlen(GIT_SYMREF));
2119+
2120+
if ((err = git_reference_lookup(&head, repo, buf.ptr)) < 0)
2121+
goto out;
2122+
if (git_reference_type(head) == GIT_REF_OID)
2123+
{
2124+
*out = head;
2125+
err = 0;
2126+
goto out;
2127+
}
2128+
2129+
err = git_reference_lookup_resolved(
2130+
out, repo, git_reference_symbolic_target(head), -1);
2131+
git_reference_free(head);
2132+
2133+
out:
2134+
git_buf_free(&buf);
2135+
2136+
return err;
2137+
}
2138+
20542139
int git_repository_head_unborn(git_repository *repo)
20552140
{
20562141
git_reference *ref = NULL;

tests/worktree/repository.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include "clar_libgit2.h"
2+
#include "worktree_helpers.h"
3+
#include "submodule/submodule_helpers.h"
4+
5+
#include "repository.h"
6+
7+
#define COMMON_REPO "testrepo"
8+
#define WORKTREE_REPO "testrepo-worktree"
9+
10+
static worktree_fixture fixture =
11+
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
12+
13+
void test_worktree_repository__initialize(void)
14+
{
15+
setup_fixture_worktree(&fixture);
16+
}
17+
18+
void test_worktree_repository__cleanup(void)
19+
{
20+
cleanup_fixture_worktree(&fixture);
21+
}
22+
23+
void test_worktree_repository__head(void)
24+
{
25+
git_reference *ref, *head;
26+
27+
cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree"));
28+
cl_git_pass(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree"));
29+
cl_assert(git_reference_cmp(ref, head) == 0);
30+
31+
git_reference_free(ref);
32+
git_reference_free(head);
33+
}
34+
35+
void test_worktree_repository__head_fails_for_invalid_worktree(void)
36+
{
37+
git_reference *head = NULL;
38+
39+
cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "invalid"));
40+
cl_assert(head == NULL);
41+
}
42+
43+
void test_worktree_repository__head_detached(void)
44+
{
45+
git_reference *ref, *head;
46+
47+
cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree"));
48+
cl_git_pass(git_repository_set_head_detached(fixture.worktree, &ref->target.oid));
49+
50+
cl_assert(git_repository_head_detached(fixture.worktree));
51+
cl_assert(git_repository_head_detached_for_worktree(fixture.repo, "testrepo-worktree"));
52+
cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree"));
53+
54+
git_reference_free(ref);
55+
}
56+
57+
void test_worktree_repository__head_detached_fails_for_invalid_worktree(void)
58+
{
59+
git_reference *head = NULL;
60+
61+
cl_git_fail(git_repository_head_detached_for_worktree(fixture.repo, "invalid"));
62+
cl_assert(head == NULL);
63+
}

tests/worktree/worktree.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "clar_libgit2.h"
22
#include "worktree_helpers.h"
33

4+
#include "checkout.h"
45
#include "repository.h"
56
#include "worktree.h"
67

0 commit comments

Comments
 (0)