If the user gave us refspecs on the command line, we should use those when deciding whether to prune a ref instead of relying on the refspecs in the config. Previously, running git fetch --prune origin refs/heads/master:refs/remotes/origin/master would delete every other ref under the origin namespace because we were using the refspec to filter the available refs but using the configured refspec to figure out if a ref had been deleted on the remote. This is clearly the wrong thing to do. Change prune_refs and get_stale_heads to simply accept a list of references and a list of refspecs. The caller of either function needs to decide what refspecs should be used to decide whether a ref is stale. Signed-off-by: Carlos Martín Nieto <cmn@xxxxxxxx> --- builtin/fetch.c | 12 ++++++--- builtin/remote.c | 3 +- remote.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++------- remote.h | 2 +- t/t5510-fetch.sh | 4 +- 5 files changed, 70 insertions(+), 17 deletions(-) diff --git a/builtin/fetch.c b/builtin/fetch.c index 30b485e..041f79e 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -505,10 +505,10 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map) return ret; } -static int prune_refs(struct transport *transport, struct ref *ref_map) +static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map) { int result = 0; - struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); + struct ref *ref, *stale_refs = get_stale_heads(ref_map, refs, ref_count); const char *dangling_msg = dry_run ? _(" (%s will become dangling)\n") : _(" (%s has become dangling)\n"); @@ -699,8 +699,12 @@ static int do_fetch(struct transport *transport, free_refs(ref_map); return 1; } - if (prune) - prune_refs(transport, ref_map); + if (prune) { + if (ref_count) + prune_refs(refs, ref_count, ref_map); + else + prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map); + } free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag diff --git a/builtin/remote.c b/builtin/remote.c index f2a9c26..79d898b 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -349,7 +349,8 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat else string_list_append(&states->tracked, abbrev_branch(ref->name)); } - stale_refs = get_stale_heads(states->remote, fetch_map); + stale_refs = get_stale_heads(fetch_map, states->remote->fetch, + states->remote->fetch_refspec_nr); for (ref = stale_refs; ref; ref = ref->next) { struct string_list_item *item = string_list_append(&states->stale, abbrev_branch(ref->name)); diff --git a/remote.c b/remote.c index b8ecfa5..13c9153 100644 --- a/remote.c +++ b/remote.c @@ -1681,36 +1681,84 @@ struct ref *guess_remote_head(const struct ref *head, } struct stale_heads_info { - struct remote *remote; struct string_list *ref_names; struct ref **stale_refs_tail; + struct refspec *refs; + int ref_count; }; +/* Returns 0 on success, -1 if it couldn't find a match in the refspecs. */ +static int find_in_refs(struct refspec *refs, int ref_count, struct refspec *query) +{ + int i; + struct refspec *refspec; + + for (i = 0; i < ref_count; ++i) { + refspec = &refs[i]; + + /* No dst means it can't be used for prunning. */ + if (!refspec->dst) + continue; + + /* + * No '*' means that it must match exactly. If it does + * have it, try to match it against the pattern. If + * the refspec matches, store the ref name as it would + * appear in the server in query->src. + */ + if (!strchr(refspec->dst, '*')) { + if (!strcmp(query->dst, refspec->dst)) { + query->src = xstrdup(refspec->src); + return 0; + } + } else if (match_name_with_pattern(refspec->dst, query->dst, + refspec->src, &query->src)) { + return 0; + } + } + + return -1; +} + static int get_stale_heads_cb(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct stale_heads_info *info = cb_data; struct refspec refspec; + int ret; memset(&refspec, 0, sizeof(refspec)); refspec.dst = (char *)refname; - if (!remote_find_tracking(info->remote, &refspec)) { - if (!((flags & REF_ISSYMREF) || - string_list_has_string(info->ref_names, refspec.src))) { - struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); - hashcpy(ref->new_sha1, sha1); - } + + ret = find_in_refs(info->refs, info->ref_count, &refspec); + + /* No matches */ + if (ret) + return 0; + + /* + * If we did find a suitable refspec and it's not a symref and + * it's not in the list of refs that currently exist in that + * remote we consider it to be stale. + */ + if (!((flags & REF_ISSYMREF) || + string_list_has_string(info->ref_names, refspec.src))) { + struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); + hashcpy(ref->new_sha1, sha1); } + + free(refspec.src); return 0; } -struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map) +struct ref *get_stale_heads(struct ref *fetch_map, struct refspec *refs, int ref_count) { struct ref *ref, *stale_refs = NULL; struct string_list ref_names = STRING_LIST_INIT_NODUP; struct stale_heads_info info; - info.remote = remote; info.ref_names = &ref_names; info.stale_refs_tail = &stale_refs; + info.refs = refs; + info.ref_count = ref_count; for (ref = fetch_map; ref; ref = ref->next) string_list_append(&ref_names, ref->name); sort_string_list(&ref_names); diff --git a/remote.h b/remote.h index 9a30a9d..5d70aff 100644 --- a/remote.h +++ b/remote.h @@ -164,6 +164,6 @@ struct ref *guess_remote_head(const struct ref *head, int all); /* Return refs which no longer exist on remote */ -struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map); +struct ref *get_stale_heads(struct ref *fetch_map, struct refspec *refs, int ref_count); #endif diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 8b5e925..581049b 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -86,7 +86,7 @@ test_expect_success 'fetch --prune on its own works as expected' ' test_must_fail git rev-parse origin/extrabranch ' -test_expect_failure 'fetch --prune with a branch name keeps branches' ' +test_expect_success 'fetch --prune with a branch name keeps branches' ' cd "$D" && git clone . prune-branch && cd prune-branch && @@ -96,7 +96,7 @@ test_expect_failure 'fetch --prune with a branch name keeps branches' ' git rev-parse origin/extrabranch ' -test_expect_failure 'fetch --prune with a namespace keeps other namespaces' ' +test_expect_success 'fetch --prune with a namespace keeps other namespaces' ' cd "$D" && git clone . prune-namespace && cd prune-namespace && -- 1.7.5.2.354.g349bf -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html