Skip to content

Commit 04de614

Browse files
authored
Merge pull request libgit2#4243 from pks-t/pks/submodule-workdir
Submodule working directory
2 parents a1023a4 + 2696c5c commit 04de614

File tree

2 files changed

+149
-38
lines changed

2 files changed

+149
-38
lines changed

src/repository.c

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -422,10 +422,10 @@ static int read_gitfile(git_buf *path_out, const char *file_path)
422422
}
423423

424424
static int find_repo(
425-
git_buf *repo_path,
426-
git_buf *parent_path,
427-
git_buf *link_path,
428-
git_buf *common_path,
425+
git_buf *gitdir_path,
426+
git_buf *workdir_path,
427+
git_buf *gitlink_path,
428+
git_buf *commondir_path,
429429
const char *start_path,
430430
uint32_t flags,
431431
const char *ceiling_dirs)
@@ -440,7 +440,7 @@ static int find_repo(
440440
bool in_dot_git;
441441
size_t ceiling_offset = 0;
442442

443-
git_buf_free(repo_path);
443+
git_buf_clear(gitdir_path);
444444

445445
error = git_path_prettify(&path, start_path, NULL);
446446
if (error < 0)
@@ -482,13 +482,13 @@ static int find_repo(
482482
if (S_ISDIR(st.st_mode)) {
483483
if (valid_repository_path(&path, &common_link)) {
484484
git_path_to_dir(&path);
485-
git_buf_set(repo_path, path.ptr, path.size);
485+
git_buf_set(gitdir_path, path.ptr, path.size);
486486

487-
if (link_path)
488-
git_buf_attach(link_path,
487+
if (gitlink_path)
488+
git_buf_attach(gitlink_path,
489489
git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0);
490-
if (common_path)
491-
git_buf_swap(&common_link, common_path);
490+
if (commondir_path)
491+
git_buf_swap(&common_link, commondir_path);
492492

493493
break;
494494
}
@@ -498,12 +498,12 @@ static int find_repo(
498498
if (error < 0)
499499
break;
500500
if (valid_repository_path(&repo_link, &common_link)) {
501-
git_buf_swap(repo_path, &repo_link);
501+
git_buf_swap(gitdir_path, &repo_link);
502502

503-
if (link_path)
504-
error = git_buf_put(link_path, path.ptr, path.size);
505-
if (common_path)
506-
git_buf_swap(&common_link, common_path);
503+
if (gitlink_path)
504+
error = git_buf_put(gitlink_path, path.ptr, path.size);
505+
if (commondir_path)
506+
git_buf_swap(&common_link, commondir_path);
507507
}
508508
break;
509509
}
@@ -529,20 +529,20 @@ static int find_repo(
529529
break;
530530
}
531531

532-
if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
533-
if (!git_buf_len(repo_path))
534-
git_buf_clear(parent_path);
532+
if (!error && workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
533+
if (!git_buf_len(gitdir_path))
534+
git_buf_clear(workdir_path);
535535
else {
536-
git_path_dirname_r(parent_path, path.ptr);
537-
git_path_to_dir(parent_path);
536+
git_path_dirname_r(workdir_path, path.ptr);
537+
git_path_to_dir(workdir_path);
538538
}
539-
if (git_buf_oom(parent_path))
539+
if (git_buf_oom(workdir_path))
540540
return -1;
541541
}
542542

543543
/* If we didn't find the repository, and we don't have any other error
544544
* to report, report that. */
545-
if (!git_buf_len(repo_path) && !error) {
545+
if (!git_buf_len(gitdir_path) && !error) {
546546
giterr_set(GITERR_REPOSITORY,
547547
"could not find repository from '%s'", start_path);
548548
error = GIT_ENOTFOUND;
@@ -758,15 +758,39 @@ static int _git_repository_open_ext_from_env(
758758
return error;
759759
}
760760

761+
static int repo_is_worktree(unsigned *out, const git_repository *repo)
762+
{
763+
git_buf gitdir_link = GIT_BUF_INIT;
764+
int error;
765+
766+
/* Worktrees cannot have the same commondir and gitdir */
767+
if (repo->commondir && repo->gitdir
768+
&& !strcmp(repo->commondir, repo->gitdir)) {
769+
*out = 0;
770+
return 0;
771+
}
772+
773+
if ((error = git_buf_joinpath(&gitdir_link, repo->gitdir, "gitdir")) < 0)
774+
return -1;
775+
776+
/* A 'gitdir' file inside a git directory is currently
777+
* only used when the repository is a working tree. */
778+
*out = !!git_path_exists(gitdir_link.ptr);
779+
780+
git_buf_free(&gitdir_link);
781+
return error;
782+
}
783+
761784
int git_repository_open_ext(
762785
git_repository **repo_ptr,
763786
const char *start_path,
764787
unsigned int flags,
765788
const char *ceiling_dirs)
766789
{
767790
int error;
768-
git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT,
769-
link_path = GIT_BUF_INIT, common_path = GIT_BUF_INIT;
791+
unsigned is_worktree;
792+
git_buf gitdir = GIT_BUF_INIT, workdir = GIT_BUF_INIT,
793+
gitlink = GIT_BUF_INIT, commondir = GIT_BUF_INIT;
770794
git_repository *repo;
771795
git_config *config = NULL;
772796

@@ -777,32 +801,29 @@ int git_repository_open_ext(
777801
*repo_ptr = NULL;
778802

779803
error = find_repo(
780-
&path, &parent, &link_path, &common_path, start_path, flags, ceiling_dirs);
804+
&gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs);
781805

782806
if (error < 0 || !repo_ptr)
783807
return error;
784808

785809
repo = repository_alloc();
786810
GITERR_CHECK_ALLOC(repo);
787811

788-
repo->gitdir = git_buf_detach(&path);
812+
repo->gitdir = git_buf_detach(&gitdir);
789813
GITERR_CHECK_ALLOC(repo->gitdir);
790814

791-
if (link_path.size) {
792-
repo->gitlink = git_buf_detach(&link_path);
815+
if (gitlink.size) {
816+
repo->gitlink = git_buf_detach(&gitlink);
793817
GITERR_CHECK_ALLOC(repo->gitlink);
794818
}
795-
if (common_path.size) {
796-
repo->commondir = git_buf_detach(&common_path);
819+
if (commondir.size) {
820+
repo->commondir = git_buf_detach(&commondir);
797821
GITERR_CHECK_ALLOC(repo->commondir);
798822
}
799823

800-
if ((error = git_buf_joinpath(&path, repo->gitdir, "gitdir")) < 0)
824+
if ((error = repo_is_worktree(&is_worktree, repo)) < 0)
801825
goto cleanup;
802-
/* A 'gitdir' file inside a git directory is currently
803-
* only used when the repository is a working tree. */
804-
if (git_path_exists(path.ptr))
805-
repo->is_worktree = 1;
826+
repo->is_worktree = is_worktree;
806827

807828
/*
808829
* We'd like to have the config, but git doesn't particularly
@@ -822,13 +843,13 @@ int git_repository_open_ext(
822843

823844
if (config &&
824845
((error = load_config_data(repo, config)) < 0 ||
825-
(error = load_workdir(repo, config, &parent)) < 0))
846+
(error = load_workdir(repo, config, &workdir)) < 0))
826847
goto cleanup;
827848
}
828849

829850
cleanup:
830-
git_buf_free(&path);
831-
git_buf_free(&parent);
851+
git_buf_free(&gitdir);
852+
git_buf_free(&workdir);
832853
git_config_free(config);
833854

834855
if (error < 0)

tests/submodule/open.c

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#include "clar_libgit2.h"
2+
#include "submodule_helpers.h"
3+
#include "path.h"
4+
5+
static git_repository *g_parent;
6+
static git_repository *g_child;
7+
static git_submodule *g_module;
8+
9+
void test_submodule_open__initialize(void)
10+
{
11+
g_parent = setup_fixture_submod2();
12+
}
13+
14+
void test_submodule_open__cleanup(void)
15+
{
16+
git_submodule_free(g_module);
17+
git_repository_free(g_child);
18+
cl_git_sandbox_cleanup();
19+
g_parent = NULL;
20+
g_child = NULL;
21+
g_module = NULL;
22+
}
23+
24+
static void assert_sm_valid(git_repository *parent, git_repository *child, const char *sm_name)
25+
{
26+
git_buf expected = GIT_BUF_INIT, actual = GIT_BUF_INIT;
27+
28+
/* assert working directory */
29+
cl_git_pass(git_buf_joinpath(&expected, git_repository_workdir(parent), sm_name));
30+
cl_git_pass(git_path_prettify_dir(&expected, expected.ptr, NULL));
31+
cl_git_pass(git_buf_sets(&actual, git_repository_workdir(child)));
32+
cl_git_pass(git_path_prettify_dir(&actual, actual.ptr, NULL));
33+
cl_assert_equal_s(expected.ptr, actual.ptr);
34+
35+
git_buf_clear(&expected);
36+
git_buf_clear(&actual);
37+
38+
/* assert common directory */
39+
cl_git_pass(git_buf_joinpath(&expected, git_repository_commondir(parent), "modules"));
40+
cl_git_pass(git_buf_joinpath(&expected, expected.ptr, sm_name));
41+
cl_git_pass(git_path_prettify_dir(&expected, expected.ptr, NULL));
42+
cl_git_pass(git_buf_sets(&actual, git_repository_commondir(child)));
43+
cl_git_pass(git_path_prettify_dir(&actual, actual.ptr, NULL));
44+
cl_assert_equal_s(expected.ptr, actual.ptr);
45+
46+
/* assert git directory */
47+
cl_git_pass(git_buf_sets(&actual, git_repository_path(child)));
48+
cl_git_pass(git_path_prettify_dir(&actual, actual.ptr, NULL));
49+
cl_assert_equal_s(expected.ptr, actual.ptr);
50+
51+
git_buf_free(&expected);
52+
git_buf_free(&actual);
53+
}
54+
55+
void test_submodule_open__opening_via_lookup_succeeds(void)
56+
{
57+
cl_git_pass(git_submodule_lookup(&g_module, g_parent, "sm_unchanged"));
58+
cl_git_pass(git_submodule_open(&g_child, g_module));
59+
assert_sm_valid(g_parent, g_child, "sm_unchanged");
60+
}
61+
62+
void test_submodule_open__direct_open_succeeds(void)
63+
{
64+
git_buf path = GIT_BUF_INIT;
65+
66+
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_parent), "sm_unchanged"));
67+
cl_git_pass(git_repository_open(&g_child, path.ptr));
68+
assert_sm_valid(g_parent, g_child, "sm_unchanged");
69+
70+
git_buf_free(&path);
71+
}
72+
73+
void test_submodule_open__direct_open_succeeds_for_broken_sm_with_gitdir(void)
74+
{
75+
git_buf path = GIT_BUF_INIT;
76+
77+
/*
78+
* This is actually not a valid submodule, but we
79+
* encountered at least one occasion where the gitdir
80+
* file existed inside of a submodule's gitdir. As we are
81+
* now able to open these submodules correctly, we still
82+
* add a test for this.
83+
*/
84+
cl_git_mkfile("submod2/.git/modules/sm_unchanged/gitdir", ".git");
85+
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_parent), "sm_unchanged"));
86+
cl_git_pass(git_repository_open(&g_child, path.ptr));
87+
assert_sm_valid(g_parent, g_child, "sm_unchanged");
88+
89+
git_buf_free(&path);
90+
}

0 commit comments

Comments
 (0)