receive-pack: handle reference deletions separately
In 9d2962a7c4 (receive-pack: use batched reference updates, 2025-05-19)
we updated the 'git-receive-pack(1)' command to use batched reference
updates. One edge case which was missed during this implementation was
when a user pushes multiple branches such as:
delete refs/heads/branch/conflict
create refs/heads/branch
Before using batched updates, the references would be applied
sequentially and hence no conflicts would arise. With batched updates,
while the first update applies, the second fails due to D/F conflict. A
similar issue was present in 'git-fetch(1)' and was fixed by separating
out reference pruning into a separate transaction in the commit 'fetch:
use batched reference updates'. Apply a similar mechanism for
'git-receive-pack(1)' and separate out reference deletions into its own
batch.
This means 'git-receive-pack(1)' will now use up to two transactions,
whereas before using batched updates it would use _at least_ two
transactions. So using batched updates is still the better option.
Add a test to validate this behavior.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
committed by
Junio C Hamano
parent
15c45c7458
commit
5c697f0b7d
@@ -1866,47 +1866,81 @@ static void execute_commands_non_atomic(struct command *commands,
|
||||
const char *reported_error = NULL;
|
||||
struct strmap failed_refs = STRMAP_INIT;
|
||||
|
||||
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
|
||||
REF_TRANSACTION_ALLOW_FAILURE, &err);
|
||||
if (!transaction) {
|
||||
rp_error("%s", err.buf);
|
||||
strbuf_reset(&err);
|
||||
reported_error = "transaction failed to start";
|
||||
goto failure;
|
||||
/*
|
||||
* Reference updates, where D/F conflicts shouldn't arise due to
|
||||
* one reference being deleted, while the other being created
|
||||
* are treated as conflicts in batched updates. This is because
|
||||
* we don't do conflict resolution inside a transaction. To
|
||||
* mitigate this, delete references in a separate batch.
|
||||
*
|
||||
* NEEDSWORK: Add conflict resolution between deletion and creation
|
||||
* of reference updates within a transaction. With that, we can
|
||||
* combine the two phases.
|
||||
*/
|
||||
enum processing_phase {
|
||||
PHASE_DELETIONS,
|
||||
PHASE_OTHERS
|
||||
};
|
||||
|
||||
for (enum processing_phase phase = PHASE_DELETIONS; phase <= PHASE_OTHERS; phase++) {
|
||||
for (cmd = commands; cmd; cmd = cmd->next) {
|
||||
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
|
||||
continue;
|
||||
|
||||
if (phase == PHASE_DELETIONS && !is_null_oid(&cmd->new_oid))
|
||||
continue;
|
||||
else if (phase == PHASE_OTHERS && is_null_oid(&cmd->new_oid))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Lazily create a transaction only when we know there are
|
||||
* updates to be added.
|
||||
*/
|
||||
if (!transaction) {
|
||||
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
|
||||
REF_TRANSACTION_ALLOW_FAILURE, &err);
|
||||
if (!transaction) {
|
||||
rp_error("%s", err.buf);
|
||||
strbuf_reset(&err);
|
||||
reported_error = "transaction failed to start";
|
||||
goto failure;
|
||||
}
|
||||
}
|
||||
|
||||
cmd->error_string = update(cmd, si);
|
||||
}
|
||||
|
||||
/* No transaction, so nothing to commit */
|
||||
if (!transaction)
|
||||
goto cleanup;
|
||||
|
||||
if (ref_transaction_commit(transaction, &err)) {
|
||||
rp_error("%s", err.buf);
|
||||
reported_error = "failed to update refs";
|
||||
goto failure;
|
||||
}
|
||||
|
||||
ref_transaction_for_each_rejected_update(transaction,
|
||||
ref_transaction_rejection_handler,
|
||||
&failed_refs);
|
||||
|
||||
if (strmap_empty(&failed_refs))
|
||||
goto cleanup;
|
||||
|
||||
failure:
|
||||
for (cmd = commands; cmd; cmd = cmd->next) {
|
||||
if (reported_error)
|
||||
cmd->error_string = reported_error;
|
||||
else if (strmap_contains(&failed_refs, cmd->ref_name))
|
||||
cmd->error_string = strmap_get(&failed_refs, cmd->ref_name);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ref_transaction_free(transaction);
|
||||
transaction = NULL;
|
||||
strmap_clear(&failed_refs, 0);
|
||||
strbuf_release(&err);
|
||||
}
|
||||
|
||||
for (cmd = commands; cmd; cmd = cmd->next) {
|
||||
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
|
||||
continue;
|
||||
|
||||
cmd->error_string = update(cmd, si);
|
||||
}
|
||||
|
||||
if (ref_transaction_commit(transaction, &err)) {
|
||||
rp_error("%s", err.buf);
|
||||
reported_error = "failed to update refs";
|
||||
goto failure;
|
||||
}
|
||||
|
||||
ref_transaction_for_each_rejected_update(transaction,
|
||||
ref_transaction_rejection_handler,
|
||||
&failed_refs);
|
||||
|
||||
if (strmap_empty(&failed_refs))
|
||||
goto cleanup;
|
||||
|
||||
failure:
|
||||
for (cmd = commands; cmd; cmd = cmd->next) {
|
||||
if (reported_error)
|
||||
cmd->error_string = reported_error;
|
||||
else if (strmap_contains(&failed_refs, cmd->ref_name))
|
||||
cmd->error_string = strmap_get(&failed_refs, cmd->ref_name);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ref_transaction_free(transaction);
|
||||
strmap_clear(&failed_refs, 0);
|
||||
strbuf_release(&err);
|
||||
}
|
||||
|
||||
static void execute_commands_atomic(struct command *commands,
|
||||
|
||||
Reference in New Issue
Block a user