summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/refspec.h8
-rw-r--r--include/git2/remote.h24
-rw-r--r--src/fetch.c27
-rw-r--r--src/pkt.c3
-rw-r--r--src/protocol.c6
-rw-r--r--src/refs.c10
-rw-r--r--src/refspec.c31
-rw-r--r--src/refspec.h4
-rw-r--r--src/remote.c144
-rw-r--r--src/remote.h3
-rw-r--r--src/transport.h4
-rw-r--r--tests-clar/network/refspecs.c1
-rw-r--r--tests-clar/refs/create.c15
13 files changed, 182 insertions, 98 deletions
diff --git a/include/git2/refspec.h b/include/git2/refspec.h
index 9e84aad99..1100e9022 100644
--- a/include/git2/refspec.h
+++ b/include/git2/refspec.h
@@ -20,14 +20,6 @@
GIT_BEGIN_DECL
/**
- * Parse a refspec string and create a refspec object
- *
- * @param refspec pointer to the refspec structure to be used
- * @param str the refspec as a string
- */
-GIT_EXTERN(int) git_refspec_parse(git_refspec *refspec, const char *str);
-
-/**
* Get the source specifier
*
* @param refspec the refspec
diff --git a/include/git2/remote.h b/include/git2/remote.h
index a3913af5b..c015289e8 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -304,6 +304,30 @@ struct git_remote_callbacks {
*/
GIT_EXTERN(void) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks);
+enum {
+ GIT_REMOTE_DOWNLOAD_TAGS_UNSET,
+ GIT_REMOTE_DOWNLOAD_TAGS_NONE,
+ GIT_REMOTE_DOWNLOAD_TAGS_AUTO,
+ GIT_REMOTE_DOWNLOAD_TAGS_ALL
+};
+
+/**
+ * Retrieve the tag auto-follow setting
+ *
+ * @param remote the remote to query
+ * @return the auto-follow setting
+ */
+GIT_EXTERN(int) git_remote_autotag(git_remote *remote);
+
+/**
+ * Set the tag auto-follow setting
+ *
+ * @param remote the remote to configure
+ * @param value a GIT_REMOTE_DOWNLOAD_TAGS value
+ */
+GIT_EXTERN(void) git_remote_set_autotag(git_remote *remote, int value);
+
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/fetch.c b/src/fetch.c
index 98e1f0b13..f9cc8aae1 100644
--- a/src/fetch.c
+++ b/src/fetch.c
@@ -21,7 +21,7 @@
struct filter_payload {
git_remote *remote;
- const git_refspec *spec;
+ const git_refspec *spec, *tagspec;
git_odb *odb;
int found_head;
};
@@ -29,18 +29,21 @@ struct filter_payload {
static int filter_ref__cb(git_remote_head *head, void *payload)
{
struct filter_payload *p = payload;
+ int match = 0;
- if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) {
+ if (!git_reference_is_valid_name(head->name))
+ return 0;
+
+ if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0)
p->found_head = 1;
- } else {
- /* If it doesn't match the refpec, we don't want it */
- if (!git_refspec_src_matches(p->spec, head->name))
- return 0;
+ else if (git_refspec_src_matches(p->spec, head->name))
+ match = 1;
+ else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL &&
+ git_refspec_src_matches(p->tagspec, head->name))
+ match = 1;
- /* Don't even try to ask for the annotation target */
- if (!git__suffixcmp(head->name, "^{}"))
- return 0;
- }
+ if (!match)
+ return 0;
/* If we have the object, mark it so we don't ask for it */
if (git_odb_exists(p->odb, &head->oid))
@@ -54,8 +57,11 @@ static int filter_ref__cb(git_remote_head *head, void *payload)
static int filter_wants(git_remote *remote)
{
struct filter_payload p;
+ git_refspec tagspec;
git_vector_clear(&remote->refs);
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
/*
* The fetch refspec can be NULL, and what this means is that the
@@ -64,6 +70,7 @@ static int filter_wants(git_remote *remote)
* HEAD, which will be stored in FETCH_HEAD after the fetch.
*/
p.spec = git_remote_fetchspec(remote);
+ p.tagspec = &tagspec;
p.found_head = 0;
p.remote = remote;
diff --git a/src/pkt.c b/src/pkt.c
index ad0149d33..91f9b65c2 100644
--- a/src/pkt.c
+++ b/src/pkt.c
@@ -354,6 +354,9 @@ static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps
if (caps->multi_ack)
git_buf_puts(&str, GIT_CAP_MULTI_ACK " ");
+ if (caps->include_tag)
+ git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " ");
+
if (git_buf_oom(&str))
return -1;
diff --git a/src/protocol.c b/src/protocol.c
index 4526c857d..8f673cda7 100644
--- a/src/protocol.c
+++ b/src/protocol.c
@@ -80,6 +80,12 @@ int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps)
continue;
}
+ if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
+ caps->common = caps->include_tag = 1;
+ ptr += strlen(GIT_CAP_INCLUDE_TAG);
+ continue;
+ }
+
/* Keep side-band check after side-band-64k */
if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
caps->common = caps->side_band_64k = 1;
diff --git a/src/refs.c b/src/refs.c
index 693870a0b..903acccbb 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -1199,6 +1199,7 @@ int git_reference_create_symbolic(
{
char normalized[GIT_REFNAME_MAX];
git_reference *ref = NULL;
+ int error;
if (git_reference__normalize_name_lax(
normalized,
@@ -1206,8 +1207,8 @@ int git_reference_create_symbolic(
name) < 0)
return -1;
- if (reference_can_write(repo, normalized, NULL, force) < 0)
- return -1;
+ if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
+ return error;
if (reference_alloc(&ref, repo, normalized) < 0)
return -1;
@@ -1236,6 +1237,7 @@ int git_reference_create_oid(
const git_oid *id,
int force)
{
+ int error;
git_reference *ref = NULL;
char normalized[GIT_REFNAME_MAX];
@@ -1245,8 +1247,8 @@ int git_reference_create_oid(
name) < 0)
return -1;
- if (reference_can_write(repo, normalized, NULL, force) < 0)
- return -1;
+ if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
+ return error;
if (reference_alloc(&ref, repo, name) < 0)
return -1;
diff --git a/src/refspec.c b/src/refspec.c
index 1265c566c..cd3a528bd 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -125,35 +125,10 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
return -1;
}
-int git_refspec_parse(git_refspec *refspec, const char *str)
+void git_refspec__free(git_refspec *refspec)
{
- char *delim;
-
- memset(refspec, 0x0, sizeof(git_refspec));
-
- if (*str == '+') {
- refspec->force = 1;
- str++;
- }
-
- delim = strchr(str, ':');
- if (delim == NULL) {
- refspec->src = git__strdup(str);
- GITERR_CHECK_ALLOC(refspec->src);
- return 0;
- }
-
- refspec->src = git__strndup(str, delim - str);
- GITERR_CHECK_ALLOC(refspec->src);
-
- refspec->dst = git__strdup(delim + 1);
- if (refspec->dst == NULL) {
- git__free(refspec->src);
- refspec->src = NULL;
- return -1;
- }
-
- return 0;
+ git__free(refspec->src);
+ git__free(refspec->dst);
}
const char *git_refspec_src(const git_refspec *refspec)
diff --git a/src/refspec.h b/src/refspec.h
index 2f46b3e59..a5df458c6 100644
--- a/src/refspec.h
+++ b/src/refspec.h
@@ -19,12 +19,16 @@ struct git_refspec {
matching :1;
};
+#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*"
+
int git_refspec_parse(struct git_refspec *refspec, const char *str);
int git_refspec__parse(
struct git_refspec *refspec,
const char *str,
bool is_fetch);
+void git_refspec__free(git_refspec *refspec);
+
/**
* Transform a reference to its target following the refspec's rules,
* and writes the results into a git_buf.
diff --git a/src/remote.c b/src/remote.c
index 7bc631d45..fd78164f3 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -18,41 +18,42 @@
#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;
-
- memset(refspec, 0x0, sizeof(git_refspec));
-
- if (*str == '+') {
- refspec->force = 1;
- str++;
- }
-
- delim = strchr(str, ':');
- if (delim == NULL) {
- giterr_set(GITERR_NET, "Invalid refspec, missing ':'");
- return -1;
- }
-
- refspec->src = git__strndup(str, delim - str);
- GITERR_CHECK_ALLOC(refspec->src);
+ int error;
+ const char *val;
- refspec->dst = git__strdup(delim + 1);
- GITERR_CHECK_ALLOC(refspec->dst);
+ if ((error = git_config_get_string(&val, cfg, var)) < 0)
+ return error;
- return 0;
+ return git_refspec__parse(refspec, val, is_fetch);
}
-static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var)
+static int download_tags_value(git_remote *remote, git_config *cfg)
{
- int error;
const char *val;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
- if ((error = git_config_get_string(&val, cfg, var)) < 0)
- return error;
+ if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSET)
+ return 0;
- return refspec_parse(refspec, val);
+ /* 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)
@@ -81,7 +82,7 @@ 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;
}
@@ -157,7 +158,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
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;
@@ -172,7 +173,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;
@@ -181,6 +182,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:
@@ -317,11 +321,10 @@ int git_remote_set_fetchspec(git_remote *remote, const char *spec)
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);
+ git_refspec__free(&remote->fetch);
remote->fetch.src = refspec.src;
remote->fetch.dst = refspec.dst;
@@ -340,11 +343,10 @@ 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;
@@ -445,25 +447,35 @@ int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats
int git_remote_update_tips(git_remote *remote)
{
- int error = 0;
+ int error = 0, autotag;
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;
assert(remote);
- refs = &remote->refs;
+ refs = &remote->transport->refs;
spec = &remote->fetch;
if (refs->length == 0)
return 0;
+ if (git_repository_odb(&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 */
- head = refs->contents[0];
+ 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;
@@ -473,10 +485,38 @@ int git_remote_update_tips(git_remote *remote)
}
for (; i < refs->length; ++i) {
- head = refs->contents[i];
+ autotag = 0;
+ git_pkt *pkt = refs->contents[i];
- if (git_refspec_transform_r(&refname, spec, head->name) < 0)
- goto on_error;
+ 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;
+
+ 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)
@@ -488,7 +528,9 @@ int git_remote_update_tips(git_remote *remote)
if (!git_oid_cmp(&old, &head->oid))
continue;
- if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0)
+ /* 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);
@@ -499,10 +541,12 @@ int git_remote_update_tips(git_remote *remote)
}
}
+ git_refspec__free(&tagspec);
git_buf_free(&refname);
return 0;
on_error:
+ git_refspec__free(&tagspec);
git_buf_free(&refname);
return -1;
@@ -536,10 +580,8 @@ 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);
@@ -655,3 +697,13 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback
remote->transport->cb_data = remote->callbacks.data;
}
}
+
+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;
+}
diff --git a/src/remote.h b/src/remote.h
index 67933a327..b8bb2c55d 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -24,7 +24,8 @@ struct git_remote {
git_repository *repo;
git_remote_callbacks callbacks;
unsigned int need_pack:1,
- check_cert;
+ download_tags:2, /* There are four possible values */
+ check_cert:1;
};
const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
diff --git a/src/transport.h b/src/transport.h
index ff3a58d13..9c91afd5b 100644
--- a/src/transport.h
+++ b/src/transport.h
@@ -23,13 +23,15 @@
#define GIT_CAP_MULTI_ACK "multi_ack"
#define GIT_CAP_SIDE_BAND "side-band"
#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
+#define GIT_CAP_INCLUDE_TAG "include-tag"
typedef struct git_transport_caps {
int common:1,
ofs_delta:1,
multi_ack: 1,
side_band:1,
- side_band_64k:1;
+ side_band_64k:1,
+ include_tag:1;
} git_transport_caps;
#ifdef GIT_SSL
diff --git a/tests-clar/network/refspecs.c b/tests-clar/network/refspecs.c
index bfe0af48c..3b1281722 100644
--- a/tests-clar/network/refspecs.c
+++ b/tests-clar/network/refspecs.c
@@ -8,6 +8,7 @@ static void assert_refspec(unsigned int direction, const char *input, bool is_ex
int error;
error = git_refspec__parse(&refspec, input, direction == GIT_DIR_FETCH);
+ git_refspec__free(&refspec);
if (is_expected_to_be_valid)
cl_assert_equal_i(0, error);
diff --git a/tests-clar/refs/create.c b/tests-clar/refs/create.c
index af5b203a3..bf234bc60 100644
--- a/tests-clar/refs/create.c
+++ b/tests-clar/refs/create.c
@@ -147,3 +147,18 @@ void test_refs_create__oid_unknown(void)
/* Ensure the reference can't be looked-up... */
cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, new_head));
}
+
+void test_refs_create__propagate_eexists(void)
+{
+ int error;
+ git_oid oid;
+ git_reference *ref;
+
+ /* Make sure it works for oid and for symbolic both */
+ git_oid_fromstr(&oid, current_master_tip);
+ error = git_reference_create_oid(&ref, g_repo, current_head_target, &oid, false);
+ cl_assert(error == GIT_EEXISTS);
+
+ error = git_reference_create_symbolic(&ref, g_repo, "HEAD", current_head_target, false);
+ cl_assert(error == GIT_EEXISTS);
+}