Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/remote.c')
-rw-r--r--src/remote.c1107
1 files changed, 994 insertions, 113 deletions
diff --git a/src/remote.c b/src/remote.c
index 9740344f8..56853834b 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -1,74 +1,94 @@
/*
- * Copyright (C) 2009-2012 the libgit2 contributors
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
* 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/remote.h"
#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 "refspec.h"
+#include "fetchhead.h"
#include <regex.h>
-static int refspec_parse(git_refspec *refspec, const char *str)
+static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var, bool is_fetch)
{
- char *delim;
+ int error;
+ const char *val;
- memset(refspec, 0x0, sizeof(git_refspec));
+ if ((error = git_config_get_string(&val, cfg, var)) < 0)
+ return error;
- if (*str == '+') {
- refspec->force = 1;
- str++;
- }
+ return git_refspec__parse(refspec, val, is_fetch);
+}
- delim = strchr(str, ':');
- if (delim == NULL) {
- giterr_set(GITERR_NET, "Invalid refspec, missing ':'");
+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;
- }
- refspec->src = git__strndup(str, delim - str);
- GITERR_CHECK_ALLOC(refspec->src);
+ 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;
- refspec->dst = git__strdup(delim + 1);
- GITERR_CHECK_ALLOC(refspec->dst);
+ if (error == GIT_ENOTFOUND)
+ error = 0;
- return 0;
+ return error;
}
-static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var)
+static int ensure_remote_name_is_valid(const char *name)
{
- int error;
- const char *val;
+ int error = 0;
- if ((error = git_config_get_string(&val, cfg, var)) < 0)
- return error;
+ if (!git_remote_is_valid_name(name)) {
+ giterr_set(
+ GITERR_CONFIG,
+ "'%s' is not a valid remote name.", name);
+ error = GIT_EINVALIDSPEC;
+ }
- return refspec_parse(refspec, val);
+ return error;
}
-int git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
+static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
{
git_remote *remote;
+ git_buf fetchbuf = GIT_BUF_INIT;
+ int error = -1;
/* name is optional */
assert(out && repo && url);
- remote = git__malloc(sizeof(git_remote));
+ remote = git__calloc(1, sizeof(git_remote));
GITERR_CHECK_ALLOC(remote);
- 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;
+ goto on_error;
remote->url = git__strdup(url);
GITERR_CHECK_ALLOC(remote->url);
@@ -79,18 +99,93 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con
}
if (fetch != NULL) {
- if (refspec_parse(&remote->fetch, fetch) < 0)
+ 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;
+ git_buf_free(&fetchbuf);
+ return 0;
+
+on_error:
+ git_remote_free(remote);
+ git_buf_free(&fetchbuf);
+ return error;
+}
+
+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;
+}
+
+
+int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_remote *remote = NULL;
+ int error;
+
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)
+ return error;
+
+ if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)
+ return -1;
+
+ if (create_internal(&remote, repo, name, url, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_free(&buf);
+
+ if (git_remote_save(remote) < 0)
+ goto on_error;
+
*out = remote;
+
return 0;
on_error:
+ git_buf_free(&buf);
git_remote_free(remote);
return -1;
}
+int git_remote_create_inmemory(git_remote **out, git_repository *repo, const char *fetch, const char *url)
+{
+ int error;
+ git_remote *remote;
+
+ if ((error = create_internal(&remote, repo, NULL, url, fetch)) < 0)
+ return error;
+
+ *out = remote;
+ return 0;
+}
+
int git_remote_load(git_remote **out, git_repository *repo, const char *name)
{
git_remote *remote;
@@ -101,6 +196,9 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
assert(out && repo && name);
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
if (git_repository_config__weakptr(&config, repo) < 0)
return -1;
@@ -108,6 +206,8 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
GITERR_CHECK_ALLOC(remote);
memset(remote, 0x0, sizeof(git_remote));
+ remote->check_cert = 1;
+ remote->update_fetchhead = 1;
remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name);
@@ -123,18 +223,46 @@ 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);
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) {
+ val = NULL;
+ 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));
+ error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf), true);
if (error == GIT_ENOTFOUND)
error = 0;
@@ -149,7 +277,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
goto cleanup;
}
- error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf));
+ error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf), false);
if (error == GIT_ENOTFOUND)
error = 0;
@@ -158,6 +286,9 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
goto cleanup;
}
+ if (download_tags_value(remote, config) < 0)
+ goto cleanup;
+
*out = remote;
cleanup:
@@ -169,15 +300,61 @@ cleanup:
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_DIRECTION_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;
- git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ const char *tagopt = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ assert(remote);
+
+ if (!remote->name) {
+ giterr_set(GITERR_INVALID, "Can't save an in-memory remote.");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = ensure_remote_name_is_valid(remote->name)) < 0)
+ return error;
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) {
@@ -185,71 +362,142 @@ int git_remote_save(const git_remote *remote)
return -1;
}
- 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);
- git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
+ 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_entry(config, git_buf_cstr(&buf));
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ giterr_clear();
+ }
+ if (error < 0) {
+ git_buf_free(&buf);
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_DIRECTION_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);
- git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
- return -1;
+ if (update_config_refspec(
+ config,
+ remote->name,
+ &remote->push,
+ GIT_DIRECTION_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 |
+ * +---------+-------------+
+ */
- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)
+ 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_entry(config, git_buf_cstr(&buf)) < 0)
goto on_error;
}
git_buf_free(&buf);
- git_buf_free(&value);
return 0;
on_error:
git_buf_free(&buf);
- git_buf_free(&value);
return -1;
}
-const char *git_remote_name(git_remote *remote)
+const char *git_remote_name(const git_remote *remote)
{
assert(remote);
return remote->name;
}
-const char *git_remote_url(git_remote *remote)
+const char *git_remote_url(const 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(const 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 (refspec_parse(&refspec, spec) < 0)
+ if (git_refspec__parse(&refspec, spec, true) < 0)
return -1;
- git__free(remote->fetch.src);
- git__free(remote->fetch.dst);
- remote->fetch.src = refspec.src;
- remote->fetch.dst = refspec.dst;
+ git_refspec__free(&remote->fetch);
+ memcpy(&remote->fetch, &refspec, sizeof(git_refspec));
return 0;
}
-const git_refspec *git_remote_fetchspec(git_remote *remote)
+const git_refspec *git_remote_fetchspec(const git_remote *remote)
{
assert(remote);
return &remote->fetch;
@@ -261,35 +509,65 @@ int git_remote_set_pushspec(git_remote *remote, const char *spec)
assert(remote && spec);
- if (refspec_parse(&refspec, spec) < 0)
+ if (git_refspec__parse(&refspec, spec, false) < 0)
return -1;
- git__free(remote->push.src);
- git__free(remote->push.dst);
+ 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)
+const git_refspec *git_remote_pushspec(const git_remote *remote)
{
assert(remote);
return &remote->push;
}
-int git_remote_connect(git_remote *remote, int direction)
+const char* git_remote__urlfordirection(git_remote *remote, int direction)
+{
+ assert(remote);
+
+ if (direction == GIT_DIRECTION_FETCH) {
+ return remote->url;
+ }
+
+ if (direction == GIT_DIRECTION_PUSH) {
+ return remote->pushurl ? remote->pushurl : remote->url;
+ }
+
+ return NULL;
+}
+
+int git_remote_connect(git_remote *remote, git_direction direction)
{
git_transport *t;
+ const char *url;
+ int flags = GIT_TRANSPORTFLAGS_NONE;
assert(remote);
- if (git_transport_new(&t, remote->url) < 0)
+ t = remote->transport;
+
+ url = git_remote__urlfordirection(remote, direction);
+ if (url == NULL )
return -1;
- if (t->connect(t, direction) < 0) {
+ /* 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.payload) < 0)
+ goto on_error;
+
+ if (!remote->check_cert)
+ flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
+
+ if (t->connect(t, url, remote->cred_acquire_cb, remote->cred_acquire_payload, direction, flags) < 0)
goto on_error;
- }
remote->transport = t;
@@ -297,6 +575,10 @@ int git_remote_connect(git_remote *remote, int direction)
on_error:
t->free(t);
+
+ if (t == remote->transport)
+ remote->transport = NULL;
+
return -1;
}
@@ -304,59 +586,283 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{
assert(remote);
- if (!remote->transport || !remote->transport->connected) {
- giterr_set(GITERR_NET, "The remote is not connected");
+ 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 || !remote->repo)
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);
}
- return remote->transport->ls(remote->transport, list_cb, payload);
+ /* 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_off_t *bytes, git_indexer_stats *stats)
+int git_remote_download(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
{
int error;
- assert(remote && bytes && stats);
+ assert(remote);
if ((error = git_fetch_negotiate(remote)) < 0)
return error;
- return git_fetch_download_pack(remote, bytes, stats);
+ 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;
+
+ return git_vector_insert(refs, head);
+}
+
+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_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b))
+static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref)
{
+ git_reference *resolved_ref = NULL;
+ git_reference *tracking_ref = NULL;
+ git_buf remote_name = GIT_BUF_INIT;
int error = 0;
+
+ assert(out && remote && ref);
+
+ *out = NULL;
+
+ if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 ||
+ (!git_reference_is_branch(resolved_ref)) ||
+ (error = git_branch_upstream(&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);
+
+ /* no heads, nothing to do */
+ if (update_heads->length == 0)
+ return 0;
+
+ 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 error;
+}
+
+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_vector *refs = &remote->refs;
+ git_odb *odb;
git_remote_head *head;
git_reference *ref;
- struct git_refspec *spec = &remote->fetch;
+ struct git_refspec *spec;
+ git_refspec tagspec;
+ git_vector refs, update_heads;
assert(remote);
- if (refs->length == 0)
- return 0;
+ spec = &remote->fetch;
+
+ if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
+ return -1;
- /* HEAD is only allowed to be the first in the list */
- 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)
- return -1;
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
- i = 1;
- git_reference_free(ref);
+ /* 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;
+
+ 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(&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 = refs->contents[i];
+ 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_transform_r(&refname, spec, head->name) < 0)
+ 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;
+
+ if (git_vector_insert(&update_heads, head) < 0)
goto on_error;
- error = git_reference_name_to_oid(&old, remote->repo, refname.ptr);
+ error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
if (error < 0 && error != GIT_ENOTFOUND)
goto on_error;
@@ -366,21 +872,33 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co
if (!git_oid_cmp(&old, &head->oid))
continue;
- if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0)
- break;
+ /* In autotag mode, don't overwrite any locally-existing tags */
+ error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag);
+ if (error < 0 && error != GIT_EEXISTS)
+ goto on_error;
git_reference_free(ref);
- if (cb != NULL) {
- if (cb(refname.ptr, &old, &head->oid) < 0)
+ if (remote->callbacks.update_tips != NULL) {
+ if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.payload) < 0)
goto on_error;
}
}
+ 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;
@@ -389,15 +907,28 @@ on_error:
int git_remote_connected(git_remote *remote)
{
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. */
+ 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 (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)
@@ -414,11 +945,10 @@ void git_remote_free(git_remote *remote)
git_vector_free(&remote->refs);
- git__free(remote->fetch.src);
- git__free(remote->fetch.dst);
- git__free(remote->push.src);
- git__free(remote->push.dst);
+ 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);
}
@@ -428,12 +958,12 @@ struct cb_data {
regex_t *preg;
};
-static int remote_list_cb(const char *name, const char *value, void *data_)
+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];
- GIT_UNUSED(value);
+ 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);
@@ -477,6 +1007,11 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo)
}
git_vector_free(&list);
+
+ /* cb error is converted to GIT_EUSER by git_config_foreach */
+ if (error == GIT_EUSER)
+ error = -1;
+
return error;
}
@@ -486,25 +1021,371 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo)
return 0;
}
-int git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url)
+void git_remote_check_cert(git_remote *remote, int check)
{
- git_buf buf = GIT_BUF_INIT;
+ assert(remote);
+
+ remote->check_cert = check;
+}
+
+int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks)
+{
+ assert(remote && callbacks);
+
+ GITERR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_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.payload);
- if (git_buf_printf(&buf, "refs/heads/*:refs/remotes/%s/*", name) < 0)
+ return 0;
+}
+
+void git_remote_set_cred_acquire_cb(
+ git_remote *remote,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *payload)
+{
+ assert(remote);
+
+ remote->cred_acquire_cb = cred_acquire_cb;
+ remote->cred_acquire_payload = payload;
+}
+
+int git_remote_set_transport(git_remote *remote, git_transport *transport)
+{
+ assert(remote && transport);
+
+ GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport");
+
+ if (remote->transport) {
+ giterr_set(GITERR_NET, "A transport is already bound to this remote");
return -1;
+ }
- if (git_remote_new(out, repo, name, url, git_buf_cstr(&buf)) < 0)
- goto on_error;
+ remote->transport = transport;
+ return 0;
+}
- git_buf_free(&buf);
+const git_transfer_progress* git_remote_stats(git_remote *remote)
+{
+ assert(remote);
+ return &remote->stats;
+}
- if (git_remote_save(*out) < 0)
- goto on_error;
+git_remote_autotag_option_t git_remote_autotag(git_remote *remote)
+{
+ return remote->download_tags;
+}
+
+void git_remote_set_autotag(git_remote *remote, git_remote_autotag_option_t value)
+{
+ remote->download_tags = value;
+}
+
+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;
+ git_reference *newref = 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(&newref, reference, git_buf_cstr(&new_name), 0);
+ git_reference_free(reference);
+
+cleanup:
+ git_reference_free(newref);
+ 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) {
+ 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_DIRECTION_FETCH);
+
+cleanup:
+ git_buf_free(&serialized);
+ git_buf_free(&dst_prefix);
+ return error;
+}
+
+int git_remote_rename(
+ git_remote *remote,
+ const char *new_name,
+ git_remote_rename_problem_cb callback,
+ void *payload)
+{
+ int error;
+
+ assert(remote && new_name);
+
+ if (!remote->name) {
+ giterr_set(GITERR_INVALID, "Can't rename an in-memory remote.");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = ensure_remote_name_is_valid(new_name)) < 0)
+ return error;
+
+ if (remote->repo) {
+ if ((error = ensure_remote_doesnot_exist(remote->repo, 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);
+
+ if (!remote->name) return 0;
+ 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;
+}
+
+int git_remote_is_valid_name(
+ const char *remote_name)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec refspec;
+ int error = -1;
+
+ if (!remote_name || *remote_name == '\0')
+ return 0;
+
+ git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name);
+ error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true);
-on_error:
git_buf_free(&buf);
- git_remote_free(*out);
- return -1;
+ git_refspec__free(&refspec);
+
+ giterr_clear();
+ return error == 0;
}