Skip to content

Commit dc39e09

Browse files
mhaggergitster
authored andcommitted
files_ref_store: use a transaction to update packed refs
When processing a `files_ref_store` transaction, it is sometimes necessary to delete some references from the "packed-refs" file. Do that using a reference transaction conducted against the `packed_ref_store`. This change further decouples `files_ref_store` from `packed_ref_store`. It also fixes multiple problems, including the two revealed by test cases added in the previous commit. First, the old code didn't obtain the `packed-refs` lock until `files_transaction_finish()`. This means that a failure to acquire the `packed-refs` lock (e.g., due to contention with another process) wasn't detected until it was too late (problems like this are supposed to be detected in the "prepare" phase). The new code acquires the `packed-refs` lock in `files_transaction_prepare()`, the same stage of the processing when the loose reference locks are being acquired, removing another reason why the "prepare" phase might succeed and the "finish" phase might nevertheless fail. Second, the old code deleted the loose version of a reference before deleting any packed version of the same reference. This left a moment when another process might think that the packed version of the reference is current, which is incorrect. (Even worse, the packed version of the reference can be arbitrarily old, and might even point at an object that has since been garbage-collected.) Third, if a reference deletion fails to acquire the `packed-refs` lock altogether, then the old code might leave the repository in the incorrect state (possibly corrupt) described in the previous paragraph. Now we activate the new "packed-refs" file (sans any references that are being deleted) *before* deleting the corresponding loose references. But we hold the "packed-refs" lock until after the loose references have been finalized, thus preventing a simultaneous "pack-refs" process from packing the loose version of the reference in the time gap, which would otherwise defeat our attempt to delete it. Signed-off-by: Michael Haggerty <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 6a2a773 commit dc39e09

File tree

2 files changed

+103
-33
lines changed

2 files changed

+103
-33
lines changed

refs/files-backend.c

Lines changed: 101 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,13 +2397,22 @@ static int lock_ref_for_update(struct files_ref_store *refs,
23972397
return 0;
23982398
}
23992399

2400+
struct files_transaction_backend_data {
2401+
struct ref_transaction *packed_transaction;
2402+
int packed_refs_locked;
2403+
};
2404+
24002405
/*
24012406
* Unlock any references in `transaction` that are still locked, and
24022407
* mark the transaction closed.
24032408
*/
2404-
static void files_transaction_cleanup(struct ref_transaction *transaction)
2409+
static void files_transaction_cleanup(struct files_ref_store *refs,
2410+
struct ref_transaction *transaction)
24052411
{
24062412
size_t i;
2413+
struct files_transaction_backend_data *backend_data =
2414+
transaction->backend_data;
2415+
struct strbuf err = STRBUF_INIT;
24072416

24082417
for (i = 0; i < transaction->nr; i++) {
24092418
struct ref_update *update = transaction->updates[i];
@@ -2415,6 +2424,17 @@ static void files_transaction_cleanup(struct ref_transaction *transaction)
24152424
}
24162425
}
24172426

2427+
if (backend_data->packed_transaction &&
2428+
ref_transaction_abort(backend_data->packed_transaction, &err)) {
2429+
error("error aborting transaction: %s", err.buf);
2430+
strbuf_release(&err);
2431+
}
2432+
2433+
if (backend_data->packed_refs_locked)
2434+
packed_refs_unlock(refs->packed_ref_store);
2435+
2436+
free(backend_data);
2437+
24182438
transaction->state = REF_TRANSACTION_CLOSED;
24192439
}
24202440

@@ -2431,12 +2451,17 @@ static int files_transaction_prepare(struct ref_store *ref_store,
24312451
char *head_ref = NULL;
24322452
int head_type;
24332453
struct object_id head_oid;
2454+
struct files_transaction_backend_data *backend_data;
2455+
struct ref_transaction *packed_transaction = NULL;
24342456

24352457
assert(err);
24362458

24372459
if (!transaction->nr)
24382460
goto cleanup;
24392461

2462+
backend_data = xcalloc(1, sizeof(*backend_data));
2463+
transaction->backend_data = backend_data;
2464+
24402465
/*
24412466
* Fail if a refname appears more than once in the
24422467
* transaction. (If we end up splitting up any updates using
@@ -2503,14 +2528,49 @@ static int files_transaction_prepare(struct ref_store *ref_store,
25032528
head_ref, &affected_refnames, err);
25042529
if (ret)
25052530
break;
2531+
2532+
if (update->flags & REF_DELETING &&
2533+
!(update->flags & REF_LOG_ONLY) &&
2534+
!(update->flags & REF_ISPRUNING)) {
2535+
/*
2536+
* This reference has to be deleted from
2537+
* packed-refs if it exists there.
2538+
*/
2539+
if (!packed_transaction) {
2540+
packed_transaction = ref_store_transaction_begin(
2541+
refs->packed_ref_store, err);
2542+
if (!packed_transaction) {
2543+
ret = TRANSACTION_GENERIC_ERROR;
2544+
goto cleanup;
2545+
}
2546+
2547+
backend_data->packed_transaction =
2548+
packed_transaction;
2549+
}
2550+
2551+
ref_transaction_add_update(
2552+
packed_transaction, update->refname,
2553+
update->flags & ~REF_HAVE_OLD,
2554+
update->new_oid.hash, update->old_oid.hash,
2555+
NULL);
2556+
}
2557+
}
2558+
2559+
if (packed_transaction) {
2560+
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
2561+
ret = TRANSACTION_GENERIC_ERROR;
2562+
goto cleanup;
2563+
}
2564+
backend_data->packed_refs_locked = 1;
2565+
ret = ref_transaction_prepare(packed_transaction, err);
25062566
}
25072567

25082568
cleanup:
25092569
free(head_ref);
25102570
string_list_clear(&affected_refnames, 0);
25112571

25122572
if (ret)
2513-
files_transaction_cleanup(transaction);
2573+
files_transaction_cleanup(refs, transaction);
25142574
else
25152575
transaction->state = REF_TRANSACTION_PREPARED;
25162576

@@ -2525,9 +2585,10 @@ static int files_transaction_finish(struct ref_store *ref_store,
25252585
files_downcast(ref_store, 0, "ref_transaction_finish");
25262586
size_t i;
25272587
int ret = 0;
2528-
struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
2529-
struct string_list_item *ref_to_delete;
25302588
struct strbuf sb = STRBUF_INIT;
2589+
struct files_transaction_backend_data *backend_data;
2590+
struct ref_transaction *packed_transaction;
2591+
25312592

25322593
assert(err);
25332594

@@ -2536,6 +2597,9 @@ static int files_transaction_finish(struct ref_store *ref_store,
25362597
return 0;
25372598
}
25382599

2600+
backend_data = transaction->backend_data;
2601+
packed_transaction = backend_data->packed_transaction;
2602+
25392603
/* Perform updates first so live commits remain referenced */
25402604
for (i = 0; i < transaction->nr; i++) {
25412605
struct ref_update *update = transaction->updates[i];
@@ -2571,7 +2635,23 @@ static int files_transaction_finish(struct ref_store *ref_store,
25712635
}
25722636
}
25732637
}
2574-
/* Perform deletes now that updates are safely completed */
2638+
2639+
/*
2640+
* Perform deletes now that updates are safely completed.
2641+
*
2642+
* First delete any packed versions of the references, while
2643+
* retaining the packed-refs lock:
2644+
*/
2645+
if (packed_transaction) {
2646+
ret = ref_transaction_commit(packed_transaction, err);
2647+
ref_transaction_free(packed_transaction);
2648+
packed_transaction = NULL;
2649+
backend_data->packed_transaction = NULL;
2650+
if (ret)
2651+
goto cleanup;
2652+
}
2653+
2654+
/* Now delete the loose versions of the references: */
25752655
for (i = 0; i < transaction->nr; i++) {
25762656
struct ref_update *update = transaction->updates[i];
25772657
struct ref_lock *lock = update->backend_data;
@@ -2589,39 +2669,27 @@ static int files_transaction_finish(struct ref_store *ref_store,
25892669
}
25902670
update->flags |= REF_DELETED_LOOSE;
25912671
}
2592-
2593-
if (!(update->flags & REF_ISPRUNING))
2594-
string_list_append(&refs_to_delete,
2595-
lock->ref_name);
25962672
}
25972673
}
25982674

2599-
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
2600-
ret = TRANSACTION_GENERIC_ERROR;
2601-
goto cleanup;
2602-
}
2603-
2604-
if (repack_without_refs(refs->packed_ref_store, &refs_to_delete, err)) {
2605-
ret = TRANSACTION_GENERIC_ERROR;
2606-
packed_refs_unlock(refs->packed_ref_store);
2607-
goto cleanup;
2608-
}
2609-
2610-
packed_refs_unlock(refs->packed_ref_store);
2611-
26122675
/* Delete the reflogs of any references that were deleted: */
2613-
for_each_string_list_item(ref_to_delete, &refs_to_delete) {
2614-
strbuf_reset(&sb);
2615-
files_reflog_path(refs, &sb, ref_to_delete->string);
2616-
if (!unlink_or_warn(sb.buf))
2617-
try_remove_empty_parents(refs, ref_to_delete->string,
2618-
REMOVE_EMPTY_PARENTS_REFLOG);
2676+
for (i = 0; i < transaction->nr; i++) {
2677+
struct ref_update *update = transaction->updates[i];
2678+
if (update->flags & REF_DELETING &&
2679+
!(update->flags & REF_LOG_ONLY) &&
2680+
!(update->flags & REF_ISPRUNING)) {
2681+
strbuf_reset(&sb);
2682+
files_reflog_path(refs, &sb, update->refname);
2683+
if (!unlink_or_warn(sb.buf))
2684+
try_remove_empty_parents(refs, update->refname,
2685+
REMOVE_EMPTY_PARENTS_REFLOG);
2686+
}
26192687
}
26202688

26212689
clear_loose_ref_cache(refs);
26222690

26232691
cleanup:
2624-
files_transaction_cleanup(transaction);
2692+
files_transaction_cleanup(refs, transaction);
26252693

26262694
for (i = 0; i < transaction->nr; i++) {
26272695
struct ref_update *update = transaction->updates[i];
@@ -2639,15 +2707,17 @@ static int files_transaction_finish(struct ref_store *ref_store,
26392707
}
26402708

26412709
strbuf_release(&sb);
2642-
string_list_clear(&refs_to_delete, 0);
26432710
return ret;
26442711
}
26452712

26462713
static int files_transaction_abort(struct ref_store *ref_store,
26472714
struct ref_transaction *transaction,
26482715
struct strbuf *err)
26492716
{
2650-
files_transaction_cleanup(transaction);
2717+
struct files_ref_store *refs =
2718+
files_downcast(ref_store, 0, "ref_transaction_abort");
2719+
2720+
files_transaction_cleanup(refs, transaction);
26512721
return 0;
26522722
}
26532723

t/t1404-update-ref-errors.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ test_expect_success 'broken reference blocks indirect create' '
404404
test_cmp expected output.err
405405
'
406406

407-
test_expect_failure 'no bogus intermediate values during delete' '
407+
test_expect_success 'no bogus intermediate values during delete' '
408408
prefix=refs/slow-transaction &&
409409
# Set up a reference with differing loose and packed versions:
410410
git update-ref $prefix/foo $C &&
@@ -461,7 +461,7 @@ test_expect_failure 'no bogus intermediate values during delete' '
461461
test_must_fail git rev-parse --verify --quiet $prefix/foo
462462
'
463463

464-
test_expect_failure 'delete fails cleanly if packed-refs file is locked' '
464+
test_expect_success 'delete fails cleanly if packed-refs file is locked' '
465465
prefix=refs/locked-packed-refs &&
466466
# Set up a reference with differing loose and packed versions:
467467
git update-ref $prefix/foo $C &&

0 commit comments

Comments
 (0)