diff options
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | include/git2/remote.h | 6 | ||||
-rw-r--r-- | src/remote.c | 75 | ||||
-rw-r--r-- | tests/remote/insteadof.c | 72 | ||||
-rw-r--r-- | tests/resources/testrepo2/.gitted/config | 12 |
5 files changed, 164 insertions, 5 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f426825d..8d4b73342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,10 @@ support for HTTPS connections insead of OpenSSL. `git_off_t` instead of `size_t` for the size of the blob, which allows putting large files into the odb on 32-bit systems. +* The remote's push and pull URLs now honor the url.$URL.insteadOf + configuration. This allows modifying URL prefixes to a custom + value via gitconfig. + ### API additions * The `git_merge_options` gained a `file_flags` member. diff --git a/include/git2/remote.h b/include/git2/remote.h index 02d73a0e6..ccd0b43f4 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -120,6 +120,9 @@ GIT_EXTERN(const char *) git_remote_name(const git_remote *remote); /** * Get the remote's url * + * If url.*.insteadOf has been configured for this URL, it will + * return the modified URL. + * * @param remote the remote * @return a pointer to the url */ @@ -128,6 +131,9 @@ GIT_EXTERN(const char *) git_remote_url(const git_remote *remote); /** * Get the remote's url for pushing * + * If url.*.pushInsteadOf has been configured for this URL, it + * will return the modified URL. + * * @param remote the remote * @return a pointer to the url or NULL if no special url for pushing is set */ diff --git a/src/remote.c b/src/remote.c index 43b34561d..b7acbb9c1 100644 --- a/src/remote.c +++ b/src/remote.c @@ -28,6 +28,7 @@ static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs); static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name); +char *apply_insteadof(git_config *config, const char *url, int direction); static int add_refspec_to(git_vector *vector, const char *string, bool is_fetch) { @@ -207,7 +208,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n canonicalize_url(&canonical_url, url) < 0) goto on_error; - remote->url = git_buf_detach(&canonical_url); + remote->url = apply_insteadof(repo->_config, canonical_url.ptr, GIT_DIRECTION_FETCH); if (name != NULL) { remote->name = git__strdup(name); @@ -216,7 +217,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0) goto on_error; - if ((error = git_config_set_string(config, var.ptr, remote->url)) < 0) + if ((error = git_config_set_string(config, var.ptr, canonical_url.ptr)) < 0) goto on_error; } @@ -341,7 +342,7 @@ int git_remote_dup(git_remote **dest, git_remote *source) if (source->url != NULL) { remote->url = git__strdup(source->url); - GITERR_CHECK_ALLOC(remote->url); + GITERR_CHECK_ALLOC(remote->url); } if (source->pushurl != NULL) { @@ -456,7 +457,7 @@ int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; if (found && strlen(val) > 0) { - remote->url = git__strdup(val); + remote->url = apply_insteadof(config, val, GIT_DIRECTION_FETCH); GITERR_CHECK_ALLOC(remote->url); } @@ -476,7 +477,7 @@ int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) } if (found && strlen(val) > 0) { - remote->pushurl = git__strdup(val); + remote->pushurl = apply_insteadof(config, val, GIT_DIRECTION_PUSH); GITERR_CHECK_ALLOC(remote->pushurl); } @@ -2421,3 +2422,67 @@ int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_ git_remote_disconnect(remote); return error; } + +#define PREFIX "url" +#define SUFFIX_FETCH "insteadof" +#define SUFFIX_PUSH "pushinsteadof" + +char *apply_insteadof(git_config *config, const char *url, int direction) +{ + size_t match_length, prefix_length, suffix_length; + char *replacement = NULL; + const char *regexp; + + git_buf result = GIT_BUF_INIT; + git_config_entry *entry; + git_config_iterator *iter; + + assert(config); + assert(url); + assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); + + /* Add 1 to prefix/suffix length due to the additional escaped dot */ + prefix_length = strlen(PREFIX) + 1; + if (direction == GIT_DIRECTION_FETCH) { + regexp = PREFIX "\\..*\\." SUFFIX_FETCH; + suffix_length = strlen(SUFFIX_FETCH) + 1; + } else { + regexp = PREFIX "\\..*\\." SUFFIX_PUSH; + suffix_length = strlen(SUFFIX_PUSH) + 1; + } + + git_config_iterator_glob_new(&iter, config, regexp); + + match_length = 0; + while (git_config_next(&entry, iter) == 0) { + size_t n, replacement_length; + + /* Check if entry value is a prefix of URL */ + if (git__prefixcmp(url, entry->value)) + continue; + /* Check if entry value is longer than previous + * prefixes */ + if ((n = strlen(entry->value)) <= match_length) + continue; + + git__free(replacement); + match_length = n; + + /* Cut off prefix and suffix of the value */ + replacement_length = + strlen(entry->name) - (prefix_length + suffix_length); + replacement = git__strndup(entry->name + prefix_length, + replacement_length); + } + + git_config_iterator_free(iter); + + if (match_length == 0) + return git__strdup(url); + + git_buf_printf(&result, "%s%s", replacement, url + match_length); + + git__free(replacement); + + return result.ptr; +} diff --git a/tests/remote/insteadof.c b/tests/remote/insteadof.c new file mode 100644 index 000000000..05d4757cf --- /dev/null +++ b/tests/remote/insteadof.c @@ -0,0 +1,72 @@ +#include "clar_libgit2.h" +#include "remote.h" +#include "repository.h" + +#define REPO_PATH "testrepo2/.gitted" +#define REMOTE_ORIGIN "origin" +#define REMOTE_INSTEADOF "insteadof-test" + +static git_repository *g_repo; +static git_remote *g_remote; + +void test_remote_insteadof__initialize(void) +{ + g_repo = NULL; + g_remote = NULL; +} + +void test_remote_insteadof__cleanup(void) +{ + git_repository_free(g_repo); + git_remote_free(g_remote); +} + +void test_remote_insteadof__url_insteadof_not_applicable(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_ORIGIN)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "https://github.com/libgit2/false.git"); +} + +void test_remote_insteadof__url_insteadof_applicable(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://github.com/libgit2/libgit2"); +} + +void test_remote_insteadof__pushurl_insteadof_not_applicable(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_ORIGIN)); + + cl_assert_equal_p(git_remote_pushurl(g_remote), NULL); +} + +void test_remote_insteadof__pushurl_insteadof_applicable(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF)); + + cl_assert_equal_s( + git_remote_pushurl(g_remote), + "git@github.com:libgit2/libgit2"); +} + +void test_remote_insteadof__anonymous_remote(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_create_anonymous(&g_remote, g_repo, + "http://example.com/libgit2/libgit2")); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://github.com/libgit2/libgit2"); + cl_assert_equal_p(git_remote_pushurl(g_remote), NULL); +} diff --git a/tests/resources/testrepo2/.gitted/config b/tests/resources/testrepo2/.gitted/config index fc2433caf..4af067f04 100644 --- a/tests/resources/testrepo2/.gitted/config +++ b/tests/resources/testrepo2/.gitted/config @@ -8,7 +8,19 @@ [remote "origin"] url = https://github.com/libgit2/false.git fetch = +refs/heads/*:refs/remotes/origin/* +[remote "insteadof-test"] + url = http://example.com/libgit2/libgit2 + pushurl = http://github.com/libgit2/libgit2 + fetch = +refs/heads/*:refs/remotes/test/* [branch "master"] remote = origin merge = refs/heads/master rebase = true +[url "longer-non-prefix-match"] + insteadOf = ttp://example.com/li +[url "shorter-prefix"] + insteadOf = http://example.co +[url "http://github.com"] + insteadOf = http://example.com +[url "git@github.com:"] + pushInsteadOf = http://github.com/ |