diff --git a/include/git2/remote.h b/include/git2/remote.h index 5c01949d2..6d4b6cc20 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -79,6 +79,36 @@ GIT_EXTERN(const char *) git_remote_name(git_remote *remote); */ GIT_EXTERN(const char *) git_remote_url(git_remote *remote); +/** + * Get the remote's url for pushing + * + * @param remote the remote + * @return a pointer to the url or NULL if no special url for pushing is set + */ +GIT_EXTERN(const char *) git_remote_pushurl(git_remote *remote); + +/** + * Set the remote's url + * + * Existing connections will not be updated. + * + * @param remote the remote + * @param url the url to set + * @return 0 or an error value + */ +GIT_EXTERN(int) git_remote_set_url(git_remote *remote, const char* url); + +/** + * Set the remote's url for pushing + * + * Existing connections will not be updated. + * + * @param remote the remote + * @param url the url to set or NULL to clear the pushurl + * @return 0 or an error value + */ +GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url); + /** * Set the remote's fetch refspec * diff --git a/src/remote.c b/src/remote.c index e46249e12..b4a21a688 100644 --- a/src/remote.c +++ b/src/remote.c @@ -130,6 +130,26 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) remote->url = git__strdup(val); GITERR_CHECK_ALLOC(remote->url); + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.pushurl", name) < 0) { + error = -1; + goto cleanup; + } + + error = git_config_get_string(&val, config, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) + error = 0; + + if (error < 0) { + error = -1; + goto cleanup; + } + + if (val) { + remote->pushurl = git__strdup(val); + GITERR_CHECK_ALLOC(remote->pushurl); + } + git_buf_clear(&buf); if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) { error = -1; @@ -179,7 +199,7 @@ int git_remote_save(const git_remote *remote) if (git_repository_config__weakptr(&config, remote->repo) < 0) return -1; - if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0) + if (git_buf_printf(&buf, "remote.%s.url", remote->name) < 0) return -1; if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) { @@ -187,6 +207,26 @@ int git_remote_save(const git_remote *remote) return -1; } + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0) + return -1; + + if (remote->pushurl) { + if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) { + git_buf_free(&buf); + return -1; + } + } else { + int error = git_config_delete(config, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) { + error = 0; + } + if (error < 0) { + git_buf_free(&buf); + return -1; + } + } + if (remote->fetch.src != NULL && remote->fetch.dst != NULL) { git_buf_clear(&buf); git_buf_clear(&value); @@ -238,6 +278,38 @@ const char *git_remote_url(git_remote *remote) return remote->url; } +int git_remote_set_url(git_remote *remote, const char* url) +{ + assert(remote); + assert(url); + + git__free(remote->url); + remote->url = git__strdup(url); + GITERR_CHECK_ALLOC(remote->url); + + return 0; +} + +const char *git_remote_pushurl(git_remote *remote) +{ + assert(remote); + return remote->pushurl; +} + +int git_remote_set_pushurl(git_remote *remote, const char* url) +{ + assert(remote); + + git__free(remote->pushurl); + if (url) { + remote->pushurl = git__strdup(url); + GITERR_CHECK_ALLOC(remote->pushurl); + } else { + remote->pushurl = NULL; + } + return 0; +} + int git_remote_set_fetchspec(git_remote *remote, const char *spec) { git_refspec refspec; @@ -284,13 +356,32 @@ const git_refspec *git_remote_pushspec(git_remote *remote) return &remote->push; } +const char* git_remote__urlfordirection(git_remote *remote, int direction) +{ + assert(remote); + + if (direction == GIT_DIR_FETCH) { + return remote->url; + } + + if (direction == GIT_DIR_PUSH) { + return remote->pushurl ? remote->pushurl : remote->url; + } + + return NULL; +} + int git_remote_connect(git_remote *remote, int direction) { git_transport *t; assert(remote); - if (git_transport_new(&t, remote->url) < 0) + const char* url = git_remote__urlfordirection(remote, direction); + if (url == NULL ) + return -1; + + if (git_transport_new(&t, url) < 0) return -1; t->check_cert = remote->check_cert; @@ -429,6 +520,7 @@ void git_remote_free(git_remote *remote) git__free(remote->push.src); git__free(remote->push.dst); git__free(remote->url); + git__free(remote->pushurl); git__free(remote->name); git__free(remote); } diff --git a/src/remote.h b/src/remote.h index 0949ad434..623d40c87 100644 --- a/src/remote.h +++ b/src/remote.h @@ -14,6 +14,7 @@ struct git_remote { char *name; char *url; + char *pushurl; git_vector refs; struct git_refspec fetch; struct git_refspec push; @@ -23,4 +24,6 @@ struct git_remote { check_cert; }; +const char* git_remote__urlfordirection(struct git_remote *remote, int direction); + #endif diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index eb7947dfb..f1d6f47c6 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -2,6 +2,7 @@ #include "buffer.h" #include "refspec.h" #include "transport.h" +#include "remote.h" static git_remote *_remote; static git_repository *_repo; @@ -27,8 +28,37 @@ void test_network_remotes__cleanup(void) void test_network_remotes__parsing(void) { + git_remote *_remote2 = NULL; + cl_assert_equal_s(git_remote_name(_remote), "test"); cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIR_FETCH), + "git://github.com/libgit2/libgit2"); + cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIR_PUSH), + "git://github.com/libgit2/libgit2"); + + cl_git_pass(git_remote_load(&_remote2, _repo, "test_with_pushurl")); + cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl"); + cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2"); + cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2"); + + cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIR_FETCH), + "git://github.com/libgit2/fetchlibgit2"); + cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIR_PUSH), + "git://github.com/libgit2/pushlibgit2"); + + git_remote_free(_remote2); +} + +void test_network_remotes__pushurl(void) +{ + cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/notlibgit2")); + cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/notlibgit2"); + + cl_git_pass(git_remote_set_pushurl(_remote, NULL)); + cl_assert(git_remote_pushurl(_remote) == NULL); } void test_network_remotes__parsing_ssh_remote(void) @@ -81,6 +111,7 @@ void test_network_remotes__save(void) cl_git_pass(git_remote_new(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2", NULL)); cl_git_pass(git_remote_set_fetchspec(_remote, "refs/heads/*:refs/remotes/upstream/*")); cl_git_pass(git_remote_set_pushspec(_remote, "refs/heads/*:refs/heads/*")); + cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/libgit2_push")); cl_git_pass(git_remote_save(_remote)); git_remote_free(_remote); _remote = NULL; @@ -98,6 +129,18 @@ void test_network_remotes__save(void) cl_assert(_refspec != NULL); cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); cl_assert_equal_s(git_refspec_dst(_refspec), "refs/heads/*"); + + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/libgit2_push"); + + /* remove the pushurl again and see if we can save that too */ + cl_git_pass(git_remote_set_pushurl(_remote, NULL)); + cl_git_pass(git_remote_save(_remote)); + git_remote_free(_remote); + _remote = NULL; + + cl_git_pass(git_remote_load(&_remote, _repo, "upstream")); + cl_assert(git_remote_pushurl(_remote) == NULL); } void test_network_remotes__fnmatch(void) @@ -143,13 +186,13 @@ void test_network_remotes__list(void) git_config *cfg; cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 1); + cl_assert(list.count == 2); git_strarray_free(&list); cl_git_pass(git_repository_config(&cfg, _repo)); cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 2); + cl_assert(list.count == 3); git_strarray_free(&list); git_config_free(cfg); @@ -180,4 +223,5 @@ void test_network_remotes__add(void) cl_assert(!strcmp(git_refspec_src(_refspec), "refs/heads/*")); cl_assert(git_refspec_force(_refspec) == 1); cl_assert(!strcmp(git_refspec_dst(_refspec), "refs/remotes/addtest/*")); + cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2"); } diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config index b4fdac6c2..c99d97153 100644 --- a/tests-clar/resources/testrepo.git/config +++ b/tests-clar/resources/testrepo.git/config @@ -7,6 +7,11 @@ url = git://github.com/libgit2/libgit2 fetch = +refs/heads/*:refs/remotes/test/* +[remote "test_with_pushurl"] + url = git://github.com/libgit2/fetchlibgit2 + pushurl = git://github.com/libgit2/pushlibgit2 + fetch = +refs/heads/*:refs/remotes/test_with_pushurl/* + [branch "master"] remote = test merge = refs/heads/master