Skip to content

Commit b019de6

Browse files
KarthikNayakgitster
authored andcommitted
refs: implement partial reference transaction support
Git's reference transactions are all-or-nothing: either all updates succeed, or none do. While this atomic behavior is generally desirable, it can be suboptimal especially when using the reftable backend, where batching multiple reference updates into a single transaction is more efficient than performing them sequentially. Introduce partial transaction support with a new flag, 'REF_TRANSACTION_ALLOW_PARTIAL'. When enabled, this flag allows individual reference updates that would typically cause the entire transaction to fail due to non-system-related errors to be marked as rejected while permitting other updates to proceed. System errors referred by 'REF_TRANSACTION_ERROR_GENERIC' continue to result in the entire transaction failing. This approach enhances flexibility while preserving transactional integrity where necessary. The implementation introduces several key components: - Add 'rejection_err' field to struct `ref_update` to track failed updates with failure reason. - Add a new struct `ref_transaction_rejections` and a field within `ref_transaction` to this struct to allow quick iteration over rejected updates. - Modify reference backends (files, packed, reftable) to handle partial transactions by using `ref_transaction_set_rejected()` instead of failing the entire transaction when `REF_TRANSACTION_ALLOW_PARTIAL` is set. - Add `ref_transaction_for_each_rejected_update()` to let callers examine which updates were rejected and why. This foundational change enables partial transaction support throughout the reference subsystem. A following commit will expose this capability to users by adding a `--allow-partial` flag to 'git-update-ref(1)', providing both a user-facing feature and a testable implementation. Signed-off-by: Karthik Nayak <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d21d296 commit b019de6

File tree

6 files changed

+155
-4
lines changed

6 files changed

+155
-4
lines changed

refs.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,10 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
11761176
tr->ref_store = refs;
11771177
tr->flags = flags;
11781178
string_list_init_dup(&tr->refnames);
1179+
1180+
if (flags & REF_TRANSACTION_ALLOW_PARTIAL)
1181+
CALLOC_ARRAY(tr->rejections, 1);
1182+
11791183
return tr;
11801184
}
11811185

@@ -1206,11 +1210,45 @@ void ref_transaction_free(struct ref_transaction *transaction)
12061210
free((char *)transaction->updates[i]->old_target);
12071211
free(transaction->updates[i]);
12081212
}
1213+
1214+
if (transaction->rejections)
1215+
free(transaction->rejections->update_indices);
1216+
free(transaction->rejections);
1217+
12091218
string_list_clear(&transaction->refnames, 0);
12101219
free(transaction->updates);
12111220
free(transaction);
12121221
}
12131222

1223+
int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
1224+
size_t update_idx,
1225+
enum ref_transaction_error err)
1226+
{
1227+
if (update_idx >= transaction->nr)
1228+
BUG("trying to set rejection on invalid update index");
1229+
1230+
if (!(transaction->flags & REF_TRANSACTION_ALLOW_PARTIAL))
1231+
return 0;
1232+
1233+
if (!transaction->rejections)
1234+
BUG("transaction not inititalized with partial support");
1235+
1236+
/*
1237+
* Don't accept generic errors, since these errors are not user
1238+
* input related.
1239+
*/
1240+
if (err == REF_TRANSACTION_ERROR_GENERIC)
1241+
return 0;
1242+
1243+
transaction->updates[update_idx]->rejection_err = err;
1244+
ALLOC_GROW(transaction->rejections->update_indices,
1245+
transaction->rejections->nr + 1,
1246+
transaction->rejections->alloc);
1247+
transaction->rejections->update_indices[transaction->rejections->nr++] = update_idx;
1248+
1249+
return 1;
1250+
}
1251+
12141252
struct ref_update *ref_transaction_add_update(
12151253
struct ref_transaction *transaction,
12161254
const char *refname, unsigned int flags,
@@ -1236,6 +1274,7 @@ struct ref_update *ref_transaction_add_update(
12361274
transaction->updates[transaction->nr++] = update;
12371275

12381276
update->flags = flags;
1277+
update->rejection_err = 0;
12391278

12401279
update->new_target = xstrdup_or_null(new_target);
12411280
update->old_target = xstrdup_or_null(old_target);
@@ -2727,6 +2766,28 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
27272766
}
27282767
}
27292768

2769+
void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction,
2770+
ref_transaction_for_each_rejected_update_fn cb,
2771+
void *cb_data)
2772+
{
2773+
if (!transaction->rejections)
2774+
return;
2775+
2776+
for (size_t i = 0; i < transaction->rejections->nr; i++) {
2777+
size_t update_index = transaction->rejections->update_indices[i];
2778+
struct ref_update *update = transaction->updates[update_index];
2779+
2780+
if (!update->rejection_err)
2781+
continue;
2782+
2783+
cb(update->refname,
2784+
(update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL,
2785+
(update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL,
2786+
update->old_target, update->new_target,
2787+
update->rejection_err, cb_data);
2788+
}
2789+
}
2790+
27302791
int refs_delete_refs(struct ref_store *refs, const char *logmsg,
27312792
struct string_list *refnames, unsigned int flags)
27322793
{

refs.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,13 @@ enum ref_transaction_flag {
673673
* either be absent or null_oid.
674674
*/
675675
REF_TRANSACTION_FLAG_INITIAL = (1 << 0),
676+
677+
/*
678+
* The transaction mechanism by default fails all updates if any conflict
679+
* is detected. This flag allows transactions to partially apply updates
680+
* while rejecting updates which do not match the expected state.
681+
*/
682+
REF_TRANSACTION_ALLOW_PARTIAL = (1 << 1),
676683
};
677684

678685
/*
@@ -903,6 +910,21 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
903910
ref_transaction_for_each_queued_update_fn cb,
904911
void *cb_data);
905912

913+
/*
914+
* Execute the given callback function for each of the reference updates which
915+
* have been rejected in the given transaction.
916+
*/
917+
typedef void ref_transaction_for_each_rejected_update_fn(const char *refname,
918+
const struct object_id *old_oid,
919+
const struct object_id *new_oid,
920+
const char *old_target,
921+
const char *new_target,
922+
enum ref_transaction_error err,
923+
void *cb_data);
924+
void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction,
925+
ref_transaction_for_each_rejected_update_fn cb,
926+
void *cb_data);
927+
906928
/*
907929
* Free `*transaction` and all associated data.
908930
*/

refs/files-backend.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2852,8 +2852,15 @@ static int files_transaction_prepare(struct ref_store *ref_store,
28522852
ret = lock_ref_for_update(refs, update, transaction,
28532853
head_ref, &refnames_to_check,
28542854
err);
2855-
if (ret)
2855+
if (ret) {
2856+
if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
2857+
strbuf_setlen(err, 0);
2858+
ret = 0;
2859+
2860+
continue;
2861+
}
28562862
goto cleanup;
2863+
}
28572864

28582865
if (update->flags & REF_DELETING &&
28592866
!(update->flags & REF_LOG_ONLY) &&
@@ -3151,6 +3158,9 @@ static int files_transaction_finish(struct ref_store *ref_store,
31513158
struct ref_update *update = transaction->updates[i];
31523159
struct ref_lock *lock = update->backend_data;
31533160

3161+
if (update->rejection_err)
3162+
continue;
3163+
31543164
if (update->flags & REF_NEEDS_COMMIT ||
31553165
update->flags & REF_LOG_ONLY) {
31563166
if (parse_and_write_reflog(refs, update, lock, err)) {

refs/packed-backend.c

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,10 +1327,11 @@ static int packed_ref_store_remove_on_disk(struct ref_store *ref_store,
13271327
* remain locked when it is done.
13281328
*/
13291329
static enum ref_transaction_error write_with_updates(struct packed_ref_store *refs,
1330-
struct string_list *updates,
1330+
struct ref_transaction *transaction,
13311331
struct strbuf *err)
13321332
{
13331333
enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
1334+
struct string_list *updates = &transaction->refnames;
13341335
struct ref_iterator *iter = NULL;
13351336
size_t i;
13361337
int ok;
@@ -1411,6 +1412,13 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
14111412
"reference already exists",
14121413
update->refname);
14131414
ret = REF_TRANSACTION_ERROR_CREATE_EXISTS;
1415+
1416+
if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
1417+
strbuf_setlen(err, 0);
1418+
ret = 0;
1419+
continue;
1420+
}
1421+
14141422
goto error;
14151423
} else if (!oideq(&update->old_oid, iter->oid)) {
14161424
strbuf_addf(err, "cannot update ref '%s': "
@@ -1419,6 +1427,13 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
14191427
oid_to_hex(iter->oid),
14201428
oid_to_hex(&update->old_oid));
14211429
ret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
1430+
1431+
if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
1432+
strbuf_setlen(err, 0);
1433+
ret = 0;
1434+
continue;
1435+
}
1436+
14221437
goto error;
14231438
}
14241439
}
@@ -1456,6 +1471,13 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
14561471
update->refname,
14571472
oid_to_hex(&update->old_oid));
14581473
return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
1474+
1475+
if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
1476+
strbuf_setlen(err, 0);
1477+
ret = 0;
1478+
continue;
1479+
}
1480+
14591481
goto error;
14601482
}
14611483
}
@@ -1521,6 +1543,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
15211543
write_error:
15221544
strbuf_addf(err, "error writing to %s: %s",
15231545
get_tempfile_path(refs->tempfile), strerror(errno));
1546+
ret = REF_TRANSACTION_ERROR_GENERIC;
15241547

15251548
error:
15261549
ref_iterator_free(iter);
@@ -1679,7 +1702,7 @@ static int packed_transaction_prepare(struct ref_store *ref_store,
16791702
data->own_lock = 1;
16801703
}
16811704

1682-
ret = write_with_updates(refs, &transaction->refnames, err);
1705+
ret = write_with_updates(refs, transaction, err);
16831706
if (ret)
16841707
goto failure;
16851708

refs/refs-internal.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ struct ref_update {
123123
*/
124124
uint64_t index;
125125

126+
/*
127+
* Used in partial transactions to mark if a given update was rejected.
128+
*/
129+
enum ref_transaction_error rejection_err;
130+
126131
/*
127132
* If this ref_update was split off of a symref update via
128133
* split_symref_update(), then this member points at that
@@ -142,6 +147,13 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
142147
struct object_id *oid, struct strbuf *referent,
143148
unsigned int *type, int *failure_errno);
144149

150+
/*
151+
* Mark a given update as rejected with a given reason.
152+
*/
153+
int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
154+
size_t update_idx,
155+
enum ref_transaction_error err);
156+
145157
/*
146158
* Add a ref_update with the specified properties to transaction, and
147159
* return a pointer to the new object. This function does not verify
@@ -183,6 +195,18 @@ enum ref_transaction_state {
183195
REF_TRANSACTION_CLOSED = 2
184196
};
185197

198+
/*
199+
* Data structure to hold indices of updates which were rejected, when
200+
* partial transactions where enabled. While the updates themselves hold
201+
* the rejection error, this structure allows a transaction to iterate
202+
* only over the rejected updates.
203+
*/
204+
struct ref_transaction_rejections {
205+
size_t *update_indices;
206+
size_t alloc;
207+
size_t nr;
208+
};
209+
186210
/*
187211
* Data structure for holding a reference transaction, which can
188212
* consist of checks and updates to multiple references, carried out
@@ -195,6 +219,7 @@ struct ref_transaction {
195219
size_t alloc;
196220
size_t nr;
197221
enum ref_transaction_state state;
222+
struct ref_transaction_rejections *rejections;
198223
void *backend_data;
199224
unsigned int flags;
200225
uint64_t max_index;

refs/reftable-backend.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1371,8 +1371,15 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
13711371
transaction->updates[i],
13721372
&refnames_to_check, head_type,
13731373
&head_referent, &referent, err);
1374-
if (ret)
1374+
if (ret) {
1375+
if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
1376+
strbuf_setlen(err, 0);
1377+
ret = 0;
1378+
1379+
continue;
1380+
}
13751381
goto done;
1382+
}
13761383
}
13771384

13781385
string_list_sort(&refnames_to_check);
@@ -1455,6 +1462,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
14551462
struct reftable_transaction_update *tx_update = &arg->updates[i];
14561463
struct ref_update *u = tx_update->update;
14571464

1465+
if (u->rejection_err)
1466+
continue;
1467+
14581468
/*
14591469
* Write a reflog entry when updating a ref to point to
14601470
* something new in either of the following cases:

0 commit comments

Comments
 (0)