@@ -1471,12 +1471,23 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
1471
1471
* with the remote-tracking branch to find the value
1472
1472
* to expect, but we did not have such a tracking
1473
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.
1474
1478
*/
1475
1479
if (ref -> expect_old_sha1 ) {
1476
1480
if (!oideq (& ref -> old_oid , & ref -> old_oid_expect ))
1477
1481
reject_reason = REF_STATUS_REJECT_STALE ;
1482
+ else if (ref -> check_reachable && ref -> unreachable )
1483
+ reject_reason =
1484
+ REF_STATUS_REJECT_REMOTE_UPDATED ;
1478
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
+ */
1480
1491
force_ref_update = 1 ;
1481
1492
}
1482
1493
@@ -2251,12 +2262,13 @@ int is_empty_cas(const struct push_cas_option *cas)
2251
2262
2252
2263
/*
2253
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[].
2256
2268
* If we cannot do so, return negative to signal an error.
2257
2269
*/
2258
2270
static int remote_tracking (struct remote * remote , const char * refname ,
2259
- struct object_id * oid )
2271
+ struct object_id * oid , char * * dst_refname )
2260
2272
{
2261
2273
char * dst ;
2262
2274
@@ -2265,9 +2277,164 @@ static int remote_tracking(struct remote *remote, const char *refname,
2265
2277
return -1 ; /* no tracking ref for refname at remote */
2266
2278
if (read_ref (dst , oid ))
2267
2279
return -1 ; /* we know what the tracking ref is but we cannot read it */
2280
+
2281
+ * dst_refname = dst ;
2268
2282
return 0 ;
2269
2283
}
2270
2284
2285
+ /*
2286
+ * The struct "reflog_commit_list" and related helper functions
2287
+ * for list manipulation are used for collecting commits into a
2288
+ * list during reflog traversals in "check_and_collect_until()".
2289
+ */
2290
+ struct reflog_commit_list {
2291
+ struct commit * * item ;
2292
+ size_t nr , alloc ;
2293
+ };
2294
+
2295
+ /* Append a commit to the list. */
2296
+ static void append_commit (struct reflog_commit_list * list ,
2297
+ struct commit * commit )
2298
+ {
2299
+ ALLOC_GROW (list -> item , list -> nr + 1 , list -> alloc );
2300
+ list -> item [list -> nr ++ ] = commit ;
2301
+ }
2302
+
2303
+ /* Free and reset the list. */
2304
+ static void free_reflog_commit_list (struct reflog_commit_list * list )
2305
+ {
2306
+ FREE_AND_NULL (list -> item );
2307
+ list -> nr = list -> alloc = 0 ;
2308
+ }
2309
+
2310
+ struct check_and_collect_until_cb_data {
2311
+ struct commit * remote_commit ;
2312
+ struct reflog_commit_list * local_commits ;
2313
+ timestamp_t remote_reflog_timestamp ;
2314
+ };
2315
+
2316
+ /* Get the timestamp of the latest entry. */
2317
+ static int peek_reflog (struct object_id * o_oid , struct object_id * n_oid ,
2318
+ const char * ident , timestamp_t timestamp ,
2319
+ int tz , const char * message , void * cb_data )
2320
+ {
2321
+ timestamp_t * ts = cb_data ;
2322
+ * ts = timestamp ;
2323
+ return 1 ;
2324
+ }
2325
+
2326
+ static int check_and_collect_until (struct object_id * o_oid ,
2327
+ struct object_id * n_oid ,
2328
+ const char * ident , timestamp_t timestamp ,
2329
+ int tz , const char * message , void * cb_data )
2330
+ {
2331
+ struct commit * commit ;
2332
+ struct check_and_collect_until_cb_data * cb = cb_data ;
2333
+
2334
+ /* An entry was found. */
2335
+ if (oideq (n_oid , & cb -> remote_commit -> object .oid ))
2336
+ return 1 ;
2337
+
2338
+ if ((commit = lookup_commit_reference (the_repository , n_oid )))
2339
+ append_commit (cb -> local_commits , commit );
2340
+
2341
+ /*
2342
+ * If the reflog entry timestamp is older than the remote ref's
2343
+ * latest reflog entry, there is no need to check or collect
2344
+ * entries older than this one.
2345
+ */
2346
+ if (timestamp < cb -> remote_reflog_timestamp )
2347
+ return -1 ;
2348
+
2349
+ return 0 ;
2350
+ }
2351
+
2352
+ #define MERGE_BASES_BATCH_SIZE 8
2353
+
2354
+ /*
2355
+ * Iterate through the reflog of the local ref to check if there is an entry
2356
+ * for the given remote-tracking ref; runs until the timestamp of an entry is
2357
+ * older than latest timestamp of remote-tracking ref's reflog. Any commits
2358
+ * are that seen along the way are collected into a list to check if the
2359
+ * remote-tracking ref is reachable from any of them.
2360
+ */
2361
+ static int is_reachable_in_reflog (const char * local , const struct ref * remote )
2362
+ {
2363
+ timestamp_t date ;
2364
+ struct commit * commit ;
2365
+ struct commit * * chunk ;
2366
+ struct check_and_collect_until_cb_data cb ;
2367
+ struct reflog_commit_list list = { NULL , 0 , 0 };
2368
+ size_t size = 0 ;
2369
+ int ret = 0 ;
2370
+
2371
+ commit = lookup_commit_reference (the_repository , & remote -> old_oid );
2372
+ if (!commit )
2373
+ goto cleanup_return ;
2374
+
2375
+ /*
2376
+ * Get the timestamp from the latest entry
2377
+ * of the remote-tracking ref's reflog.
2378
+ */
2379
+ for_each_reflog_ent_reverse (remote -> tracking_ref , peek_reflog , & date );
2380
+
2381
+ cb .remote_commit = commit ;
2382
+ cb .local_commits = & list ;
2383
+ cb .remote_reflog_timestamp = date ;
2384
+ ret = for_each_reflog_ent_reverse (local , check_and_collect_until , & cb );
2385
+
2386
+ /* We found an entry in the reflog. */
2387
+ if (ret > 0 )
2388
+ goto cleanup_return ;
2389
+
2390
+ /*
2391
+ * Check if the remote commit is reachable from any
2392
+ * of the commits in the collected list, in batches.
2393
+ */
2394
+ for (chunk = list .item ; chunk < list .item + list .nr ; chunk += size ) {
2395
+ size = list .item + list .nr - chunk ;
2396
+ if (MERGE_BASES_BATCH_SIZE < size )
2397
+ size = MERGE_BASES_BATCH_SIZE ;
2398
+
2399
+ if ((ret = in_merge_bases_many (commit , size , chunk )))
2400
+ break ;
2401
+ }
2402
+
2403
+ cleanup_return :
2404
+ free_reflog_commit_list (& list );
2405
+ return ret ;
2406
+ }
2407
+
2408
+ /* Toggle the "commit-graph" feature; return the previously set state. */
2409
+ static int toggle_commit_graph (struct repository * repo , int disable ) {
2410
+ int prev = repo -> commit_graph_disabled ;
2411
+ repo -> commit_graph_disabled = disable ;
2412
+ return prev ;
2413
+ }
2414
+
2415
+ /*
2416
+ * Check for reachability of a remote-tracking
2417
+ * ref in the reflog entries of its local ref.
2418
+ */
2419
+ static void check_if_includes_upstream (struct ref * remote )
2420
+ {
2421
+ int prev ;
2422
+ struct ref * local = get_local_ref (remote -> name );
2423
+ if (!local )
2424
+ return ;
2425
+
2426
+ /*
2427
+ * TODO: Remove "toggle_commit_graph()" calls around the check.
2428
+ * Depending on whether "commit-graph" enabled or not,
2429
+ * "in_merge_bases_many()" returns different results;
2430
+ * disable it temporarily when the check runs.
2431
+ */
2432
+ prev = toggle_commit_graph (the_repository , 1 );
2433
+ if (is_reachable_in_reflog (local -> name , remote ) <= 0 )
2434
+ remote -> unreachable = 1 ;
2435
+ toggle_commit_graph (the_repository , prev );
2436
+ }
2437
+
2271
2438
static void apply_cas (struct push_cas_option * cas ,
2272
2439
struct remote * remote ,
2273
2440
struct ref * ref )
@@ -2282,8 +2449,12 @@ static void apply_cas(struct push_cas_option *cas,
2282
2449
ref -> expect_old_sha1 = 1 ;
2283
2450
if (!entry -> use_tracking )
2284
2451
oidcpy (& ref -> old_oid_expect , & entry -> expect );
2285
- else if (remote_tracking (remote , ref -> name , & ref -> old_oid_expect ))
2452
+ else if (remote_tracking (remote , ref -> name ,
2453
+ & ref -> old_oid_expect ,
2454
+ & ref -> tracking_ref ))
2286
2455
oidclr (& ref -> old_oid_expect );
2456
+ else
2457
+ ref -> check_reachable = cas -> use_force_if_includes ;
2287
2458
return ;
2288
2459
}
2289
2460
@@ -2292,15 +2463,28 @@ static void apply_cas(struct push_cas_option *cas,
2292
2463
return ;
2293
2464
2294
2465
ref -> expect_old_sha1 = 1 ;
2295
- if (remote_tracking (remote , ref -> name , & ref -> old_oid_expect ))
2466
+ if (remote_tracking (remote , ref -> name ,
2467
+ & ref -> old_oid_expect ,
2468
+ & ref -> tracking_ref ))
2296
2469
oidclr (& ref -> old_oid_expect );
2470
+ else
2471
+ ref -> check_reachable = cas -> use_force_if_includes ;
2297
2472
}
2298
2473
2299
2474
void apply_push_cas (struct push_cas_option * cas ,
2300
2475
struct remote * remote ,
2301
2476
struct ref * remote_refs )
2302
2477
{
2303
2478
struct ref * ref ;
2304
- for (ref = remote_refs ; ref ; ref = ref -> next )
2479
+ for (ref = remote_refs ; ref ; ref = ref -> next ) {
2305
2480
apply_cas (cas , remote , ref );
2481
+
2482
+ /*
2483
+ * If "compare-and-swap" is in "use_tracking[_for_rest]"
2484
+ * mode, and if "--force-if-includes" was specified, run
2485
+ * the check.
2486
+ */
2487
+ if (ref -> check_reachable )
2488
+ check_if_includes_upstream (ref );
2489
+ }
2306
2490
}
0 commit comments