Skip to content

Commit c09fd54

Browse files
committed
repository: introduce commondir variable
The commondir variable stores the path to the common directory. The common directory is used to store objects and references shared across multiple repositories. A current use case is the newly introduced `git worktree` feature, which sets up a separate working copy, where the backing git object store and references are pointed to by the common directory.
1 parent 807d57e commit c09fd54

File tree

4 files changed

+138
-15
lines changed

4 files changed

+138
-15
lines changed

include/git2/repository.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,17 @@ GIT_EXTERN(const char *) git_repository_path(git_repository *repo);
392392
*/
393393
GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo);
394394

395+
/**
396+
* Get the path of the shared common directory for this repository
397+
*
398+
* If the repository is bare is not a worktree, the git directory
399+
* path is returned.
400+
*
401+
* @param repo A repository object
402+
* @return the path to the common dir
403+
*/
404+
GIT_EXTERN(const char *) git_repository_commondir(git_repository *repo);
405+
395406
/**
396407
* Set the path to the working directory for this repository
397408
*

src/repository.c

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ GIT__USE_STRMAP
3838

3939
static int check_repositoryformatversion(git_config *config);
4040

41+
#define GIT_COMMONDIR_FILE "commondir"
42+
4143
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
4244

4345
#define GIT_BRANCH_MASTER "master"
@@ -143,6 +145,7 @@ void git_repository_free(git_repository *repo)
143145

144146
git__free(repo->path_gitlink);
145147
git__free(repo->path_repository);
148+
git__free(repo->commondir);
146149
git__free(repo->workdir);
147150
git__free(repo->namespace);
148151
git__free(repo->ident_name);
@@ -157,17 +160,41 @@ void git_repository_free(git_repository *repo)
157160
*
158161
* Open a repository object from its path
159162
*/
160-
static bool valid_repository_path(git_buf *repository_path)
163+
static bool valid_repository_path(git_buf *repository_path, git_buf *common_path)
161164
{
162-
/* Check OBJECTS_DIR first, since it will generate the longest path name */
163-
if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false)
164-
return false;
165+
/* Check if we have a separate commondir (e.g. we have a
166+
* worktree) */
167+
if (git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) {
168+
git_buf common_link = GIT_BUF_INIT;
169+
git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE);
170+
171+
git_futils_readbuffer(&common_link, common_link.ptr);
172+
git_buf_rtrim(&common_link);
173+
174+
if (git_path_is_relative(common_link.ptr)) {
175+
git_buf_joinpath(common_path, repository_path->ptr, common_link.ptr);
176+
} else {
177+
git_buf_swap(common_path, &common_link);
178+
}
179+
180+
git_buf_free(&common_link);
181+
}
182+
else {
183+
git_buf_set(common_path, repository_path->ptr, repository_path->size);
184+
}
185+
186+
/* Make sure the commondir path always has a trailing * slash */
187+
if (git_buf_rfind(common_path, '/') != (ssize_t)common_path->size - 1)
188+
git_buf_putc(common_path, '/');
165189

166190
/* Ensure HEAD file exists */
167191
if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false)
168192
return false;
169193

170-
if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false)
194+
/* Check files in common dir */
195+
if (git_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false)
196+
return false;
197+
if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false)
171198
return false;
172199

173200
return true;
@@ -356,13 +383,15 @@ static int find_repo(
356383
git_buf *repo_path,
357384
git_buf *parent_path,
358385
git_buf *link_path,
386+
git_buf *common_path,
359387
const char *start_path,
360388
uint32_t flags,
361389
const char *ceiling_dirs)
362390
{
363391
int error;
364392
git_buf path = GIT_BUF_INIT;
365393
git_buf repo_link = GIT_BUF_INIT;
394+
git_buf common_link = GIT_BUF_INIT;
366395
struct stat st;
367396
dev_t initial_device = 0;
368397
int min_iterations;
@@ -409,21 +438,27 @@ static int find_repo(
409438
break;
410439

411440
if (S_ISDIR(st.st_mode)) {
412-
if (valid_repository_path(&path)) {
441+
if (valid_repository_path(&path, &common_link)) {
413442
git_path_to_dir(&path);
414443
git_buf_set(repo_path, path.ptr, path.size);
444+
445+
if (common_path)
446+
git_buf_swap(&common_link, common_path);
447+
415448
break;
416449
}
417450
}
418451
else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) {
419452
error = read_gitfile(&repo_link, path.ptr);
420453
if (error < 0)
421454
break;
422-
if (valid_repository_path(&repo_link)) {
455+
if (valid_repository_path(&repo_link, &common_link)) {
423456
git_buf_swap(repo_path, &repo_link);
424457

425458
if (link_path)
426459
error = git_buf_put(link_path, path.ptr, path.size);
460+
if (common_path)
461+
git_buf_swap(&common_link, common_path);
427462
}
428463
break;
429464
}
@@ -470,6 +505,7 @@ static int find_repo(
470505

471506
git_buf_free(&path);
472507
git_buf_free(&repo_link);
508+
git_buf_free(&common_link);
473509
return error;
474510
}
475511

@@ -478,14 +514,15 @@ int git_repository_open_bare(
478514
const char *bare_path)
479515
{
480516
int error;
481-
git_buf path = GIT_BUF_INIT;
517+
git_buf path = GIT_BUF_INIT, common_path = GIT_BUF_INIT;
482518
git_repository *repo = NULL;
483519

484520
if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0)
485521
return error;
486522

487-
if (!valid_repository_path(&path)) {
523+
if (!valid_repository_path(&path, &common_path)) {
488524
git_buf_free(&path);
525+
git_buf_free(&common_path);
489526
giterr_set(GITERR_REPOSITORY, "path is not a repository: %s", bare_path);
490527
return GIT_ENOTFOUND;
491528
}
@@ -495,6 +532,8 @@ int git_repository_open_bare(
495532

496533
repo->path_repository = git_buf_detach(&path);
497534
GITERR_CHECK_ALLOC(repo->path_repository);
535+
repo->commondir = git_buf_detach(&common_path);
536+
GITERR_CHECK_ALLOC(repo->commondir);
498537

499538
/* of course we're bare! */
500539
repo->is_bare = 1;
@@ -681,7 +720,7 @@ int git_repository_open_ext(
681720
{
682721
int error;
683722
git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT,
684-
link_path = GIT_BUF_INIT;
723+
link_path = GIT_BUF_INIT, common_path = GIT_BUF_INIT;
685724
git_repository *repo;
686725
git_config *config = NULL;
687726

@@ -692,7 +731,7 @@ int git_repository_open_ext(
692731
*repo_ptr = NULL;
693732

694733
error = find_repo(
695-
&path, &parent, &link_path, start_path, flags, ceiling_dirs);
734+
&path, &parent, &link_path, &common_path, start_path, flags, ceiling_dirs);
696735

697736
if (error < 0 || !repo_ptr)
698737
return error;
@@ -707,6 +746,10 @@ int git_repository_open_ext(
707746
repo->path_gitlink = git_buf_detach(&link_path);
708747
GITERR_CHECK_ALLOC(repo->path_gitlink);
709748
}
749+
if (common_path.size) {
750+
repo->commondir = git_buf_detach(&common_path);
751+
GITERR_CHECK_ALLOC(repo->commondir);
752+
}
710753

711754
/*
712755
* We'd like to have the config, but git doesn't particularly
@@ -773,7 +816,7 @@ int git_repository_discover(
773816

774817
git_buf_sanitize(out);
775818

776-
return find_repo(out, NULL, NULL, start_path, flags, ceiling_dirs);
819+
return find_repo(out, NULL, NULL, NULL, start_path, flags, ceiling_dirs);
777820
}
778821

779822
static int load_config(
@@ -928,7 +971,7 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo)
928971
git_buf odb_path = GIT_BUF_INIT;
929972
git_odb *odb;
930973

931-
if ((error = git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR)) < 0)
974+
if ((error = git_buf_joinpath(&odb_path, repo->commondir, GIT_OBJECTS_DIR)) < 0)
932975
return error;
933976

934977
error = git_odb_open(&odb, odb_path.ptr);
@@ -1856,7 +1899,8 @@ int git_repository_init_ext(
18561899
git_repository_init_options *opts)
18571900
{
18581901
int error;
1859-
git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT;
1902+
git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT,
1903+
common_path = GIT_BUF_INIT;
18601904
const char *wd;
18611905

18621906
assert(out && given_repo && opts);
@@ -1868,7 +1912,7 @@ int git_repository_init_ext(
18681912
goto cleanup;
18691913

18701914
wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_buf_cstr(&wd_path);
1871-
if (valid_repository_path(&repo_path)) {
1915+
if (valid_repository_path(&repo_path, &common_path)) {
18721916

18731917
if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) {
18741918
giterr_set(GITERR_REPOSITORY,
@@ -1901,6 +1945,7 @@ int git_repository_init_ext(
19011945
error = repo_init_create_origin(*out, opts->origin_url);
19021946

19031947
cleanup:
1948+
git_buf_free(&common_path);
19041949
git_buf_free(&repo_path);
19051950
git_buf_free(&wd_path);
19061951

@@ -2023,6 +2068,12 @@ const char *git_repository_workdir(git_repository *repo)
20232068
return repo->workdir;
20242069
}
20252070

2071+
const char *git_repository_commondir(git_repository *repo)
2072+
{
2073+
assert(repo);
2074+
return repo->commondir;
2075+
}
2076+
20262077
int git_repository_set_workdir(
20272078
git_repository *repo, const char *workdir, int update_gitlink)
20282079
{

src/repository.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ struct git_repository {
128128

129129
char *path_repository;
130130
char *path_gitlink;
131+
char *commondir;
131132
char *workdir;
132133
char *namespace;
133134

tests/worktree/open.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "clar_libgit2.h"
2+
#include "worktree_helpers.h"
3+
4+
#define WORKTREE_PARENT "submodules-worktree-parent"
5+
#define WORKTREE_CHILD "submodules-worktree-child"
6+
7+
void test_worktree_open__repository(void)
8+
{
9+
worktree_fixture fixture =
10+
WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree");
11+
setup_fixture_worktree(&fixture);
12+
13+
cl_assert(git_repository_path(fixture.worktree) != NULL);
14+
cl_assert(git_repository_workdir(fixture.worktree) != NULL);
15+
16+
cleanup_fixture_worktree(&fixture);
17+
}
18+
19+
void test_worktree_open__repository_with_nonexistent_parent(void)
20+
{
21+
git_repository *repo;
22+
23+
cl_fixture_sandbox("testrepo-worktree");
24+
cl_git_pass(p_chdir("testrepo-worktree"));
25+
cl_git_pass(cl_rename(".gitted", ".git"));
26+
cl_git_pass(p_chdir(".."));
27+
28+
cl_git_fail(git_repository_open(&repo, "testrepo-worktree"));
29+
30+
cl_fixture_cleanup("testrepo-worktree");
31+
}
32+
33+
void test_worktree_open__submodule_worktree_parent(void)
34+
{
35+
worktree_fixture fixture =
36+
WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT);
37+
setup_fixture_worktree(&fixture);
38+
39+
cl_assert(git_repository_path(fixture.worktree) != NULL);
40+
cl_assert(git_repository_workdir(fixture.worktree) != NULL);
41+
42+
cleanup_fixture_worktree(&fixture);
43+
}
44+
45+
void test_worktree_open__submodule_worktree_child(void)
46+
{
47+
worktree_fixture parent_fixture =
48+
WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT);
49+
worktree_fixture child_fixture =
50+
WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD);
51+
52+
setup_fixture_worktree(&parent_fixture);
53+
cl_git_pass(p_rename(
54+
"submodules/testrepo/.gitted",
55+
"submodules/testrepo/.git"));
56+
setup_fixture_worktree(&child_fixture);
57+
58+
cleanup_fixture_worktree(&child_fixture);
59+
cleanup_fixture_worktree(&parent_fixture);
60+
}

0 commit comments

Comments
 (0)