diff options
Diffstat (limited to 'remote.c')
-rw-r--r-- | remote.c | 182 |
1 files changed, 126 insertions, 56 deletions
@@ -42,6 +42,7 @@ 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; @@ -136,26 +137,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; } @@ -170,7 +196,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) && @@ -186,10 +211,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; } @@ -488,9 +510,8 @@ static void read_config(void) current_branch = NULL; head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag); if (head_ref && (flag & REF_ISSYMREF) && - starts_with(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); if (branch_pushremote_name) { @@ -523,7 +544,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; @@ -722,13 +743,16 @@ struct remote *pushremote_get(const char *name) 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) @@ -1062,11 +1086,13 @@ 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; } } @@ -1086,18 +1112,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) @@ -1127,12 +1160,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; @@ -1141,23 +1199,8 @@ 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]; @@ -1175,7 +1218,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", @@ -1202,7 +1245,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; @@ -1384,6 +1429,31 @@ static void prepare_ref_index(struct string_list *ref_index, struct ref *ref) } /* + * 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; +} + +/* * Given the set of refs the local repository has, the set of refs the * remote repository has, and the refspec used for push, determine * what remote refs we will update and with what value by setting |