diff --git a/include/git2/remote.h b/include/git2/remote.h index 9494a8b01..7a161796f 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -142,30 +142,22 @@ GIT_EXTERN(int) git_remote_set_url(git_remote *remote, const char* url); GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url); /** - * Set the remote's fetch refspec + * Add a fetch refspec to the remote * * @param remote the remote - * @apram spec the new fetch refspec + * @apram refspec the new fetch refspec * @return 0 or an error value */ -GIT_EXTERN(int) git_remote_set_fetchspec(git_remote *remote, const char *spec); +GIT_EXTERN(int) git_remote_add_fetchspec(git_remote *remote, const char *refspec); /** - * Get the fetch refspec + * Add a push refspec to the remote * * @param remote the remote - * @return a pointer to the fetch refspec or NULL if it doesn't exist - */ -GIT_EXTERN(const git_refspec *) git_remote_fetchspec(const git_remote *remote); - -/** - * Set the remote's push refspec - * - * @param remote the remote - * @param spec the new push refspec + * @param refspec the new push refspec * @return 0 or an error value */ -GIT_EXTERN(int) git_remote_set_pushspec(git_remote *remote, const char *spec); +GIT_EXTERN(int) git_remote_add_pushspec(git_remote *remote, const char *refspec); /** * Get the push refspec @@ -176,6 +168,15 @@ GIT_EXTERN(int) git_remote_set_pushspec(git_remote *remote, const char *spec); GIT_EXTERN(const git_refspec *) git_remote_pushspec(const git_remote *remote); +/** + * Clear the refspecs + * + * Remove all configured fetch and push refspecs from the remote. + * + * @param remote the remote + */ +GIT_EXTERN(void) git_remote_clear_refspecs(git_remote *remote); + /** * Open a connection to a remote * diff --git a/src/branch.c b/src/branch.c index 956286b74..88f052529 100644 --- a/src/branch.c +++ b/src/branch.c @@ -11,6 +11,7 @@ #include "config.h" #include "refspec.h" #include "refs.h" +#include "remote.h" #include "git2/branch.h" @@ -283,12 +284,10 @@ int git_branch_upstream__name( if ((error = git_remote_load(&remote, repo, remote_name)) < 0) goto cleanup; - refspec = git_remote_fetchspec(remote); - if (refspec == NULL - || refspec->src == NULL - || refspec->dst == NULL) { - error = GIT_ENOTFOUND; - goto cleanup; + refspec = git_remote__matching_refspec(remote, merge_name); + if (!refspec) { + error = GIT_ENOTFOUND; + goto cleanup; } if (git_refspec_transform_r(&buf, refspec, merge_name) < 0) @@ -333,11 +332,8 @@ static int remote_name(git_buf *buf, git_repository *repo, const char *canonical if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0) continue; - fetchspec = git_remote_fetchspec(remote); - - /* Defensivly check that we have a fetchspec */ - if (fetchspec && - git_refspec_dst_matches(fetchspec, canonical_branch_name)) { + fetchspec = git_remote__matching_dst_refspec(remote, canonical_branch_name); + if (fetchspec) { /* If we have not already set out yet, then set * it to the matching remote name. Otherwise * multiple remotes match this reference, and it @@ -522,9 +518,9 @@ int git_branch_set_upstream(git_reference *branch, const char *upstream_name) if (git_remote_load(&remote, repo, git_buf_cstr(&value)) < 0) goto on_error; - fetchspec = git_remote_fetchspec(remote); + fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream)); git_buf_clear(&value); - if (git_refspec_transform_l(&value, fetchspec, git_reference_name(upstream)) < 0) + if (!fetchspec || git_refspec_transform_l(&value, fetchspec, git_reference_name(upstream)) < 0) goto on_error; git_remote_free(remote); diff --git a/src/clone.c b/src/clone.c index 0bbccd44b..cb4053736 100644 --- a/src/clone.c +++ b/src/clone.c @@ -187,6 +187,7 @@ static int get_head_callback(git_remote_head *head, void *payload) static int update_head_to_remote(git_repository *repo, git_remote *remote) { int retcode = -1; + git_refspec dummy_spec; git_remote_head *remote_head; struct head_info head_info; git_buf remote_master_name = GIT_BUF_INIT; @@ -211,8 +212,13 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); git_buf_init(&head_info.branchname, 16); head_info.repo = repo; - head_info.refspec = git_remote_fetchspec(remote); + head_info.refspec = git_remote__matching_refspec(remote, GIT_REFS_HEADS_MASTER_FILE); head_info.found = 0; + + if (head_info.refspec == NULL) { + memset(&dummy_spec, 0, sizeof(git_refspec)); + head_info.refspec = &dummy_spec; + } /* Determine the remote tracking reference name from the local master */ if (git_refspec_transform_r( @@ -318,11 +324,11 @@ static int create_and_configure_origin( goto on_error; if (options->fetch_spec && - (error = git_remote_set_fetchspec(origin, options->fetch_spec)) < 0) + (error = git_remote_add_fetchspec(origin, options->fetch_spec)) < 0) goto on_error; if (options->push_spec && - (error = git_remote_set_pushspec(origin, options->push_spec)) < 0) + (error = git_remote_add_pushspec(origin, options->push_spec)) < 0) goto on_error; if (options->pushurl && diff --git a/src/fetch.c b/src/fetch.c index b60a95232..8ae34bddf 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -34,7 +34,7 @@ static int filter_ref__cb(git_remote_head *head, void *payload) if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) p->found_head = 1; - else if (git_refspec_src_matches(p->spec, head->name)) + else if (git_remote__matching_refspec(p->remote, head->name)) match = 1; else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL && git_refspec_src_matches(p->tagspec, head->name)) @@ -68,7 +68,6 @@ static int filter_wants(git_remote *remote) * not interested in any particular branch but just the remote's * 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/push.c b/src/push.c index cec4c64af..b6be1a4e1 100644 --- a/src/push.c +++ b/src/push.c @@ -177,9 +177,9 @@ int git_push_add_refspec(git_push *push, const char *refspec) int git_push_update_tips(git_push *push) { - git_refspec *fetch_spec = &push->remote->fetch; git_buf remote_ref_name = GIT_BUF_INIT; size_t i, j; + git_refspec *fetch_spec; push_spec *push_spec; git_reference *remote_ref; push_status *status; @@ -191,7 +191,8 @@ int git_push_update_tips(git_push *push) continue; /* Find the corresponding remote ref */ - if (!git_refspec_src_matches(fetch_spec, status->ref)) + fetch_spec = git_remote__matching_refspec(push->remote, status->ref); + if (!fetch_spec) continue; if ((error = git_refspec_transform_r(&remote_ref_name, fetch_spec, status->ref)) < 0) diff --git a/src/refspec.c b/src/refspec.c index a51b0cfab..0ed02f48b 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -25,6 +25,7 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) assert(refspec && input); memset(refspec, 0x0, sizeof(git_refspec)); + refspec->push = !is_fetch; lhs = input; if (*lhs == '+') { diff --git a/src/refspec.h b/src/refspec.h index a7a4dd834..339d10eb2 100644 --- a/src/refspec.h +++ b/src/refspec.h @@ -11,10 +11,10 @@ #include "buffer.h" struct git_refspec { - struct git_refspec *next; char *src; char *dst; unsigned int force :1, + push : 1, pattern :1, matching :1; }; diff --git a/src/remote.c b/src/remote.c index 56853834b..1c4b22bb9 100644 --- a/src/remote.c +++ b/src/remote.c @@ -19,15 +19,34 @@ #include -static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var, bool is_fetch) +static int add_refspec(git_remote *remote, const char *string, bool is_fetch) { - int error; - const char *val; + char *name_dup; + git_refspec *spec; - if ((error = git_config_get_string(&val, cfg, var)) < 0) - return error; + spec = git__calloc(1, sizeof(git_refspec)); + GITERR_CHECK_ALLOC(spec); - return git_refspec__parse(refspec, val, is_fetch); + name_dup = git__strdup(string); + if (!name_dup) + goto on_error; + + if (git_refspec__parse(spec, string, is_fetch) < 0) + goto on_error; + + spec->push = !is_fetch; + if (git_vector_insert(&remote->refspec_strings, name_dup) < 0) + goto on_error; + + if (git_vector_insert(&remote->refspecs, spec) < 0) + goto on_error; + + return 0; + +on_error: + git__free(spec); + git__free(name_dup); + return -1; } static int download_tags_value(git_remote *remote, git_config *cfg) @@ -99,7 +118,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n } if (fetch != NULL) { - if (git_refspec__parse(&remote->fetch, fetch, true) < 0) + if (add_refspec(remote, fetch, true) < 0) goto on_error; } @@ -186,6 +205,18 @@ int git_remote_create_inmemory(git_remote **out, git_repository *repo, const cha return 0; } +struct refspec_cb_data { + git_remote *remote; + int fetch; +}; + +static int refspec_cb(const git_config_entry *entry, void *payload) +{ + const struct refspec_cb_data *data = (struct refspec_cb_data *)payload; + + return add_refspec(data->remote, entry->value, data->fetch); +} + int git_remote_load(git_remote **out, git_repository *repo, const char *name) { git_remote *remote; @@ -193,6 +224,8 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) const char *val; int error = 0; git_config *config; + struct refspec_cb_data data; + assert(out && repo && name); @@ -211,7 +244,9 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) remote->name = git__strdup(name); GITERR_CHECK_ALLOC(remote->name); - if (git_vector_init(&remote->refs, 32, NULL) < 0) { + if ((git_vector_init(&remote->refs, 32, NULL) < 0) || + (git_vector_init(&remote->refspecs, 2, NULL)) || + (git_vector_init(&remote->refspec_strings, 2, NULL))) { error = -1; goto cleanup; } @@ -262,7 +297,9 @@ 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), true); + data.remote = remote; + data.fetch = true; + error = git_config_get_multivar(config, git_buf_cstr(&buf), NULL, refspec_cb, &data); if (error == GIT_ENOTFOUND) error = 0; @@ -277,7 +314,8 @@ 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), false); + data.fetch = false; + error = git_config_get_multivar(config, git_buf_cstr(&buf), NULL, refspec_cb, &data); if (error == GIT_ENOTFOUND) error = 0; @@ -300,36 +338,46 @@ cleanup: return error; } -static int update_config_refspec( - git_config *config, - const char *remote_name, - const git_refspec *refspec, - int git_direction) +static int update_config_refspec(const git_remote *remote, git_config *config, int direction) { - git_buf name = GIT_BUF_INIT, value = GIT_BUF_INIT; + git_buf name = GIT_BUF_INIT; + int push; + const char *dir; + size_t i; int error = -1; - if (refspec->src == NULL || refspec->dst == NULL) - return 0; + push = direction == GIT_DIRECTION_PUSH; + dir = push ? "push" : "fetch"; - if (git_buf_printf( - &name, - "remote.%s.%s", - remote_name, - git_direction == GIT_DIRECTION_FETCH ? "fetch" : "push") < 0) + if (git_buf_printf(&name, "remote.%s.%s", remote->name, dir) < 0) + return -1; + + /* Clear out the existing config */ + do { + error = git_config_delete_entry(config, git_buf_cstr(&name)); + } while (!error); + + if (error != GIT_ENOTFOUND) + return error; + + for (i = 0; i < remote->refspec_strings.length; i++) { + git_refspec *spec = git_vector_get(&remote->refspecs, i); + const char *str = git_vector_get(&remote->refspec_strings, i); + assert(spec && str); + + if (spec->push != push) + continue; + + if ((error = git_config_set_multivar(config, git_buf_cstr(&name), "", str)) < 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)); + giterr_clear(); + error = 0; cleanup: git_buf_free(&name); - git_buf_free(&value); return error; } @@ -383,19 +431,11 @@ int git_remote_save(const git_remote *remote) } } - if (update_config_refspec( - config, - remote->name, - &remote->fetch, - GIT_DIRECTION_FETCH) < 0) - goto on_error; + if (update_config_refspec(remote, config, GIT_DIRECTION_FETCH) < 0) + goto on_error; - if (update_config_refspec( - config, - remote->name, - &remote->push, - GIT_DIRECTION_PUSH) < 0) - goto on_error; + if (update_config_refspec(remote, config, GIT_DIRECTION_PUSH) < 0) + goto on_error; /* * What action to take depends on the old and new values. This @@ -482,49 +522,6 @@ int git_remote_set_pushurl(git_remote *remote, const char* url) return 0; } -int git_remote_set_fetchspec(git_remote *remote, const char *spec) -{ - git_refspec refspec; - - assert(remote && spec); - - if (git_refspec__parse(&refspec, spec, true) < 0) - return -1; - - git_refspec__free(&remote->fetch); - memcpy(&remote->fetch, &refspec, sizeof(git_refspec)); - - return 0; -} - -const git_refspec *git_remote_fetchspec(const git_remote *remote) -{ - assert(remote); - return &remote->fetch; -} - -int git_remote_set_pushspec(git_remote *remote, const char *spec) -{ - git_refspec refspec; - - assert(remote && spec); - - if (git_refspec__parse(&refspec, spec, false) < 0) - return -1; - - git_refspec__free(&remote->push); - remote->push.src = refspec.src; - remote->push.dst = refspec.dst; - - return 0; -} - -const git_refspec *git_remote_pushspec(const git_remote *remote) -{ - assert(remote); - return &remote->push; -} - const char* git_remote__urlfordirection(git_remote *remote, int direction) { assert(remote); @@ -687,21 +684,21 @@ static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *upda return 0; } -static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref) +static int remote_head_for_ref(git_remote_head **out, git_refspec *spec, 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); + assert(out && spec && 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) { + (error = git_refspec_transform_l(&remote_name, spec, git_reference_name(tracking_ref))) < 0) { /* Not an error if HEAD is orphaned or no tracking branch */ if (error == GIT_ENOTFOUND) error = 0; @@ -718,9 +715,8 @@ cleanup: return error; } -static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_heads) +static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, 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; @@ -735,8 +731,6 @@ static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_hea 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; @@ -746,7 +740,7 @@ static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_hea /* 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) + (error = remote_head_for_ref(&merge_remote_ref, spec, 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. */ @@ -786,7 +780,7 @@ cleanup: return error; } -int git_remote_update_tips(git_remote *remote) +static int update_tips_for_spec(git_remote *remote, git_refspec *spec, git_vector *refs) { int error = 0, autotag; unsigned int i = 0; @@ -795,14 +789,11 @@ int git_remote_update_tips(git_remote *remote) git_odb *odb; git_remote_head *head; git_reference *ref; - struct git_refspec *spec; git_refspec tagspec; - git_vector refs, update_heads; + git_vector update_heads; assert(remote); - spec = &remote->fetch; - if (git_repository_odb__weakptr(&odb, remote->repo) < 0) return -1; @@ -810,16 +801,12 @@ int git_remote_update_tips(git_remote *remote) return -1; /* Make a copy of the transport's refs */ - if (git_vector_init(&refs, 16, NULL) < 0 || - git_vector_init(&update_heads, 16, NULL) < 0) + if (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 (refs->length > 0) { + head = git_vector_get(refs, 0); if (!strcmp(head->name, GIT_HEAD_FILE)) { if (git_reference_create(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0) @@ -830,8 +817,8 @@ int git_remote_update_tips(git_remote *remote) } } - for (; i < refs.length; ++i) { - head = (git_remote_head *)refs.contents[i]; + for (; i < refs->length; ++i) { + head = git_vector_get(refs, i); autotag = 0; /* Ignore malformed ref names (which also saves us from tag^{} */ @@ -886,17 +873,15 @@ int git_remote_update_tips(git_remote *remote) } if (git_remote_update_fetchhead(remote) && - (error = git_remote_write_fetchhead(remote, &update_heads)) < 0) + (error = git_remote_write_fetchhead(remote, spec, &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); @@ -904,6 +889,34 @@ on_error: } +int git_remote_update_tips(git_remote *remote) +{ + git_refspec *spec; + git_vector refs; + size_t i; + + if (git_vector_init(&refs, 16, NULL) < 0) + return -1; + + if (git_remote_ls(remote, update_tips_callback, &refs) < 0) + goto on_error; + + git_vector_foreach(&remote->refspecs, i, spec) { + if (spec->push) + continue; + + if (update_tips_for_spec(remote, spec, &refs) < 0) + goto on_error; + } + + git_vector_free(&refs); + return 0; + +on_error: + git_vector_free(&refs); + return -1; +} + int git_remote_connected(git_remote *remote) { assert(remote); @@ -933,6 +946,10 @@ void git_remote_disconnect(git_remote *remote) void git_remote_free(git_remote *remote) { + git_refspec *spec; + char *str; + size_t i; + if (remote == NULL) return; @@ -945,8 +962,16 @@ void git_remote_free(git_remote *remote) git_vector_free(&remote->refs); - git_refspec__free(&remote->fetch); - git_refspec__free(&remote->push); + git_vector_foreach(&remote->refspecs, i, spec) { + git_refspec__free(spec); + git__free(spec); + } + git_vector_free(&remote->refspecs); + + git_vector_foreach(&remote->refspec_strings, i, str) + git__free(str); + git_vector_free(&remote->refspec_strings); + git__free(remote->url); git__free(remote->pushurl); git__free(remote->name); @@ -1237,58 +1262,61 @@ static int rename_fetch_refspecs( void *payload) { git_config *config; - const git_refspec *fetch_refspec; - git_buf dst_prefix = GIT_BUF_INIT, serialized = GIT_BUF_INIT; - const char* pos; + git_buf base = GIT_BUF_INIT, var = GIT_BUF_INIT, val = GIT_BUF_INIT; + const char *refspec; + size_t i; 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) + if (git_buf_printf(&base, "+refs/heads/*:refs/remotes/%s/*", remote->name) < 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; - } + git_vector_foreach(&remote->refspec_strings, i, refspec) { + git_refspec *spec = git_vector_get(&remote->refspecs, i); + assert(spec); - if (git_buf_printf(&dst_prefix, ":refs/remotes/%s/", remote->name) < 0) - goto cleanup; + if (spec->push) + continue; - pos = strstr(git_buf_cstr(&serialized), git_buf_cstr(&dst_prefix)); + /* Every refspec is a problem refspec for an in-memory remote */ + if (!remote->name) { + if (callback(refspec, payload) < 0) { + error = GIT_EUSER; + goto cleanup; + } - /* 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; - } + continue; + } - if (git_buf_splice( - &serialized, - pos - git_buf_cstr(&serialized) + strlen(":refs/remotes/"), - strlen(remote->name), new_name, - strlen(new_name)) < 0) + /* Does the dst part of the refspec follow the extected standard format? */ + if (strcmp(git_buf_cstr(&base), refspec)) { + if (callback(refspec, payload) < 0) { + error = GIT_EUSER; + goto cleanup; + } + + continue; + } + + /* If we do want to move it to the new section */ + if (git_buf_printf(&val, "+refs/heads/*:refs/remotes/%s/*", new_name) < 0) goto cleanup; - git_refspec__free(&remote->fetch); + if (git_buf_printf(&var, "remote.%s.fetch", new_name) < 0) + goto cleanup; - 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; - if (git_repository_config__weakptr(&config, remote->repo) < 0) - goto cleanup; + if (git_config_set_string(config, git_buf_cstr(&var), git_buf_cstr(&val)) < 0) + goto cleanup; + } - error = update_config_refspec(config, new_name, &remote->fetch, GIT_DIRECTION_FETCH); + error = 0; cleanup: - git_buf_free(&serialized); - git_buf_free(&dst_prefix); + git_buf_free(&base); + git_buf_free(&var); + git_buf_free(&val); return error; } @@ -1389,3 +1417,62 @@ int git_remote_is_valid_name( giterr_clear(); return error == 0; } + +git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(&remote->refspecs, i, spec) { + if (spec->push) + continue; + + if (git_refspec_src_matches(spec, refname)) + return spec; + } + + return NULL; +} + +git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(&remote->refspecs, i, spec) { + if (spec->push) + continue; + + if (git_refspec_dst_matches(spec, refname)) + return spec; + } + + return NULL; +} + +void git_remote_clear_refspecs(git_remote *remote) +{ + git_refspec *spec; + char *str; + size_t i; + + git_vector_foreach(&remote->refspecs, i, spec) { + git_refspec__free(spec); + } + git_vector_clear(&remote->refspecs); + + git_vector_foreach(&remote->refspec_strings, i, str) { + git__free(str); + } + git_vector_clear(&remote->refspec_strings); +} + +int git_remote_add_fetchspec(git_remote *remote, const char *refspec) +{ + return add_refspec(remote, refspec, true); +} + +int git_remote_add_pushspec(git_remote *remote, const char *refspec) +{ + return add_refspec(remote, refspec, false); +} diff --git a/src/remote.h b/src/remote.h index 4c1a18aa7..b1f33f0e7 100644 --- a/src/remote.h +++ b/src/remote.h @@ -20,8 +20,8 @@ struct git_remote { char *url; char *pushurl; git_vector refs; - struct git_refspec fetch; - struct git_refspec push; + git_vector refspecs; + git_vector refspec_strings; git_cred_acquire_cb cred_acquire_cb; void *cred_acquire_payload; git_transport *transport; @@ -37,4 +37,7 @@ struct git_remote { const char* git_remote__urlfordirection(struct git_remote *remote, int direction); int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url); +git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname); +git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname); + #endif diff --git a/tests-clar/clone/nonetwork.c b/tests-clar/clone/nonetwork.c index c4b482234..02066e07d 100644 --- a/tests-clar/clone/nonetwork.c +++ b/tests-clar/clone/nonetwork.c @@ -2,6 +2,7 @@ #include "git2/clone.h" #include "repository.h" +#include "remote.h" #define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository" @@ -148,7 +149,7 @@ void test_clone_nonetwork__custom_fetch_spec(void) cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); cl_git_pass(git_remote_load(&g_remote, g_repo, "origin")); - actual_fs = git_remote_fetchspec(g_remote); + actual_fs = git_vector_get(&g_remote->refspecs, 0); cl_assert_equal_s("refs/heads/master", git_refspec_src(actual_fs)); cl_assert_equal_s("refs/heads/foo", git_refspec_dst(actual_fs)); @@ -164,7 +165,7 @@ void test_clone_nonetwork__custom_push_spec(void) cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); cl_git_pass(git_remote_load(&g_remote, g_repo, "origin")); - actual_fs = git_remote_pushspec(g_remote); + actual_fs = git_vector_get(&g_remote->refspecs, g_remote->refspecs.length - 1); cl_assert_equal_s("refs/heads/master", git_refspec_src(actual_fs)); cl_assert_equal_s("refs/heads/foo", git_refspec_dst(actual_fs)); } diff --git a/tests-clar/network/remote/remotes.c b/tests-clar/network/remote/remotes.c index a5ff7415f..92c9d8ac1 100644 --- a/tests-clar/network/remote/remotes.c +++ b/tests-clar/network/remote/remotes.c @@ -13,7 +13,7 @@ void test_network_remote_remotes__initialize(void) cl_git_pass(git_remote_load(&_remote, _repo, "test")); - _refspec = git_remote_fetchspec(_remote); + _refspec = git_vector_get(&_remote->refspecs, 0); cl_assert(_refspec != NULL); } @@ -109,20 +109,41 @@ void test_network_remote_remotes__refspec_parsing(void) cl_assert_equal_s(git_refspec_dst(_refspec), "refs/remotes/test/*"); } -void test_network_remote_remotes__set_fetchspec(void) +void test_network_remote_remotes__add_fetchspec(void) { - cl_git_pass(git_remote_set_fetchspec(_remote, "refs/*:refs/*")); - _refspec = git_remote_fetchspec(_remote); + size_t size; + + size = _remote->refspecs.length; + cl_assert_equal_i(size, _remote->refspec_strings.length); + + cl_git_pass(git_remote_add_fetchspec(_remote, "refs/*:refs/*")); + + size++; + cl_assert_equal_i(size, _remote->refspec_strings.length); + cl_assert_equal_i(size, _remote->refspecs.length); + + _refspec = git_vector_get(&_remote->refspecs, size-1); cl_assert_equal_s(git_refspec_src(_refspec), "refs/*"); cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*"); + cl_assert_equal_i(_refspec->push, false); } -void test_network_remote_remotes__set_pushspec(void) +void test_network_remote_remotes__add_pushspec(void) { - cl_git_pass(git_remote_set_pushspec(_remote, "refs/*:refs/*")); - _refspec = git_remote_pushspec(_remote); + size_t size; + + size = _remote->refspecs.length; + + cl_git_pass(git_remote_add_pushspec(_remote, "refs/*:refs/*")); + size++; + cl_assert_equal_i(size, _remote->refspec_strings.length); + cl_assert_equal_i(size, _remote->refspecs.length); + + _refspec = git_vector_get(&_remote->refspecs, size-1); cl_assert_equal_s(git_refspec_src(_refspec), "refs/*"); cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*"); + + cl_assert_equal_i(_refspec->push, true); } void test_network_remote_remotes__save(void) @@ -132,8 +153,18 @@ void test_network_remote_remotes__save(void) /* Set up the remote and save it to config */ cl_git_pass(git_remote_create(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2")); - cl_git_pass(git_remote_set_fetchspec(_remote, "refs/heads/*:refs/remotes/upstream/*")); - cl_git_pass(git_remote_set_pushspec(_remote, "refs/heads/*:refs/heads/*")); + git_remote_clear_refspecs(_remote); + cl_assert_equal_i(0, _remote->refspecs.length); + cl_assert_equal_i(0, _remote->refspec_strings.length); + + cl_git_pass(git_remote_add_fetchspec(_remote, "refs/heads/*:refs/remotes/upstream/*")); + cl_assert_equal_i(1, _remote->refspecs.length); + cl_assert_equal_i(1, _remote->refspec_strings.length); + + cl_git_pass(git_remote_add_pushspec(_remote, "refs/heads/*:refs/heads/*")); + cl_assert_equal_i(2, _remote->refspecs.length); + cl_assert_equal_i(2, _remote->refspec_strings.length); + cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/libgit2_push")); cl_git_pass(git_remote_save(_remote)); git_remote_free(_remote); @@ -142,13 +173,14 @@ void test_network_remote_remotes__save(void) /* Load it from config and make sure everything matches */ cl_git_pass(git_remote_load(&_remote, _repo, "upstream")); - _refspec = git_remote_fetchspec(_remote); + _refspec = git_vector_get(&_remote->refspecs, 0); cl_assert(_refspec != NULL); cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); cl_assert_equal_s(git_refspec_dst(_refspec), "refs/remotes/upstream/*"); cl_assert_equal_i(0, git_refspec_force(_refspec)); - _refspec = git_remote_pushspec(_remote); + cl_assert(_refspec != git_vector_get(&_remote->refspecs, 1)); + _refspec = git_vector_get(&_remote->refspecs, 1); cl_assert(_refspec != NULL); cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); cl_assert_equal_s(git_refspec_dst(_refspec), "refs/heads/*"); @@ -265,7 +297,7 @@ void test_network_remote_remotes__add(void) _remote = NULL; cl_git_pass(git_remote_load(&_remote, _repo, "addtest")); - _refspec = git_remote_fetchspec(_remote); + _refspec = git_vector_get(&_remote->refspecs, 0); cl_assert_equal_s("refs/heads/*", git_refspec_src(_refspec)); cl_assert(git_refspec_force(_refspec) == 1); cl_assert_equal_s("refs/remotes/addtest/*", git_refspec_dst(_refspec)); diff --git a/tests-clar/online/fetchhead.c b/tests-clar/online/fetchhead.c index a8a5bb918..3cbdc7e93 100644 --- a/tests-clar/online/fetchhead.c +++ b/tests-clar/online/fetchhead.c @@ -42,8 +42,10 @@ static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fet cl_git_pass(git_remote_load(&remote, g_repo, "origin")); git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_AUTO); - if(fetchspec != NULL) - git_remote_set_fetchspec(remote, fetchspec); + if(fetchspec != NULL) { + git_remote_clear_refspecs(remote); + git_remote_add_fetchspec(remote, fetchspec); + } cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); cl_git_pass(git_remote_download(remote, NULL, NULL)); diff --git a/tests-clar/online/push.c b/tests-clar/online/push.c index 907d6d29f..5dc7974c7 100644 --- a/tests-clar/online/push.c +++ b/tests-clar/online/push.c @@ -160,7 +160,7 @@ static int tracking_branch_list_cb(const char *branch_name, git_branch_t branch_ */ static void verify_tracking_branches(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) { - git_refspec *fetch_spec = &remote->fetch; + git_refspec *fetch_spec; size_t i, j; git_buf msg = GIT_BUF_INIT; git_buf ref_name = GIT_BUF_INIT; @@ -179,7 +179,8 @@ static void verify_tracking_branches(git_remote *remote, expected_ref expected_r /* Convert remote reference name into tracking branch name. * If the spec is not under refs/heads/, then skip. */ - if (!git_refspec_src_matches(fetch_spec, expected_refs[i].name)) + fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name); + if (!fetch_spec) continue; cl_git_pass(git_refspec_transform_r(&ref_name, fetch_spec, expected_refs[i].name)); diff --git a/tests-clar/refs/branches/remote.c b/tests-clar/refs/branches/remote.c index 2beef3724..475fa54a8 100644 --- a/tests-clar/refs/branches/remote.c +++ b/tests-clar/refs/branches/remote.c @@ -69,7 +69,8 @@ void test_refs_branches_remote__ambiguous_remote_returns_error(void) cl_git_pass(git_remote_create(&remote, g_repo, "addtest", "http://github.com/libgit2/libgit2")); /* Update the remote fetch spec */ - cl_git_pass(git_remote_set_fetchspec(remote, "refs/heads/*:refs/remotes/test/*")); + git_remote_clear_refspecs(remote); + cl_git_pass(git_remote_add_fetchspec(remote, "refs/heads/*:refs/remotes/test/*")); cl_git_pass(git_remote_save(remote)); git_remote_free(remote);