mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-29 13:52:17 +00:00
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
This commit is contained in:
parent
12d4ed4de3
commit
f2fb4bac68
150
src/submodule.c
150
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;
|
||||
|
||||
/* 1. resolve HEAD -> refs/heads/BRANCH
|
||||
* 2. lookup config branch.BRANCH.remote -> ORIGIN
|
||||
* 3. return remote.ORIGIN.url
|
||||
*/
|
||||
assert(url && repo);
|
||||
|
||||
git_buf_clear(key);
|
||||
|
||||
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
|
||||
return error;
|
||||
|
||||
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;
|
||||
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 (git_reference_type(head) != GIT_REF_SYMBOLIC) {
|
||||
giterr_set(GITERR_SUBMODULE,
|
||||
"Cannot resolve relative URL when HEAD is not symbolic");
|
||||
error = GIT_ENOTFOUND;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((error = git_branch_upstream(&remote, head)) < 0)
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* remote should refer to something like refs/remotes/ORIGIN/BRANCH */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 */
|
||||
|
||||
error = git_buf_printf(key, "remote.%.*s.url", (int)(scan - tgt), tgt);
|
||||
error = git_buf_sets(url, url_ptr);
|
||||
|
||||
cleanup:
|
||||
if (error < 0)
|
||||
git_buf_clear(key);
|
||||
|
||||
git_reference_free(head);
|
||||
git_reference_free(remote);
|
||||
git_remote_free(remote);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int lookup_head_remote(git_buf *url, git_repository *repo)
|
||||
/**
|
||||
* 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;
|
||||
git_config *cfg;
|
||||
const char *tgt;
|
||||
git_buf key = GIT_BUF_INIT;
|
||||
error = lookup_head_remote(remote, repo);
|
||||
|
||||
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);
|
||||
|
||||
// if that failed, use 'origin' instead
|
||||
if (error == GIT_ENOTFOUND) {
|
||||
error = git_remote_load(remote, repo, "origin");
|
||||
}
|
||||
|
||||
if (error == GIT_ENOTFOUND) {
|
||||
giterr_set(GITERR_SUBMODULE,
|
||||
"Neither HEAD points to a local tracking branch, nor does origin exist");
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/* lookup and dereference HEAD */
|
||||
if ((error = git_repository_head(&head, repo) < 0))
|
||||
goto cleanup;
|
||||
|
||||
/* lookup remote tracking branch of HEAD */
|
||||
if ((error = git_branch_upstream_name(&upstream_name, repo, git_reference_name(head))) < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* lookup remote of remote tracking branch */
|
||||
if ((error = git_branch_remote_name(remote_name, repo, upstream_name.ptr)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&upstream_name);
|
||||
if (head)
|
||||
git_reference_free(head);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_buf remote_name = GIT_BUF_INIT;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
115
tests/submodule/add.c
Normal file
115
tests/submodule/add.c
Normal file
@ -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);
|
||||
}
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user