Commit b0c9bc920fabfd814946d555738ac7ba042154d7

Patrick Steinhardt 2017-03-15T13:38:54

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.

diff --git a/src/submodule.c b/src/submodule.c
index 095bbb0..ff65591 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 d6dcad1..8e55711 100644
--- a/tests/resources/submodules/testrepo/.gitted/config
+++ b/tests/resources/submodules/testrepo/.gitted/config
@@ -4,9 +4,6 @@
 	bare = false
 	logallrefupdates = true
 	ignorecase = true
-[remote "origin"]
-	fetch = +refs/heads/*:refs/remotes/origin/*
-	url = /Users/rb/src/libgit2/tests/resources/testrepo.git
 [branch "master"]
 	remote = origin
 	merge = refs/heads/master
diff --git a/tests/worktree/submodule.c b/tests/worktree/submodule.c
index b435070..5620775 100644
--- a/tests/worktree/submodule.c
+++ b/tests/worktree/submodule.c
@@ -1,5 +1,6 @@
 #include "clar_libgit2.h"
 #include "repository.h"
+#include "worktree.h"
 #include "worktree_helpers.h"
 
 #define WORKTREE_PARENT "submodules-worktree-parent"
@@ -57,3 +58,35 @@ void test_worktree_submodule__open_discovered_submodule_worktree(void)
 	git_buf_free(&path);
 	git_repository_free(repo);
 }
+
+void test_worktree_submodule__resolve_relative_url(void)
+{
+	git_buf wt_path = GIT_BUF_INIT;
+	git_buf sm_relative_path = GIT_BUF_INIT, wt_relative_path = GIT_BUF_INIT;
+	git_repository *repo;
+	git_worktree *wt;
+
+	cl_git_pass(git_futils_mkdir("subdir", 0755, GIT_MKDIR_PATH));
+	cl_git_pass(git_path_prettify_dir(&wt_path, "subdir", NULL));
+	cl_git_pass(git_buf_joinpath(&wt_path, wt_path.ptr, "wt"));
+
+	/* Open child repository, which is a submodule */
+	cl_git_pass(git_repository_open(&child.repo, WORKTREE_CHILD));
+
+	/* Create worktree of submodule repository */
+	cl_git_pass(git_worktree_add(&wt, child.repo, "subdir", wt_path.ptr));
+	cl_git_pass(git_repository_open_from_worktree(&repo, wt));
+
+	cl_git_pass(git_submodule_resolve_url(&sm_relative_path, repo,
+		    "../" WORKTREE_CHILD));
+	cl_git_pass(git_submodule_resolve_url(&wt_relative_path, child.repo,
+		    "../" WORKTREE_CHILD));
+
+	cl_assert_equal_s(sm_relative_path.ptr, wt_relative_path.ptr);
+
+	git_worktree_free(wt);
+	git_repository_free(repo);
+	git_buf_free(&wt_path);
+	git_buf_free(&sm_relative_path);
+	git_buf_free(&wt_relative_path);
+}