diff --git a/include/git2/repository.h b/include/git2/repository.h index 3d70d1b89..f60544553 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -392,6 +392,17 @@ GIT_EXTERN(const char *) git_repository_path(git_repository *repo); */ GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo); +/** + * Get the path of the shared common directory for this repository + * + * If the repository is bare is not a worktree, the git directory + * path is returned. + * + * @param repo A repository object + * @return the path to the common dir + */ +GIT_EXTERN(const char *) git_repository_commondir(git_repository *repo); + /** * Set the path to the working directory for this repository * diff --git a/src/repository.c b/src/repository.c index 2185632bf..71b6ec44f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -38,6 +38,8 @@ GIT__USE_STRMAP static int check_repositoryformatversion(git_config *config); +#define GIT_COMMONDIR_FILE "commondir" + #define GIT_FILE_CONTENT_PREFIX "gitdir:" #define GIT_BRANCH_MASTER "master" @@ -143,6 +145,7 @@ void git_repository_free(git_repository *repo) git__free(repo->path_gitlink); git__free(repo->path_repository); + git__free(repo->commondir); git__free(repo->workdir); git__free(repo->namespace); git__free(repo->ident_name); @@ -157,17 +160,41 @@ void git_repository_free(git_repository *repo) * * Open a repository object from its path */ -static bool valid_repository_path(git_buf *repository_path) +static bool valid_repository_path(git_buf *repository_path, git_buf *common_path) { - /* Check OBJECTS_DIR first, since it will generate the longest path name */ - if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false) - return false; + /* Check if we have a separate commondir (e.g. we have a + * worktree) */ + if (git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) { + git_buf common_link = GIT_BUF_INIT; + git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE); + + git_futils_readbuffer(&common_link, common_link.ptr); + git_buf_rtrim(&common_link); + + if (git_path_is_relative(common_link.ptr)) { + git_buf_joinpath(common_path, repository_path->ptr, common_link.ptr); + } else { + git_buf_swap(common_path, &common_link); + } + + git_buf_free(&common_link); + } + else { + git_buf_set(common_path, repository_path->ptr, repository_path->size); + } + + /* Make sure the commondir path always has a trailing * slash */ + if (git_buf_rfind(common_path, '/') != (ssize_t)common_path->size - 1) + git_buf_putc(common_path, '/'); /* Ensure HEAD file exists */ if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false) return false; - if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false) + /* Check files in common dir */ + if (git_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false) + return false; + if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false) return false; return true; @@ -356,6 +383,7 @@ static int find_repo( git_buf *repo_path, git_buf *parent_path, git_buf *link_path, + git_buf *common_path, const char *start_path, uint32_t flags, const char *ceiling_dirs) @@ -363,6 +391,7 @@ static int find_repo( int error; git_buf path = GIT_BUF_INIT; git_buf repo_link = GIT_BUF_INIT; + git_buf common_link = GIT_BUF_INIT; struct stat st; dev_t initial_device = 0; int min_iterations; @@ -409,9 +438,13 @@ static int find_repo( break; if (S_ISDIR(st.st_mode)) { - if (valid_repository_path(&path)) { + if (valid_repository_path(&path, &common_link)) { git_path_to_dir(&path); git_buf_set(repo_path, path.ptr, path.size); + + if (common_path) + git_buf_swap(&common_link, common_path); + break; } } @@ -419,11 +452,13 @@ static int find_repo( error = read_gitfile(&repo_link, path.ptr); if (error < 0) break; - if (valid_repository_path(&repo_link)) { + if (valid_repository_path(&repo_link, &common_link)) { git_buf_swap(repo_path, &repo_link); if (link_path) error = git_buf_put(link_path, path.ptr, path.size); + if (common_path) + git_buf_swap(&common_link, common_path); } break; } @@ -470,6 +505,7 @@ static int find_repo( git_buf_free(&path); git_buf_free(&repo_link); + git_buf_free(&common_link); return error; } @@ -478,14 +514,15 @@ int git_repository_open_bare( const char *bare_path) { int error; - git_buf path = GIT_BUF_INIT; + git_buf path = GIT_BUF_INIT, common_path = GIT_BUF_INIT; git_repository *repo = NULL; if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0) return error; - if (!valid_repository_path(&path)) { + if (!valid_repository_path(&path, &common_path)) { git_buf_free(&path); + git_buf_free(&common_path); giterr_set(GITERR_REPOSITORY, "path is not a repository: %s", bare_path); return GIT_ENOTFOUND; } @@ -495,6 +532,8 @@ int git_repository_open_bare( repo->path_repository = git_buf_detach(&path); GITERR_CHECK_ALLOC(repo->path_repository); + repo->commondir = git_buf_detach(&common_path); + GITERR_CHECK_ALLOC(repo->commondir); /* of course we're bare! */ repo->is_bare = 1; @@ -681,7 +720,7 @@ int git_repository_open_ext( { int error; git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT, - link_path = GIT_BUF_INIT; + link_path = GIT_BUF_INIT, common_path = GIT_BUF_INIT; git_repository *repo; git_config *config = NULL; @@ -692,7 +731,7 @@ int git_repository_open_ext( *repo_ptr = NULL; error = find_repo( - &path, &parent, &link_path, start_path, flags, ceiling_dirs); + &path, &parent, &link_path, &common_path, start_path, flags, ceiling_dirs); if (error < 0 || !repo_ptr) return error; @@ -707,6 +746,10 @@ int git_repository_open_ext( repo->path_gitlink = git_buf_detach(&link_path); GITERR_CHECK_ALLOC(repo->path_gitlink); } + if (common_path.size) { + repo->commondir = git_buf_detach(&common_path); + GITERR_CHECK_ALLOC(repo->commondir); + } /* * We'd like to have the config, but git doesn't particularly @@ -773,7 +816,7 @@ int git_repository_discover( git_buf_sanitize(out); - return find_repo(out, NULL, NULL, start_path, flags, ceiling_dirs); + return find_repo(out, NULL, NULL, NULL, start_path, flags, ceiling_dirs); } static int load_config( @@ -928,7 +971,7 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) git_buf odb_path = GIT_BUF_INIT; git_odb *odb; - if ((error = git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR)) < 0) + if ((error = git_buf_joinpath(&odb_path, repo->commondir, GIT_OBJECTS_DIR)) < 0) return error; error = git_odb_open(&odb, odb_path.ptr); @@ -1856,7 +1899,8 @@ int git_repository_init_ext( git_repository_init_options *opts) { int error; - git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT; + git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT, + common_path = GIT_BUF_INIT; const char *wd; assert(out && given_repo && opts); @@ -1868,7 +1912,7 @@ int git_repository_init_ext( goto cleanup; wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_buf_cstr(&wd_path); - if (valid_repository_path(&repo_path)) { + if (valid_repository_path(&repo_path, &common_path)) { if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) { giterr_set(GITERR_REPOSITORY, @@ -1901,6 +1945,7 @@ int git_repository_init_ext( error = repo_init_create_origin(*out, opts->origin_url); cleanup: + git_buf_free(&common_path); git_buf_free(&repo_path); git_buf_free(&wd_path); @@ -2023,6 +2068,12 @@ const char *git_repository_workdir(git_repository *repo) return repo->workdir; } +const char *git_repository_commondir(git_repository *repo) +{ + assert(repo); + return repo->commondir; +} + int git_repository_set_workdir( git_repository *repo, const char *workdir, int update_gitlink) { diff --git a/src/repository.h b/src/repository.h index 9d276f376..5dc67218f 100644 --- a/src/repository.h +++ b/src/repository.h @@ -128,6 +128,7 @@ struct git_repository { char *path_repository; char *path_gitlink; + char *commondir; char *workdir; char *namespace; diff --git a/tests/worktree/open.c b/tests/worktree/open.c new file mode 100644 index 000000000..772f7601f --- /dev/null +++ b/tests/worktree/open.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +#define WORKTREE_PARENT "submodules-worktree-parent" +#define WORKTREE_CHILD "submodules-worktree-child" + +void test_worktree_open__repository(void) +{ + worktree_fixture fixture = + WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree"); + setup_fixture_worktree(&fixture); + + cl_assert(git_repository_path(fixture.worktree) != NULL); + cl_assert(git_repository_workdir(fixture.worktree) != NULL); + + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_open__repository_with_nonexistent_parent(void) +{ + git_repository *repo; + + cl_fixture_sandbox("testrepo-worktree"); + cl_git_pass(p_chdir("testrepo-worktree")); + cl_git_pass(cl_rename(".gitted", ".git")); + cl_git_pass(p_chdir("..")); + + cl_git_fail(git_repository_open(&repo, "testrepo-worktree")); + + cl_fixture_cleanup("testrepo-worktree"); +} + +void test_worktree_open__submodule_worktree_parent(void) +{ + worktree_fixture fixture = + WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); + setup_fixture_worktree(&fixture); + + cl_assert(git_repository_path(fixture.worktree) != NULL); + cl_assert(git_repository_workdir(fixture.worktree) != NULL); + + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_open__submodule_worktree_child(void) +{ + worktree_fixture parent_fixture = + WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); + worktree_fixture child_fixture = + WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); + + setup_fixture_worktree(&parent_fixture); + cl_git_pass(p_rename( + "submodules/testrepo/.gitted", + "submodules/testrepo/.git")); + setup_fixture_worktree(&child_fixture); + + cleanup_fixture_worktree(&child_fixture); + cleanup_fixture_worktree(&parent_fixture); +}