diff options
Diffstat (limited to 'remote.c')
-rw-r--r-- | remote.c | 207 |
1 files changed, 194 insertions, 13 deletions
@@ -284,7 +284,7 @@ static void read_branches_file(struct remote *remote) if (frag) *(frag++) = '\0'; else - frag = (char *)git_default_branch_name(); + frag = (char *)git_default_branch_name(0); add_url_alias(remote, strbuf_detach(&buf, NULL)); refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s", @@ -355,7 +355,7 @@ static int handle_config(const char *key, const char *value, void *cb) remote = make_remote(name, namelen); remote->origin = REMOTE_CONFIG; if (current_config_scope() == CONFIG_SCOPE_LOCAL || - current_config_scope() == CONFIG_SCOPE_WORKTREE) + current_config_scope() == CONFIG_SCOPE_WORKTREE) remote->configured_in_repo = 1; if (!strcmp(subkey, "mirror")) remote->mirror = git_config_bool(key, value); @@ -736,6 +736,12 @@ static int query_matches_negative_refspec(struct refspec *rs, struct refspec_ite * 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]; @@ -751,9 +757,13 @@ static int query_matches_negative_refspec(struct refspec *rs, struct refspec_ite if (match_name_with_pattern(key, needle, value, &expn_name)) string_list_append_nodup(&reversed, expn_name); - } else { - if (!strcmp(needle, refspec->src)) - string_list_append(&reversed, refspec->src); + } 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); } } @@ -1568,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 from one of the reflog entries of + * the local branch, force the update. + */ force_ref_update = 1; } @@ -2195,7 +2216,8 @@ struct ref *guess_remote_head(const struct ref *head, /* 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()); + char *ref = xstrfmt("refs/heads/%s", + git_default_branch_name(0)); r = find_ref_by_name(refs, ref); free(ref); @@ -2351,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; @@ -2365,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) @@ -2382,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; } @@ -2392,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, @@ -2401,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); + } } |