summaryrefslogtreecommitdiff
path: root/src/remote.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/remote.c')
-rw-r--r--src/remote.c682
1 files changed, 601 insertions, 81 deletions
diff --git a/src/remote.c b/src/remote.c
index e05ea059f..4a4d160eb 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -14,7 +14,8 @@
#include "remote.h"
#include "fetch.h"
#include "refs.h"
-#include "pkt.h"
+#include "refspec.h"
+#include "fetchhead.h"
#include <regex.h>
@@ -69,6 +70,7 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con
memset(remote, 0x0, sizeof(git_remote));
remote->repo = repo;
remote->check_cert = 1;
+ remote->update_fetchhead = 1;
if (git_vector_init(&remote->refs, 32, NULL) < 0)
return -1;
@@ -117,6 +119,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
memset(remote, 0x0, sizeof(git_remote));
remote->check_cert = 1;
+ remote->update_fetchhead = 1;
remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name);
@@ -132,6 +135,12 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0)
goto cleanup;
+
+ if (strlen(val) == 0) {
+ giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name);
+ error = -1;
+ goto cleanup;
+ }
remote->repo = repo;
remote->url = git__strdup(val);
@@ -144,8 +153,10 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
}
error = git_config_get_string(&val, config, git_buf_cstr(&buf));
- if (error == GIT_ENOTFOUND)
+ if (error == GIT_ENOTFOUND) {
+ val = NULL;
error = 0;
+ }
if (error < 0) {
error = -1;
@@ -201,12 +212,75 @@ cleanup:
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, value = GIT_BUF_INIT;
+ 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;
@@ -232,6 +306,7 @@ int git_remote_save(const git_remote *remote)
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);
@@ -239,33 +314,19 @@ int git_remote_save(const git_remote *remote)
}
}
- if (remote->fetch.src != NULL && remote->fetch.dst != NULL) {
- git_buf_clear(&buf);
- git_buf_clear(&value);
- git_buf_printf(&buf, "remote.%s.fetch", remote->name);
- if (remote->fetch.force)
- git_buf_putc(&value, '+');
- git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
- return -1;
-
- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)
+ if (update_config_refspec(
+ config,
+ remote->name,
+ &remote->fetch,
+ GIT_DIR_FETCH) < 0)
goto on_error;
- }
-
- if (remote->push.src != NULL && remote->push.dst != NULL) {
- git_buf_clear(&buf);
- git_buf_clear(&value);
- git_buf_printf(&buf, "remote.%s.push", remote->name);
- if (remote->push.force)
- git_buf_putc(&value, '+');
- git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
- return -1;
- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)
+ 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
@@ -300,13 +361,11 @@ int git_remote_save(const git_remote *remote)
}
git_buf_free(&buf);
- git_buf_free(&value);
return 0;
on_error:
git_buf_free(&buf);
- git_buf_free(&value);
return -1;
}
@@ -417,23 +476,30 @@ 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;
- if (git_transport_new(&t, url) < 0)
+ /* A transport could have been supplied in advance with
+ * git_remote_set_transport */
+ if (!t && git_transport_new(&t, url) < 0)
return -1;
- t->progress_cb = remote->callbacks.progress;
- t->cb_data = remote->callbacks.data;
+ 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;
- t->check_cert = remote->check_cert;
- if (t->connect(t, direction) < 0) {
+ if (t->connect(t, url, remote->cred_acquire_cb, direction, flags) < 0)
goto on_error;
- }
remote->transport = t;
@@ -446,42 +512,151 @@ on_error:
int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{
- git_vector *refs = &remote->transport->refs;
- unsigned int i;
- git_pkt *p = NULL;
-
assert(remote);
- if (!remote->transport || !remote->transport->connected) {
+ if (!remote->transport) {
giterr_set(GITERR_NET, "The remote is not connected");
return -1;
}
- git_vector_foreach(refs, i, p) {
- git_pkt_ref *pkt = NULL;
+ return remote->transport->ls(remote->transport, list_cb, payload);
+}
- if (p->type != GIT_PKT_REF)
- continue;
+int git_remote_download(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ int error;
- pkt = (git_pkt_ref *)p;
+ assert(remote);
- if (list_cb(&pkt->head, payload))
- return GIT_EUSER;
+ 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;
+}
+
+static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src)
+{
+ unsigned int i;
+ git_remote_head *remote_ref;
+
+ assert(update_heads && fetchspec_src);
+
+ *out = NULL;
+
+ git_vector_foreach(update_heads, i, remote_ref) {
+ if (strcmp(remote_ref->name, fetchspec_src) == 0) {
+ *out = remote_ref;
+ break;
+ }
}
return 0;
}
-int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats)
+static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref)
{
- int error;
+ git_reference *resolved_ref = NULL;
+ git_reference *tracking_ref = NULL;
+ git_buf remote_name = GIT_BUF_INIT;
+ int error = 0;
- assert(remote && bytes && stats);
+ assert(out && remote && ref);
- if ((error = git_fetch_negotiate(remote)) < 0)
- return error;
+ *out = NULL;
+
+ if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 ||
+ (!git_reference_is_branch(resolved_ref)) ||
+ (error = git_branch_tracking(&tracking_ref, resolved_ref)) < 0 ||
+ (error = git_refspec_transform_l(&remote_name, &remote->fetch, git_reference_name(tracking_ref))) < 0) {
+ /* Not an error if HEAD is orphaned or no tracking branch */
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+
+ goto cleanup;
+ }
+
+ error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name));
+
+cleanup:
+ git_reference_free(tracking_ref);
+ git_reference_free(resolved_ref);
+ git_buf_free(&remote_name);
+ return error;
+}
+
+static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_heads)
+{
+ struct git_refspec *spec;
+ git_reference *head_ref = NULL;
+ git_fetchhead_ref *fetchhead_ref;
+ git_remote_head *remote_ref, *merge_remote_ref;
+ git_vector fetchhead_refs;
+ bool include_all_fetchheads;
+ unsigned int i = 0;
+ int error = 0;
+
+ assert(remote);
+
+ spec = &remote->fetch;
+
+ if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0)
+ return -1;
+
+ /* Iff refspec is * (but not subdir slash star), include tags */
+ include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0);
+
+ /* Determine what to merge: if refspec was a wildcard, just use HEAD */
+ if (git_refspec_is_wildcard(spec)) {
+ if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 ||
+ (error = remote_head_for_ref(&merge_remote_ref, remote, update_heads, head_ref)) < 0)
+ goto cleanup;
+ } else {
+ /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */
+ if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0)
+ goto cleanup;
+ }
+
+ /* Create the FETCH_HEAD file */
+ git_vector_foreach(update_heads, i, remote_ref) {
+ int merge_this_fetchhead = (merge_remote_ref == remote_ref);
+
+ if (!include_all_fetchheads &&
+ !git_refspec_src_matches(spec, remote_ref->name) &&
+ !merge_this_fetchhead)
+ continue;
+
+ if (git_fetchhead_ref_create(&fetchhead_ref,
+ &remote_ref->oid,
+ merge_this_fetchhead,
+ remote_ref->name,
+ git_remote_url(remote)) < 0)
+ goto cleanup;
+
+ if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0)
+ goto cleanup;
+ }
+
+ git_fetchhead_write(remote->repo, &fetchhead_refs);
+
+cleanup:
+ for (i = 0; i < fetchhead_refs.length; ++i)
+ git_fetchhead_ref_free(fetchhead_refs.contents[i]);
+
+ git_vector_free(&fetchhead_refs);
+ git_reference_free(head_ref);
- return git_fetch_download_pack(remote, bytes, stats);
+ return error;
}
int git_remote_update_tips(git_remote *remote)
@@ -490,48 +665,48 @@ int git_remote_update_tips(git_remote *remote)
unsigned int i = 0;
git_buf refname = GIT_BUF_INIT;
git_oid old;
- git_pkt *pkt;
git_odb *odb;
- git_vector *refs;
git_remote_head *head;
git_reference *ref;
struct git_refspec *spec;
git_refspec tagspec;
+ git_vector refs, update_heads;
assert(remote);
- refs = &remote->transport->refs;
spec = &remote->fetch;
-
- if (refs->length == 0)
- return 0;
-
+
if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
return -1;
if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
return -1;
- /* HEAD is only allowed to be the first in the list */
- pkt = refs->contents[0];
- head = &((git_pkt_ref *)pkt)->head;
- if (!strcmp(head->name, GIT_HEAD_FILE)) {
- if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
- return -1;
+ /* Make a copy of the transport's refs */
+ if (git_vector_init(&refs, 16, NULL) < 0 ||
+ git_vector_init(&update_heads, 16, NULL) < 0)
+ return -1;
- i = 1;
- git_reference_free(ref);
+ if (remote->transport->ls(remote->transport, 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) {
- git_pkt *pkt = refs->contents[i];
+ for (; i < refs.length; ++i) {
+ head = (git_remote_head *)refs.contents[i];
autotag = 0;
- if (pkt->type == GIT_PKT_REF)
- head = &((git_pkt_ref *)pkt)->head;
- else
- continue;
-
/* Ignore malformed ref names (which also saves us from tag^{} */
if (!git_reference_is_valid_name(head->name))
continue;
@@ -557,6 +732,9 @@ int git_remote_update_tips(git_remote *remote)
if (autotag && !git_odb_exists(odb, &head->oid))
continue;
+ if (git_vector_insert(&update_heads, head) < 0)
+ goto on_error;
+
error = git_reference_name_to_oid(&old, remote->repo, refname.ptr);
if (error < 0 && error != GIT_ENOTFOUND)
goto on_error;
@@ -580,11 +758,19 @@ int git_remote_update_tips(git_remote *remote)
}
}
+ if (git_remote_update_fetchhead(remote) &&
+ (error = git_remote_write_fetchhead(remote, &update_heads)) < 0)
+ goto on_error;
+
+ git_vector_free(&refs);
+ git_vector_free(&update_heads);
git_refspec__free(&tagspec);
git_buf_free(&refname);
return 0;
on_error:
+ git_vector_free(&refs);
+ git_vector_free(&update_heads);
git_refspec__free(&tagspec);
git_buf_free(&refname);
return -1;
@@ -593,21 +779,31 @@ on_error:
int git_remote_connected(git_remote *remote)
{
+ int connected;
+
assert(remote);
- return remote->transport == NULL ? 0 : remote->transport->connected;
+
+ if (!remote->transport || !remote->transport->is_connected)
+ return 0;
+
+ /* Ask the transport if it's connected. */
+ remote->transport->is_connected(remote->transport, &connected);
+
+ return connected;
}
void git_remote_stop(git_remote *remote)
{
- git_atomic_set(&remote->transport->cancel, 1);
+ if (remote->transport->cancel)
+ remote->transport->cancel(remote->transport);
}
void git_remote_disconnect(git_remote *remote)
{
assert(remote);
- if (remote->transport != NULL && remote->transport->connected)
- remote->transport->close(remote->transport);
+ if (git_remote_connected(remote))
+ remote->transport->close(remote->transport);
}
void git_remote_free(git_remote *remote)
@@ -736,10 +932,39 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback
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) {
- remote->transport->progress_cb = remote->callbacks.progress;
- remote->transport->cb_data = remote->callbacks.data;
+ 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)
@@ -751,3 +976,298 @@ 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;
+}
+
+int git_remote_update_fetchhead(git_remote *remote)
+{
+ return remote->update_fetchhead;
+}
+
+void git_remote_set_update_fetchhead(git_remote *remote, int value)
+{
+ remote->update_fetchhead = value;
+}