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 @@ -19,14 +19,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 * 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,33 +18,7 @@ #include -static int refspec_parse(git_refspec *refspec, const char *str) -{ - 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); - - refspec->dst = git__strdup(delim + 1); - GITERR_CHECK_ALLOC(refspec->dst); - - return 0; -} - -static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var) +static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var, bool is_fetch) { int error; const char *val; @@ -52,7 +26,34 @@ static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const cha if ((error = git_config_get_string(&val, cfg, var)) < 0) return error; - return refspec_parse(refspec, val); + return git_refspec__parse(refspec, val, is_fetch); +} + +static int download_tags_value(git_remote *remote, git_config *cfg) +{ + const char *val; + git_buf buf = GIT_BUF_INIT; + int error; + + if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSET) + return 0; + + /* This is the default, let's see if we need to change it */ + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; + if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0) + return -1; + + error = git_config_get_string(&val, cfg, git_buf_cstr(&buf)); + git_buf_free(&buf); + if (!error && !strcmp(val, "--no-tags")) + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; + else if (!error && !strcmp(val, "--tags")) + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; + + if (error == GIT_ENOTFOUND) + error = 0; + + return error; } int git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) @@ -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); +}