summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/sys/remote.h31
-rw-r--r--include/git2/sys/transport.h10
-rw-r--r--src/fetch.c80
-rw-r--r--src/oid.h12
-rw-r--r--src/remote.c322
-rw-r--r--src/remote.h3
-rw-r--r--src/transports/local.c11
-rw-r--r--src/transports/smart.c17
-rw-r--r--src/transports/smart.h6
-rw-r--r--src/transports/smart_protocol.c12
-rw-r--r--tests/core/oid.c9
-rw-r--r--tests/fetch/local.c67
-rw-r--r--tests/online/fetch.c31
13 files changed, 474 insertions, 137 deletions
diff --git a/include/git2/sys/remote.h b/include/git2/sys/remote.h
new file mode 100644
index 000000000..dd243ca55
--- /dev/null
+++ b/include/git2/sys/remote.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#ifndef INCLUDE_sys_git_remote_h
+#define INCLUDE_sys_git_remote_h
+
+/**
+ * @file git2/sys/remote.h
+ * @brief Low-level remote functionality for custom transports
+ * @defgroup git_remote Low-level remote functionality
+ * @ingroup Git
+ * @{
+*/
+
+GIT_BEGIN_DECL
+
+typedef enum {
+ /** Remote supports fetching an advertised object by ID. */
+ GIT_REMOTE_CAPABILITY_TIP_OID = (1 << 0),
+
+ /** Remote supports fetching an individual reachable object. */
+ GIT_REMOTE_CAPABILITY_REACHABLE_OID = (1 << 1),
+} git_remote_capability_t;
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h
index 89e687649..f0c2a3eab 100644
--- a/include/git2/sys/transport.h
+++ b/include/git2/sys/transport.h
@@ -47,6 +47,16 @@ struct git_transport {
const git_remote_connect_options *connect_opts);
/**
+ * Gets the capabilities for this remote repository.
+ *
+ * This function may be called after a successful call to
+ * `connect()`.
+ */
+ int GIT_CALLBACK(capabilities)(
+ unsigned int *capabilities,
+ git_transport *transport);
+
+ /**
* Get the list of available references in the remote repository.
*
* This function may be called after a successful call to
diff --git a/src/fetch.c b/src/fetch.c
index 117c8f26f..03d38452c 100644
--- a/src/fetch.c
+++ b/src/fetch.c
@@ -11,6 +11,7 @@
#include "git2/refs.h"
#include "git2/revwalk.h"
#include "git2/transport.h"
+#include "git2/sys/remote.h"
#include "remote.h"
#include "refspec.h"
@@ -19,7 +20,7 @@
#include "repository.h"
#include "refs.h"
-static int maybe_want(git_remote *remote, git_remote_head *head, git_odb *odb, git_refspec *tagspec, git_remote_autotag_option_t tagopt)
+static int maybe_want(git_remote *remote, git_remote_head *head, git_refspec *tagspec, git_remote_autotag_option_t tagopt)
{
int match = 0, valid;
@@ -44,23 +45,57 @@ static int maybe_want(git_remote *remote, git_remote_head *head, git_odb *odb, g
if (!match)
return 0;
- /* If we have the object, mark it so we don't ask for it */
- if (git_odb_exists(odb, &head->oid)) {
- head->local = 1;
+ return git_vector_insert(&remote->refs, head);
+}
+
+static int mark_local(git_remote *remote)
+{
+ git_remote_head *head;
+ git_odb *odb;
+ size_t i;
+
+ if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
+ return -1;
+
+ git_vector_foreach(&remote->refs, i, head) {
+ /* If we have the object, mark it so we don't ask for it */
+ if (git_odb_exists(odb, &head->oid))
+ head->local = 1;
+ else
+ remote->need_pack = 1;
}
- else
- remote->need_pack = 1;
- return git_vector_insert(&remote->refs, head);
+ return 0;
+}
+
+static int maybe_want_oid(git_remote *remote, git_refspec *spec)
+{
+ git_remote_head *oid_head;
+
+ oid_head = git__calloc(1, sizeof(git_remote_head));
+ GIT_ERROR_CHECK_ALLOC(oid_head);
+
+ git_oid_fromstr(&oid_head->oid, spec->src);
+ oid_head->name = git__strdup(spec->dst);
+ GIT_ERROR_CHECK_ALLOC(oid_head->name);
+
+ if (git_vector_insert(&remote->local_heads, oid_head) < 0 ||
+ git_vector_insert(&remote->refs, oid_head) < 0)
+ return -1;
+
+ return 0;
}
static int filter_wants(git_remote *remote, const git_fetch_options *opts)
{
git_remote_head **heads;
- git_refspec tagspec, head;
+ git_refspec tagspec, head, *spec;
int error = 0;
git_odb *odb;
size_t i, heads_len;
+ unsigned int remote_caps;
+ unsigned int oid_mask = GIT_REMOTE_CAPABILITY_TIP_OID |
+ GIT_REMOTE_CAPABILITY_REACHABLE_OID;
git_remote_autotag_option_t tagopt = remote->download_tags;
if (opts && opts->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED)
@@ -90,14 +125,33 @@ static int filter_wants(git_remote *remote, const git_fetch_options *opts)
if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0)
goto cleanup;
- if ((error = git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote)) < 0)
+ if ((error = git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote)) < 0 ||
+ (error = git_remote_capabilities(&remote_caps, remote)) < 0)
goto cleanup;
+ /* Handle remote heads */
for (i = 0; i < heads_len; i++) {
- if ((error = maybe_want(remote, heads[i], odb, &tagspec, tagopt)) < 0)
- break;
+ if ((error = maybe_want(remote, heads[i], &tagspec, tagopt)) < 0)
+ goto cleanup;
+ }
+
+ /* Handle explicitly specified OID specs */
+ git_vector_foreach(&remote->active_refspecs, i, spec) {
+ if (!git_oid__is_hexstr(spec->src))
+ continue;
+
+ if (!(remote_caps & oid_mask)) {
+ git_error_set(GIT_ERROR_INVALID, "cannot fetch a specific object from the remote repository");
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = maybe_want_oid(remote, spec)) < 0)
+ goto cleanup;
}
+ error = mark_local(remote);
+
cleanup:
git_refspec__dispose(&tagspec);
@@ -115,10 +169,8 @@ int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts)
remote->need_pack = 0;
- if (filter_wants(remote, opts) < 0) {
- git_error_set(GIT_ERROR_NET, "failed to filter the reference list for wants");
+ if (filter_wants(remote, opts) < 0)
return -1;
- }
/* Don't try to negotiate when we don't want anything */
if (!remote->need_pack)
diff --git a/src/oid.h b/src/oid.h
index 84231ffca..7a89d2187 100644
--- a/src/oid.h
+++ b/src/oid.h
@@ -48,4 +48,16 @@ GIT_INLINE(void) git_oid__cpy_prefix(
out->id[len / 2] &= 0xF0;
}
+GIT_INLINE(bool) git_oid__is_hexstr(const char *str)
+{
+ size_t i;
+
+ for (i = 0; str[i] != '\0'; i++) {
+ if (git__fromhex(str[i]) < 0)
+ return false;
+ }
+
+ return (i == GIT_OID_HEXSZ);
+}
+
#endif
diff --git a/src/remote.c b/src/remote.c
index f1010415a..038afc6f5 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -1012,6 +1012,20 @@ int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote
return remote->transport->ls(out, size, remote->transport);
}
+int git_remote_capabilities(unsigned int *out, git_remote *remote)
+{
+ GIT_ASSERT_ARG(remote);
+
+ *out = 0;
+
+ if (!remote->transport) {
+ git_error_set(GIT_ERROR_NET, "this remote has never connected");
+ return -1;
+ }
+
+ return remote->transport->capabilities(out, remote->transport);
+}
+
static int lookup_config(char **out, git_config *cfg, const char *name)
{
git_config_entry *ce = NULL;
@@ -1702,141 +1716,207 @@ cleanup:
return error;
}
-static int update_tips_for_spec(
- git_remote *remote,
- const git_remote_callbacks *callbacks,
- int update_fetchhead,
- git_remote_autotag_option_t tagopt,
- git_refspec *spec,
- git_vector *refs,
- const char *log_message)
+static int update_ref(
+ const git_remote *remote,
+ const char *ref_name,
+ git_oid *id,
+ const char *msg,
+ const git_remote_callbacks *callbacks)
{
- int error = 0, autotag, valid;
- unsigned int i = 0;
- git_str refname = GIT_STR_INIT;
- git_oid old;
- git_odb *odb;
- git_remote_head *head;
git_reference *ref;
- git_refspec tagspec;
- git_vector update_heads;
+ git_oid old_id;
+ int error;
- GIT_ASSERT_ARG(remote);
+ error = git_reference_name_to_id(&old_id, remote->repo, ref_name);
- if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
- return -1;
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+ else if (error == 0 && git_oid_equal(&old_id, id))
+ return 0;
- if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
- return -1;
+ /* If we did find a current reference, make sure we haven't lost a race */
+ if (error)
+ error = git_reference_create(&ref, remote->repo, ref_name, id, true, msg);
+ else
+ error = git_reference_create_matching(&ref, remote->repo, ref_name, id, true, &old_id, msg);
- /* Make a copy of the transport's refs */
- if (git_vector_init(&update_heads, 16, NULL) < 0)
- return -1;
+ git_reference_free(ref);
- for (; i < refs->length; ++i) {
- head = git_vector_get(refs, i);
- autotag = 0;
- git_str_clear(&refname);
+ if (error < 0)
+ return error;
- /* Ignore malformed ref names (which also saves us from tag^{} */
- if (git_reference_name_is_valid(&valid, head->name) < 0)
- goto on_error;
+ if (callbacks && callbacks->update_tips &&
+ (error = callbacks->update_tips(ref_name, &old_id, id, callbacks->payload)) < 0)
+ return error;
- if (!valid)
- continue;
+ return 0;
+}
- /* If we have a tag, see if the auto-follow rules say to update it */
- if (git_refspec_src_matches(&tagspec, head->name)) {
- if (tagopt != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
+static int update_one_tip(
+ git_vector *update_heads,
+ git_remote *remote,
+ git_refspec *spec,
+ git_remote_head *head,
+ git_refspec *tagspec,
+ git_remote_autotag_option_t tagopt,
+ const char *log_message,
+ const git_remote_callbacks *callbacks)
+{
+ git_odb *odb;
+ git_str refname = GIT_STR_INIT;
+ git_reference *ref = NULL;
+ bool autotag = false;
+ git_oid old;
+ int valid;
+ int error;
- if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_AUTO)
- autotag = 1;
+ if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0)
+ goto done;
- git_str_clear(&refname);
- if (git_str_puts(&refname, head->name) < 0)
- goto on_error;
- }
- }
+ /* Ignore malformed ref names (which also saves us from tag^{} */
+ if ((error = git_reference_name_is_valid(&valid, head->name)) < 0)
+ goto done;
- /* If we didn't want to auto-follow the tag, check if the refspec matches */
- if (!autotag && git_refspec_src_matches(spec, head->name)) {
- if (spec->dst) {
- if (git_refspec__transform(&refname, spec, head->name) < 0)
- goto on_error;
- } else {
- /*
- * no rhs mans store it in FETCH_HEAD, even if we don't
- update anything else.
- */
- if ((error = git_vector_insert(&update_heads, head)) < 0)
- goto on_error;
+ if (!valid)
+ goto done;
- continue;
- }
+ /* If we have a tag, see if the auto-follow rules say to update it */
+ if (git_refspec_src_matches(tagspec, head->name)) {
+ if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_AUTO)
+ autotag = true;
+
+ if (tagopt != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
+ if (git_str_puts(&refname, head->name) < 0)
+ goto done;
}
+ }
- /* If we still don't have a refname, we don't want it */
- if (git_str_len(&refname) == 0) {
- continue;
+ /* If we didn't want to auto-follow the tag, check if the refspec matches */
+ if (!autotag && git_refspec_src_matches(spec, head->name)) {
+ if (spec->dst) {
+ if ((error = git_refspec__transform(&refname, spec, head->name)) < 0)
+ goto done;
+ } else {
+ /*
+ * no rhs means store it in FETCH_HEAD, even if we don't
+ * update anything else.
+ */
+ error = git_vector_insert(update_heads, head);
+ goto done;
}
+ }
- /* In autotag mode, only create tags for objects already in db */
- if (autotag && !git_odb_exists(odb, &head->oid))
- continue;
+ /* If we still don't have a refname, we don't want it */
+ if (git_str_len(&refname) == 0)
+ goto done;
- if (!autotag && git_vector_insert(&update_heads, head) < 0)
- goto on_error;
+ /* In autotag mode, only create tags for objects already in db */
+ if (autotag && !git_odb_exists(odb, &head->oid))
+ goto done;
- error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
- if (error < 0 && error != GIT_ENOTFOUND)
- goto on_error;
+ if (!autotag && (error = git_vector_insert(update_heads, head)) < 0)
+ goto done;
- if (!(error || error == GIT_ENOTFOUND)
- && !spec->force
- && !git_graph_descendant_of(remote->repo, &head->oid, &old))
- continue;
+ error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
- if (error == GIT_ENOTFOUND) {
- memset(&old, 0, GIT_OID_RAWSZ);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto done;
- if (autotag && git_vector_insert(&update_heads, head) < 0)
- goto on_error;
- }
+ if (!(error || error == GIT_ENOTFOUND) &&
+ !spec->force &&
+ !git_graph_descendant_of(remote->repo, &head->oid, &old)) {
+ error = 0;
+ goto done;
+ }
- if (!git_oid__cmp(&old, &head->oid))
- continue;
+ if (error == GIT_ENOTFOUND) {
+ memset(&old, 0, GIT_OID_RAWSZ);
+ error = 0;
- /* In autotag mode, don't overwrite any locally-existing tags */
- error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag,
- log_message);
+ if (autotag && (error = git_vector_insert(update_heads, head)) < 0)
+ goto done;
+ }
+ if (!git_oid__cmp(&old, &head->oid))
+ goto done;
+
+ /* In autotag mode, don't overwrite any locally-existing tags */
+ error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag,
+ log_message);
+
+ if (error < 0) {
if (error == GIT_EEXISTS)
- continue;
+ error = 0;
- if (error < 0)
+ goto done;
+ }
+
+ if (callbacks && callbacks->update_tips != NULL &&
+ callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload) < 0)
+ git_error_set_after_callback_function(error, "git_remote_fetch");
+
+done:
+ git_reference_free(ref);
+ git_str_dispose(&refname);
+ return error;
+}
+
+static int update_tips_for_spec(
+ git_remote *remote,
+ const git_remote_callbacks *callbacks,
+ int update_fetchhead,
+ git_remote_autotag_option_t tagopt,
+ git_refspec *spec,
+ git_vector *refs,
+ const char *log_message)
+{
+ git_refspec tagspec;
+ git_remote_head *head, oid_head;
+ git_vector update_heads;
+ int error = 0;
+ size_t i;
+
+ GIT_ASSERT_ARG(remote);
+
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
+
+ /* Make a copy of the transport's refs */
+ if (git_vector_init(&update_heads, 16, NULL) < 0)
+ return -1;
+
+ /* Update tips based on the remote heads */
+ git_vector_foreach(refs, i, head) {
+ if (update_one_tip(&update_heads, remote, spec, head, &tagspec, tagopt, log_message, callbacks) < 0)
goto on_error;
+ }
- git_reference_free(ref);
+ /* Handle specified oid sources */
+ if (git_oid__is_hexstr(spec->src)) {
+ git_oid id;
- if (callbacks && callbacks->update_tips != NULL) {
- if (callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload) < 0)
- goto on_error;
- }
+ if ((error = git_oid_fromstr(&id, spec->src)) < 0 ||
+ (error = update_ref(remote, spec->dst, &id, log_message, callbacks)) < 0)
+ goto on_error;
+
+ git_oid_cpy(&oid_head.oid, &id);
+ oid_head.name = spec->src;
+
+ if ((error = git_vector_insert(&update_heads, &oid_head)) < 0)
+ goto on_error;
}
if (update_fetchhead &&
(error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0)
goto on_error;
- git_vector_free(&update_heads);
git_refspec__dispose(&tagspec);
- git_str_dispose(&refname);
+ git_vector_free(&update_heads);
return 0;
on_error:
- git_vector_free(&update_heads);
git_refspec__dispose(&tagspec);
- git_str_dispose(&refname);
+ git_vector_free(&update_heads);
return -1;
}
@@ -1902,20 +1982,22 @@ static int next_head(const git_remote *remote, git_vector *refs,
return GIT_ITEROVER;
}
-static int opportunistic_updates(const git_remote *remote, const git_remote_callbacks *callbacks,
- git_vector *refs, const char *msg)
+static int opportunistic_updates(
+ const git_remote *remote,
+ const git_remote_callbacks *callbacks,
+ git_vector *refs,
+ const char *msg)
{
size_t i, j, k;
git_refspec *spec;
git_remote_head *head;
- git_reference *ref;
git_str refname = GIT_STR_INIT;
int error = 0;
i = j = k = 0;
+ /* Handle refspecs matching remote heads */
while ((error = next_head(remote, refs, &spec, &head, &i, &j, &k)) == 0) {
- git_oid old = {{ 0 }};
/*
* If we got here, there is a refspec which was used
* for fetching which matches the source of one of the
@@ -1925,33 +2007,15 @@ static int opportunistic_updates(const git_remote *remote, const git_remote_call
*/
git_str_clear(&refname);
- if ((error = git_refspec__transform(&refname, spec, head->name)) < 0)
- goto cleanup;
-
- error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
- if (error < 0 && error != GIT_ENOTFOUND)
- goto cleanup;
-
- if (!git_oid_cmp(&old, &head->oid))
- continue;
-
- /* If we did find a current reference, make sure we haven't lost a race */
- if (error)
- error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, true, msg);
- else
- error = git_reference_create_matching(&ref, remote->repo, refname.ptr, &head->oid, true, &old, msg);
- git_reference_free(ref);
- if (error < 0)
+ if ((error = git_refspec__transform(&refname, spec, head->name)) < 0 ||
+ (error = update_ref(remote, refname.ptr, &head->oid, msg, callbacks)) < 0)
goto cleanup;
-
- if (callbacks && callbacks->update_tips != NULL) {
- if (callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload) < 0)
- goto cleanup;
- }
}
- if (error == GIT_ITEROVER)
- error = 0;
+ if (error != GIT_ITEROVER)
+ goto cleanup;
+
+ error = 0;
cleanup:
git_str_dispose(&refname);
@@ -2018,7 +2082,7 @@ int git_remote_update_tips(
goto out;
}
- /* Only try to do opportunistic updates if the refpec lists differ. */
+ /* Only try to do opportunistic updates if the refspec lists differ. */
if (remote->passed_refspecs)
error = opportunistic_updates(remote, callbacks, &refs, reflog_message);
@@ -2059,6 +2123,17 @@ int git_remote_disconnect(git_remote *remote)
return 0;
}
+static void free_heads(git_vector *heads)
+{
+ git_remote_head *head;
+ size_t i;
+
+ git_vector_foreach(heads, i, head) {
+ git__free(head->name);
+ git__free(head);
+ }
+}
+
void git_remote_free(git_remote *remote)
{
if (remote == NULL)
@@ -2082,6 +2157,9 @@ void git_remote_free(git_remote *remote)
free_refspecs(&remote->passive_refspecs);
git_vector_free(&remote->passive_refspecs);
+ free_heads(&remote->local_heads);
+ git_vector_free(&remote->local_heads);
+
git_push_free(remote->push);
git__free(remote->url);
git__free(remote->pushurl);
diff --git a/src/remote.h b/src/remote.h
index 3cf0fd953..ea9c7d17f 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -27,6 +27,7 @@ struct git_remote {
git_vector refspecs;
git_vector active_refspecs;
git_vector passive_refspecs;
+ git_vector local_heads;
git_transport *transport;
git_repository *repo;
git_push *push;
@@ -54,4 +55,6 @@ int git_remote_connect_options_normalize(
const git_remote_connect_options *src);
void git_remote_connect_options_dispose(git_remote_connect_options *opts);
+int git_remote_capabilities(unsigned int *out, git_remote *remote);
+
#endif
diff --git a/src/transports/local.c b/src/transports/local.c
index 86524edf1..6c754a034 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -28,6 +28,7 @@
#include "git2/pack.h"
#include "git2/commit.h"
#include "git2/revparse.h"
+#include "git2/sys/remote.h"
typedef struct {
git_transport parent;
@@ -256,6 +257,15 @@ static int local_set_connect_opts(
return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts);
}
+static int local_capabilities(unsigned int *capabilities, git_transport *transport)
+{
+ GIT_UNUSED(transport);
+
+ *capabilities = GIT_REMOTE_CAPABILITY_TIP_OID |
+ GIT_REMOTE_CAPABILITY_REACHABLE_OID;
+ return 0;
+}
+
static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport)
{
transport_local *t = (transport_local *)transport;
@@ -721,6 +731,7 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param)
t->parent.version = GIT_TRANSPORT_VERSION;
t->parent.connect = local_connect;
t->parent.set_connect_opts = local_set_connect_opts;
+ t->parent.capabilities = local_capabilities;
t->parent.negotiate_fetch = local_negotiate_fetch;
t->parent.download_pack = local_download_pack;
t->parent.push = local_push;
diff --git a/src/transports/smart.c b/src/transports/smart.c
index e76c18fc3..801fcbe53 100644
--- a/src/transports/smart.c
+++ b/src/transports/smart.c
@@ -8,6 +8,7 @@
#include "smart.h"
#include "git2.h"
+#include "git2/sys/remote.h"
#include "refs.h"
#include "refspec.h"
#include "proxy.h"
@@ -226,6 +227,21 @@ static int git_smart__set_connect_opts(
return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, opts);
}
+static int git_smart__capabilities(unsigned int *capabilities, git_transport *transport)
+{
+ transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
+
+ *capabilities = 0;
+
+ if (t->caps.want_tip_sha1)
+ *capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID;
+
+ if (t->caps.want_reachable_sha1)
+ *capabilities |= GIT_REMOTE_CAPABILITY_REACHABLE_OID;
+
+ return 0;
+}
+
static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
{
transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
@@ -423,6 +439,7 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param)
t->parent.version = GIT_TRANSPORT_VERSION;
t->parent.connect = git_smart__connect;
t->parent.set_connect_opts = git_smart__set_connect_opts;
+ t->parent.capabilities = git_smart__capabilities;
t->parent.close = git_smart__close;
t->parent.free = git_smart__free;
t->parent.negotiate_fetch = git_smart__negotiate_fetch;
diff --git a/src/transports/smart.h b/src/transports/smart.h
index 8860a1ebd..9323d6c44 100644
--- a/src/transports/smart.h
+++ b/src/transports/smart.h
@@ -30,6 +30,8 @@
#define GIT_CAP_REPORT_STATUS "report-status"
#define GIT_CAP_THIN_PACK "thin-pack"
#define GIT_CAP_SYMREF "symref"
+#define GIT_CAP_WANT_TIP_SHA1 "allow-tip-sha1-in-want"
+#define GIT_CAP_WANT_REACHABLE_SHA1 "allow-reachable-sha1-in-want"
extern bool git_smart__ofs_delta_enabled;
@@ -128,7 +130,9 @@ typedef struct transport_smart_caps {
include_tag:1,
delete_refs:1,
report_status:1,
- thin_pack:1;
+ thin_pack:1,
+ want_tip_sha1:1,
+ want_reachable_sha1:1;
} transport_smart_caps;
typedef int (*packetsize_cb)(size_t received, void *payload);
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index adfc30466..8cf027133 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -205,6 +205,18 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vec
continue;
}
+ if (!git__prefixcmp(ptr, GIT_CAP_WANT_TIP_SHA1)) {
+ caps->common = caps->want_tip_sha1 = 1;
+ ptr += strlen(GIT_CAP_DELETE_REFS);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_WANT_REACHABLE_SHA1)) {
+ caps->common = caps->want_reachable_sha1 = 1;
+ ptr += strlen(GIT_CAP_DELETE_REFS);
+ continue;
+ }
+
/* We don't know this capability, so skip it */
ptr = strchr(ptr, ' ');
}
diff --git a/tests/core/oid.c b/tests/core/oid.c
index 7ee6fb67d..894feadf6 100644
--- a/tests/core/oid.c
+++ b/tests/core/oid.c
@@ -1,4 +1,5 @@
#include "clar_libgit2.h"
+#include "oid.h"
static git_oid id;
static git_oid idp;
@@ -68,3 +69,11 @@ void test_core_oid__ncmp(void)
cl_assert(!git_oid_ncmp(&id, &id, 40));
cl_assert(!git_oid_ncmp(&id, &id, 41));
}
+
+void test_core_oid__is_hexstr(void)
+{
+ cl_assert(git_oid__is_hexstr("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ cl_assert(!git_oid__is_hexstr("deadbeefdeadbeef"));
+ cl_assert(!git_oid__is_hexstr("zeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ cl_assert(!git_oid__is_hexstr("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef1"));
+}
diff --git a/tests/fetch/local.c b/tests/fetch/local.c
new file mode 100644
index 000000000..20bd7adf4
--- /dev/null
+++ b/tests/fetch/local.c
@@ -0,0 +1,67 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+
+static git_repository *repo;
+
+void test_fetch_local__initialize(void)
+{
+ cl_git_pass(git_repository_init(&repo, "./fetch", 0));
+}
+
+void test_fetch_local__cleanup(void)
+{
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_fixture_cleanup("./fetch");
+}
+
+void test_fetch_local__defaults(void)
+{
+ git_remote *remote;
+ git_object *obj;
+ git_oid expected_id;
+
+ cl_git_pass(git_remote_create(&remote, repo, "test",
+ cl_fixture("testrepo.git")));
+ cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL));
+
+ git_oid_fromstr(&expected_id, "258f0e2a959a364e40ed6603d5d44fbb24765b10");
+
+ cl_git_pass(git_revparse_single(&obj, repo, "refs/remotes/test/haacked"));
+ cl_assert_equal_oid(&expected_id, git_object_id(obj));
+
+ git_object_free(obj);
+ git_remote_free(remote);
+}
+
+void test_fetch_local__reachable_commit(void)
+{
+ git_remote *remote;
+ git_strarray refspecs;
+ git_object *obj;
+ git_oid expected_id;
+ git_str fetchhead = GIT_STR_INIT;
+ char *refspec = "+5b5b025afb0b4c913b4c338a42934a3863bf3644:refs/success";
+
+ refspecs.strings = &refspec;
+ refspecs.count = 1;
+
+ git_oid_fromstr(&expected_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+
+ cl_git_pass(git_remote_create(&remote, repo, "test",
+ cl_fixture("testrepo.git")));
+ cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL));
+
+ cl_git_pass(git_revparse_single(&obj, repo, "refs/success"));
+ cl_assert_equal_oid(&expected_id, git_object_id(obj));
+
+ cl_git_pass(git_futils_readbuffer(&fetchhead, "./fetch/.git/FETCH_HEAD"));
+ cl_assert_equal_strn(fetchhead.ptr,
+ "5b5b025afb0b4c913b4c338a42934a3863bf3644\t\t'5b5b025afb0b4c913b4c338a42934a3863bf3644' of ",
+ strlen("5b5b025afb0b4c913b4c338a42934a3863bf3644\t\t'5b5b025afb0b4c913b4c338a42934a3863bf3644' of "));
+
+ git_str_dispose(&fetchhead);
+ git_object_free(obj);
+ git_remote_free(remote);
+}
diff --git a/tests/online/fetch.c b/tests/online/fetch.c
index 2be96839d..7334f7e8b 100644
--- a/tests/online/fetch.c
+++ b/tests/online/fetch.c
@@ -1,4 +1,5 @@
#include "clar_libgit2.h"
+#include "futils.h"
static git_repository *_repo;
static int counter;
@@ -290,3 +291,33 @@ void test_online_fetch__redirect_config(void)
cl_git_fail(do_redirected_fetch(_remote_redirect_initial, "initial", "false"));
cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", "false"));
}
+
+void test_online_fetch__reachable_commit(void)
+{
+ git_remote *remote;
+ git_strarray refspecs;
+ git_object *obj;
+ git_oid expected_id;
+ git_str fetchhead = GIT_STR_INIT;
+ char *refspec = "+2c349335b7f797072cf729c4f3bb0914ecb6dec9:refs/success";
+
+ refspecs.strings = &refspec;
+ refspecs.count = 1;
+
+ git_oid_fromstr(&expected_id, "2c349335b7f797072cf729c4f3bb0914ecb6dec9");
+
+ cl_git_pass(git_remote_create(&remote, _repo, "test",
+ "https://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL));
+
+ cl_git_pass(git_revparse_single(&obj, _repo, "refs/success"));
+ cl_assert_equal_oid(&expected_id, git_object_id(obj));
+
+ cl_git_pass(git_futils_readbuffer(&fetchhead, "./fetch/.git/FETCH_HEAD"));
+ cl_assert_equal_s(fetchhead.ptr,
+ "2c349335b7f797072cf729c4f3bb0914ecb6dec9\t\t'2c349335b7f797072cf729c4f3bb0914ecb6dec9' of https://github.com/libgit2/TestGitRepository\n");
+
+ git_str_dispose(&fetchhead);
+ git_object_free(obj);
+ git_remote_free(remote);
+}