Message ID | 20200926114626.28823-2-shrinidhi.kaushik@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v7,1/3] push: add reflog check for "--force-if-includes" | expand |
Srinidhi Kaushik <shrinidhi.kaushik@gmail.com> writes: > @@ -2252,11 +2263,11 @@ 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[]. > + * value in sha1[], and as a string. I think the array being referred to was renamed to oid[] sometime ago. "and as a string" makes it sound as if sha1[] gets the value as 40-hex object name text, but that is not what is being done. Fill the name of the remote-tracking branch in *dst_refname, and the name of the commit object at tis tip in oid[]. perhaps? > * 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; > > @@ -2265,9 +2276,154 @@ 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_list" and related helper functions > + * for list manipulation are used for collecting commits into a > + * list during reflog traversals in "if_exists_or_grab_until()". Has the name of that function changed since this comment was written? > + */ > +struct reflog_commit_list { > + struct commit **items; Name an array in singular when its primary use is to work on an element at a time---that will let you say item[4] to call the 4-th item, instead of items[4] that smells awkward. An array that is used mostly to pass around a collection as a whole is easier to think about when given a plural name, though. > + size_t nr, alloc; > +}; > + > +/* Add a commit to list. */ > +static void add_commit(struct reflog_commit_list *list, struct commit *commit) > +{ > + ALLOC_GROW(list->items, list->nr + 1, list->alloc); > + list->items[list->nr++] = commit; > +} > + > +/* Free and reset the list. */ > +static void free_reflog_commit_list(struct reflog_commit_list *list) > +{ > + FREE_AND_NULL(list->items); > + list->nr = list->alloc = 0; > +} > + > +struct check_and_collect_until_cb_data { > + struct commit *remote_commit; > + struct reflog_commit_list *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; > +} The idea is to use a callback that immediately says "no more" to grab the data from the first item in the iteration. It feels somewhat awkward but because there is no "give us the Nth entry" API function, it is the cleanest way we can do this. > +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; > + > + /* > + * 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; > + > + /* An entry was found. */ > + if (oideq(n_oid, &cb->remote_commit->object.oid)) > + return 1; > + > + /* Look-up the commit and append it to the list. */ > + if ((commit = lookup_commit_reference(the_repository, n_oid))) > + add_commit(cb->local_commits, commit); This is merely a minor naming thing, but if you rename add_commit() to append_commit(), you probably do not even need the comment before this statement. > return 0; > } > > +#define MERGE_BASES_BATCH_SIZE 8 Hmph. Do we still need batching? > +/* > + * 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 a list 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_list list = { NULL, 0, 0 }; > + size_t count = 0, batch_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 = &list; > + 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; Good. So '1' from the callback is "we found one, no need to look further and no need to do merge-base", and '-1' from the callback is "we looked at all entries that are young enough to matter and we didn't find exact match". Makes sense. > + /* > + * Check if the remote commit is reachable from any > + * of the commits in the collected list, in batches. > + */ I do not know if batching would help (have you measured it?), but if we were to batch, it is more common to arrange the loop like this: for (chunk = list.items; chunk < list.items + list.nr; chunk += size) { size = list.items + list.nr - chunk; if (MERGE_BASES_BATCH_SIZE < size) size = MERGE_BASES_BATCH_SIZE; ... use chunk[0..size] ... chunk += size; } That is, assume that we can grab everything during this round, and if that bites off too many, clamp it to the maximum value. If you are not comfortable with pointer arithmetic, it is also fine to use an auxiliary variable 'count', but ... > + chunk = list.items; > + while (count < list.nr) { > + batch_size = MERGE_BASES_BATCH_SIZE; > + > + /* For any leftover entries. */ > + if ((count + MERGE_BASES_BATCH_SIZE) > list.nr) > + batch_size = list.nr - count; > + > + if ((ret = in_merge_bases_many(commit, batch_size, chunk))) > + break; > + > + chunk += batch_size; > + count += MERGE_BASES_BATCH_SIZE; ... you are risking chunk and count to go out of sync here. It does not matter within this loop (count will point beyond the end of list.item[] while chunk will never go past the array), but future developers can be confused into thinking that they can use chunk and count interchangeably after this loop exits, and at that point the discrepancy may start to matter. But all of the above matters if it is a good idea to batch. Does it make a difference? ... goes and looks at in_merge_bases_many() ... Ah, it probably would. I thought in_merge_bases_many() would stop early as soon as any of the traversal from chunk[] reaches commit, but it uses a rather more generic paint_down_to_common() so extra items in chunk[] that are topologically older than commit would result in additional traversal from commit down to them, which would not contribute much to the end result. It may be a good #leftovebit idea for future improvement to teach in_merge_bases_many() to use a custom replacement for paint_down_to_common() that stops early as soon as we find the answer is true. > + } > + > +cleanup_return: > + free_reflog_commit_list(&list); > + return ret; > +} > + Thanks.
Hi Junio, On 09/26/2020 16:42, Junio C Hamano wrote: > Srinidhi Kaushik <shrinidhi.kaushik@gmail.com> writes: > > > @@ -2252,11 +2263,11 @@ 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[]. > > + * value in sha1[], and as a string. > > I think the array being referred to was renamed to oid[] sometime > ago. "and as a string" makes it sound as if sha1[] gets the value > as 40-hex object name text, but that is not what is being done. > > Fill the name of the remote-tracking branch in *dst_refname, > and the name of the commit object at tis tip in oid[]. > > perhaps? Of course, that sounds better; will update. > > + * The struct "reflog_commit_list" and related helper functions > > + * for list manipulation are used for collecting commits into a > > + * list during reflog traversals in "if_exists_or_grab_until()". > > Has the name of that function changed since this comment was > written? Heh, it sure has. It should have been "check_and_collect_until()". > > + */ > > +struct reflog_commit_list { > > + struct commit **items; > > Name an array in singular when its primary use is to work on an > element at a time---that will let you say item[4] to call the 4-th > item, instead of items[4] that smells awkward. > > An array that is used mostly to pass around a collection as a whole > is easier to think about when given a plural name, though. Yup. > > + > > +/* 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; > > +} > > The idea is to use a callback that immediately says "no more" to > grab the data from the first item in the iteration. It feels > somewhat awkward but because there is no "give us the Nth entry" API > function, it is the cleanest way we can do this. I considered using "grab_1st_entry_timestamp()" briefy, but "peek_reflog" is shorter compared to that. > > + /* Look-up the commit and append it to the list. */ > > + if ((commit = lookup_commit_reference(the_repository, n_oid))) > > + add_commit(cb->local_commits, commit); > > This is merely a minor naming thing, but if you rename add_commit() > to append_commit(), you probably do not even need the comment before > this statement. Will do. > > return 0; > > } > > > > +#define MERGE_BASES_BATCH_SIZE 8 > > Hmph. Do we still need batching? > > > +/* > > + * 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 a list 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_list list = { NULL, 0, 0 }; > > + size_t count = 0, batch_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 = &list; > > + 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; > > Good. So '1' from the callback is "we found one, no need to look > further and no need to do merge-base", and '-1' from the callback is > "we looked at all entries that are young enough to matter and we > didn't find exact match". Makes sense. > > > + /* > > + * Check if the remote commit is reachable from any > > + * of the commits in the collected list, in batches. > > + */ > > I do not know if batching would help (have you measured it?), but if > we were to batch, it is more common to arrange the loop like this: > > for (chunk = list.items; > chunk < list.items + list.nr; > chunk += size) { > size = list.items + list.nr - chunk; > if (MERGE_BASES_BATCH_SIZE < size) > size = MERGE_BASES_BATCH_SIZE; > ... use chunk[0..size] ... > chunk += size; > } > > That is, assume that we can grab everything during this round, and > if that bites off too many, clamp it to the maximum value. If you > are not comfortable with pointer arithmetic, it is also fine to use > an auxiliary variable 'count', but ... Actually, the "for" version looks much cleaner and avoids the use of "count". However, I think ... > chunk += size; ... should be skipped because "for ( ... ; chunk += size)" is already doing it for us; otherwise we would offset 16 entries instead of 8 per iteration, no? > > + chunk = list.items; > > + while (count < list.nr) { > > + batch_size = MERGE_BASES_BATCH_SIZE; > > + > > + /* For any leftover entries. */ > > + if ((count + MERGE_BASES_BATCH_SIZE) > list.nr) > > + batch_size = list.nr - count; > > + > > + if ((ret = in_merge_bases_many(commit, batch_size, chunk))) > > + break; > > + > > + chunk += batch_size; > > + count += MERGE_BASES_BATCH_SIZE; > > ... you are risking chunk and count to go out of sync here. > > It does not matter within this loop (count will point beyond the end > of list.item[] while chunk will never go past the array), but future > developers can be confused into thinking that they can use chunk and > count interchangeably after this loop exits, and at that point the > discrepancy may start to matter. I agree, it should have been "count += batch_size;". But, I think the "for" version looks cleaner; I will change it to that the next set. > But all of the above matters if it is a good idea to batch. Does it > make a difference? > > ... goes and looks at in_merge_bases_many() ... > > Ah, it probably would. > > I thought in_merge_bases_many() would stop early as soon as any of > the traversal from chunk[] reaches commit, but it uses a rather more > generic paint_down_to_common() so extra items in chunk[] that are > topologically older than commit would result in additional traversal > from commit down to them, which would not contribute much to the end > result. It may be a good #leftovebit idea for future improvement to > teach in_merge_bases_many() to use a custom replacement for > paint_down_to_common() that stops early as soon as we find the > answer is true. If we consider the amount of time it takes when "in_merge_bases_many()" has to be run for all the entries, there isn't much of a difference in performance between batching and non-batching -- they took about the same. But, as you said if the remote is reachable in the first few entries, batching would help with returning early if a descendant is found. Making the function stop early when a descendent is found does sound like a good #leftoverbits idea. :) Thanks again, for a detailed review.
diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 7af148d733..516cba7336 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -71,6 +71,11 @@ static void print_helper_status(struct ref *ref) msg = "stale info"; break; + case REF_STATUS_REJECT_REMOTE_UPDATED: + res = "error"; + msg = "remote ref updated since checkout"; + break; + case REF_STATUS_REJECT_ALREADY_EXISTS: res = "error"; msg = "already exists"; diff --git a/remote.c b/remote.c index eafc14cbe7..a696243247 100644 --- a/remote.c +++ b/remote.c @@ -1471,12 +1471,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; } @@ -2252,11 +2263,11 @@ 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[]. + * value in sha1[], and as a string. * 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; @@ -2265,9 +2276,154 @@ 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_list" and related helper functions + * for list manipulation are used for collecting commits into a + * list during reflog traversals in "if_exists_or_grab_until()". + */ +struct reflog_commit_list { + struct commit **items; + size_t nr, alloc; +}; + +/* Add a commit to list. */ +static void add_commit(struct reflog_commit_list *list, struct commit *commit) +{ + ALLOC_GROW(list->items, list->nr + 1, list->alloc); + list->items[list->nr++] = commit; +} + +/* Free and reset the list. */ +static void free_reflog_commit_list(struct reflog_commit_list *list) +{ + FREE_AND_NULL(list->items); + list->nr = list->alloc = 0; +} + +struct check_and_collect_until_cb_data { + struct commit *remote_commit; + struct reflog_commit_list *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; + + /* + * 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; + + /* An entry was found. */ + if (oideq(n_oid, &cb->remote_commit->object.oid)) + return 1; + + /* Look-up the commit and append it to the list. */ + if ((commit = lookup_commit_reference(the_repository, n_oid))) + add_commit(cb->local_commits, commit); + 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 a list 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_list list = { NULL, 0, 0 }; + size_t count = 0, batch_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 = &list; + 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 list, in batches. + */ + chunk = list.items; + while (count < list.nr) { + batch_size = MERGE_BASES_BATCH_SIZE; + + /* For any leftover entries. */ + if ((count + MERGE_BASES_BATCH_SIZE) > list.nr) + batch_size = list.nr - count; + + if ((ret = in_merge_bases_many(commit, batch_size, chunk))) + break; + + chunk += batch_size; + count += MERGE_BASES_BATCH_SIZE; + } + +cleanup_return: + free_reflog_commit_list(&list); + 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) @@ -2282,8 +2438,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; } @@ -2292,8 +2452,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, @@ -2301,6 +2465,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); + } } diff --git a/remote.h b/remote.h index eb62a47044..2d5391d281 100644 --- a/remote.h +++ b/remote.h @@ -107,12 +107,20 @@ struct ref { struct object_id new_oid; struct object_id old_oid_expect; /* used by expect-old */ char *symref; + char *tracking_ref; unsigned int force:1, forced_update:1, expect_old_sha1:1, exact_oid:1, - deletion:1; + deletion:1, + /* Need to check if local reflog reaches the remote tip. */ + check_reachable:1, + /* + * Store the result of the check enabled by "check_reachable"; + * implies the local reflog does not reach the remote tip. + */ + unreachable:1; enum { REF_NOT_MATCHED = 0, /* initial value */ @@ -142,6 +150,7 @@ struct ref { REF_STATUS_REJECT_NEEDS_FORCE, REF_STATUS_REJECT_STALE, REF_STATUS_REJECT_SHALLOW, + REF_STATUS_REJECT_REMOTE_UPDATED, REF_STATUS_UPTODATE, REF_STATUS_REMOTE_REJECT, REF_STATUS_EXPECTING_REPORT, @@ -341,6 +350,7 @@ struct ref *get_stale_heads(struct refspec *rs, struct ref *fetch_map); struct push_cas_option { unsigned use_tracking_for_rest:1; + unsigned use_force_if_includes:1; struct push_cas { struct object_id expect; unsigned use_tracking:1; diff --git a/send-pack.c b/send-pack.c index c9698070fc..eb4a44270b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -299,6 +299,7 @@ static int check_to_send_update(const struct ref *ref, const struct send_pack_ar case REF_STATUS_REJECT_FETCH_FIRST: case REF_STATUS_REJECT_NEEDS_FORCE: case REF_STATUS_REJECT_STALE: + case REF_STATUS_REJECT_REMOTE_UPDATED: case REF_STATUS_REJECT_NODELETE: return CHECK_REF_STATUS_REJECTED; case REF_STATUS_UPTODATE: diff --git a/transport-helper.c b/transport-helper.c index b573b6c730..6157de30c7 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -827,6 +827,10 @@ static int push_update_ref_status(struct strbuf *buf, status = REF_STATUS_REJECT_STALE; FREE_AND_NULL(msg); } + else if (!strcmp(msg, "remote ref updated since checkout")) { + status = REF_STATUS_REJECT_REMOTE_UPDATED; + FREE_AND_NULL(msg); + } else if (!strcmp(msg, "forced update")) { forced = 1; FREE_AND_NULL(msg); @@ -967,6 +971,7 @@ static int push_refs_with_push(struct transport *transport, case REF_STATUS_REJECT_NONFASTFORWARD: case REF_STATUS_REJECT_STALE: case REF_STATUS_REJECT_ALREADY_EXISTS: + case REF_STATUS_REJECT_REMOTE_UPDATED: if (atomic) { reject_atomic_push(remote_refs, mirror); string_list_clear(&cas_options, 0); diff --git a/transport.c b/transport.c index ffe2115845..65fcd22b20 100644 --- a/transport.c +++ b/transport.c @@ -633,6 +633,11 @@ static int print_one_push_report(struct ref *ref, const char *dest, int count, "stale info", report, porcelain, summary_width); break; + case REF_STATUS_REJECT_REMOTE_UPDATED: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "remote ref updated since checkout", + report, porcelain, summary_width); + break; case REF_STATUS_REJECT_SHALLOW: print_ref_status('!', "[rejected]", ref, ref->peer_ref, "new shallow roots not allowed", @@ -1185,6 +1190,7 @@ static int run_pre_push_hook(struct transport *transport, if (!r->peer_ref) continue; if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue; if (r->status == REF_STATUS_REJECT_STALE) continue; + if (r->status == REF_STATUS_REJECT_REMOTE_UPDATED) continue; if (r->status == REF_STATUS_UPTODATE) continue; strbuf_reset(&buf);
Add a check to verify if the remote-tracking ref of the local branch is reachable from one of its "reflog" entries. The check iterates through the local ref's reflog to see if there is an entry for the remote-tracking ref and collecting any commits that are seen, into a list; the iteration stops if an entry in the reflog matches the remote ref or if the entry timestamp is older the latest entry of the remote ref's "reflog". If there wasn't an entry found for the remote ref, "in_merge_bases_many()" is called to check if it is reachable from the list of collected commits. When a local branch that is based on a remote ref, has been rewound and is to be force pushed on the remote, "--force-if-includes" runs a check that ensures any updates to the remote-tracking ref that may have happened (by push from another repository) in-between the time of the last update to the local branch (via "git-pull", for instance) and right before the time of push, have been integrated locally before allowing a forced update. If the new option is passed without specifying "--force-with-lease", or specified along with "--force-with-lease=<refname>:<expect>" it is a "no-op". Signed-off-by: Srinidhi Kaushik <shrinidhi.kaushik@gmail.com> --- builtin/send-pack.c | 5 ++ remote.c | 185 ++++++++++++++++++++++++++++++++++++++++++-- remote.h | 12 ++- send-pack.c | 1 + transport-helper.c | 5 ++ transport.c | 6 ++ 6 files changed, 207 insertions(+), 7 deletions(-)