| /* |
| * Copyright (C) 2009-2012 the libgit2 contributors |
| * |
| * This file is part of libgit2, distributed under the GNU GPL v2 with |
| * a Linking Exception. For full terms see the included COPYING file. |
| */ |
| |
| #include "git2/config.h" |
| #include "git2/types.h" |
| #include "git2/oid.h" |
| |
| #include "config.h" |
| #include "repository.h" |
| #include "remote.h" |
| #include "fetch.h" |
| #include "refs.h" |
| |
| #include <regex.h> |
| |
| static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var, bool is_fetch) |
| { |
| int error; |
| const char *val; |
| |
| if ((error = git_config_get_string(&val, cfg, var)) < 0) |
| return error; |
| |
| return git_refspec__parse(refspec, val, is_fetch); |
| } |
| |
| static int download_tags_value(git_remote *remote, git_config *cfg) |
| { |
| const char *val; |
| git_buf buf = GIT_BUF_INIT; |
| int error; |
| |
| if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSET) |
| return 0; |
| |
| /* This is the default, let's see if we need to change it */ |
| remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; |
| if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0) |
| return -1; |
| |
| error = git_config_get_string(&val, cfg, git_buf_cstr(&buf)); |
| git_buf_free(&buf); |
| if (!error && !strcmp(val, "--no-tags")) |
| remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; |
| else if (!error && !strcmp(val, "--tags")) |
| remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; |
| |
| if (error == GIT_ENOTFOUND) |
| error = 0; |
| |
| return error; |
| } |
| |
| int git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) |
| { |
| git_remote *remote; |
| |
| /* name is optional */ |
| assert(out && repo && url); |
| |
| remote = git__malloc(sizeof(git_remote)); |
| GITERR_CHECK_ALLOC(remote); |
| |
| memset(remote, 0x0, sizeof(git_remote)); |
| remote->repo = repo; |
| remote->check_cert = 1; |
| |
| if (git_vector_init(&remote->refs, 32, NULL) < 0) |
| return -1; |
| |
| remote->url = git__strdup(url); |
| GITERR_CHECK_ALLOC(remote->url); |
| |
| if (name != NULL) { |
| remote->name = git__strdup(name); |
| GITERR_CHECK_ALLOC(remote->name); |
| } |
| |
| if (fetch != NULL) { |
| if (git_refspec__parse(&remote->fetch, fetch, true) < 0) |
| goto on_error; |
| } |
| |
| /* A remote without a name doesn't download tags */ |
| if (!name) { |
| remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; |
| } |
| |
| *out = remote; |
| return 0; |
| |
| on_error: |
| git_remote_free(remote); |
| return -1; |
| } |
| |
| int git_remote_load(git_remote **out, git_repository *repo, const char *name) |
| { |
| git_remote *remote; |
| git_buf buf = GIT_BUF_INIT; |
| const char *val; |
| int error = 0; |
| git_config *config; |
| |
| assert(out && repo && name); |
| |
| if (git_repository_config__weakptr(&config, repo) < 0) |
| return -1; |
| |
| remote = git__malloc(sizeof(git_remote)); |
| GITERR_CHECK_ALLOC(remote); |
| |
| memset(remote, 0x0, sizeof(git_remote)); |
| remote->check_cert = 1; |
| remote->name = git__strdup(name); |
| GITERR_CHECK_ALLOC(remote->name); |
| |
| if (git_vector_init(&remote->refs, 32, NULL) < 0) { |
| error = -1; |
| goto cleanup; |
| } |
| |
| if (git_buf_printf(&buf, "remote.%s.url", name) < 0) { |
| error = -1; |
| goto cleanup; |
| } |
| |
| if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0) |
| goto cleanup; |
| |
| if (!val) { |
| giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name); |
| error = -1; |
| goto cleanup; |
| } |
| |
| remote->repo = repo; |
| 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; |
| goto cleanup; |
| } |
| |
| error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf), true); |
| if (error == GIT_ENOTFOUND) |
| error = 0; |
| |
| if (error < 0) { |
| error = -1; |
| goto cleanup; |
| } |
| |
| git_buf_clear(&buf); |
| if (git_buf_printf(&buf, "remote.%s.push", name) < 0) { |
| error = -1; |
| goto cleanup; |
| } |
| |
| error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf), false); |
| if (error == GIT_ENOTFOUND) |
| error = 0; |
| |
| if (error < 0) { |
| error = -1; |
| goto cleanup; |
| } |
| |
| if (download_tags_value(remote, config) < 0) |
| goto cleanup; |
| |
| *out = remote; |
| |
| cleanup: |
| git_buf_free(&buf); |
| |
| if (error < 0) |
| git_remote_free(remote); |
| |
| return error; |
| } |
| |
| static int ensure_remote_name_is_valid(const char *name) |
| { |
| git_buf buf = GIT_BUF_INIT; |
| git_refspec refspec; |
| int error = -1; |
| |
| if (!name || *name == '\0') |
| goto cleanup; |
| |
| git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", name); |
| error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true); |
| |
| git_buf_free(&buf); |
| git_refspec__free(&refspec); |
| |
| cleanup: |
| if (error) |
| giterr_set( |
| GITERR_CONFIG, |
| "'%s' is not a valid remote name.", name); |
| |
| return error; |
| } |
| |
| static int update_config_refspec( |
| git_config *config, |
| const char *remote_name, |
| const git_refspec *refspec, |
| int git_direction) |
| { |
| git_buf name = GIT_BUF_INIT, value = GIT_BUF_INIT; |
| int error = -1; |
| |
| if (refspec->src == NULL || refspec->dst == NULL) |
| return 0; |
| |
| if (git_buf_printf( |
| &name, |
| "remote.%s.%s", |
| remote_name, |
| git_direction == GIT_DIR_FETCH ? "fetch" : "push") < 0) |
| goto cleanup; |
| |
| if (git_refspec__serialize(&value, refspec) < 0) |
| goto cleanup; |
| |
| error = git_config_set_string( |
| config, |
| git_buf_cstr(&name), |
| git_buf_cstr(&value)); |
| |
| cleanup: |
| git_buf_free(&name); |
| git_buf_free(&value); |
| |
| return error; |
| } |
| |
| int git_remote_save(const git_remote *remote) |
| { |
| int error; |
| git_config *config; |
| const char *tagopt = NULL; |
| git_buf buf = GIT_BUF_INIT; |
| |
| assert(remote); |
| |
| if (ensure_remote_name_is_valid(remote->name) < 0) |
| return -1; |
| |
| if (git_repository_config__weakptr(&config, remote->repo) < 0) |
| return -1; |
| |
| 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) { |
| git_buf_free(&buf); |
| 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; |
| giterr_clear(); |
| } |
| if (error < 0) { |
| git_buf_free(&buf); |
| return -1; |
| } |
| } |
| |
| if (update_config_refspec( |
| config, |
| remote->name, |
| &remote->fetch, |
| GIT_DIR_FETCH) < 0) |
| goto on_error; |
| |
| if (update_config_refspec( |
| config, |
| remote->name, |
| &remote->push, |
| GIT_DIR_PUSH) < 0) |
| goto on_error; |
| |
| /* |
| * What action to take depends on the old and new values. This |
| * is describes by the table below. tagopt means whether the |
| * is already a value set in the config |
| * |
| * AUTO ALL or NONE |
| * +-----------------------+ |
| * tagopt | remove | set | |
| * +---------+-------------| |
| * !tagopt | nothing | set | |
| * +---------+-------------+ |
| */ |
| |
| git_buf_clear(&buf); |
| if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0) |
| goto on_error; |
| |
| error = git_config_get_string(&tagopt, config, git_buf_cstr(&buf)); |
| if (error < 0 && error != GIT_ENOTFOUND) |
| goto on_error; |
| |
| if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { |
| if (git_config_set_string(config, git_buf_cstr(&buf), "--tags") < 0) |
| goto on_error; |
| } else if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_NONE) { |
| if (git_config_set_string(config, git_buf_cstr(&buf), "--no-tags") < 0) |
| goto on_error; |
| } else if (tagopt) { |
| if (git_config_delete(config, git_buf_cstr(&buf)) < 0) |
| goto on_error; |
| } |
| |
| git_buf_free(&buf); |
| |
| return 0; |
| |
| on_error: |
| git_buf_free(&buf); |
| return -1; |
| } |
| |
| const char *git_remote_name(git_remote *remote) |
| { |
| assert(remote); |
| return remote->name; |
| } |
| |
| const char *git_remote_url(git_remote *remote) |
| { |
| assert(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; |
| |
| assert(remote && spec); |
| |
| if (git_refspec__parse(&refspec, spec, true) < 0) |
| return -1; |
| |
| git_refspec__free(&remote->fetch); |
| remote->fetch.src = refspec.src; |
| remote->fetch.dst = refspec.dst; |
| |
| return 0; |
| } |
| |
| const git_refspec *git_remote_fetchspec(git_remote *remote) |
| { |
| assert(remote); |
| return &remote->fetch; |
| } |
| |
| int git_remote_set_pushspec(git_remote *remote, const char *spec) |
| { |
| git_refspec refspec; |
| |
| assert(remote && spec); |
| |
| if (git_refspec__parse(&refspec, spec, false) < 0) |
| return -1; |
| |
| git_refspec__free(&remote->push); |
| remote->push.src = refspec.src; |
| remote->push.dst = refspec.dst; |
| |
| return 0; |
| } |
| |
| const git_refspec *git_remote_pushspec(git_remote *remote) |
| { |
| assert(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; |
| const char *url; |
| int flags = GIT_TRANSPORTFLAGS_NONE; |
| |
| assert(remote); |
| |
| t = remote->transport; |
| |
| url = git_remote__urlfordirection(remote, direction); |
| if (url == NULL ) |
| return -1; |
| |
| /* A transport could have been supplied in advance with |
| * git_remote_set_transport */ |
| if (!t && git_transport_new(&t, remote, url) < 0) |
| return -1; |
| |
| if (t->set_callbacks && |
| t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.data) < 0) |
| goto on_error; |
| |
| if (!remote->check_cert) |
| flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT; |
| |
| if (t->connect(t, url, remote->cred_acquire_cb, direction, flags) < 0) |
| goto on_error; |
| |
| remote->transport = t; |
| |
| return 0; |
| |
| on_error: |
| t->free(t); |
| |
| if (t == remote->transport) |
| remote->transport = NULL; |
| |
| return -1; |
| } |
| |
| int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) |
| { |
| assert(remote); |
| |
| if (!git_remote_connected(remote)) { |
| giterr_set(GITERR_NET, "The remote is not connected"); |
| return -1; |
| } |
| |
| return remote->transport->ls(remote->transport, list_cb, payload); |
| } |
| |
| int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url) |
| { |
| git_config *cfg; |
| const char *val; |
| |
| assert(remote); |
| |
| if (!proxy_url) |
| return -1; |
| |
| *proxy_url = NULL; |
| |
| if (git_repository_config__weakptr(&cfg, remote->repo) < 0) |
| return -1; |
| |
| /* Go through the possible sources for proxy configuration, from most specific |
| * to least specific. */ |
| |
| /* remote.<name>.proxy config setting */ |
| if (remote->name && 0 != *(remote->name)) { |
| git_buf buf = GIT_BUF_INIT; |
| |
| if (git_buf_printf(&buf, "remote.%s.proxy", remote->name) < 0) |
| return -1; |
| |
| if (!git_config_get_string(&val, cfg, git_buf_cstr(&buf)) && |
| val && ('\0' != *val)) { |
| git_buf_free(&buf); |
| |
| *proxy_url = git__strdup(val); |
| GITERR_CHECK_ALLOC(*proxy_url); |
| return 0; |
| } |
| |
| git_buf_free(&buf); |
| } |
| |
| /* http.proxy config setting */ |
| if (!git_config_get_string(&val, cfg, "http.proxy") && |
| val && ('\0' != *val)) { |
| *proxy_url = git__strdup(val); |
| GITERR_CHECK_ALLOC(*proxy_url); |
| return 0; |
| } |
| |
| /* HTTP_PROXY / HTTPS_PROXY environment variables */ |
| val = use_ssl ? getenv("HTTPS_PROXY") : getenv("HTTP_PROXY"); |
| |
| if (val && ('\0' != *val)) { |
| *proxy_url = git__strdup(val); |
| GITERR_CHECK_ALLOC(*proxy_url); |
| return 0; |
| } |
| |
| return 0; |
| } |
| |
| int git_remote_download( |
| git_remote *remote, |
| git_transfer_progress_callback progress_cb, |
| void *progress_payload) |
| { |
| int error; |
| |
| assert(remote); |
| |
| if ((error = git_fetch_negotiate(remote)) < 0) |
| return error; |
| |
| return git_fetch_download_pack(remote, progress_cb, progress_payload); |
| } |
| |
| static int update_tips_callback(git_remote_head *head, void *payload) |
| { |
| git_vector *refs = (git_vector *)payload; |
| git_vector_insert(refs, head); |
| |
| return 0; |
| } |
| |
| int git_remote_update_tips(git_remote *remote) |
| { |
| int error = 0, autotag; |
| unsigned int i = 0; |
| git_buf refname = GIT_BUF_INIT; |
| git_oid old; |
| git_odb *odb; |
| git_remote_head *head; |
| git_reference *ref; |
| struct git_refspec *spec; |
| git_refspec tagspec; |
| git_vector refs; |
| |
| assert(remote); |
| |
| spec = &remote->fetch; |
| |
| if (git_repository_odb__weakptr(&odb, remote->repo) < 0) |
| return -1; |
| |
| if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) |
| return -1; |
| |
| /* Make a copy of the transport's refs */ |
| if (git_vector_init(&refs, 16, NULL) < 0) |
| return -1; |
| |
| if (git_remote_ls(remote, update_tips_callback, &refs) < 0) |
| goto on_error; |
| |
| /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */ |
| if (refs.length > 0) { |
| head = (git_remote_head *)refs.contents[0]; |
| |
| if (!strcmp(head->name, GIT_HEAD_FILE)) { |
| if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0) |
| goto on_error; |
| |
| i = 1; |
| git_reference_free(ref); |
| } |
| } |
| |
| for (; i < refs.length; ++i) { |
| head = (git_remote_head *)refs.contents[i]; |
| autotag = 0; |
| |
| /* Ignore malformed ref names (which also saves us from tag^{} */ |
| if (!git_reference_is_valid_name(head->name)) |
| continue; |
| |
| if (git_refspec_src_matches(spec, head->name)) { |
| if (git_refspec_transform_r(&refname, spec, head->name) < 0) |
| goto on_error; |
| } else if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_NONE) { |
| |
| if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_ALL) |
| autotag = 1; |
| |
| if (!git_refspec_src_matches(&tagspec, head->name)) |
| continue; |
| |
| git_buf_clear(&refname); |
| if (git_buf_puts(&refname, head->name) < 0) |
| goto on_error; |
| } else { |
| continue; |
| } |
| |
| if (autotag && !git_odb_exists(odb, &head->oid)) |
| continue; |
| |
| error = git_reference_name_to_oid(&old, remote->repo, refname.ptr); |
| if (error < 0 && error != GIT_ENOTFOUND) |
| goto on_error; |
| |
| if (error == GIT_ENOTFOUND) |
| memset(&old, 0, GIT_OID_RAWSZ); |
| |
| if (!git_oid_cmp(&old, &head->oid)) |
| continue; |
| |
| /* In autotag mode, don't overwrite any locally-existing tags */ |
| error = git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, !autotag); |
| if (error < 0 && error != GIT_EEXISTS) |
| goto on_error; |
| |
| git_reference_free(ref); |
| |
| if (remote->callbacks.update_tips != NULL) { |
| if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.data) < 0) |
| goto on_error; |
| } |
| } |
| |
| git_vector_free(&refs); |
| git_refspec__free(&tagspec); |
| git_buf_free(&refname); |
| return 0; |
| |
| on_error: |
| git_vector_free(&refs); |
| git_refspec__free(&tagspec); |
| git_buf_free(&refname); |
| return -1; |
| |
| } |
| |
| int git_remote_connected(git_remote *remote) |
| { |
| assert(remote); |
| |
| if (!remote->transport || !remote->transport->is_connected) |
| return 0; |
| |
| /* Ask the transport if it's connected. */ |
| return remote->transport->is_connected(remote->transport); |
| } |
| |
| void git_remote_stop(git_remote *remote) |
| { |
| assert(remote); |
| |
| if (remote->transport && remote->transport->cancel) |
| remote->transport->cancel(remote->transport); |
| } |
| |
| void git_remote_disconnect(git_remote *remote) |
| { |
| assert(remote); |
| |
| if (git_remote_connected(remote)) |
| remote->transport->close(remote->transport); |
| } |
| |
| void git_remote_free(git_remote *remote) |
| { |
| if (remote == NULL) |
| return; |
| |
| if (remote->transport != NULL) { |
| git_remote_disconnect(remote); |
| |
| remote->transport->free(remote->transport); |
| remote->transport = NULL; |
| } |
| |
| git_vector_free(&remote->refs); |
| |
| git_refspec__free(&remote->fetch); |
| git_refspec__free(&remote->push); |
| git__free(remote->url); |
| git__free(remote->pushurl); |
| git__free(remote->name); |
| git__free(remote); |
| } |
| |
| struct cb_data { |
| git_vector *list; |
| regex_t *preg; |
| }; |
| |
| static int remote_list_cb(const git_config_entry *entry, void *data_) |
| { |
| struct cb_data *data = (struct cb_data *)data_; |
| size_t nmatch = 2; |
| regmatch_t pmatch[2]; |
| const char *name = entry->name; |
| |
| if (!regexec(data->preg, name, nmatch, pmatch, 0)) { |
| char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so); |
| GITERR_CHECK_ALLOC(remote_name); |
| |
| if (git_vector_insert(data->list, remote_name) < 0) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int git_remote_list(git_strarray *remotes_list, git_repository *repo) |
| { |
| git_config *cfg; |
| git_vector list; |
| regex_t preg; |
| struct cb_data data; |
| int error; |
| |
| if (git_repository_config__weakptr(&cfg, repo) < 0) |
| return -1; |
| |
| if (git_vector_init(&list, 4, NULL) < 0) |
| return -1; |
| |
| if (regcomp(&preg, "^remote\\.(.*)\\.url$", REG_EXTENDED) < 0) { |
| giterr_set(GITERR_OS, "Remote catch regex failed to compile"); |
| return -1; |
| } |
| |
| data.list = &list; |
| data.preg = &preg; |
| error = git_config_foreach(cfg, remote_list_cb, &data); |
| regfree(&preg); |
| if (error < 0) { |
| size_t i; |
| char *elem; |
| git_vector_foreach(&list, i, elem) { |
| git__free(elem); |
| } |
| |
| git_vector_free(&list); |
| |
| /* cb error is converted to GIT_EUSER by git_config_foreach */ |
| if (error == GIT_EUSER) |
| error = -1; |
| |
| return error; |
| } |
| |
| remotes_list->strings = (char **)list.contents; |
| remotes_list->count = list.length; |
| |
| return 0; |
| } |
| |
| int git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url) |
| { |
| git_buf buf = GIT_BUF_INIT; |
| |
| if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0) |
| return -1; |
| |
| if (git_remote_new(out, repo, name, url, git_buf_cstr(&buf)) < 0) |
| goto on_error; |
| |
| git_buf_free(&buf); |
| |
| if (git_remote_save(*out) < 0) |
| goto on_error; |
| |
| return 0; |
| |
| on_error: |
| git_buf_free(&buf); |
| git_remote_free(*out); |
| return -1; |
| } |
| |
| void git_remote_check_cert(git_remote *remote, int check) |
| { |
| assert(remote); |
| |
| remote->check_cert = check; |
| } |
| |
| void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks) |
| { |
| assert(remote && callbacks); |
| |
| memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks)); |
| |
| if (remote->transport && remote->transport->set_callbacks) |
| remote->transport->set_callbacks(remote->transport, |
| remote->callbacks.progress, |
| NULL, |
| remote->callbacks.data); |
| } |
| |
| void git_remote_set_cred_acquire_cb( |
| git_remote *remote, |
| git_cred_acquire_cb cred_acquire_cb) |
| { |
| assert(remote); |
| |
| remote->cred_acquire_cb = cred_acquire_cb; |
| } |
| |
| int git_remote_set_transport(git_remote *remote, git_transport *transport) |
| { |
| assert(remote && transport); |
| |
| if (remote->transport) { |
| giterr_set(GITERR_NET, "A transport is already bound to this remote"); |
| return -1; |
| } |
| |
| remote->transport = transport; |
| return 0; |
| } |
| |
| const git_transfer_progress* git_remote_stats(git_remote *remote) |
| { |
| assert(remote); |
| return &remote->stats; |
| } |
| |
| int git_remote_autotag(git_remote *remote) |
| { |
| return remote->download_tags; |
| } |
| |
| void git_remote_set_autotag(git_remote *remote, int value) |
| { |
| remote->download_tags = value; |
| } |
| |
| static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) |
| { |
| int error; |
| git_remote *remote; |
| |
| error = git_remote_load(&remote, repo, name); |
| |
| if (error == GIT_ENOTFOUND) |
| return 0; |
| |
| if (error < 0) |
| return error; |
| |
| git_remote_free(remote); |
| |
| giterr_set( |
| GITERR_CONFIG, |
| "Remote '%s' already exists.", name); |
| |
| return GIT_EEXISTS; |
| } |
| |
| static int rename_remote_config_section( |
| git_repository *repo, |
| const char *old_name, |
| const char *new_name) |
| { |
| git_buf old_section_name = GIT_BUF_INIT, |
| new_section_name = GIT_BUF_INIT; |
| int error = -1; |
| |
| if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0) |
| goto cleanup; |
| |
| if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0) |
| goto cleanup; |
| |
| error = git_config_rename_section( |
| repo, |
| git_buf_cstr(&old_section_name), |
| git_buf_cstr(&new_section_name)); |
| |
| cleanup: |
| git_buf_free(&old_section_name); |
| git_buf_free(&new_section_name); |
| |
| return error; |
| } |
| |
| struct update_data |
| { |
| git_config *config; |
| const char *old_remote_name; |
| const char *new_remote_name; |
| }; |
| |
| static int update_config_entries_cb( |
| const git_config_entry *entry, |
| void *payload) |
| { |
| struct update_data *data = (struct update_data *)payload; |
| |
| if (strcmp(entry->value, data->old_remote_name)) |
| return 0; |
| |
| return git_config_set_string( |
| data->config, |
| entry->name, |
| data->new_remote_name); |
| } |
| |
| static int update_branch_remote_config_entry( |
| git_repository *repo, |
| const char *old_name, |
| const char *new_name) |
| { |
| git_config *config; |
| struct update_data data; |
| |
| if (git_repository_config__weakptr(&config, repo) < 0) |
| return -1; |
| |
| data.config = config; |
| data.old_remote_name = old_name; |
| data.new_remote_name = new_name; |
| |
| return git_config_foreach_match( |
| config, |
| "branch\\..+\\.remote", |
| update_config_entries_cb, &data); |
| } |
| |
| static int rename_cb(const char *ref, void *data) |
| { |
| if (git__prefixcmp(ref, GIT_REFS_REMOTES_DIR)) |
| return 0; |
| |
| return git_vector_insert((git_vector *)data, git__strdup(ref)); |
| } |
| |
| static int rename_one_remote_reference( |
| git_repository *repo, |
| const char *reference_name, |
| const char *old_remote_name, |
| const char *new_remote_name) |
| { |
| int error = -1; |
| git_buf new_name = GIT_BUF_INIT; |
| git_reference *reference = NULL; |
| |
| if (git_buf_printf( |
| &new_name, |
| GIT_REFS_REMOTES_DIR "%s%s", |
| new_remote_name, |
| reference_name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0) |
| return -1; |
| |
| if (git_reference_lookup(&reference, repo, reference_name) < 0) |
| goto cleanup; |
| |
| error = git_reference_rename(reference, git_buf_cstr(&new_name), 0); |
| |
| cleanup: |
| git_reference_free(reference); |
| git_buf_free(&new_name); |
| return error; |
| } |
| |
| static int rename_remote_references( |
| git_repository *repo, |
| const char *old_name, |
| const char *new_name) |
| { |
| git_vector refnames; |
| int error = -1; |
| unsigned int i; |
| char *name; |
| |
| if (git_vector_init(&refnames, 8, NULL) < 0) |
| goto cleanup; |
| |
| if (git_reference_foreach( |
| repo, |
| GIT_REF_LISTALL, |
| rename_cb, |
| &refnames) < 0) |
| goto cleanup; |
| |
| git_vector_foreach(&refnames, i, name) { |
| if ((error = rename_one_remote_reference(repo, name, old_name, new_name)) < 0) |
| goto cleanup; |
| } |
| |
| error = 0; |
| cleanup: |
| git_vector_foreach(&refnames, i, name) { |
| git__free(name); |
| } |
| |
| git_vector_free(&refnames); |
| return error; |
| } |
| |
| static int rename_fetch_refspecs( |
| git_remote *remote, |
| const char *new_name, |
| int (*callback)(const char *problematic_refspec, void *payload), |
| void *payload) |
| { |
| git_config *config; |
| const git_refspec *fetch_refspec; |
| git_buf dst_prefix = GIT_BUF_INIT, serialized = GIT_BUF_INIT; |
| const char* pos; |
| int error = -1; |
| |
| fetch_refspec = git_remote_fetchspec(remote); |
| |
| /* Is there a refspec to deal with? */ |
| if (fetch_refspec->src == NULL && |
| fetch_refspec->dst == NULL) |
| return 0; |
| |
| if (git_refspec__serialize(&serialized, fetch_refspec) < 0) |
| goto cleanup; |
| |
| /* Is it an in-memory remote? */ |
| if (remote->name == '\0') { |
| error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0; |
| goto cleanup; |
| } |
| |
| if (git_buf_printf(&dst_prefix, ":refs/remotes/%s/", remote->name) < 0) |
| goto cleanup; |
| |
| pos = strstr(git_buf_cstr(&serialized), git_buf_cstr(&dst_prefix)); |
| |
| /* Does the dst part of the refspec follow the extected standard format? */ |
| if (!pos) { |
| error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0; |
| goto cleanup; |
| } |
| |
| if (git_buf_splice( |
| &serialized, |
| pos - git_buf_cstr(&serialized) + strlen(":refs/remotes/"), |
| strlen(remote->name), new_name, |
| strlen(new_name)) < 0) |
| goto cleanup; |
| |
| git_refspec__free(&remote->fetch); |
| |
| if (git_refspec__parse(&remote->fetch, git_buf_cstr(&serialized), true) < 0) |
| goto cleanup; |
| |
| if (git_repository_config__weakptr(&config, remote->repo) < 0) |
| goto cleanup; |
| |
| error = update_config_refspec(config, new_name, &remote->fetch, GIT_DIR_FETCH); |
| |
| cleanup: |
| git_buf_free(&serialized); |
| git_buf_free(&dst_prefix); |
| return error; |
| } |
| |
| int git_remote_rename( |
| git_remote *remote, |
| const char *new_name, |
| int (*callback)(const char *problematic_refspec, void *payload), |
| void *payload) |
| { |
| int error; |
| |
| assert(remote && new_name); |
| |
| if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0) |
| return error; |
| |
| if ((error = ensure_remote_name_is_valid(new_name)) < 0) |
| return error; |
| |
| if (!remote->name) { |
| if ((error = rename_fetch_refspecs( |
| remote, |
| new_name, |
| callback, |
| payload)) < 0) |
| return error; |
| |
| remote->name = git__strdup(new_name); |
| |
| return git_remote_save(remote); |
| } |
| |
| if ((error = rename_remote_config_section( |
| remote->repo, |
| remote->name, |
| new_name)) < 0) |
| return error; |
| |
| if ((error = update_branch_remote_config_entry( |
| remote->repo, |
| remote->name, |
| new_name)) < 0) |
| return error; |
| |
| if ((error = rename_remote_references( |
| remote->repo, |
| remote->name, |
| new_name)) < 0) |
| return error; |
| |
| if ((error = rename_fetch_refspecs( |
| remote, |
| new_name, |
| callback, |
| payload)) < 0) |
| return error; |
| |
| git__free(remote->name); |
| remote->name = git__strdup(new_name); |
| |
| return 0; |
| } |