blob: 4f1801ed0622d8cc672a6cf3c7dff91d5344a986 [file] [log] [blame]
/*
* 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;
}