From f2fb4bac68e7ab38cf6082655b2da153866a012d Mon Sep 17 00:00:00 2001 From: Jan Melcher Date: Wed, 2 Apr 2014 23:55:21 +0200 Subject: git_submodule_resolve_url supports relative urls The base for the relative urls is determined as follows, with descending priority: - remote url of HEAD's remote tracking branch - remote "origin" - workdir This follows git.git behaviour --- src/submodule.c | 136 +++++++++++++++++++++++++++++------------------ tests/submodule/add.c | 115 +++++++++++++++++++++++++++++++++++++++ tests/submodule/modify.c | 73 ------------------------- 3 files changed, 198 insertions(+), 126 deletions(-) create mode 100644 tests/submodule/add.c diff --git a/src/submodule.c b/src/submodule.c index 913c97a87..24f250aa1 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -91,8 +91,10 @@ static int submodule_cache_init(git_repository *repo, int refresh); static void submodule_cache_free(git_submodule_cache *cache); static git_config_backend *open_gitmodules(git_submodule_cache *, int gitmod); -static int lookup_head_remote_key(git_buf *key, git_repository *repo); -static int lookup_head_remote(git_buf *url, git_repository *repo); +static int get_url_base(git_buf *url, git_repository *repo); +static int lookup_default_remote(git_remote **remote, git_repository *repo); +static int lookup_head_remote_key(git_buf *remote_key, git_repository *repo); +static int lookup_head_remote(git_remote **remote, git_repository *repo); static int submodule_get(git_submodule **, git_submodule_cache *, const char *, const char *); static int submodule_load_from_config(const git_config_entry *, void *); static int submodule_load_from_wd_lite(git_submodule *); @@ -644,7 +646,7 @@ int git_submodule_resolve_url(git_buf *out, git_repository *repo, const char *ur assert(url); if (git_path_is_relative(url)) { - if (!(error = lookup_head_remote(out, repo))) + if (!(error = get_url_base(out, repo))) error = git_path_apply_relative(out, url); } else if (strchr(url, ':') != NULL || url[0] == '/') { error = git_buf_sets(out, url); @@ -1795,81 +1797,109 @@ static int submodule_cache_init(git_repository *repo, int cache_refresh) return error; } -static int lookup_head_remote_key(git_buf *key, git_repository *repo) +static int get_url_base(git_buf *url, git_repository *repo) { int error; - git_config *cfg; - git_reference *head = NULL, *remote = NULL; - const char *tgt, *scan; + git_remote *remote; + error = lookup_default_remote(&remote, repo); + const char *url_ptr; + + assert(url && repo); + + if (!error) { + url_ptr = git_remote_url(remote); + } else if (error == GIT_ENOTFOUND) { + /* if repository does not have a default remote, use workdir instead */ + giterr_clear(); + error = 0; + url_ptr = git_repository_workdir(repo); + } + + if (error < 0) + goto cleanup; + + error = git_buf_sets(url, url_ptr); - /* 1. resolve HEAD -> refs/heads/BRANCH - * 2. lookup config branch.BRANCH.remote -> ORIGIN - * 3. return remote.ORIGIN.url - */ +cleanup: + git_remote_free(remote); - git_buf_clear(key); + return error; +} - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; +/** + * Lookup the remote that is considered the default remote in the current state + */ +static int lookup_default_remote(git_remote **remote, git_repository *repo) +{ + int error; + error = lookup_head_remote(remote, repo); - if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) { - giterr_set(GITERR_SUBMODULE, - "Cannot resolve relative URL when HEAD cannot be resolved"); - return GIT_ENOTFOUND; + assert(remote && repo); + + // if that failed, use 'origin' instead + if (error == GIT_ENOTFOUND) { + error = git_remote_load(remote, repo, "origin"); } - if (git_reference_type(head) != GIT_REF_SYMBOLIC) { + if (error == GIT_ENOTFOUND) { giterr_set(GITERR_SUBMODULE, - "Cannot resolve relative URL when HEAD is not symbolic"); - error = GIT_ENOTFOUND; - goto cleanup; + "Neither HEAD points to a local tracking branch, nor does origin exist"); } - if ((error = git_branch_upstream(&remote, head)) < 0) - goto cleanup; + return error; +} - /* remote should refer to something like refs/remotes/ORIGIN/BRANCH */ +/** + * Lookup name of remote of the local tracking branch HEAD points to + */ +static int lookup_head_remote_key(git_buf *remote_name, git_repository *repo) +{ + int error; + git_reference *head = NULL; + git_buf upstream_name = GIT_BUF_INIT; - if (git_reference_type(remote) != GIT_REF_SYMBOLIC || - git__prefixcmp( - git_reference_symbolic_target(remote), GIT_REFS_REMOTES_DIR) != 0) - { - giterr_set(GITERR_SUBMODULE, - "Cannot resolve relative URL when HEAD is not symbolic"); - error = GIT_ENOTFOUND; - goto cleanup; - } + /* lookup and dereference HEAD */ + if ((error = git_repository_head(&head, repo) < 0)) + goto cleanup; - scan = tgt = git_reference_symbolic_target(remote) + - strlen(GIT_REFS_REMOTES_DIR); - while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\'))) - scan++; /* find non-escaped slash to end ORIGIN name */ + /* lookup remote tracking branch of HEAD */ + if ((error = git_branch_upstream_name(&upstream_name, repo, git_reference_name(head))) < 0) + goto cleanup; - error = git_buf_printf(key, "remote.%.*s.url", (int)(scan - tgt), tgt); + /* lookup remote of remote tracking branch */ + if ((error = git_branch_remote_name(remote_name, repo, upstream_name.ptr)) < 0) + goto cleanup; cleanup: - if (error < 0) - git_buf_clear(key); - - git_reference_free(head); - git_reference_free(remote); + git_buf_free(&upstream_name); + if (head) + git_reference_free(head); return error; } -static int lookup_head_remote(git_buf *url, git_repository *repo) +/** + * Lookup the remote of the local tracking branch HEAD points to + */ +static int lookup_head_remote(git_remote **remote, git_repository *repo) { int error; - git_config *cfg; - const char *tgt; - git_buf key = GIT_BUF_INIT; + git_buf remote_name = GIT_BUF_INIT; - if (!(error = lookup_head_remote_key(&key, repo)) && - !(error = git_repository_config__weakptr(&cfg, repo)) && - !(error = git_config_get_string(&tgt, cfg, key.ptr))) - error = git_buf_sets(url, tgt); + assert(remote && repo); + + /* should be NULL in case of error */ + *remote = NULL; + + /* lookup remote of remote tracking branch name */ + if ((error = lookup_head_remote_key(&remote_name, repo)) < 0) + goto cleanup; + + error = git_remote_load(remote, repo, remote_name.ptr); + +cleanup: + git_buf_free(&remote_name); - git_buf_free(&key); return error; } diff --git a/tests/submodule/add.c b/tests/submodule/add.c new file mode 100644 index 000000000..2d51b3d7e --- /dev/null +++ b/tests/submodule/add.c @@ -0,0 +1,115 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" + +static git_repository *g_repo = NULL; + +static void assert_submodule_url(const char* name, const char *url); + +void test_submodule_add__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_submodule_add__url_absolute(void) +{ + g_repo = setup_fixture_submod2(); + git_submodule *sm; + + /* re-add existing submodule */ + cl_assert_equal_i( + GIT_EEXISTS, + git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1)); + + /* add a submodule using a gitlink */ + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2", 1) + ); + git_submodule_free(sm); + + cl_assert(git_path_isfile("submod2/" "sm_libgit2" "/.git")); + + cl_assert(git_path_isdir("submod2/.git/modules")); + cl_assert(git_path_isdir("submod2/.git/modules/" "sm_libgit2")); + cl_assert(git_path_isfile("submod2/.git/modules/" "sm_libgit2" "/HEAD")); + assert_submodule_url("sm_libgit2", "https://github.com/libgit2/libgit2.git"); + + /* add a submodule not using a gitlink */ + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2b", 0) + ); + git_submodule_free(sm); + + cl_assert(git_path_isdir("submod2/" "sm_libgit2b" "/.git")); + cl_assert(git_path_isfile("submod2/" "sm_libgit2b" "/.git/HEAD")); + cl_assert(!git_path_exists("submod2/.git/modules/" "sm_libgit2b")); + assert_submodule_url("sm_libgit2b", "https://github.com/libgit2/libgit2.git"); +} + +void test_submodule_add__url_relative(void) { + git_submodule *sm; + git_remote *remote; + + /* default remote url is https://github.com/libgit2/false.git */ + g_repo = cl_git_sandbox_init("testrepo2"); + + /* make sure we're not defaulting to origin - rename origin -> test_remote */ + cl_git_pass(git_remote_load(&remote, g_repo, "origin")); + cl_git_pass(git_remote_rename(remote, "test_remote", NULL, NULL)); + cl_git_fail(git_remote_load(&remote, g_repo, "origin")); + git_remote_free(remote); + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1) + ); + git_submodule_free(sm); + + assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository"); +} + +void test_submodule_add__url_relative_to_origin(void) { + git_submodule *sm; + + /* default remote url is https://github.com/libgit2/false.git */ + g_repo = cl_git_sandbox_init("testrepo2"); + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1) + ); + git_submodule_free(sm); + + assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository"); +} + +void test_submodule_add__url_relative_to_workdir(void) { + git_submodule *sm; + + /* In this repo, HEAD (master) has no remote tracking branc h*/ + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "./", "TestGitRepository", 1) + ); + git_submodule_free(sm); + + assert_submodule_url("TestGitRepository", git_repository_workdir(g_repo)); +} + +static void assert_submodule_url(const char* name, const char *url) +{ + git_config *cfg; + const char *s; + git_buf key = GIT_BUF_INIT; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + + cl_git_pass(git_buf_printf(&key, "submodule.%s.url", name)); + cl_git_pass(git_config_get_string(&s, cfg, git_buf_cstr(&key))); + cl_assert_equal_s(s, url); + + git_config_free(cfg); + git_buf_free(&key); +} diff --git a/tests/submodule/modify.c b/tests/submodule/modify.c index 7cd44e23e..7e76f3572 100644 --- a/tests/submodule/modify.c +++ b/tests/submodule/modify.c @@ -7,85 +7,12 @@ static git_repository *g_repo = NULL; #define SM_LIBGIT2_URL "https://github.com/libgit2/libgit2.git" #define SM_LIBGIT2 "sm_libgit2" -#define SM_LIBGIT2B "sm_libgit2b" - -#define SM_RELATIVE_URL "../TestGitRepository" -#define SM_RELATIVE_RESOLVED_URL "https://github.com/libgit2/TestGitRepository" -#define SM_RELATIVE "TestGitRepository" void test_submodule_modify__initialize(void) { g_repo = setup_fixture_submod2(); } -void test_submodule_modify__add(void) -{ - git_submodule *sm; - git_config *cfg; - const char *s; - - /* re-add existing submodule */ - cl_assert_equal_i( - GIT_EEXISTS, - git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1)); - - /* add a submodule using a gitlink */ - - cl_git_pass( - git_submodule_add_setup(&sm, g_repo, SM_LIBGIT2_URL, SM_LIBGIT2, 1) - ); - git_submodule_free(sm); - - cl_assert(git_path_isfile("submod2/" SM_LIBGIT2 "/.git")); - - cl_assert(git_path_isdir("submod2/.git/modules")); - cl_assert(git_path_isdir("submod2/.git/modules/" SM_LIBGIT2)); - cl_assert(git_path_isfile("submod2/.git/modules/" SM_LIBGIT2 "/HEAD")); - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass( - git_config_get_string(&s, cfg, "submodule." SM_LIBGIT2 ".url")); - cl_assert_equal_s(s, SM_LIBGIT2_URL); - git_config_free(cfg); - - /* add a submodule not using a gitlink */ - - cl_git_pass( - git_submodule_add_setup(&sm, g_repo, SM_LIBGIT2_URL, SM_LIBGIT2B, 0) - ); - git_submodule_free(sm); - - cl_assert(git_path_isdir("submod2/" SM_LIBGIT2B "/.git")); - cl_assert(git_path_isfile("submod2/" SM_LIBGIT2B "/.git/HEAD")); - cl_assert(!git_path_exists("submod2/.git/modules/" SM_LIBGIT2B)); - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass( - git_config_get_string(&s, cfg, "submodule." SM_LIBGIT2B ".url")); - cl_assert_equal_s(s, SM_LIBGIT2_URL); - git_config_free(cfg); -} - -void test_submodule_modify__add_with_relative_url(void) { - git_submodule *sm; - git_config *cfg; - const char *s; - - git_repository* repo; - /* setup_fixture_submod2 does not work here because it does not set up origin configuration */ - cl_git_pass(git_clone(&repo, SM_LIBGIT2_URL, "./sandbox/submodules_cloned", NULL)); - - cl_git_pass( - git_submodule_add_setup(&sm, repo, SM_RELATIVE_URL, SM_RELATIVE, 1) - ); - - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass( - git_config_get_string(&s, cfg, "submodule." SM_RELATIVE ".url")); - cl_assert_equal_s(s, SM_RELATIVE_RESOLVED_URL); - git_config_free(cfg); -} - static int delete_one_config(const git_config_entry *entry, void *payload) { git_config *cfg = payload; -- cgit v1.2.1