diff options
Diffstat (limited to 'remote.c')
-rw-r--r-- | remote.c | 627 |
1 files changed, 490 insertions, 137 deletions
@@ -11,8 +11,9 @@ #include "tag.h" #include "string-list.h" #include "mergesort.h" -#include "argv-array.h" +#include "strvec.h" #include "commit-reach.h" +#include "advice.h" enum map_direction { FROM_SRC, FROM_DST }; @@ -110,14 +111,16 @@ struct remotes_hash_key { }; static int remotes_hash_cmp(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *keydata) { - const struct remote *a = entry; - const struct remote *b = entry_or_key; + const struct remote *a, *b; const struct remotes_hash_key *key = keydata; + a = container_of(eptr, const struct remote, ent); + b = container_of(entry_or_key, const struct remote, ent); + if (key) return strncmp(a->name, key->str, key->len) || a->name[key->len]; else @@ -134,7 +137,7 @@ static struct remote *make_remote(const char *name, int len) { struct remote *ret, *replaced; struct remotes_hash_key lookup; - struct hashmap_entry lookup_entry; + struct hashmap_entry lookup_entry, *e; if (!len) len = strlen(name); @@ -144,10 +147,11 @@ static struct remote *make_remote(const char *name, int len) lookup.len = len; hashmap_entry_init(&lookup_entry, memhash(name, len)); - if ((ret = hashmap_get(&remotes_hash, &lookup_entry, &lookup)) != NULL) - return ret; + e = hashmap_get(&remotes_hash, &lookup_entry, &lookup); + if (e) + return container_of(e, struct remote, ent); - ret = xcalloc(1, sizeof(struct remote)); + CALLOC_ARRAY(ret, 1); ret->prune = -1; /* unspecified */ ret->prune_tags = -1; /* unspecified */ ret->name = xstrndup(name, len); @@ -157,8 +161,8 @@ static struct remote *make_remote(const char *name, int len) ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc); remotes[remotes_nr++] = ret; - hashmap_entry_init(ret, lookup_entry.hash); - replaced = hashmap_put(&remotes_hash, ret); + hashmap_entry_init(&ret->ent, lookup_entry.hash); + replaced = hashmap_put_entry(&remotes_hash, ret, ent); assert(replaced == NULL); /* no previous entry overwritten */ return ret; } @@ -170,54 +174,43 @@ static void add_merge(struct branch *branch, const char *name) branch->merge_name[branch->merge_nr++] = name; } -static struct branch *make_branch(const char *name, int len) +static struct branch *make_branch(const char *name, size_t len) { struct branch *ret; int i; for (i = 0; i < branches_nr; i++) { - if (len ? (!strncmp(name, branches[i]->name, len) && - !branches[i]->name[len]) : - !strcmp(name, branches[i]->name)) + if (!strncmp(name, branches[i]->name, len) && + !branches[i]->name[len]) return branches[i]; } ALLOC_GROW(branches, branches_nr + 1, branches_alloc); - ret = xcalloc(1, sizeof(struct branch)); + CALLOC_ARRAY(ret, 1); branches[branches_nr++] = ret; - if (len) - ret->name = xstrndup(name, len); - else - ret->name = xstrdup(name); + ret->name = xstrndup(name, len); ret->refname = xstrfmt("refs/heads/%s", ret->name); return ret; } -static struct rewrite *make_rewrite(struct rewrites *r, const char *base, int len) +static struct rewrite *make_rewrite(struct rewrites *r, + const char *base, size_t len) { struct rewrite *ret; int i; for (i = 0; i < r->rewrite_nr; i++) { - if (len - ? (len == r->rewrite[i]->baselen && - !strncmp(base, r->rewrite[i]->base, len)) - : !strcmp(base, r->rewrite[i]->base)) + if (len == r->rewrite[i]->baselen && + !strncmp(base, r->rewrite[i]->base, len)) return r->rewrite[i]; } ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc); - ret = xcalloc(1, sizeof(struct rewrite)); + CALLOC_ARRAY(ret, 1); r->rewrite[r->rewrite_nr++] = ret; - if (len) { - ret->base = xstrndup(base, len); - ret->baselen = len; - } - else { - ret->base = xstrdup(base); - ret->baselen = strlen(base); - } + ret->base = xstrndup(base, len); + ret->baselen = len; return ret; } @@ -283,7 +276,7 @@ static void read_branches_file(struct remote *remote) /* * The branches file would have URL and optionally - * #branch specified. The "master" (or specified) branch is + * #branch specified. The default (or specified) branch is * fetched and stored in the local branch matching the * remote name. */ @@ -291,28 +284,24 @@ static void read_branches_file(struct remote *remote) if (frag) *(frag++) = '\0'; else - frag = "master"; + frag = (char *)git_default_branch_name(0); add_url_alias(remote, strbuf_detach(&buf, NULL)); - strbuf_addf(&buf, "refs/heads/%s:refs/heads/%s", - frag, remote->name); - refspec_append(&remote->fetch, buf.buf); + refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s", + frag, remote->name); /* * Cogito compatible push: push current HEAD to remote #branch * (master if missing) */ - strbuf_reset(&buf); - strbuf_addf(&buf, "HEAD:refs/heads/%s", frag); - refspec_append(&remote->push, buf.buf); + refspec_appendf(&remote->push, "HEAD:refs/heads/%s", frag); remote->fetch_tags = 1; /* always auto-follow */ - strbuf_release(&buf); } static int handle_config(const char *key, const char *value, void *cb) { const char *name; - int namelen; + size_t namelen; const char *subkey; struct remote *remote; struct branch *branch; @@ -336,14 +325,14 @@ static int handle_config(const char *key, const char *value, void *cb) if (!name) return 0; if (!strcmp(subkey, "insteadof")) { - rewrite = make_rewrite(&rewrites, name, namelen); if (!value) return config_error_nonbool(key); + rewrite = make_rewrite(&rewrites, name, namelen); add_instead_of(rewrite, xstrdup(value)); } else if (!strcmp(subkey, "pushinsteadof")) { - rewrite = make_rewrite(&rewrites_push, name, namelen); if (!value) return config_error_nonbool(key); + rewrite = make_rewrite(&rewrites_push, name, namelen); add_instead_of(rewrite, xstrdup(value)); } } @@ -359,13 +348,14 @@ static int handle_config(const char *key, const char *value, void *cb) return 0; /* Handle remote.<name>.* variables */ if (*name == '/') { - warning("Config remote shorthand cannot begin with '/': %s", + warning(_("config remote shorthand cannot begin with '/': %s"), name); return 0; } remote = make_remote(name, namelen); remote->origin = REMOTE_CONFIG; - if (current_config_scope() == CONFIG_SCOPE_REPO) + if (current_config_scope() == CONFIG_SCOPE_LOCAL || + current_config_scope() == CONFIG_SCOPE_WORKTREE) remote->configured_in_repo = 1; if (!strcmp(subkey, "mirror")) remote->mirror = git_config_bool(key, value); @@ -406,7 +396,7 @@ static int handle_config(const char *key, const char *value, void *cb) if (!remote->receivepack) remote->receivepack = v; else - error("more than one receivepack given, using the first"); + error(_("more than one receivepack given, using the first")); } else if (!strcmp(subkey, "uploadpack")) { const char *v; if (git_config_string(&v, key, value)) @@ -414,7 +404,7 @@ static int handle_config(const char *key, const char *value, void *cb) if (!remote->uploadpack) remote->uploadpack = v; else - error("more than one uploadpack given, using the first"); + error(_("more than one uploadpack given, using the first")); } else if (!strcmp(subkey, "tagopt")) { if (!strcmp(value, "--no-tags")) remote->fetch_tags = -1; @@ -465,7 +455,7 @@ static void read_config(void) const char *head_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flag); if (head_ref && (flag & REF_ISSYMREF) && skip_prefix(head_ref, "refs/heads/", &head_ref)) { - current_branch = make_branch(head_ref, 0); + current_branch = make_branch(head_ref, strlen(head_ref)); } } git_config(handle_config, NULL); @@ -511,14 +501,11 @@ const char *pushremote_for_branch(struct branch *branch, int *explicit) return remote_for_branch(branch, explicit); } -const char *remote_ref_for_branch(struct branch *branch, int for_push, - int *explicit) +const char *remote_ref_for_branch(struct branch *branch, int for_push) { if (branch) { if (!for_push) { if (branch->merge_nr) { - if (explicit) - *explicit = 1; return branch->merge_name[0]; } } else { @@ -529,15 +516,11 @@ const char *remote_ref_for_branch(struct branch *branch, int for_push, if (remote && remote->push.nr && (dst = apply_refspecs(&remote->push, branch->refname))) { - if (explicit) - *explicit = 1; return dst; } } } - if (explicit) - *explicit = 0; - return ""; + return NULL; } static struct remote *remote_get_1(const char *name, @@ -620,7 +603,7 @@ static void handle_duplicate(struct ref *ref1, struct ref *ref2) * FETCH_HEAD_IGNORE entries always appear at * the end of the list. */ - die(_("Internal error")); + BUG("Internal error"); } } free(ref2->peer_ref); @@ -680,7 +663,7 @@ static int match_name_with_pattern(const char *key, const char *name, size_t namelen; int ret; if (!kstar) - die("Key '%s' of pattern had no '*'", key); + die(_("key '%s' of pattern had no '*'"), key); klen = kstar - key; ksuffixlen = strlen(kstar + 1); namelen = strlen(name); @@ -690,7 +673,7 @@ static int match_name_with_pattern(const char *key, const char *name, struct strbuf sb = STRBUF_INIT; const char *vstar = strchr(value, '*'); if (!vstar) - die("Value '%s' of pattern has no '*'", value); + die(_("value '%s' of pattern has no '*'"), value); strbuf_add(&sb, value, vstar - value); strbuf_add(&sb, name + klen, namelen - klen - ksuffixlen); strbuf_addstr(&sb, vstar + 1); @@ -699,6 +682,101 @@ static int match_name_with_pattern(const char *key, const char *name, return ret; } +static int refspec_match(const struct refspec_item *refspec, + const char *name) +{ + if (refspec->pattern) + return match_name_with_pattern(refspec->src, name, NULL, NULL); + + return !strcmp(refspec->src, name); +} + +static int omit_name_by_refspec(const char *name, struct refspec *rs) +{ + int i; + + for (i = 0; i < rs->nr; i++) { + if (rs->items[i].negative && refspec_match(&rs->items[i], name)) + return 1; + } + return 0; +} + +struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs) +{ + struct ref **tail; + + for (tail = &ref_map; *tail; ) { + struct ref *ref = *tail; + + if (omit_name_by_refspec(ref->name, rs)) { + *tail = ref->next; + free(ref->peer_ref); + free(ref); + } else + tail = &ref->next; + } + + return ref_map; +} + +static int query_matches_negative_refspec(struct refspec *rs, struct refspec_item *query) +{ + int i, matched_negative = 0; + int find_src = !query->src; + struct string_list reversed = STRING_LIST_INIT_NODUP; + const char *needle = find_src ? query->dst : query->src; + + /* + * Check whether the queried ref matches any negative refpsec. If so, + * then we should ultimately treat this as not matching the query at + * all. + * + * Note that negative refspecs always match the source, but the query + * item uses the destination. To handle this, we apply pattern + * refspecs in reverse to figure out if the query source matches any + * of the negative refspecs. + * + * The first loop finds and expands all positive refspecs + * matched by the queried ref. + * + * The second loop checks if any of the results of the first loop + * match any negative refspec. + */ + for (i = 0; i < rs->nr; i++) { + struct refspec_item *refspec = &rs->items[i]; + char *expn_name; + + if (refspec->negative) + continue; + + /* Note the reversal of src and dst */ + if (refspec->pattern) { + const char *key = refspec->dst ? refspec->dst : refspec->src; + const char *value = refspec->src; + + if (match_name_with_pattern(key, needle, value, &expn_name)) + string_list_append_nodup(&reversed, expn_name); + } else if (refspec->matching) { + /* For the special matching refspec, any query should match */ + string_list_append(&reversed, needle); + } else if (!refspec->src) { + BUG("refspec->src should not be null here"); + } else if (!strcmp(needle, refspec->src)) { + string_list_append(&reversed, refspec->src); + } + } + + for (i = 0; !matched_negative && i < reversed.nr; i++) { + if (omit_name_by_refspec(reversed.items[i].string, rs)) + matched_negative = 1; + } + + string_list_clear(&reversed, 0); + + return matched_negative; +} + static void query_refspecs_multiple(struct refspec *rs, struct refspec_item *query, struct string_list *results) @@ -707,7 +785,10 @@ static void query_refspecs_multiple(struct refspec *rs, int find_src = !query->src; if (find_src && !query->dst) - error("query_refspecs_multiple: need either src or dst"); + BUG("query_refspecs_multiple: need either src or dst"); + + if (query_matches_negative_refspec(rs, query)) + return; for (i = 0; i < rs->nr; i++) { struct refspec_item *refspec = &rs->items[i]; @@ -716,7 +797,7 @@ static void query_refspecs_multiple(struct refspec *rs, const char *needle = find_src ? query->dst : query->src; char **result = find_src ? &query->src : &query->dst; - if (!refspec->dst) + if (!refspec->dst || refspec->negative) continue; if (refspec->pattern) { if (match_name_with_pattern(key, needle, value, result)) @@ -735,14 +816,17 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query) char **result = find_src ? &query->src : &query->dst; if (find_src && !query->dst) - return error("query_refspecs: need either src or dst"); + BUG("query_refspecs: need either src or dst"); + + if (query_matches_negative_refspec(rs, query)) + return -1; for (i = 0; i < rs->nr; i++) { struct refspec_item *refspec = &rs->items[i]; const char *key = find_src ? refspec->dst : refspec->src; const char *value = find_src ? refspec->src : refspec->dst; - if (!refspec->dst) + if (!refspec->dst || refspec->negative) continue; if (refspec->pattern) { if (match_name_with_pattern(key, needle, value, result)) { @@ -819,11 +903,11 @@ struct ref *copy_ref_list(const struct ref *ref) return ret; } -static void free_ref(struct ref *ref) +void free_one_ref(struct ref *ref) { if (!ref) return; - free_ref(ref->peer_ref); + free_one_ref(ref->peer_ref); free(ref->remote_status); free(ref->symref); free(ref); @@ -834,7 +918,7 @@ void free_refs(struct ref *ref) struct ref *next; while (ref) { next = ref->next; - free_ref(ref); + free_one_ref(ref); ref = next; } } @@ -968,12 +1052,13 @@ static char *guess_ref(const char *name, struct ref *peer) if (!r) return NULL; - if (starts_with(r, "refs/heads/")) + if (starts_with(r, "refs/heads/")) { strbuf_addstr(&buf, "refs/heads/"); - else if (starts_with(r, "refs/tags/")) + } else if (starts_with(r, "refs/tags/")) { strbuf_addstr(&buf, "refs/tags/"); - else + } else { return NULL; + } strbuf_addstr(&buf, name); return strbuf_detach(&buf, NULL); @@ -995,12 +1080,68 @@ static int match_explicit_lhs(struct ref *src, * 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); + 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); + return error(_("src refspec %s matches more than one"), rs->src); + } +} + +static void show_push_unqualified_ref_name_error(const char *dst_value, + const char *matched_src_name) +{ + struct object_id oid; + enum object_type type; + + /* + * TRANSLATORS: "matches '%s'%" is the <dst> part of "git push + * <remote> <src>:<dst>" push, and "being pushed ('%s')" is + * the <src>. + */ + error(_("The destination you provided is not a full refname (i.e.,\n" + "starting with \"refs/\"). We tried to guess what you meant by:\n" + "\n" + "- Looking for a ref that matches '%s' on the remote side.\n" + "- Checking if the <src> being pushed ('%s')\n" + " is a ref in \"refs/{heads,tags}/\". If so we add a corresponding\n" + " refs/{heads,tags}/ prefix on the remote side.\n" + "\n" + "Neither worked, so we gave up. You must fully qualify the ref."), + dst_value, matched_src_name); + + if (!advice_push_unqualified_ref_name) + return; + + if (get_oid(matched_src_name, &oid)) + BUG("'%s' is not a valid object, " + "match_explicit_lhs() should catch this!", + matched_src_name); + type = oid_object_info(the_repository, &oid, NULL); + if (type == OBJ_COMMIT) { + advise(_("The <src> part of the refspec is a commit object.\n" + "Did you mean to create a new branch by pushing to\n" + "'%s:refs/heads/%s'?"), + matched_src_name, dst_value); + } else if (type == OBJ_TAG) { + advise(_("The <src> part of the refspec is a tag object.\n" + "Did you mean to create a new tag by pushing to\n" + "'%s:refs/tags/%s'?"), + matched_src_name, dst_value); + } else if (type == OBJ_TREE) { + advise(_("The <src> part of the refspec is a tree object.\n" + "Did you mean to tag a new tree by pushing to\n" + "'%s:refs/tags/%s'?"), + matched_src_name, dst_value); + } else if (type == OBJ_BLOB) { + advise(_("The <src> part of the refspec is a blob object.\n" + "Did you mean to tag a new blob by pushing to\n" + "'%s:refs/tags/%s'?"), + matched_src_name, dst_value); + } else { + BUG("'%s' should be commit/tag/tree/blob, is '%d'", + matched_src_name, type); } } @@ -1014,7 +1155,7 @@ static int match_explicit(struct ref *src, struct ref *dst, const char *dst_value = rs->dst; char *dst_guess; - if (rs->pattern || rs->matching) + if (rs->pattern || rs->matching || rs->negative) return 0; matched_src = matched_dst = NULL; @@ -1030,7 +1171,7 @@ static int match_explicit(struct ref *src, struct ref *dst, if (!dst_value || ((flag & REF_ISSYMREF) && !starts_with(dst_value, "refs/heads/"))) - die("%s cannot be resolved to branch.", + die(_("%s cannot be resolved to branch"), matched_src->name); } @@ -1038,33 +1179,30 @@ static int match_explicit(struct ref *src, struct ref *dst, case 1: break; case 0: - if (starts_with(dst_value, "refs/")) + if (starts_with(dst_value, "refs/")) { matched_dst = make_linked_ref(dst_value, dst_tail); - else if (is_null_oid(&matched_src->new_oid)) - error("unable to delete '%s': remote ref does not exist", + } else if (is_null_oid(&matched_src->new_oid)) { + error(_("unable to delete '%s': remote ref does not exist"), dst_value); - else if ((dst_guess = guess_ref(dst_value, matched_src))) { + } else if ((dst_guess = guess_ref(dst_value, matched_src))) { matched_dst = make_linked_ref(dst_guess, dst_tail); free(dst_guess); - } else - error("unable to push to unqualified destination: %s\n" - "The destination refspec neither matches an " - "existing ref on the remote nor\n" - "begins with refs/, and we are unable to " - "guess a prefix based on the source ref.", - dst_value); + } else { + show_push_unqualified_ref_name_error(dst_value, + matched_src->name); + } break; default: matched_dst = NULL; - error("dst refspec %s matches more than one.", + error(_("dst refspec %s matches more than one"), dst_value); break; } if (!matched_dst) return -1; if (matched_dst->peer_ref) - return error("dst ref %s receives from more than one src.", - matched_dst->name); + return error(_("dst ref %s receives from more than one src"), + matched_dst->name); else { matched_dst->peer_ref = allocated_src ? matched_src : @@ -1093,6 +1231,10 @@ static char *get_ref_match(const struct refspec *rs, const struct ref *ref, int matching_refs = -1; for (i = 0; i < rs->nr; i++) { const struct refspec_item *item = &rs->items[i]; + + if (item->negative) + continue; + if (item->matching && (matching_refs == -1 || item->force)) { matching_refs = i; @@ -1298,7 +1440,7 @@ int check_push_refs(struct ref *src, struct refspec *rs) for (i = 0; i < rs->nr; i++) { struct refspec_item *item = &rs->items[i]; - if (item->pattern || item->matching) + if (item->pattern || item->matching || item->negative) continue; ret |= match_explicit_lhs(src, item, NULL, NULL); @@ -1400,6 +1542,8 @@ int match_push_refs(struct ref *src, struct ref **dst, string_list_clear(&src_ref_index, 0); } + *dst = apply_negative_refspecs(*dst, rs); + if (errs) return -1; return 0; @@ -1434,12 +1578,23 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, * with the remote-tracking branch to find the value * to expect, but we did not have such a tracking * branch. + * + * If the tip of the remote-tracking ref is unreachable + * from any reflog entry of its local ref indicating a + * possible update since checkout; reject the push. */ if (ref->expect_old_sha1) { if (!oideq(&ref->old_oid, &ref->old_oid_expect)) reject_reason = REF_STATUS_REJECT_STALE; + else if (ref->check_reachable && ref->unreachable) + reject_reason = + REF_STATUS_REJECT_REMOTE_UPDATED; else - /* If the ref isn't stale then force the update. */ + /* + * If the ref isn't stale, and is reachable + * from one of the reflog entries of + * the local branch, force the update. + */ force_ref_update = 1; } @@ -1509,7 +1664,7 @@ static void set_merge(struct branch *ret) remote = remote_get(ret->remote_name); - ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge)); + CALLOC_ARRAY(ret->merge, ret->merge_nr); 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]); @@ -1517,7 +1672,7 @@ static void set_merge(struct branch *ret) strcmp(ret->remote_name, ".")) continue; if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]), - &oid, &ref) == 1) + &oid, &ref, 0) == 1) ret->merge[i]->dst = ref; else ret->merge[i]->dst = xstrdup(ret->merge_name[i]); @@ -1532,7 +1687,7 @@ struct branch *branch_get(const char *name) if (!name || !*name || !strcmp(name, "HEAD")) ret = current_branch; else - ret = make_branch(name, 0); + ret = make_branch(name, strlen(name)); set_merge(ret); return ret; } @@ -1769,6 +1924,9 @@ int get_fetch_map(const struct ref *remote_refs, { struct ref *ref_map, **rmp; + if (refspec->negative) + return 0; + if (refspec->pattern) { ref_map = get_expanded_map(remote_refs, refspec); } else { @@ -1782,7 +1940,7 @@ int get_fetch_map(const struct ref *remote_refs, ref_map = get_remote_ref(remote_refs, name); } if (!missing_ok && !ref_map) - die("Couldn't find remote ref %s", name); + die(_("couldn't find remote ref %s"), name); if (ref_map) { ref_map->peer_ref = get_local_ref(refspec->dst); if (ref_map->peer_ref && refspec->force) @@ -1795,7 +1953,7 @@ int get_fetch_map(const struct ref *remote_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", + error(_("* Ignoring funny ref '%s' locally"), (*rmp)->peer_ref->name); *rmp = (*rmp)->next; free(ignore->peer_ref); @@ -1825,36 +1983,26 @@ int resolve_remote_symref(struct ref *ref, struct ref *list) } /* - * Lookup the upstream branch for the given branch and if present, optionally - * compute the commit ahead/behind values for the pair. + * Compute the commit ahead/behind values for the pair branch_name, base. * * If abf is AHEAD_BEHIND_FULL, compute the full ahead/behind and return the * counts in *num_ours and *num_theirs. If abf is AHEAD_BEHIND_QUICK, skip * the (potentially expensive) a/b computation (*num_ours and *num_theirs are * set to zero). * - * 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). Returns 0 if the commits are - * identical. Returns 1 if commits are different. + * Returns -1 if num_ours and num_theirs could not be filled in (e.g., ref + * does not exist). Returns 0 if the commits are identical. Returns 1 if + * commits are different. */ -int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, - const char **upstream_name, enum ahead_behind_flags abf) + +static int stat_branch_pair(const char *branch_name, const char *base, + int *num_ours, int *num_theirs, + enum ahead_behind_flags abf) { struct object_id oid; struct commit *ours, *theirs; struct rev_info revs; - const char *base; - struct argv_array argv = ARGV_ARRAY_INIT; - - /* 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; + struct strvec argv = STRVEC_INIT; /* Cannot stat if what we used to build on no longer exists */ if (read_ref(base, &oid)) @@ -1863,7 +2011,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, if (!theirs) return -1; - if (read_ref(branch->refname, &oid)) + if (read_ref(branch_name, &oid)) return -1; ours = lookup_commit_reference(the_repository, &oid); if (!ours) @@ -1877,20 +2025,20 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, if (abf == AHEAD_BEHIND_QUICK) return 1; if (abf != AHEAD_BEHIND_FULL) - BUG("stat_tracking_info: invalid abf '%d'", abf); + BUG("stat_branch_pair: invalid abf '%d'", abf); /* Run "rev-list --left-right ours...theirs" internally... */ - argv_array_push(&argv, ""); /* ignored */ - argv_array_push(&argv, "--left-right"); - argv_array_pushf(&argv, "%s...%s", - oid_to_hex(&ours->object.oid), - oid_to_hex(&theirs->object.oid)); - argv_array_push(&argv, "--"); + strvec_push(&argv, ""); /* ignored */ + strvec_push(&argv, "--left-right"); + strvec_pushf(&argv, "%s...%s", + oid_to_hex(&ours->object.oid), + oid_to_hex(&theirs->object.oid)); + strvec_push(&argv, "--"); repo_init_revisions(the_repository, &revs, NULL); - setup_revisions(argv.argc, argv.argv, &revs, NULL); + setup_revisions(argv.nr, argv.v, &revs, NULL); if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); /* ... and count the commits on each side. */ while (1) { @@ -1907,11 +2055,47 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, clear_commit_marks(ours, ALL_REV_FLAGS); clear_commit_marks(theirs, ALL_REV_FLAGS); - argv_array_clear(&argv); + strvec_clear(&argv); return 1; } /* + * Lookup the tracking branch for the given branch and if present, optionally + * compute the commit ahead/behind values for the pair. + * + * If for_push is true, the tracking branch refers to the push branch, + * otherwise it refers to the upstream branch. + * + * The name of the tracking branch (or NULL if it is not defined) is + * returned via *tracking_name, if it is not itself NULL. + * + * If abf is AHEAD_BEHIND_FULL, compute the full ahead/behind and return the + * counts in *num_ours and *num_theirs. If abf is AHEAD_BEHIND_QUICK, skip + * the (potentially expensive) a/b computation (*num_ours and *num_theirs are + * set to zero). + * + * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no + * upstream defined, or ref does not exist). Returns 0 if the commits are + * identical. Returns 1 if commits are different. + */ +int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, + const char **tracking_name, int for_push, + enum ahead_behind_flags abf) +{ + const char *base; + + /* Cannot stat unless we are marked to build on top of somebody else. */ + base = for_push ? branch_get_push(branch, NULL) : + branch_get_upstream(branch, NULL); + if (tracking_name) + *tracking_name = base; + if (!base) + return -1; + + return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf); +} + +/* * Return true when there is anything to report, otherwise false. */ int format_tracking_info(struct branch *branch, struct strbuf *sb, @@ -1922,7 +2106,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, char *base; int upstream_is_gone = 0; - sti = stat_tracking_info(branch, &ours, &theirs, &full_base, abf); + sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf); if (sti < 0) { if (!full_base) return 0; @@ -2030,8 +2214,17 @@ struct ref *guess_remote_head(const struct ref *head, if (head->symref) return copy_ref(find_ref_by_name(refs, head->symref)); - /* If refs/heads/master could be right, it is. */ + /* If a remote branch exists with the default branch name, let's use it. */ if (!all) { + char *ref = xstrfmt("refs/heads/%s", + git_default_branch_name(0)); + + r = find_ref_by_name(refs, ref); + free(ref); + if (r && oideq(&r->old_oid, &head->old_oid)) + return copy_ref(r); + + /* Fall back to the hard-coded historical default */ r = find_ref_by_name(refs, "refs/heads/master"); if (r && oideq(&r->old_oid, &head->old_oid)) return copy_ref(r); @@ -2163,7 +2356,8 @@ static int parse_push_cas_option(struct push_cas_option *cas, const char *arg, i else if (!colon[1]) oidclr(&entry->expect); else if (get_oid(colon + 1, &entry->expect)) - return error("cannot parse expected object name '%s'", colon + 1); + return error(_("cannot parse expected object name '%s'"), + colon + 1); return 0; } @@ -2179,12 +2373,13 @@ int is_empty_cas(const struct push_cas_option *cas) /* * Look at remote.fetch refspec and see if we have a remote - * tracking branch for the refname there. Fill its current - * value in sha1[]. + * tracking branch for the refname there. Fill the name of + * the remote-tracking branch in *dst_refname, and the name + * of the commit object at its tip in oid[]. * If we cannot do so, return negative to signal an error. */ static int remote_tracking(struct remote *remote, const char *refname, - struct object_id *oid) + struct object_id *oid, char **dst_refname) { char *dst; @@ -2193,9 +2388,150 @@ static int remote_tracking(struct remote *remote, const char *refname, return -1; /* no tracking ref for refname at remote */ if (read_ref(dst, oid)) return -1; /* we know what the tracking ref is but we cannot read it */ + + *dst_refname = dst; return 0; } +/* + * The struct "reflog_commit_array" and related helper functions + * are used for collecting commits into an array during reflog + * traversals in "check_and_collect_until()". + */ +struct reflog_commit_array { + struct commit **item; + size_t nr, alloc; +}; + +#define REFLOG_COMMIT_ARRAY_INIT { NULL, 0, 0 } + +/* Append a commit to the array. */ +static void append_commit(struct reflog_commit_array *arr, + struct commit *commit) +{ + ALLOC_GROW(arr->item, arr->nr + 1, arr->alloc); + arr->item[arr->nr++] = commit; +} + +/* Free and reset the array. */ +static void free_commit_array(struct reflog_commit_array *arr) +{ + FREE_AND_NULL(arr->item); + arr->nr = arr->alloc = 0; +} + +struct check_and_collect_until_cb_data { + struct commit *remote_commit; + struct reflog_commit_array *local_commits; + timestamp_t remote_reflog_timestamp; +}; + +/* Get the timestamp of the latest entry. */ +static int peek_reflog(struct object_id *o_oid, struct object_id *n_oid, + const char *ident, timestamp_t timestamp, + int tz, const char *message, void *cb_data) +{ + timestamp_t *ts = cb_data; + *ts = timestamp; + return 1; +} + +static int check_and_collect_until(struct object_id *o_oid, + struct object_id *n_oid, + const char *ident, timestamp_t timestamp, + int tz, const char *message, void *cb_data) +{ + struct commit *commit; + struct check_and_collect_until_cb_data *cb = cb_data; + + /* An entry was found. */ + if (oideq(n_oid, &cb->remote_commit->object.oid)) + return 1; + + if ((commit = lookup_commit_reference(the_repository, n_oid))) + append_commit(cb->local_commits, commit); + + /* + * If the reflog entry timestamp is older than the remote ref's + * latest reflog entry, there is no need to check or collect + * entries older than this one. + */ + if (timestamp < cb->remote_reflog_timestamp) + return -1; + + return 0; +} + +#define MERGE_BASES_BATCH_SIZE 8 + +/* + * Iterate through the reflog of the local ref to check if there is an entry + * for the given remote-tracking ref; runs until the timestamp of an entry is + * older than latest timestamp of remote-tracking ref's reflog. Any commits + * are that seen along the way are collected into an array to check if the + * remote-tracking ref is reachable from any of them. + */ +static int is_reachable_in_reflog(const char *local, const struct ref *remote) +{ + timestamp_t date; + struct commit *commit; + struct commit **chunk; + struct check_and_collect_until_cb_data cb; + struct reflog_commit_array arr = REFLOG_COMMIT_ARRAY_INIT; + size_t size = 0; + int ret = 0; + + commit = lookup_commit_reference(the_repository, &remote->old_oid); + if (!commit) + goto cleanup_return; + + /* + * Get the timestamp from the latest entry + * of the remote-tracking ref's reflog. + */ + for_each_reflog_ent_reverse(remote->tracking_ref, peek_reflog, &date); + + cb.remote_commit = commit; + cb.local_commits = &arr; + cb.remote_reflog_timestamp = date; + ret = for_each_reflog_ent_reverse(local, check_and_collect_until, &cb); + + /* We found an entry in the reflog. */ + if (ret > 0) + goto cleanup_return; + + /* + * Check if the remote commit is reachable from any + * of the commits in the collected array, in batches. + */ + for (chunk = arr.item; chunk < arr.item + arr.nr; chunk += size) { + size = arr.item + arr.nr - chunk; + if (MERGE_BASES_BATCH_SIZE < size) + size = MERGE_BASES_BATCH_SIZE; + + if ((ret = in_merge_bases_many(commit, size, chunk))) + break; + } + +cleanup_return: + free_commit_array(&arr); + return ret; +} + +/* + * Check for reachability of a remote-tracking + * ref in the reflog entries of its local ref. + */ +static void check_if_includes_upstream(struct ref *remote) +{ + struct ref *local = get_local_ref(remote->name); + if (!local) + return; + + if (is_reachable_in_reflog(local->name, remote) <= 0) + remote->unreachable = 1; +} + static void apply_cas(struct push_cas_option *cas, struct remote *remote, struct ref *ref) @@ -2210,8 +2546,12 @@ static void apply_cas(struct push_cas_option *cas, ref->expect_old_sha1 = 1; if (!entry->use_tracking) oidcpy(&ref->old_oid_expect, &entry->expect); - else if (remote_tracking(remote, ref->name, &ref->old_oid_expect)) + else if (remote_tracking(remote, ref->name, + &ref->old_oid_expect, + &ref->tracking_ref)) oidclr(&ref->old_oid_expect); + else + ref->check_reachable = cas->use_force_if_includes; return; } @@ -2220,8 +2560,12 @@ static void apply_cas(struct push_cas_option *cas, return; ref->expect_old_sha1 = 1; - if (remote_tracking(remote, ref->name, &ref->old_oid_expect)) + if (remote_tracking(remote, ref->name, + &ref->old_oid_expect, + &ref->tracking_ref)) oidclr(&ref->old_oid_expect); + else + ref->check_reachable = cas->use_force_if_includes; } void apply_push_cas(struct push_cas_option *cas, @@ -2229,6 +2573,15 @@ void apply_push_cas(struct push_cas_option *cas, struct ref *remote_refs) { struct ref *ref; - for (ref = remote_refs; ref; ref = ref->next) + for (ref = remote_refs; ref; ref = ref->next) { apply_cas(cas, remote, ref); + + /* + * If "compare-and-swap" is in "use_tracking[_for_rest]" + * mode, and if "--force-if-includes" was specified, run + * the check. + */ + if (ref->check_reachable) + check_if_includes_upstream(ref); + } } |