From aa593a65dfb5d3b59b9797e53311e03a9c9c691d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 12:01:02 +0100 Subject: [PATCH 01/13] tests: worktree: move submodule tests into own suite --- tests/worktree/open.c | 65 ----------------------------------- tests/worktree/submodule.c | 69 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 65 deletions(-) create mode 100644 tests/worktree/submodule.c diff --git a/tests/worktree/open.c b/tests/worktree/open.c index bdc8bcf9d..a97b43b15 100644 --- a/tests/worktree/open.c +++ b/tests/worktree/open.c @@ -2,9 +2,6 @@ #include "repository.h" #include "worktree_helpers.h" -#define WORKTREE_PARENT "submodules-worktree-parent" -#define WORKTREE_CHILD "submodules-worktree-child" - #define COMMON_REPO "testrepo" #define WORKTREE_REPO "testrepo-worktree" @@ -130,65 +127,3 @@ void test_worktree_open__repository_with_nonexistent_parent(void) cl_fixture_cleanup(WORKTREE_REPO); } -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); - - cl_assert(!fixture.repo->is_worktree); - cl_assert(fixture.worktree->is_worktree); - - 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); - - cl_assert(!parent_fixture.repo->is_worktree); - cl_assert(parent_fixture.worktree->is_worktree); - cl_assert(child_fixture.worktree->is_worktree); - - cleanup_fixture_worktree(&child_fixture); - cleanup_fixture_worktree(&parent_fixture); -} - -void test_worktree_open__open_discovered_submodule_worktree(void) -{ - worktree_fixture parent_fixture = - WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); - worktree_fixture child_fixture = - WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); - git_buf path = GIT_BUF_INIT; - git_repository *repo; - - setup_fixture_worktree(&parent_fixture); - cl_git_pass(p_rename( - "submodules/testrepo/.gitted", - "submodules/testrepo/.git")); - setup_fixture_worktree(&child_fixture); - - cl_git_pass(git_repository_discover(&path, - git_repository_workdir(child_fixture.worktree), false, NULL)); - cl_git_pass(git_repository_open(&repo, path.ptr)); - cl_assert_equal_s(git_repository_workdir(child_fixture.worktree), - git_repository_workdir(repo)); - - git_buf_free(&path); - git_repository_free(repo); - cleanup_fixture_worktree(&child_fixture); - cleanup_fixture_worktree(&parent_fixture); -} diff --git a/tests/worktree/submodule.c b/tests/worktree/submodule.c new file mode 100644 index 000000000..1d8e46cc8 --- /dev/null +++ b/tests/worktree/submodule.c @@ -0,0 +1,69 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "worktree_helpers.h" + +#define WORKTREE_PARENT "submodules-worktree-parent" +#define WORKTREE_CHILD "submodules-worktree-child" + +void test_worktree_submodule__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); + + cl_assert(!fixture.repo->is_worktree); + cl_assert(fixture.worktree->is_worktree); + + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_submodule__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); + + cl_assert(!parent_fixture.repo->is_worktree); + cl_assert(parent_fixture.worktree->is_worktree); + cl_assert(child_fixture.worktree->is_worktree); + + cleanup_fixture_worktree(&child_fixture); + cleanup_fixture_worktree(&parent_fixture); +} + +void test_worktree_submodule__open_discovered_submodule_worktree(void) +{ + worktree_fixture parent_fixture = + WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); + worktree_fixture child_fixture = + WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); + git_buf path = GIT_BUF_INIT; + git_repository *repo; + + setup_fixture_worktree(&parent_fixture); + cl_git_pass(p_rename( + "submodules/testrepo/.gitted", + "submodules/testrepo/.git")); + setup_fixture_worktree(&child_fixture); + + cl_git_pass(git_repository_discover(&path, + git_repository_workdir(child_fixture.worktree), false, NULL)); + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert_equal_s(git_repository_workdir(child_fixture.worktree), + git_repository_workdir(repo)); + + git_buf_free(&path); + git_repository_free(repo); + cleanup_fixture_worktree(&child_fixture); + cleanup_fixture_worktree(&parent_fixture); +} From 548e5bdf1e3a20cdb8c7d063f60ffb31872b15f7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 12:28:05 +0100 Subject: [PATCH 02/13] tests: worktree: unify init/cleanup in submodule tests --- tests/worktree/submodule.c | 72 ++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/tests/worktree/submodule.c b/tests/worktree/submodule.c index 1d8e46cc8..b43507045 100644 --- a/tests/worktree/submodule.c +++ b/tests/worktree/submodule.c @@ -5,65 +5,55 @@ #define WORKTREE_PARENT "submodules-worktree-parent" #define WORKTREE_CHILD "submodules-worktree-child" +static worktree_fixture parent + = WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); +static worktree_fixture child + = WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); + +void test_worktree_submodule__initialize(void) +{ + setup_fixture_worktree(&parent); + + cl_git_pass(p_rename( + "submodules/testrepo/.gitted", + "submodules/testrepo/.git")); + + setup_fixture_worktree(&child); +} + +void test_worktree_submodule__cleanup(void) +{ + cleanup_fixture_worktree(&child); + cleanup_fixture_worktree(&parent); +} + void test_worktree_submodule__submodule_worktree_parent(void) { - worktree_fixture fixture = - WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); - setup_fixture_worktree(&fixture); + cl_assert(git_repository_path(parent.worktree) != NULL); + cl_assert(git_repository_workdir(parent.worktree) != NULL); - cl_assert(git_repository_path(fixture.worktree) != NULL); - cl_assert(git_repository_workdir(fixture.worktree) != NULL); - - cl_assert(!fixture.repo->is_worktree); - cl_assert(fixture.worktree->is_worktree); - - cleanup_fixture_worktree(&fixture); + cl_assert(!parent.repo->is_worktree); + cl_assert(parent.worktree->is_worktree); } void test_worktree_submodule__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); - - cl_assert(!parent_fixture.repo->is_worktree); - cl_assert(parent_fixture.worktree->is_worktree); - cl_assert(child_fixture.worktree->is_worktree); - - cleanup_fixture_worktree(&child_fixture); - cleanup_fixture_worktree(&parent_fixture); + cl_assert(!parent.repo->is_worktree); + cl_assert(parent.worktree->is_worktree); + cl_assert(child.worktree->is_worktree); } void test_worktree_submodule__open_discovered_submodule_worktree(void) { - worktree_fixture parent_fixture = - WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); - worktree_fixture child_fixture = - WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); git_buf path = GIT_BUF_INIT; git_repository *repo; - setup_fixture_worktree(&parent_fixture); - cl_git_pass(p_rename( - "submodules/testrepo/.gitted", - "submodules/testrepo/.git")); - setup_fixture_worktree(&child_fixture); - cl_git_pass(git_repository_discover(&path, - git_repository_workdir(child_fixture.worktree), false, NULL)); + git_repository_workdir(child.worktree), false, NULL)); cl_git_pass(git_repository_open(&repo, path.ptr)); - cl_assert_equal_s(git_repository_workdir(child_fixture.worktree), + cl_assert_equal_s(git_repository_workdir(child.worktree), git_repository_workdir(repo)); git_buf_free(&path); git_repository_free(repo); - cleanup_fixture_worktree(&child_fixture); - cleanup_fixture_worktree(&parent_fixture); } From fdb3e24ac4054ba27cefc8b2252cfe540fb3a201 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 14:23:59 +0100 Subject: [PATCH 03/13] tests: worktree: unify init/cleanup in open tests --- tests/worktree/open.c | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/tests/worktree/open.c b/tests/worktree/open.c index a97b43b15..f5e0bc124 100644 --- a/tests/worktree/open.c +++ b/tests/worktree/open.c @@ -5,6 +5,9 @@ #define COMMON_REPO "testrepo" #define WORKTREE_REPO "testrepo-worktree" +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + static void assert_worktree_valid(git_repository *wt, const char *parentdir, const char *wtdir) { git_buf path = GIT_BUF_INIT; @@ -31,56 +34,46 @@ static void assert_worktree_valid(git_repository *wt, const char *parentdir, con git_buf_free(&path); } +void test_worktree_open__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_open__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + void test_worktree_open__repository(void) { - worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); - setup_fixture_worktree(&fixture); - assert_worktree_valid(fixture.worktree, COMMON_REPO, WORKTREE_REPO); - - cleanup_fixture_worktree(&fixture); } void test_worktree_open__repository_through_workdir(void) { - worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); git_repository *wt; - setup_fixture_worktree(&fixture); - cl_git_pass(git_repository_open(&wt, WORKTREE_REPO)); assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); git_repository_free(wt); - cleanup_fixture_worktree(&fixture); } void test_worktree_open__repository_through_gitlink(void) { - worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); git_repository *wt; - setup_fixture_worktree(&fixture); - cl_git_pass(git_repository_open(&wt, WORKTREE_REPO "/.git")); assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); git_repository_free(wt); - cleanup_fixture_worktree(&fixture); } void test_worktree_open__repository_through_gitdir(void) { - worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); git_buf gitdir_path = GIT_BUF_INIT; git_repository *wt; - setup_fixture_worktree(&fixture); - cl_git_pass(git_buf_joinpath(&gitdir_path, COMMON_REPO, ".git")); cl_git_pass(git_buf_joinpath(&gitdir_path, gitdir_path.ptr, "worktrees")); cl_git_pass(git_buf_joinpath(&gitdir_path, gitdir_path.ptr, "testrepo-worktree")); @@ -90,18 +83,13 @@ void test_worktree_open__repository_through_gitdir(void) git_buf_free(&gitdir_path); git_repository_free(wt); - cleanup_fixture_worktree(&fixture); } void test_worktree_open__open_discovered_worktree(void) { - worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); git_buf path = GIT_BUF_INIT; git_repository *repo; - setup_fixture_worktree(&fixture); - cl_git_pass(git_repository_discover(&path, git_repository_workdir(fixture.worktree), false, NULL)); cl_git_pass(git_repository_open(&repo, path.ptr)); @@ -110,13 +98,14 @@ void test_worktree_open__open_discovered_worktree(void) git_buf_free(&path); git_repository_free(repo); - cleanup_fixture_worktree(&fixture); } void test_worktree_open__repository_with_nonexistent_parent(void) { git_repository *repo; + cleanup_fixture_worktree(&fixture); + cl_fixture_sandbox(WORKTREE_REPO); cl_git_pass(p_chdir(WORKTREE_REPO)); cl_git_pass(cl_rename(".gitted", ".git")); @@ -126,4 +115,3 @@ void test_worktree_open__repository_with_nonexistent_parent(void) cl_fixture_cleanup(WORKTREE_REPO); } - From f3c30686701ed0593cdcbb50085fd2fa0e706cf7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 15:04:24 +0100 Subject: [PATCH 04/13] tests: worktree: use joinpath instead of printf to join paths --- tests/worktree/worktree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index f0c423599..86554c56a 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -115,7 +115,7 @@ void test_worktree_worktree__lookup(void) cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - git_buf_printf(&gitdir_path, "%s/worktrees/%s", fixture.repo->commondir, "testrepo-worktree"); + cl_git_pass(git_buf_joinpath(&gitdir_path, fixture.repo->commondir, "worktrees/testrepo-worktree/")); cl_assert_equal_s(wt->gitdir_path, gitdir_path.ptr); cl_assert_equal_s(wt->parent_path, fixture.repo->gitdir); From 3e9c5d8a05b364a27514bd3f9e0c4170f2e16084 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 13:42:52 +0100 Subject: [PATCH 05/13] worktree: have `is_worktree_dir` accept a string instead of buffer This will be used in later commits, where it becomes cumbersome to always pass in a buffer. --- src/worktree.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/worktree.c b/src/worktree.c index 5abc98945..7977d8e7b 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -14,11 +14,20 @@ #include "repository.h" #include "worktree.h" -static bool is_worktree_dir(git_buf *dir) +static bool is_worktree_dir(const char *dir) { - return git_path_contains_file(dir, "commondir") - && git_path_contains_file(dir, "gitdir") - && git_path_contains_file(dir, "HEAD"); + git_buf buf = GIT_BUF_INIT; + int error; + + if (git_buf_sets(&buf, dir) < 0) + return -1; + + error = git_path_contains_file(&buf, "commondir") + && git_path_contains_file(&buf, "gitdir") + && git_path_contains_file(&buf, "HEAD"); + + git_buf_free(&buf); + return error; } int git_worktree_list(git_strarray *wts, git_repository *repo) @@ -47,7 +56,7 @@ int git_worktree_list(git_strarray *wts, git_repository *repo) git_buf_truncate(&path, len); git_buf_puts(&path, worktree); - if (!is_worktree_dir(&path)) { + if (!is_worktree_dir(path.ptr)) { git_vector_remove(&worktrees, i); git__free(worktree); } @@ -125,7 +134,7 @@ int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *na if ((error = git_buf_printf(&path, "%s/worktrees/%s", repo->commondir, name)) < 0) goto out; - if (!is_worktree_dir(&path)) { + if (!is_worktree_dir(path.ptr)) { error = -1; goto out; } @@ -177,7 +186,7 @@ int git_worktree_validate(const git_worktree *wt) assert(wt); git_buf_puts(&buf, wt->gitdir_path); - if (!is_worktree_dir(&buf)) { + if (!is_worktree_dir(buf.ptr)) { giterr_set(GITERR_WORKTREE, "Worktree gitdir ('%s') is not valid", wt->gitlink_path); From dfc98706479c252c023ba093b690c6202a5021e5 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 13:54:14 +0100 Subject: [PATCH 06/13] worktree: split off function opening working directory Separate the logic of finding the worktree directory of a repository and actually opening the working tree's directory. This is a preparatory step for opening the worktree structure of a repository itself. --- src/worktree.c | 61 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/worktree.c b/src/worktree.c index 7977d8e7b..ad1a6e597 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -121,6 +121,46 @@ out: return err; } +static int open_worktree_dir(git_worktree **out, const char *parent, const char *dir, const char *name) +{ + git_buf gitdir = GIT_BUF_INIT; + git_worktree *wt = NULL; + int error = 0; + + if (!is_worktree_dir(dir)) { + error = -1; + goto out; + } + + if ((wt = git__calloc(1, sizeof(struct git_repository))) == NULL) { + error = -1; + goto out; + } + + if ((wt->name = git__strdup(name)) == NULL + || (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL + || (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL + || (wt->parent_path = git__strdup(parent)) == NULL) { + error = -1; + goto out; + } + + if ((error = git_path_prettify_dir(&gitdir, dir, NULL)) < 0) + goto out; + wt->gitdir_path = git_buf_detach(&gitdir); + + wt->locked = !!git_worktree_is_locked(NULL, wt); + + *out = wt; + +out: + if (error) + git_worktree_free(wt); + git_buf_free(&gitdir); + + return error; +} + int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name) { git_buf path = GIT_BUF_INIT; @@ -134,27 +174,8 @@ int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *na if ((error = git_buf_printf(&path, "%s/worktrees/%s", repo->commondir, name)) < 0) goto out; - if (!is_worktree_dir(path.ptr)) { - error = -1; + if ((error = (open_worktree_dir(out, git_repository_path(repo), path.ptr, name))) < 0) goto out; - } - - if ((wt = git__malloc(sizeof(struct git_repository))) == NULL) { - error = -1; - goto out; - } - - if ((wt->name = git__strdup(name)) == NULL - || (wt->commondir_path = git_worktree__read_link(path.ptr, "commondir")) == NULL - || (wt->gitlink_path = git_worktree__read_link(path.ptr, "gitdir")) == NULL - || (wt->parent_path = git__strdup(git_repository_path(repo))) == NULL) { - error = -1; - goto out; - } - wt->gitdir_path = git_buf_detach(&path); - wt->locked = !!git_worktree_is_locked(NULL, wt); - - (*out) = wt; out: git_buf_free(&path); From 3017ba94a33a5dae07521afd96a94e21afb07b8c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 14:24:25 +0100 Subject: [PATCH 07/13] worktree: implement `git_worktree_open_from_repository` While we already provide functionality to look up a worktree from a repository, we cannot do so the other way round. That is given a repository, we want to look up its worktree if it actually exists. Getting the worktree of a repository is useful when we want to get certain meta information like the parent's location, getting the locked status, etc. --- include/git2/worktree.h | 12 ++++++++++++ src/worktree.c | 33 +++++++++++++++++++++++++++++++++ tests/worktree/open.c | 26 ++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/include/git2/worktree.h b/include/git2/worktree.h index cad1284fa..4c4f9284d 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -43,6 +43,18 @@ GIT_EXTERN(int) git_worktree_list(git_strarray *out, git_repository *repo); */ GIT_EXTERN(int) git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name); +/** + * Open a worktree of a given repository + * + * If a repository is not the main tree but a worktree, this + * function will look up the worktree inside the parent + * repository and create a new `git_worktree` structure. + * + * @param out Out-pointer for the newly allocated worktree + * @param repo Repository to look up worktree for + */ +GIT_EXTERN(int) git_worktree_open_from_repository(git_worktree **out, git_repository *repo); + /** * Free a previously allocated worktree * diff --git a/src/worktree.c b/src/worktree.c index ad1a6e597..783b183b7 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -186,6 +186,39 @@ out: return error; } +int git_worktree_open_from_repository(git_worktree **out, git_repository *repo) +{ + git_buf parent = GIT_BUF_INIT; + const char *gitdir, *commondir; + char *name = NULL; + int error = 0; + + if (!git_repository_is_worktree(repo)) { + giterr_set(GITERR_WORKTREE, "cannot open worktree of a non-worktree repo"); + error = -1; + goto out; + } + + gitdir = git_repository_path(repo); + commondir = git_repository_commondir(repo); + + if ((error = git_path_prettify_dir(&parent, commondir, NULL)) < 0) + goto out; + + /* The name is defined by the last component in '.git/worktree/%s' */ + name = git_path_basename(gitdir); + + if ((error = open_worktree_dir(out, parent.ptr, gitdir, name)) < 0) + goto out; + +out: + if (error) + free(name); + git_buf_free(&parent); + + return error; +} + void git_worktree_free(git_worktree *wt) { if (!wt) diff --git a/tests/worktree/open.c b/tests/worktree/open.c index f5e0bc124..74b9007d9 100644 --- a/tests/worktree/open.c +++ b/tests/worktree/open.c @@ -1,5 +1,6 @@ #include "clar_libgit2.h" #include "repository.h" +#include "worktree.h" #include "worktree_helpers.h" #define COMMON_REPO "testrepo" @@ -115,3 +116,28 @@ void test_worktree_open__repository_with_nonexistent_parent(void) cl_fixture_cleanup(WORKTREE_REPO); } + +void test_worktree_open__open_from_repository(void) +{ + git_worktree *opened, *lookedup; + + cl_git_pass(git_worktree_open_from_repository(&opened, fixture.worktree)); + cl_git_pass(git_worktree_lookup(&lookedup, fixture.repo, WORKTREE_REPO)); + + cl_assert_equal_s(opened->name, lookedup->name); + cl_assert_equal_s(opened->gitdir_path, lookedup->gitdir_path); + cl_assert_equal_s(opened->gitlink_path, lookedup->gitlink_path); + cl_assert_equal_s(opened->parent_path, lookedup->parent_path); + cl_assert_equal_s(opened->commondir_path, lookedup->commondir_path); + cl_assert_equal_i(opened->locked, lookedup->locked); + + git_worktree_free(opened); + git_worktree_free(lookedup); +} + +void test_worktree_open__open_from_nonworktree_fails(void) +{ + git_worktree *wt; + + cl_git_fail(git_worktree_open_from_repository(&wt, fixture.repo)); +} From 20a368e2d72e65b6401f18a440c24df0dd9683ae Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 15:29:29 +0100 Subject: [PATCH 08/13] worktree: parent path should point to the working dir The working tree's parent path should not point to the parent's gitdir, but to the parent's working directory. Pointing to the gitdir would not make any sense, as the parent's working directory is actually equal to both repository's common directory. Fix the issue. --- src/worktree.c | 6 +++--- src/worktree.h | 2 +- tests/worktree/worktree.c | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/worktree.c b/src/worktree.c index 783b183b7..13113f846 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -174,7 +174,7 @@ int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *na if ((error = git_buf_printf(&path, "%s/worktrees/%s", repo->commondir, name)) < 0) goto out; - if ((error = (open_worktree_dir(out, git_repository_path(repo), path.ptr, name))) < 0) + if ((error = (open_worktree_dir(out, git_repository_workdir(repo), path.ptr, name))) < 0) goto out; out: @@ -202,7 +202,7 @@ int git_worktree_open_from_repository(git_worktree **out, git_repository *repo) gitdir = git_repository_path(repo); commondir = git_repository_commondir(repo); - if ((error = git_path_prettify_dir(&parent, commondir, NULL)) < 0) + if ((error = git_path_prettify_dir(&parent, "..", commondir)) < 0) goto out; /* The name is defined by the last component in '.git/worktree/%s' */ @@ -457,7 +457,7 @@ int git_worktree_prune(git_worktree *wt, unsigned flags) } /* Delete gitdir in parent repository */ - if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->parent_path, wt->name)) < 0) + if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->commondir_path, wt->name)) < 0) goto out; if (!git_path_exists(path.ptr)) { diff --git a/src/worktree.h b/src/worktree.h index b8e527968..57c2e65f0 100644 --- a/src/worktree.h +++ b/src/worktree.h @@ -24,7 +24,7 @@ struct git_worktree { /* Path to the common directory contained in the parent * repository */ char *commondir_path; - /* Path to the parent's .git directory */ + /* Path to the parent's working directory */ char *parent_path; int locked:1; diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 86554c56a..e74647f54 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -118,8 +118,9 @@ void test_worktree_worktree__lookup(void) cl_git_pass(git_buf_joinpath(&gitdir_path, fixture.repo->commondir, "worktrees/testrepo-worktree/")); cl_assert_equal_s(wt->gitdir_path, gitdir_path.ptr); - cl_assert_equal_s(wt->parent_path, fixture.repo->gitdir); + cl_assert_equal_s(wt->parent_path, fixture.repo->workdir); cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink); + cl_assert_equal_s(wt->commondir_path, fixture.repo->gitdir); cl_assert_equal_s(wt->commondir_path, fixture.repo->commondir); git_buf_free(&gitdir_path); From 9dcc79bc6a6ef9d0704e598c38e2e98cd4f41f30 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 16:35:43 +0100 Subject: [PATCH 09/13] worktree: use fully qualified reference name for created HEAD When creating a new worktree, we have to set up the initial data structures. Next to others, this also includes the HEAD pseudo-ref. We currently set it to the worktree respectively branch name, which is actually not fully qualified. Use the fully qualified branch name instead. --- src/worktree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/worktree.c b/src/worktree.c index 13113f846..f90d8222a 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -323,7 +323,7 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, goto out; /* Set worktree's HEAD */ - if ((err = git_repository_create_head(path.ptr, name)) < 0) + if ((err = git_repository_create_head(path.ptr, git_reference_name(ref))) < 0) goto out; if ((err = git_repository_open(&wt, worktree)) < 0) goto out; From 7cf7a407497dab6978ca6b8fc68713e2a50eb803 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 17 Mar 2017 08:06:49 +0100 Subject: [PATCH 10/13] worktree: rename variable in `git_worktree_add` --- src/worktree.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/worktree.c b/src/worktree.c index f90d8222a..2b0ebfb83 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -272,7 +272,7 @@ out: int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree) { - git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT; + git_buf gitdir = GIT_BUF_INIT, buf = GIT_BUF_INIT; git_reference *ref = NULL, *head = NULL; git_commit *commit = NULL; git_repository *wt = NULL; @@ -283,15 +283,15 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, *out = NULL; - /* Create worktree related files in commondir */ - if ((err = git_buf_joinpath(&path, repo->commondir, "worktrees")) < 0) + /* Create gitdir directory ".git/worktrees/" */ + if ((err = git_buf_joinpath(&gitdir, repo->commondir, "worktrees")) < 0) goto out; - if (!git_path_exists(path.ptr)) - if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0) + if (!git_path_exists(gitdir.ptr)) + if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) goto out; - if ((err = git_buf_joinpath(&path, path.ptr, name)) < 0) + if ((err = git_buf_joinpath(&gitdir, gitdir.ptr, name)) < 0) goto out; - if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0) + if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) goto out; /* Create worktree work dir */ @@ -299,19 +299,19 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, goto out; /* Create worktree .git file */ - if ((err = git_buf_printf(&buf, "gitdir: %s\n", path.ptr)) < 0) + if ((err = git_buf_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0) goto out; if ((err = write_wtfile(worktree, ".git", &buf)) < 0) goto out; - /* Create commondir files */ + /* Create gitdir files */ if ((err = git_buf_sets(&buf, repo->commondir)) < 0 || (err = git_buf_putc(&buf, '\n')) < 0 - || (err = write_wtfile(path.ptr, "commondir", &buf)) < 0) + || (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0) goto out; if ((err = git_buf_joinpath(&buf, worktree, ".git")) < 0 || (err = git_buf_putc(&buf, '\n')) < 0 - || (err = write_wtfile(path.ptr, "gitdir", &buf)) < 0) + || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0) goto out; /* Create new branch */ @@ -323,7 +323,7 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, goto out; /* Set worktree's HEAD */ - if ((err = git_repository_create_head(path.ptr, git_reference_name(ref))) < 0) + if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0) goto out; if ((err = git_repository_open(&wt, worktree)) < 0) goto out; @@ -338,7 +338,7 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, goto out; out: - git_buf_free(&path); + git_buf_free(&gitdir); git_buf_free(&buf); git_reference_free(ref); git_reference_free(head); From 8f154be3eb04c44755e7b0a5020dc9b82c0c1f24 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 17 Mar 2017 08:13:59 +0100 Subject: [PATCH 11/13] worktree: write resolved paths into link files The three link files "worktree/.git", ".git/worktrees//commondir" and ".git/worktrees//gitdir" should always contain absolute and resolved paths. Adjust the logic creating new worktrees to first use `git_path_prettify_dir` before writing out these files, so that paths are resolved first. --- src/worktree.c | 15 ++++++++++----- tests/worktree/worktree.c | 2 ++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/worktree.c b/src/worktree.c index 2b0ebfb83..393a088fe 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -272,7 +272,7 @@ out: int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree) { - git_buf gitdir = GIT_BUF_INIT, buf = GIT_BUF_INIT; + git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT, buf = GIT_BUF_INIT; git_reference *ref = NULL, *head = NULL; git_commit *commit = NULL; git_repository *wt = NULL; @@ -293,23 +293,27 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, goto out; if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) goto out; + if ((err = git_path_prettify_dir(&gitdir, gitdir.ptr, NULL)) < 0) + goto out; /* Create worktree work dir */ if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0) goto out; + if ((err = git_path_prettify_dir(&wddir, worktree, NULL)) < 0) + goto out; /* Create worktree .git file */ if ((err = git_buf_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0) goto out; - if ((err = write_wtfile(worktree, ".git", &buf)) < 0) + if ((err = write_wtfile(wddir.ptr, ".git", &buf)) < 0) goto out; /* Create gitdir files */ - if ((err = git_buf_sets(&buf, repo->commondir)) < 0 + if ((err = git_path_prettify_dir(&buf, repo->commondir, NULL) < 0) || (err = git_buf_putc(&buf, '\n')) < 0 || (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0) goto out; - if ((err = git_buf_joinpath(&buf, worktree, ".git")) < 0 + if ((err = git_buf_joinpath(&buf, wddir.ptr, ".git")) < 0 || (err = git_buf_putc(&buf, '\n')) < 0 || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0) goto out; @@ -325,7 +329,7 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, /* Set worktree's HEAD */ if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0) goto out; - if ((err = git_repository_open(&wt, worktree)) < 0) + if ((err = git_repository_open(&wt, wddir.ptr)) < 0) goto out; /* Checkout worktree's HEAD */ @@ -339,6 +343,7 @@ int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, out: git_buf_free(&gitdir); + git_buf_free(&wddir); git_buf_free(&buf); git_reference_free(ref); git_reference_free(head); diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index e74647f54..6e90e6ac0 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -306,7 +306,9 @@ void test_worktree_worktree__init_submodule(void) cl_git_pass(git_worktree_add(&worktree, sm, "repo-worktree", path.ptr)); cl_git_pass(git_repository_open_from_worktree(&wt, worktree)); + cl_git_pass(git_path_prettify_dir(&path, path.ptr, NULL)); cl_assert_equal_s(path.ptr, wt->workdir); + cl_git_pass(git_path_prettify_dir(&path, sm->commondir, NULL)); cl_assert_equal_s(sm->commondir, wt->commondir); cl_git_pass(git_buf_joinpath(&path, sm->gitdir, "worktrees/repo-worktree/")); From 097f0105b4f49be3f26273f2323a5b39404180db Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 17 Mar 2017 08:54:53 +0100 Subject: [PATCH 12/13] refdb: create references in commondir References for a repository are usually created inside of its gitdir. When using worktrees, though, these references are not to be created inside the worktree gitdir, but instead inside the gitdir of its parent repository, which is the commondir. Like this, branches will still be available after the worktree itself has been deleted. The filesystem refdb currently still creates new references inside of the gitdir. Fix this and have it create references in commondir. --- src/refdb_fs.c | 10 ++++++++-- tests/worktree/refs.c | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index ac5a6a6a5..713095865 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -738,6 +738,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * { int error; git_buf ref_path = GIT_BUF_INIT; + const char *basedir; assert(file && backend && name); @@ -746,13 +747,18 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * return GIT_EINVALIDSPEC; } + if (is_per_worktree_ref(name)) + basedir = backend->gitpath; + else + basedir = backend->commonpath; + /* Remove a possibly existing empty directory hierarchy * which name would collide with the reference name */ - if ((error = git_futils_rmdir_r(name, backend->gitpath, GIT_RMDIR_SKIP_NONEMPTY)) < 0) + if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0) return error; - if (git_buf_joinpath(&ref_path, backend->gitpath, name) < 0) + if (git_buf_joinpath(&ref_path, basedir, name) < 0) return -1; error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE); diff --git a/tests/worktree/refs.c b/tests/worktree/refs.c index ccac8be29..b9a05606d 100644 --- a/tests/worktree/refs.c +++ b/tests/worktree/refs.c @@ -1,4 +1,6 @@ #include "clar_libgit2.h" +#include "path.h" +#include "refs.h" #include "worktree.h" #include "worktree_helpers.h" @@ -128,3 +130,27 @@ void test_worktree_refs__delete_succeeds_after_pruning_worktree(void) cl_git_pass(git_branch_delete(branch)); git_reference_free(branch); } + +void test_worktree_refs__creating_refs_uses_commondir(void) +{ + git_reference *head, *branch, *lookup; + git_commit *commit; + git_buf refpath = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&refpath, + git_repository_commondir(fixture.worktree), "refs/heads/testbranch")); + cl_assert(!git_path_exists(refpath.ptr)); + + cl_git_pass(git_repository_head(&head, fixture.worktree)); + cl_git_pass(git_commit_lookup(&commit, fixture.worktree, git_reference_target(head))); + cl_git_pass(git_branch_create(&branch, fixture.worktree, "testbranch", commit, 0)); + cl_git_pass(git_branch_lookup(&lookup, fixture.worktree, "testbranch", GIT_BRANCH_LOCAL)); + cl_assert(git_reference_cmp(branch, lookup) == 0); + cl_assert(git_path_exists(refpath.ptr)); + + git_reference_free(lookup); + git_reference_free(branch); + git_reference_free(head); + git_commit_free(commit); + git_buf_free(&refpath); +} From b0c9bc920fabfd814946d555738ac7ba042154d7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 Mar 2017 13:38:54 +0100 Subject: [PATCH 13/13] submodule: resolve URLs relative to main worktree It is possible to specify submodule URLs relative to the repository location. E.g. having a submodule with URL "../submodule" will look for the submodule at "repo/../submodule". With the introduction of worktrees, though, we cannot simply resolve the URL relative to the repository location itself. If the repository for which a URL is to be resolved is a working tree, we have to resolve the URL relative to the parent's repository path. Otherwise, the URL would change depending on where the working tree is located. Fix this by special-casing when we have a working tree while getting the URL base. --- src/submodule.c | 24 +++++++++---- .../submodules/testrepo/.gitted/config | Bin 294 -> 173 bytes tests/worktree/submodule.c | 33 ++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/submodule.c b/src/submodule.c index 095bbb090..ff65591d2 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -22,6 +22,7 @@ #include "iterator.h" #include "path.h" #include "index.h" +#include "worktree.h" #define GIT_MODULES_FILE ".gitmodules" @@ -2030,17 +2031,28 @@ static int lookup_default_remote(git_remote **remote, git_repository *repo) static int get_url_base(git_buf *url, git_repository *repo) { int error; + git_worktree *wt = NULL; git_remote *remote = NULL; - if (!(error = lookup_default_remote(&remote, repo))) { + if ((error = lookup_default_remote(&remote, repo)) == 0) { error = git_buf_sets(url, git_remote_url(remote)); - git_remote_free(remote); - } - else if (error == GIT_ENOTFOUND) { - /* if repository does not have a default remote, use workdir instead */ + goto out; + } else if (error != GIT_ENOTFOUND) + goto out; + else giterr_clear(); + + /* if repository does not have a default remote, use workdir instead */ + if (git_repository_is_worktree(repo)) { + if ((error = git_worktree_open_from_repository(&wt, repo)) < 0) + goto out; + error = git_buf_sets(url, wt->parent_path); + } else error = git_buf_sets(url, git_repository_workdir(repo)); - } + +out: + git_remote_free(remote); + git_worktree_free(wt); return error; } diff --git a/tests/resources/submodules/testrepo/.gitted/config b/tests/resources/submodules/testrepo/.gitted/config index d6dcad12b3e80886731a6358d7c1fc5408050a39..8e557119184b82b0622f9b335fc104b1089e82f3 100644 GIT binary patch delta 9 QcmZ3+w3cx~!Nixg022ZP4gdfE delta 137 zcmZ3>xQuB+L48qbZhlFsf>M4_W_o6xQY;r|T53shhJvkvc2R0tv3^EsVoI^TmKB%- zQCzGKQLL}U#aUXE15~OXTAW%`tY4I*UtE-|pOcxCo>^j~Uy@o}0u)Rw&Mz%WPAvxW Wi&6{n^?