@matheusbsbs / matheus-bagliano / commits / 99a1f9ae10
Fork 0

push: add reflog check for "--force-if-includes"

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> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Srinidhi Kaushik committed Oct 3, 2020 at 17:40 UTC 99a1f9ae10816c2527d7197a5dde714f980b712b
6 files changed +205 -8
builtin/send-pack.c
+5
@@ -71,6 +71,11 @@ static void print_helper_status(struct ref *ref)
71 msg = "stale info";
72 break;
73
74 + case REF_STATUS_REJECT_REMOTE_UPDATED:
75 + res = "error";
76 + msg = "remote ref updated since checkout";
77 + break;
78 +
79 case REF_STATUS_REJECT_ALREADY_EXISTS:
80 res = "error";
81 msg = "already exists";
remote.c
+177 -7
@@ -1471,12 +1471,23 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
1471 * with the remote-tracking branch to find the value
1472 * to expect, but we did not have such a tracking
1473 * branch.
1474 + *
1475 + * If the tip of the remote-tracking ref is unreachable
1476 + * from any reflog entry of its local ref indicating a
1477 + * possible update since checkout; reject the push.
1478 */
1479 if (ref->expect_old_sha1) {
1480 if (!oideq(&ref->old_oid, &ref->old_oid_expect))
1481 reject_reason = REF_STATUS_REJECT_STALE;
1482 + else if (ref->check_reachable && ref->unreachable)
1483 + reject_reason =
1484 + REF_STATUS_REJECT_REMOTE_UPDATED;
1485 else
1479 - /* If the ref isn't stale then force the update. */
1486 + /*
1487 + * If the ref isn't stale, and is reachable
1488 + * from from one of the reflog entries of
1489 + * the local branch, force the update.
1490 + */
1491 force_ref_update = 1;
1492 }
1493
@@ -2251,12 +2262,13 @@ int is_empty_cas(const struct push_cas_option *cas)
2262
2263 /*
2264 * Look at remote.fetch refspec and see if we have a remote
2254 - * tracking branch for the refname there. Fill its current
2255 - * value in sha1[].
2265 + * tracking branch for the refname there. Fill the name of
2266 + * the remote-tracking branch in *dst_refname, and the name
2267 + * of the commit object at its tip in oid[].
2268 * If we cannot do so, return negative to signal an error.
2269 */
2270 static int remote_tracking(struct remote *remote, const char *refname,
2259 - struct object_id *oid)
2271 + struct object_id *oid, char **dst_refname)
2272 {
2273 char *dst;
2274
@@ -2265,9 +2277,150 @@ static int remote_tracking(struct remote *remote, const char *refname,
2277 return -1; /* no tracking ref for refname at remote */
2278 if (read_ref(dst, oid))
2279 return -1; /* we know what the tracking ref is but we cannot read it */
2280 +
2281 + *dst_refname = dst;
2282 return 0;
2283 }
2284
2285 +/*
2286 + * The struct "reflog_commit_array" and related helper functions
2287 + * are used for collecting commits into an array during reflog
2288 + * traversals in "check_and_collect_until()".
2289 + */
2290 +struct reflog_commit_array {
2291 + struct commit **item;
2292 + size_t nr, alloc;
2293 +};
2294 +
2295 +#define REFLOG_COMMIT_ARRAY_INIT { NULL, 0, 0 }
2296 +
2297 +/* Append a commit to the array. */
2298 +static void append_commit(struct reflog_commit_array *arr,
2299 + struct commit *commit)
2300 +{
2301 + ALLOC_GROW(arr->item, arr->nr + 1, arr->alloc);
2302 + arr->item[arr->nr++] = commit;
2303 +}
2304 +
2305 +/* Free and reset the array. */
2306 +static void free_commit_array(struct reflog_commit_array *arr)
2307 +{
2308 + FREE_AND_NULL(arr->item);
2309 + arr->nr = arr->alloc = 0;
2310 +}
2311 +
2312 +struct check_and_collect_until_cb_data {
2313 + struct commit *remote_commit;
2314 + struct reflog_commit_array *local_commits;
2315 + timestamp_t remote_reflog_timestamp;
2316 +};
2317 +
2318 +/* Get the timestamp of the latest entry. */
2319 +static int peek_reflog(struct object_id *o_oid, struct object_id *n_oid,
2320 + const char *ident, timestamp_t timestamp,
2321 + int tz, const char *message, void *cb_data)
2322 +{
2323 + timestamp_t *ts = cb_data;
2324 + *ts = timestamp;
2325 + return 1;
2326 +}
2327 +
2328 +static int check_and_collect_until(struct object_id *o_oid,
2329 + struct object_id *n_oid,
2330 + const char *ident, timestamp_t timestamp,
2331 + int tz, const char *message, void *cb_data)
2332 +{
2333 + struct commit *commit;
2334 + struct check_and_collect_until_cb_data *cb = cb_data;
2335 +
2336 + /* An entry was found. */
2337 + if (oideq(n_oid, &cb->remote_commit->object.oid))
2338 + return 1;
2339 +
2340 + if ((commit = lookup_commit_reference(the_repository, n_oid)))
2341 + append_commit(cb->local_commits, commit);
2342 +
2343 + /*
2344 + * If the reflog entry timestamp is older than the remote ref's
2345 + * latest reflog entry, there is no need to check or collect
2346 + * entries older than this one.
2347 + */
2348 + if (timestamp < cb->remote_reflog_timestamp)
2349 + return -1;
2350 +
2351 + return 0;
2352 +}
2353 +
2354 +#define MERGE_BASES_BATCH_SIZE 8
2355 +
2356 +/*
2357 + * Iterate through the reflog of the local ref to check if there is an entry
2358 + * for the given remote-tracking ref; runs until the timestamp of an entry is
2359 + * older than latest timestamp of remote-tracking ref's reflog. Any commits
2360 + * are that seen along the way are collected into an array to check if the
2361 + * remote-tracking ref is reachable from any of them.
2362 + */
2363 +static int is_reachable_in_reflog(const char *local, const struct ref *remote)
2364 +{
2365 + timestamp_t date;
2366 + struct commit *commit;
2367 + struct commit **chunk;
2368 + struct check_and_collect_until_cb_data cb;
2369 + struct reflog_commit_array arr = REFLOG_COMMIT_ARRAY_INIT;
2370 + size_t size = 0;
2371 + int ret = 0;
2372 +
2373 + commit = lookup_commit_reference(the_repository, &remote->old_oid);
2374 + if (!commit)
2375 + goto cleanup_return;
2376 +
2377 + /*
2378 + * Get the timestamp from the latest entry
2379 + * of the remote-tracking ref's reflog.
2380 + */
2381 + for_each_reflog_ent_reverse(remote->tracking_ref, peek_reflog, &date);
2382 +
2383 + cb.remote_commit = commit;
2384 + cb.local_commits = &arr;
2385 + cb.remote_reflog_timestamp = date;
2386 + ret = for_each_reflog_ent_reverse(local, check_and_collect_until, &cb);
2387 +
2388 + /* We found an entry in the reflog. */
2389 + if (ret > 0)
2390 + goto cleanup_return;
2391 +
2392 + /*
2393 + * Check if the remote commit is reachable from any
2394 + * of the commits in the collected array, in batches.
2395 + */
2396 + for (chunk = arr.item; chunk < arr.item + arr.nr; chunk += size) {
2397 + size = arr.item + arr.nr - chunk;
2398 + if (MERGE_BASES_BATCH_SIZE < size)
2399 + size = MERGE_BASES_BATCH_SIZE;
2400 +
2401 + if ((ret = in_merge_bases_many(commit, size, chunk)))
2402 + break;
2403 + }
2404 +
2405 +cleanup_return:
2406 + free_commit_array(&arr);
2407 + return ret;
2408 +}
2409 +
2410 +/*
2411 + * Check for reachability of a remote-tracking
2412 + * ref in the reflog entries of its local ref.
2413 + */
2414 +static void check_if_includes_upstream(struct ref *remote)
2415 +{
2416 + struct ref *local = get_local_ref(remote->name);
2417 + if (!local)
2418 + return;
2419 +
2420 + if (is_reachable_in_reflog(local->name, remote) <= 0)
2421 + remote->unreachable = 1;
2422 +}
2423 +
2424 static void apply_cas(struct push_cas_option *cas,
2425 struct remote *remote,
2426 struct ref *ref)
@@ -2282,8 +2435,12 @@ static void apply_cas(struct push_cas_option *cas,
2435 ref->expect_old_sha1 = 1;
2436 if (!entry->use_tracking)
2437 oidcpy(&ref->old_oid_expect, &entry->expect);
2285 - else if (remote_tracking(remote, ref->name, &ref->old_oid_expect))
2438 + else if (remote_tracking(remote, ref->name,
2439 + &ref->old_oid_expect,
2440 + &ref->tracking_ref))
2441 oidclr(&ref->old_oid_expect);
2442 + else
2443 + ref->check_reachable = cas->use_force_if_includes;
2444 return;
2445 }
2446
@@ -2292,8 +2449,12 @@ static void apply_cas(struct push_cas_option *cas,
2449 return;
2450
2451 ref->expect_old_sha1 = 1;
2295 - if (remote_tracking(remote, ref->name, &ref->old_oid_expect))
2452 + if (remote_tracking(remote, ref->name,
2453 + &ref->old_oid_expect,
2454 + &ref->tracking_ref))
2455 oidclr(&ref->old_oid_expect);
2456 + else
2457 + ref->check_reachable = cas->use_force_if_includes;
2458 }
2459
2460 void apply_push_cas(struct push_cas_option *cas,
@@ -2301,6 +2462,15 @@ void apply_push_cas(struct push_cas_option *cas,
2462 struct ref *remote_refs)
2463 {
2464 struct ref *ref;
2304 - for (ref = remote_refs; ref; ref = ref->next)
2465 + for (ref = remote_refs; ref; ref = ref->next) {
2466 apply_cas(cas, remote, ref);
2467 +
2468 + /*
2469 + * If "compare-and-swap" is in "use_tracking[_for_rest]"
2470 + * mode, and if "--force-if-includes" was specified, run
2471 + * the check.
2472 + */
2473 + if (ref->check_reachable)
2474 + check_if_includes_upstream(ref);
2475 + }
2476 }
remote.h
+11 -1
@@ -107,12 +107,20 @@ struct ref {
107 struct object_id new_oid;
108 struct object_id old_oid_expect; /* used by expect-old */
109 char *symref;
110 + char *tracking_ref;
111 unsigned int
112 force:1,
113 forced_update:1,
114 expect_old_sha1:1,
115 exact_oid:1,
115 - deletion:1;
116 + deletion:1,
117 + /* Need to check if local reflog reaches the remote tip. */
118 + check_reachable:1,
119 + /*
120 + * Store the result of the check enabled by "check_reachable";
121 + * implies the local reflog does not reach the remote tip.
122 + */
123 + unreachable:1;
124
125 enum {
126 REF_NOT_MATCHED = 0, /* initial value */
@@ -142,6 +150,7 @@ struct ref {
150 REF_STATUS_REJECT_NEEDS_FORCE,
151 REF_STATUS_REJECT_STALE,
152 REF_STATUS_REJECT_SHALLOW,
153 + REF_STATUS_REJECT_REMOTE_UPDATED,
154 REF_STATUS_UPTODATE,
155 REF_STATUS_REMOTE_REJECT,
156 REF_STATUS_EXPECTING_REPORT,
@@ -341,6 +350,7 @@ struct ref *get_stale_heads(struct refspec *rs, struct ref *fetch_map);
350
351 struct push_cas_option {
352 unsigned use_tracking_for_rest:1;
353 + unsigned use_force_if_includes:1;
354 struct push_cas {
355 struct object_id expect;
356 unsigned use_tracking:1;
send-pack.c
+1
@@ -299,6 +299,7 @@ static int check_to_send_update(const struct ref *ref, const struct send_pack_ar
299 case REF_STATUS_REJECT_FETCH_FIRST:
300 case REF_STATUS_REJECT_NEEDS_FORCE:
301 case REF_STATUS_REJECT_STALE:
302 + case REF_STATUS_REJECT_REMOTE_UPDATED:
303 case REF_STATUS_REJECT_NODELETE:
304 return CHECK_REF_STATUS_REJECTED;
305 case REF_STATUS_UPTODATE:
transport-helper.c
+5
@@ -827,6 +827,10 @@ static int push_update_ref_status(struct strbuf *buf,
827 status = REF_STATUS_REJECT_STALE;
828 FREE_AND_NULL(msg);
829 }
830 + else if (!strcmp(msg, "remote ref updated since checkout")) {
831 + status = REF_STATUS_REJECT_REMOTE_UPDATED;
832 + FREE_AND_NULL(msg);
833 + }
834 else if (!strcmp(msg, "forced update")) {
835 forced = 1;
836 FREE_AND_NULL(msg);
@@ -967,6 +971,7 @@ static int push_refs_with_push(struct transport *transport,
971 case REF_STATUS_REJECT_NONFASTFORWARD:
972 case REF_STATUS_REJECT_STALE:
973 case REF_STATUS_REJECT_ALREADY_EXISTS:
974 + case REF_STATUS_REJECT_REMOTE_UPDATED:
975 if (atomic) {
976 reject_atomic_push(remote_refs, mirror);
977 string_list_clear(&cas_options, 0);
transport.c
+6
@@ -633,6 +633,11 @@ static int print_one_push_report(struct ref *ref, const char *dest, int count,
633 "stale info",
634 report, porcelain, summary_width);
635 break;
636 + case REF_STATUS_REJECT_REMOTE_UPDATED:
637 + print_ref_status('!', "[rejected]", ref, ref->peer_ref,
638 + "remote ref updated since checkout",
639 + report, porcelain, summary_width);
640 + break;
641 case REF_STATUS_REJECT_SHALLOW:
642 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
643 "new shallow roots not allowed",
@@ -1185,6 +1190,7 @@ static int run_pre_push_hook(struct transport *transport,
1190 if (!r->peer_ref) continue;
1191 if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
1192 if (r->status == REF_STATUS_REJECT_STALE) continue;
1193 + if (r->status == REF_STATUS_REJECT_REMOTE_UPDATED) continue;
1194 if (r->status == REF_STATUS_UPTODATE) continue;
1195
1196 strbuf_reset(&buf);