diff options
Diffstat (limited to 'remote.c')
-rw-r--r-- | remote.c | 949 |
1 files changed, 715 insertions, 234 deletions
@@ -42,15 +42,14 @@ struct rewrites { static struct remote **remotes; static int remotes_alloc; static int remotes_nr; +static struct hashmap remotes_hash; static struct branch **branches; static int branches_alloc; static int branches_nr; static struct branch *current_branch; -static const char *default_remote_name; static const char *pushremote_name; -static int explicit_default_remote_name; static struct rewrites rewrites; static struct rewrites rewrites_push; @@ -76,7 +75,7 @@ static const char *alias_url(const char *url, struct rewrites *r) if (!r->rewrite[i]) continue; for (j = 0; j < r->rewrite[i]->instead_of_nr; j++) { - if (!prefixcmp(url, r->rewrite[i]->instead_of[j].s) && + if (starts_with(url, r->rewrite[i]->instead_of[j].s) && (!longest || longest->len < r->rewrite[i]->instead_of[j].len)) { longest = &(r->rewrite[i]->instead_of[j]); @@ -135,25 +134,51 @@ static void add_url_alias(struct remote *remote, const char *url) add_pushurl_alias(remote, url); } +struct remotes_hash_key { + const char *str; + int len; +}; + +static int remotes_hash_cmp(const struct remote *a, const struct remote *b, const struct remotes_hash_key *key) +{ + if (key) + return strncmp(a->name, key->str, key->len) || a->name[key->len]; + else + return strcmp(a->name, b->name); +} + +static inline void init_remotes_hash(void) +{ + if (!remotes_hash.cmpfn) + hashmap_init(&remotes_hash, (hashmap_cmp_fn)remotes_hash_cmp, 0); +} + static struct remote *make_remote(const char *name, int len) { - struct remote *ret; - int i; + struct remote *ret, *replaced; + struct remotes_hash_key lookup; + struct hashmap_entry lookup_entry; - for (i = 0; i < remotes_nr; i++) { - if (len ? (!strncmp(name, remotes[i]->name, len) && - !remotes[i]->name[len]) : - !strcmp(name, remotes[i]->name)) - return remotes[i]; - } + if (!len) + len = strlen(name); + + init_remotes_hash(); + lookup.str = name; + lookup.len = len; + hashmap_entry_init(&lookup_entry, memhash(name, len)); + + if ((ret = hashmap_get(&remotes_hash, &lookup_entry, &lookup)) != NULL) + return ret; ret = xcalloc(1, sizeof(struct remote)); + ret->prune = -1; /* unspecified */ ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc); remotes[remotes_nr++] = ret; - if (len) - ret->name = xstrndup(name, len); - else - ret->name = xstrdup(name); + ret->name = xstrndup(name, len); + + hashmap_entry_init(ret, lookup_entry.hash); + replaced = hashmap_put(&remotes_hash, ret); + assert(replaced == NULL); /* no previous entry overwritten */ return ret; } @@ -168,7 +193,6 @@ static struct branch *make_branch(const char *name, int len) { struct branch *ret; int i; - char *refname; for (i = 0; i < branches_nr; i++) { if (len ? (!strncmp(name, branches[i]->name, len) && @@ -184,10 +208,7 @@ static struct branch *make_branch(const char *name, int len) ret->name = xstrndup(name, len); else ret->name = xstrdup(name); - refname = xmalloc(strlen(name) + strlen("refs/heads/") + 1); - strcpy(refname, "refs/heads/"); - strcpy(refname + strlen("refs/heads/"), ret->name); - ret->refname = refname; + ret->refname = xstrfmt("refs/heads/%s", ret->name); return ret; } @@ -238,13 +259,13 @@ static void read_remotes_file(struct remote *remote) int value_list; char *s, *p; - if (!prefixcmp(buffer, "URL:")) { + if (starts_with(buffer, "URL:")) { value_list = 0; s = buffer + 4; - } else if (!prefixcmp(buffer, "Push:")) { + } else if (starts_with(buffer, "Push:")) { value_list = 1; s = buffer + 5; - } else if (!prefixcmp(buffer, "Pull:")) { + } else if (starts_with(buffer, "Pull:")) { value_list = 2; s = buffer + 5; } else @@ -336,23 +357,16 @@ static int handle_config(const char *key, const char *value, void *cb) const char *subkey; struct remote *remote; struct branch *branch; - if (!prefixcmp(key, "branch.")) { + if (starts_with(key, "branch.")) { name = key + 7; subkey = strrchr(name, '.'); if (!subkey) return 0; branch = make_branch(name, subkey - name); if (!strcmp(subkey, ".remote")) { - if (git_config_string(&branch->remote_name, key, value)) - return -1; - if (branch == current_branch) { - default_remote_name = branch->remote_name; - explicit_default_remote_name = 1; - } + return git_config_string(&branch->remote_name, key, value); } else if (!strcmp(subkey, ".pushremote")) { - if (branch == current_branch) - if (git_config_string(&pushremote_name, key, value)) - return -1; + return git_config_string(&branch->pushremote_name, key, value); } else if (!strcmp(subkey, ".merge")) { if (!value) return config_error_nonbool(key); @@ -360,7 +374,7 @@ static int handle_config(const char *key, const char *value, void *cb) } return 0; } - if (!prefixcmp(key, "url.")) { + if (starts_with(key, "url.")) { struct rewrite *rewrite; name = key + 4; subkey = strrchr(name, '.'); @@ -379,7 +393,7 @@ static int handle_config(const char *key, const char *value, void *cb) } } - if (prefixcmp(key, "remote.")) + if (!starts_with(key, "remote.")) return 0; name = key + 7; @@ -404,6 +418,8 @@ static int handle_config(const char *key, const char *value, void *cb) remote->skip_default_update = git_config_bool(key, value); else if (!strcmp(subkey, ".skipfetchall")) remote->skip_default_update = git_config_bool(key, value); + else if (!strcmp(subkey, ".prune")) + remote->prune = git_config_bool(key, value); else if (!strcmp(subkey, ".url")) { const char *v; if (git_config_string(&v, key, value)) @@ -475,18 +491,20 @@ static void alias_all_urls(void) static void read_config(void) { + static int loaded; unsigned char sha1[20]; const char *head_ref; int flag; - if (default_remote_name) /* did this already */ + + if (loaded) return; - default_remote_name = xstrdup("origin"); + loaded = 1; + current_branch = NULL; - head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag); + head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag); if (head_ref && (flag & REF_ISSYMREF) && - !prefixcmp(head_ref, "refs/heads/")) { - current_branch = - make_branch(head_ref + strlen("refs/heads/"), 0); + skip_prefix(head_ref, "refs/heads/", &head_ref)) { + current_branch = make_branch(head_ref, 0); } git_config(handle_config, NULL); alias_all_urls(); @@ -515,7 +533,7 @@ static void free_refspecs(struct refspec *refspec, int nr_refspec) static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify) { int i; - struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec); + struct refspec *rs = xcalloc(nr_refspec, sizeof(*rs)); for (i = 0; i < nr_refspec; i++) { size_t llen; @@ -667,22 +685,45 @@ static int valid_remote_nick(const char *name) return !strchr(name, '/'); /* no slash */ } -static struct remote *remote_get_1(const char *name, const char *pushremote_name) +const char *remote_for_branch(struct branch *branch, int *explicit) +{ + if (branch && branch->remote_name) { + if (explicit) + *explicit = 1; + return branch->remote_name; + } + if (explicit) + *explicit = 0; + return "origin"; +} + +const char *pushremote_for_branch(struct branch *branch, int *explicit) +{ + if (branch && branch->pushremote_name) { + if (explicit) + *explicit = 1; + return branch->pushremote_name; + } + if (pushremote_name) { + if (explicit) + *explicit = 1; + return pushremote_name; + } + return remote_for_branch(branch, explicit); +} + +static struct remote *remote_get_1(const char *name, + const char *(*get_default)(struct branch *, int *)) { struct remote *ret; int name_given = 0; + read_config(); + if (name) name_given = 1; - else { - if (pushremote_name) { - name = pushremote_name; - name_given = 1; - } else { - name = default_remote_name; - name_given = explicit_default_remote_name; - } - } + else + name = get_default(current_branch, &name_given); ret = make_remote(name, 0); if (valid_remote_nick(name)) { @@ -702,25 +743,26 @@ static struct remote *remote_get_1(const char *name, const char *pushremote_name struct remote *remote_get(const char *name) { - read_config(); - return remote_get_1(name, NULL); + return remote_get_1(name, remote_for_branch); } struct remote *pushremote_get(const char *name) { - read_config(); - return remote_get_1(name, pushremote_name); + return remote_get_1(name, pushremote_for_branch); } int remote_is_configured(const char *name) { - int i; + struct remotes_hash_key lookup; + struct hashmap_entry lookup_entry; read_config(); - for (i = 0; i < remotes_nr; i++) - if (!strcmp(name, remotes[i]->name)) - return 1; - return 0; + init_remotes_hash(); + lookup.str = name; + lookup.len = strlen(name); + hashmap_entry_init(&lookup_entry, memhash(name, lookup.len)); + + return hashmap_get(&remotes_hash, &lookup_entry, &lookup) != NULL; } int for_each_remote(each_remote_fn fn, void *priv) @@ -742,35 +784,66 @@ int for_each_remote(each_remote_fn fn, void *priv) return result; } -void ref_remove_duplicates(struct ref *ref_map) +static void handle_duplicate(struct ref *ref1, struct ref *ref2) +{ + if (strcmp(ref1->name, ref2->name)) { + if (ref1->fetch_head_status != FETCH_HEAD_IGNORE && + ref2->fetch_head_status != FETCH_HEAD_IGNORE) { + die(_("Cannot fetch both %s and %s to %s"), + ref1->name, ref2->name, ref2->peer_ref->name); + } else if (ref1->fetch_head_status != FETCH_HEAD_IGNORE && + ref2->fetch_head_status == FETCH_HEAD_IGNORE) { + warning(_("%s usually tracks %s, not %s"), + ref2->peer_ref->name, ref2->name, ref1->name); + } else if (ref1->fetch_head_status == FETCH_HEAD_IGNORE && + ref2->fetch_head_status == FETCH_HEAD_IGNORE) { + die(_("%s tracks both %s and %s"), + ref2->peer_ref->name, ref1->name, ref2->name); + } else { + /* + * This last possibility doesn't occur because + * FETCH_HEAD_IGNORE entries always appear at + * the end of the list. + */ + die(_("Internal error")); + } + } + free(ref2->peer_ref); + free(ref2); +} + +struct ref *ref_remove_duplicates(struct ref *ref_map) { struct string_list refs = STRING_LIST_INIT_NODUP; - struct string_list_item *item = NULL; - struct ref *prev = NULL, *next = NULL; - for (; ref_map; prev = ref_map, ref_map = next) { - next = ref_map->next; - if (!ref_map->peer_ref) - continue; + struct ref *retval = NULL; + struct ref **p = &retval; - item = string_list_lookup(&refs, ref_map->peer_ref->name); - if (item) { - if (strcmp(((struct ref *)item->util)->name, - ref_map->name)) - die("%s tracks both %s and %s", - ref_map->peer_ref->name, - ((struct ref *)item->util)->name, - ref_map->name); - prev->next = ref_map->next; - free(ref_map->peer_ref); - free(ref_map); - ref_map = prev; /* skip this; we freed it */ - continue; - } + while (ref_map) { + struct ref *ref = ref_map; + + ref_map = ref_map->next; + ref->next = NULL; + + if (!ref->peer_ref) { + *p = ref; + p = &ref->next; + } else { + struct string_list_item *item = + string_list_insert(&refs, ref->peer_ref->name); - item = string_list_insert(&refs, ref_map->peer_ref->name); - item->util = ref_map; + if (item->util) { + /* Entry already existed */ + handle_duplicate((struct ref *)item->util, ref); + } else { + *p = ref; + p = &ref->next; + item->util = ref; + } + } } + string_list_clear(&refs, 0); + return retval; } int remote_has_url(struct remote *remote, const char *url) @@ -799,32 +872,25 @@ static int match_name_with_pattern(const char *key, const char *name, ret = !strncmp(name, key, klen) && namelen >= klen + ksuffixlen && !memcmp(name + namelen - ksuffixlen, kstar + 1, ksuffixlen); if (ret && value) { + struct strbuf sb = STRBUF_INIT; const char *vstar = strchr(value, '*'); - size_t vlen; - size_t vsuffixlen; if (!vstar) die("Value '%s' of pattern has no '*'", value); - vlen = vstar - value; - vsuffixlen = strlen(vstar + 1); - *result = xmalloc(vlen + vsuffixlen + - strlen(name) - - klen - ksuffixlen + 1); - strncpy(*result, value, vlen); - strncpy(*result + vlen, - name + klen, namelen - klen - ksuffixlen); - strcpy(*result + vlen + namelen - klen - ksuffixlen, - vstar + 1); + strbuf_add(&sb, value, vstar - value); + strbuf_add(&sb, name + klen, namelen - klen - ksuffixlen); + strbuf_addstr(&sb, vstar + 1); + *result = strbuf_detach(&sb, NULL); } return ret; } -static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query) +static void query_refspecs_multiple(struct refspec *refs, int ref_count, struct refspec *query, struct string_list *results) { int i; int find_src = !query->src; if (find_src && !query->dst) - return error("query_refspecs: need either src or dst"); + error("query_refspecs_multiple: need either src or dst"); for (i = 0; i < ref_count; i++) { struct refspec *refspec = &refs[i]; @@ -836,6 +902,32 @@ static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *q if (!refspec->dst) continue; if (refspec->pattern) { + if (match_name_with_pattern(key, needle, value, result)) + string_list_append_nodup(results, *result); + } else if (!strcmp(needle, key)) { + string_list_append(results, value); + } + } +} + +int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query) +{ + int i; + int find_src = !query->src; + const char *needle = find_src ? query->dst : query->src; + char **result = find_src ? &query->src : &query->dst; + + if (find_src && !query->dst) + return error("query_refspecs: need either src or dst"); + + for (i = 0; i < ref_count; i++) { + struct refspec *refspec = &refs[i]; + const char *key = find_src ? refspec->dst : refspec->src; + const char *value = find_src ? refspec->src : refspec->dst; + + if (!refspec->dst) + continue; + if (refspec->pattern) { if (match_name_with_pattern(key, needle, value, result)) { query->force = refspec->force; return 0; @@ -893,8 +985,8 @@ struct ref *copy_ref(const struct ref *ref) cpy = xmalloc(sizeof(struct ref) + len + 1); memcpy(cpy, ref, sizeof(struct ref) + len + 1); cpy->next = NULL; - cpy->symref = ref->symref ? xstrdup(ref->symref) : NULL; - cpy->remote_status = ref->remote_status ? xstrdup(ref->remote_status) : NULL; + cpy->symref = xstrdup_or_null(ref->symref); + cpy->remote_status = xstrdup_or_null(ref->remote_status); cpy->peer_ref = copy_ref(ref->peer_ref); return cpy; } @@ -952,9 +1044,9 @@ void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *)) *l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp); } -static int count_refspec_match(const char *pattern, - struct ref *refs, - struct ref **matched_ref) +int count_refspec_match(const char *pattern, + struct ref *refs, + struct ref **matched_ref) { int patlen = strlen(pattern); struct ref *matched_weak = NULL; @@ -966,7 +1058,7 @@ static int count_refspec_match(const char *pattern, char *name = refs->name; int namelen = strlen(name); - if (!refname_match(pattern, name, ref_rev_parse_rules)) + if (!refname_match(pattern, name)) continue; /* A match is "weak" if it is with refs outside @@ -979,8 +1071,8 @@ static int count_refspec_match(const char *pattern, */ if (namelen != patlen && patlen != namelen - 5 && - prefixcmp(name, "refs/heads/") && - prefixcmp(name, "refs/tags/")) { + !starts_with(name, "refs/heads/") && + !starts_with(name, "refs/tags/")) { /* We want to catch the case where only weak * matches are found and there are multiple * matches, and where more than one strong @@ -997,11 +1089,13 @@ static int count_refspec_match(const char *pattern, } } if (!matched) { - *matched_ref = matched_weak; + if (matched_ref) + *matched_ref = matched_weak; return weak_match; } else { - *matched_ref = matched; + if (matched_ref) + *matched_ref = matched; return match; } } @@ -1021,18 +1115,25 @@ static struct ref *alloc_delete_ref(void) return ref; } -static struct ref *try_explicit_object_name(const char *name) +static int try_explicit_object_name(const char *name, + struct ref **match) { unsigned char sha1[20]; - struct ref *ref; - if (!*name) - return alloc_delete_ref(); + if (!*name) { + if (match) + *match = alloc_delete_ref(); + return 0; + } + if (get_sha1(name, sha1)) - return NULL; - ref = alloc_ref(name); - hashcpy(ref->new_sha1, sha1); - return ref; + return -1; + + if (match) { + *match = alloc_ref(name); + hashcpy((*match)->new_sha1, sha1); + } + return 0; } static struct ref *make_linked_ref(const char *name, struct ref ***tail) @@ -1047,13 +1148,14 @@ static char *guess_ref(const char *name, struct ref *peer) struct strbuf buf = STRBUF_INIT; unsigned char sha1[20]; - const char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL); + const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING, + sha1, NULL); if (!r) return NULL; - if (!prefixcmp(r, "refs/heads/")) + if (starts_with(r, "refs/heads/")) strbuf_addstr(&buf, "refs/heads/"); - else if (!prefixcmp(r, "refs/tags/")) + else if (starts_with(r, "refs/tags/")) strbuf_addstr(&buf, "refs/tags/"); else return NULL; @@ -1062,12 +1164,37 @@ static char *guess_ref(const char *name, struct ref *peer) return strbuf_detach(&buf, NULL); } +static int match_explicit_lhs(struct ref *src, + struct refspec *rs, + struct ref **match, + int *allocated_match) +{ + switch (count_refspec_match(rs->src, src, match)) { + case 1: + if (allocated_match) + *allocated_match = 0; + return 0; + case 0: + /* The source could be in the get_sha1() format + * not a reference name. :refs/other is a + * way to delete 'other' ref at the remote end. + */ + if (try_explicit_object_name(rs->src, match) < 0) + return error("src refspec %s does not match any.", rs->src); + if (allocated_match) + *allocated_match = 1; + return 0; + default: + return error("src refspec %s matches more than one.", rs->src); + } +} + static int match_explicit(struct ref *src, struct ref *dst, struct ref ***dst_tail, struct refspec *rs) { struct ref *matched_src, *matched_dst; - int copy_src; + int allocated_src; const char *dst_value = rs->dst; char *dst_guess; @@ -1076,32 +1203,19 @@ static int match_explicit(struct ref *src, struct ref *dst, return 0; matched_src = matched_dst = NULL; - switch (count_refspec_match(rs->src, src, &matched_src)) { - case 1: - copy_src = 1; - break; - case 0: - /* The source could be in the get_sha1() format - * not a reference name. :refs/other is a - * way to delete 'other' ref at the remote end. - */ - matched_src = try_explicit_object_name(rs->src); - if (!matched_src) - return error("src refspec %s does not match any.", rs->src); - copy_src = 0; - break; - default: - return error("src refspec %s matches more than one.", rs->src); - } + if (match_explicit_lhs(src, rs, &matched_src, &allocated_src) < 0) + return -1; if (!dst_value) { unsigned char sha1[20]; int flag; - dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag); + dst_value = resolve_ref_unsafe(matched_src->name, + RESOLVE_REF_READING, + sha1, &flag); if (!dst_value || ((flag & REF_ISSYMREF) && - prefixcmp(dst_value, "refs/heads/"))) + !starts_with(dst_value, "refs/heads/"))) die("%s cannot be resolved to branch.", matched_src->name); } @@ -1110,7 +1224,7 @@ static int match_explicit(struct ref *src, struct ref *dst, case 1: break; case 0: - if (!memcmp(dst_value, "refs/", 5)) + if (starts_with(dst_value, "refs/")) matched_dst = make_linked_ref(dst_value, dst_tail); else if (is_null_sha1(matched_src->new_sha1)) error("unable to delete '%s': remote ref does not exist", @@ -1137,7 +1251,9 @@ static int match_explicit(struct ref *src, struct ref *dst, return error("dst ref %s receives from more than one src.", matched_dst->name); else { - matched_dst->peer_ref = copy_src ? copy_ref(matched_src) : matched_src; + matched_dst->peer_ref = allocated_src ? + matched_src : + copy_ref(matched_src); matched_dst->force = rs->force; } return 0; @@ -1190,7 +1306,7 @@ static char *get_ref_match(const struct refspec *rs, int rs_nr, const struct ref * including refs outside refs/heads/ hierarchy, but * that does not make much sense these days. */ - if (!send_mirror && prefixcmp(ref->name, "refs/heads/")) + if (!send_mirror && !starts_with(ref->name, "refs/heads/")) return NULL; name = xstrdup(ref->name); } @@ -1245,16 +1361,16 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds add_to_tips(&sent_tips, ref->peer_ref->new_sha1); else add_to_tips(&sent_tips, ref->old_sha1); - if (!prefixcmp(ref->name, "refs/tags/")) + if (starts_with(ref->name, "refs/tags/")) string_list_append(&dst_tag, ref->name); } clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK); - sort_string_list(&dst_tag); + string_list_sort(&dst_tag); /* Collect tags they do not have. */ for (ref = src; ref; ref = ref->next) { - if (prefixcmp(ref->name, "refs/tags/")) + if (!starts_with(ref->name, "refs/tags/")) continue; /* not a tag */ if (string_list_has_string(&dst_tag, ref->name)) continue; /* they already have it */ @@ -1302,12 +1418,45 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds free(sent_tips.tip); } +struct ref *find_ref_by_name(const struct ref *list, const char *name) +{ + for ( ; list; list = list->next) + if (!strcmp(list->name, name)) + return (struct ref *)list; + return NULL; +} + static void prepare_ref_index(struct string_list *ref_index, struct ref *ref) { for ( ; ref; ref = ref->next) string_list_append_nodup(ref_index, ref->name)->util = ref; - sort_string_list(ref_index); + string_list_sort(ref_index); +} + +/* + * Given only the set of local refs, sanity-check the set of push + * refspecs. We can't catch all errors that match_push_refs would, + * but we can catch some errors early before even talking to the + * remote side. + */ +int check_push_refs(struct ref *src, int nr_refspec, const char **refspec_names) +{ + struct refspec *refspec = parse_push_refspec(nr_refspec, refspec_names); + int ret = 0; + int i; + + for (i = 0; i < nr_refspec; i++) { + struct refspec *rs = refspec + i; + + if (rs->pattern || rs->matching) + continue; + + ret |= match_explicit_lhs(src, rs, NULL, NULL); + } + + free_refspec(nr_refspec, refspec); + return ret; } /* @@ -1362,7 +1511,7 @@ int match_push_refs(struct ref *src, struct ref **dst, /* * Remote doesn't have it, and we have no * explicit pattern, and we don't have - * --all nor --mirror. + * --all or --mirror. */ goto free_name; @@ -1411,12 +1560,13 @@ int match_push_refs(struct ref *src, struct ref **dst, } void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, - int force_update) + int force_update) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) { int force_ref_update = ref->force || force_update; + int reject_reason = 0; if (ref->peer_ref) hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); @@ -1431,6 +1581,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, } /* + * Bypass the usual "must fast-forward" check but + * replace it with a weaker "the old value must be + * this value we observed". If the remote ref has + * moved and is now different from what we expect, + * reject any push. + * + * It also is an error if the user told us to check + * with the remote-tracking branch to find the value + * to expect, but we did not have such a tracking + * branch. + */ + if (ref->expect_old_sha1) { + if (ref->expect_old_no_trackback || + hashcmp(ref->old_sha1, ref->old_sha1_expect)) + reject_reason = REF_STATUS_REJECT_STALE; + } + + /* + * The usual "must fast-forward" rules. + * * Decide whether an individual refspec A:B can be * pushed. The push will succeed if any of the * following are true: @@ -1448,24 +1618,63 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, * passing the --force argument */ - if (!ref->deletion && !is_null_sha1(ref->old_sha1)) { - int why = 0; /* why would this push require --force? */ - - if (!prefixcmp(ref->name, "refs/tags/")) - why = REF_STATUS_REJECT_ALREADY_EXISTS; + else if (!ref->deletion && !is_null_sha1(ref->old_sha1)) { + if (starts_with(ref->name, "refs/tags/")) + reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS; else if (!has_sha1_file(ref->old_sha1)) - why = REF_STATUS_REJECT_FETCH_FIRST; + reject_reason = REF_STATUS_REJECT_FETCH_FIRST; else if (!lookup_commit_reference_gently(ref->old_sha1, 1) || !lookup_commit_reference_gently(ref->new_sha1, 1)) - why = REF_STATUS_REJECT_NEEDS_FORCE; + reject_reason = REF_STATUS_REJECT_NEEDS_FORCE; else if (!ref_newer(ref->new_sha1, ref->old_sha1)) - why = REF_STATUS_REJECT_NONFASTFORWARD; - - if (!force_ref_update) - ref->status = why; - else if (why) - ref->forced_update = 1; + reject_reason = REF_STATUS_REJECT_NONFASTFORWARD; } + + /* + * "--force" will defeat any rejection implemented + * by the rules above. + */ + if (!force_ref_update) + ref->status = reject_reason; + else if (reject_reason) + ref->forced_update = 1; + } +} + +static void set_merge(struct branch *ret) +{ + struct remote *remote; + char *ref; + unsigned char sha1[20]; + int i; + + if (!ret) + return; /* no branch */ + if (ret->merge) + return; /* already run */ + if (!ret->remote_name || !ret->merge_nr) { + /* + * no merge config; let's make sure we don't confuse callers + * with a non-zero merge_nr but a NULL merge + */ + ret->merge_nr = 0; + return; + } + + remote = remote_get(ret->remote_name); + + ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge)); + for (i = 0; i < ret->merge_nr; i++) { + ret->merge[i] = xcalloc(1, sizeof(**ret->merge)); + ret->merge[i]->src = xstrdup(ret->merge_name[i]); + if (!remote_find_tracking(remote, ret->merge[i]) || + strcmp(ret->remote_name, ".")) + continue; + if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]), + sha1, &ref) == 1) + ret->merge[i]->dst = ref; + else + ret->merge[i]->dst = xstrdup(ret->merge_name[i]); } } @@ -1478,20 +1687,7 @@ struct branch *branch_get(const char *name) ret = current_branch; else ret = make_branch(name, 0); - if (ret && ret->remote_name) { - ret->remote = remote_get(ret->remote_name); - if (ret->merge_nr) { - int i; - ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge)); - for (i = 0; i < ret->merge_nr; i++) { - ret->merge[i] = xcalloc(1, sizeof(**ret->merge)); - ret->merge[i]->src = xstrdup(ret->merge_name[i]); - if (remote_find_tracking(ret->remote, ret->merge[i]) - && !strcmp(ret->remote_name, ".")) - ret->merge[i]->dst = xstrdup(ret->merge_name[i]); - } - } - } + set_merge(ret); return ret; } @@ -1506,7 +1702,131 @@ int branch_merge_matches(struct branch *branch, { if (!branch || i < 0 || i >= branch->merge_nr) return 0; - return refname_match(branch->merge[i]->src, refname, ref_fetch_rules); + return refname_match(branch->merge[i]->src, refname); +} + +__attribute((format (printf,2,3))) +static const char *error_buf(struct strbuf *err, const char *fmt, ...) +{ + if (err) { + va_list ap; + va_start(ap, fmt); + strbuf_vaddf(err, fmt, ap); + va_end(ap); + } + return NULL; +} + +const char *branch_get_upstream(struct branch *branch, struct strbuf *err) +{ + if (!branch) + return error_buf(err, _("HEAD does not point to a branch")); + + if (!branch->merge || !branch->merge[0]) { + /* + * no merge config; is it because the user didn't define any, + * or because it is not a real branch, and get_branch + * auto-vivified it? + */ + if (!ref_exists(branch->refname)) + return error_buf(err, _("no such branch: '%s'"), + branch->name); + return error_buf(err, + _("no upstream configured for branch '%s'"), + branch->name); + } + + if (!branch->merge[0]->dst) + return error_buf(err, + _("upstream branch '%s' not stored as a remote-tracking branch"), + branch->merge[0]->src); + + return branch->merge[0]->dst; +} + +static const char *tracking_for_push_dest(struct remote *remote, + const char *refname, + struct strbuf *err) +{ + char *ret; + + ret = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname); + if (!ret) + return error_buf(err, + _("push destination '%s' on remote '%s' has no local tracking branch"), + refname, remote->name); + return ret; +} + +static const char *branch_get_push_1(struct branch *branch, struct strbuf *err) +{ + struct remote *remote; + + if (!branch) + return error_buf(err, _("HEAD does not point to a branch")); + + remote = remote_get(pushremote_for_branch(branch, NULL)); + if (!remote) + return error_buf(err, + _("branch '%s' has no remote for pushing"), + branch->name); + + if (remote->push_refspec_nr) { + char *dst; + const char *ret; + + dst = apply_refspecs(remote->push, remote->push_refspec_nr, + branch->refname); + if (!dst) + return error_buf(err, + _("push refspecs for '%s' do not include '%s'"), + remote->name, branch->name); + + ret = tracking_for_push_dest(remote, dst, err); + free(dst); + return ret; + } + + if (remote->mirror) + return tracking_for_push_dest(remote, branch->refname, err); + + switch (push_default) { + case PUSH_DEFAULT_NOTHING: + return error_buf(err, _("push has no destination (push.default is 'nothing')")); + + case PUSH_DEFAULT_MATCHING: + case PUSH_DEFAULT_CURRENT: + return tracking_for_push_dest(remote, branch->refname, err); + + case PUSH_DEFAULT_UPSTREAM: + return branch_get_upstream(branch, err); + + case PUSH_DEFAULT_UNSPECIFIED: + case PUSH_DEFAULT_SIMPLE: + { + const char *up, *cur; + + up = branch_get_upstream(branch, err); + if (!up) + return NULL; + cur = tracking_for_push_dest(remote, branch->refname, err); + if (!cur) + return NULL; + if (strcmp(cur, up)) + return error_buf(err, + _("cannot resolve 'simple' push to a single destination")); + return cur; + } + } + + die("BUG: unhandled push situation"); +} + +const char *branch_get_push(struct branch *branch, struct strbuf *err) +{ + if (!branch->push_tracking_ref) + branch->push_tracking_ref = branch_get_push_1(branch, err); + return branch->push_tracking_ref; } static int ignore_symref_update(const char *refname) @@ -1514,11 +1834,18 @@ static int ignore_symref_update(const char *refname) unsigned char sha1[20]; int flag; - if (!resolve_ref_unsafe(refname, sha1, 0, &flag)) + if (!resolve_ref_unsafe(refname, 0, sha1, &flag)) return 0; /* non-existing refs are OK */ return (flag & REF_ISSYMREF); } +/* + * Create and return a list of (struct ref) consisting of copies of + * each remote_ref that matches refspec. refspec must be a pattern. + * Fill in the copies' peer_ref to describe the local tracking refs to + * which they map. Omit any references that would map to an existing + * local symbolic ref. + */ static struct ref *get_expanded_map(const struct ref *remote_refs, const struct refspec *refspec) { @@ -1526,9 +1853,9 @@ static struct ref *get_expanded_map(const struct ref *remote_refs, struct ref *ret = NULL; struct ref **tail = &ret; - char *expn_name; - for (ref = remote_refs; ref; ref = ref->next) { + char *expn_name = NULL; + if (strchr(ref->name, '^')) continue; /* a dereference item */ if (match_name_with_pattern(refspec->src, ref->name, @@ -1537,12 +1864,12 @@ static struct ref *get_expanded_map(const struct ref *remote_refs, struct ref *cpy = copy_ref(ref); cpy->peer_ref = alloc_ref(expn_name); - free(expn_name); if (refspec->force) cpy->peer_ref->force = 1; *tail = cpy; tail = &cpy->next; } + free(expn_name); } return ret; @@ -1552,7 +1879,7 @@ static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const c { const struct ref *ref; for (ref = refs; ref; ref = ref->next) { - if (refname_match(name, ref->name, ref_fetch_rules)) + if (refname_match(name, ref->name)) return ref; } return NULL; @@ -1573,12 +1900,12 @@ static struct ref *get_local_ref(const char *name) if (!name || name[0] == '\0') return NULL; - if (!prefixcmp(name, "refs/")) + if (starts_with(name, "refs/")) return alloc_ref(name); - if (!prefixcmp(name, "heads/") || - !prefixcmp(name, "tags/") || - !prefixcmp(name, "remotes/")) + if (starts_with(name, "heads/") || + starts_with(name, "tags/") || + starts_with(name, "remotes/")) return alloc_ref_with_prefix("refs/", 5, name); return alloc_ref_with_prefix("refs/heads/", 11, name); @@ -1613,7 +1940,7 @@ int get_fetch_map(const struct ref *remote_refs, for (rmp = &ref_map; *rmp; ) { if ((*rmp)->peer_ref) { - if (prefixcmp((*rmp)->peer_ref->name, "refs/") || + if (!starts_with((*rmp)->peer_ref->name, "refs/") || check_refname_format((*rmp)->peer_ref->name, 0)) { struct ref *ignore = *rmp; error("* Ignoring funny ref '%s' locally", @@ -1695,9 +2022,16 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1) } /* - * Return true if there is anything to report, otherwise false. + * Compare a branch with its upstream, and save their differences (number + * of commits) in *num_ours and *num_theirs. The name of the upstream branch + * (or NULL if no upstream is defined) is returned via *upstream_name, if it + * is not itself NULL. + * + * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no + * upstream defined, or ref does not exist), 0 otherwise. */ -int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) +int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, + const char **upstream_name) { unsigned char sha1[20]; struct commit *ours, *theirs; @@ -1706,34 +2040,31 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) const char *rev_argv[10], *base; int rev_argc; - /* - * Nothing to report unless we are marked to build on top of - * somebody else. - */ - if (!branch || - !branch->merge || !branch->merge[0] || !branch->merge[0]->dst) - return 0; + /* Cannot stat unless we are marked to build on top of somebody else. */ + base = branch_get_upstream(branch, NULL); + if (upstream_name) + *upstream_name = base; + if (!base) + return -1; - /* - * If what we used to build on no longer exists, there is - * nothing to report. - */ - base = branch->merge[0]->dst; + /* Cannot stat if what we used to build on no longer exists */ if (read_ref(base, sha1)) - return 0; + return -1; theirs = lookup_commit_reference(sha1); if (!theirs) - return 0; + return -1; if (read_ref(branch->refname, sha1)) - return 0; + return -1; ours = lookup_commit_reference(sha1); if (!ours) - return 0; + return -1; /* are we the same? */ - if (theirs == ours) + if (theirs == ours) { + *num_theirs = *num_ours = 0; return 0; + } /* Run "rev-list --left-right ours...theirs" internally... */ rev_argc = 0; @@ -1749,7 +2080,8 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) init_revisions(&revs, NULL); setup_revisions(rev_argc, rev_argv, &revs, NULL); - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); /* ... and count the commits on each side. */ *num_ours = 0; @@ -1767,7 +2099,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) /* clear object flags smudged by the above traversal */ clear_commit_marks(ours, ALL_REV_FLAGS); clear_commit_marks(theirs, ALL_REV_FLAGS); - return 1; + return 0; } /* @@ -1775,31 +2107,46 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) */ int format_tracking_info(struct branch *branch, struct strbuf *sb) { - int num_ours, num_theirs; - const char *base; + int ours, theirs; + const char *full_base; + char *base; + int upstream_is_gone = 0; - if (!stat_tracking_info(branch, &num_ours, &num_theirs)) - return 0; + if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) { + if (!full_base) + return 0; + upstream_is_gone = 1; + } - base = branch->merge[0]->dst; - base = shorten_unambiguous_ref(base, 0); - if (!num_theirs) { + base = shorten_unambiguous_ref(full_base, 0); + if (upstream_is_gone) { + strbuf_addf(sb, + _("Your branch is based on '%s', but the upstream is gone.\n"), + base); + if (advice_status_hints) + strbuf_addf(sb, + _(" (use \"git branch --unset-upstream\" to fixup)\n")); + } else if (!ours && !theirs) { + strbuf_addf(sb, + _("Your branch is up-to-date with '%s'.\n"), + base); + } else if (!theirs) { strbuf_addf(sb, Q_("Your branch is ahead of '%s' by %d commit.\n", "Your branch is ahead of '%s' by %d commits.\n", - num_ours), - base, num_ours); + ours), + base, ours); if (advice_status_hints) strbuf_addf(sb, _(" (use \"git push\" to publish your local commits)\n")); - } else if (!num_ours) { + } else if (!ours) { strbuf_addf(sb, Q_("Your branch is behind '%s' by %d commit, " "and can be fast-forwarded.\n", "Your branch is behind '%s' by %d commits, " "and can be fast-forwarded.\n", - num_theirs), - base, num_theirs); + theirs), + base, theirs); if (advice_status_hints) strbuf_addf(sb, _(" (use \"git pull\" to update your local branch)\n")); @@ -1811,16 +2158,18 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) "Your branch and '%s' have diverged,\n" "and have %d and %d different commits each, " "respectively.\n", - num_theirs), - base, num_ours, num_theirs); + theirs), + base, ours, theirs); if (advice_status_hints) strbuf_addf(sb, _(" (use \"git pull\" to merge the remote branch into yours)\n")); } + free(base); return 1; } -static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int one_local_ref(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { struct ref ***local_tail = cb_data; struct ref *ref; @@ -1832,7 +2181,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); - hashcpy(ref->new_sha1, sha1); + hashcpy(ref->new_sha1, oid->hash); memcpy(ref->name, refname, len); **local_tail = ref; *local_tail = &ref->next; @@ -1842,6 +2191,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla struct ref *get_local_heads(void) { struct ref *local_refs = NULL, **local_tail = &local_refs; + for_each_ref(one_local_ref, &local_tail); return local_refs; } @@ -1875,7 +2225,7 @@ struct ref *guess_remote_head(const struct ref *head, /* Look for another ref that points there */ for (r = refs; r; r = r->next) { if (r != head && - !prefixcmp(r->name, "refs/heads/") && + starts_with(r->name, "refs/heads/") && !hashcmp(r->old_sha1, head->old_sha1)) { *tail = copy_ref(r); tail = &((*tail)->next); @@ -1894,29 +2244,41 @@ struct stale_heads_info { int ref_count; }; -static int get_stale_heads_cb(const char *refname, - const unsigned char *sha1, int flags, void *cb_data) +static int get_stale_heads_cb(const char *refname, const struct object_id *oid, + int flags, void *cb_data) { struct stale_heads_info *info = cb_data; + struct string_list matches = STRING_LIST_INIT_DUP; struct refspec query; + int i, stale = 1; memset(&query, 0, sizeof(struct refspec)); query.dst = (char *)refname; - if (query_refspecs(info->refs, info->ref_count, &query)) - return 0; /* No matches */ + query_refspecs_multiple(info->refs, info->ref_count, &query, &matches); + if (matches.nr == 0) + goto clean_exit; /* No matches */ /* * 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. + * remote, we consider it to be stale. In order to deal with + * overlapping refspecs, we need to go over all of the + * matching refs. */ - if (!((flags & REF_ISSYMREF) || - string_list_has_string(info->ref_names, query.src))) { + if (flags & REF_ISSYMREF) + goto clean_exit; + + for (i = 0; stale && i < matches.nr; i++) + if (string_list_has_string(info->ref_names, matches.items[i].string)) + stale = 0; + + if (stale) { struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); - hashcpy(ref->new_sha1, sha1); + hashcpy(ref->new_sha1, oid->hash); } - free(query.src); +clean_exit: + string_list_clear(&matches, 0); return 0; } @@ -1925,14 +2287,133 @@ struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fet struct ref *ref, *stale_refs = NULL; struct string_list ref_names = STRING_LIST_INIT_NODUP; struct stale_heads_info info; + 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); + string_list_sort(&ref_names); for_each_ref(get_stale_heads_cb, &info); string_list_clear(&ref_names, 0); return stale_refs; } + +/* + * Compare-and-swap + */ +static void clear_cas_option(struct push_cas_option *cas) +{ + int i; + + for (i = 0; i < cas->nr; i++) + free(cas->entry[i].refname); + free(cas->entry); + memset(cas, 0, sizeof(*cas)); +} + +static struct push_cas *add_cas_entry(struct push_cas_option *cas, + const char *refname, + size_t refnamelen) +{ + struct push_cas *entry; + ALLOC_GROW(cas->entry, cas->nr + 1, cas->alloc); + entry = &cas->entry[cas->nr++]; + memset(entry, 0, sizeof(*entry)); + entry->refname = xmemdupz(refname, refnamelen); + return entry; +} + +int parse_push_cas_option(struct push_cas_option *cas, const char *arg, int unset) +{ + const char *colon; + struct push_cas *entry; + + if (unset) { + /* "--no-<option>" */ + clear_cas_option(cas); + return 0; + } + + if (!arg) { + /* just "--<option>" */ + cas->use_tracking_for_rest = 1; + return 0; + } + + /* "--<option>=refname" or "--<option>=refname:value" */ + colon = strchrnul(arg, ':'); + entry = add_cas_entry(cas, arg, colon - arg); + if (!*colon) + entry->use_tracking = 1; + else if (get_sha1(colon + 1, entry->expect)) + return error("cannot parse expected object name '%s'", colon + 1); + return 0; +} + +int parseopt_push_cas_option(const struct option *opt, const char *arg, int unset) +{ + return parse_push_cas_option(opt->value, arg, unset); +} + +int is_empty_cas(const struct push_cas_option *cas) +{ + return !cas->use_tracking_for_rest && !cas->nr; +} + +/* + * Look at remote.fetch refspec and see if we have a remote + * tracking branch for the refname there. Fill its current + * value in sha1[]. + * If we cannot do so, return negative to signal an error. + */ +static int remote_tracking(struct remote *remote, const char *refname, + unsigned char sha1[20]) +{ + char *dst; + + dst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname); + if (!dst) + return -1; /* no tracking ref for refname at remote */ + if (read_ref(dst, sha1)) + return -1; /* we know what the tracking ref is but we cannot read it */ + return 0; +} + +static void apply_cas(struct push_cas_option *cas, + struct remote *remote, + struct ref *ref) +{ + int i; + + /* Find an explicit --<option>=<name>[:<value>] entry */ + for (i = 0; i < cas->nr; i++) { + struct push_cas *entry = &cas->entry[i]; + if (!refname_match(entry->refname, ref->name)) + continue; + ref->expect_old_sha1 = 1; + if (!entry->use_tracking) + hashcpy(ref->old_sha1_expect, cas->entry[i].expect); + else if (remote_tracking(remote, ref->name, ref->old_sha1_expect)) + ref->expect_old_no_trackback = 1; + return; + } + + /* Are we using "--<option>" to cover all? */ + if (!cas->use_tracking_for_rest) + return; + + ref->expect_old_sha1 = 1; + if (remote_tracking(remote, ref->name, ref->old_sha1_expect)) + ref->expect_old_no_trackback = 1; +} + +void apply_push_cas(struct push_cas_option *cas, + struct remote *remote, + struct ref *remote_refs) +{ + struct ref *ref; + for (ref = remote_refs; ref; ref = ref->next) + apply_cas(cas, remote, ref); +} |