refs: allow multiple reflog entries for the same refname

The reference transaction only allows a single update for a given reference to avoid conflicts. This, however, isn't an issue for reflogs. There are no conflicts to be resolved in reflogs and when migrating reflogs between backends we'd have multiple reflog entries for the same refname. So allow multiple reflog updates within a single transaction. Also the reflog creation logic isn't exposed to the end user. While this might change in the future, currently, this reduces the scope of issues to think about. In the reftable backend, the writer sorts all updates based on the update_index before writing to the block. When there are multiple reflogs for a given refname, it is essential that the order of the reflogs is maintained. So add the `index` value to the `update_index`. The `index` field is only set when multiple reflog entries for a given refname are added and as such in most scenarios the old behavior remains. This is required to add reflog migration support to `git refs migrate`. Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karthik Nayak committed Dec 16, 2024 at 17:44 UTC 297c09eabb1e8b44230bca86fc7fd344175e0be7
2 files changed +30 -7
refs/files-backend.c
+11 -4
@@ -2611,6 +2611,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,
2611
2612 update->backend_data = lock;
2613
2614 + if (update->flags & REF_LOG_ONLY)
2615 + goto out;
2616 +
2617 if (update->type & REF_ISSYMREF) {
2618 if (update->flags & REF_NO_DEREF) {
2619 /*
@@ -2829,13 +2832,16 @@ static int files_transaction_prepare(struct ref_store *ref_store,
2832 */
2833 for (i = 0; i < transaction->nr; i++) {
2834 struct ref_update *update = transaction->updates[i];
2832 - struct string_list_item *item =
2833 - string_list_append(&affected_refnames, update->refname);
2835 + struct string_list_item *item;
2836
2837 if ((update->flags & REF_IS_PRUNING) &&
2838 !(update->flags & REF_NO_DEREF))
2839 BUG("REF_IS_PRUNING set without REF_NO_DEREF");
2840
2841 + if (update->flags & REF_LOG_ONLY)
2842 + continue;
2843 +
2844 + item = string_list_append(&affected_refnames, update->refname);
2845 /*
2846 * We store a pointer to update in item->util, but at
2847 * the moment we never use the value of this field
@@ -3035,8 +3041,9 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
3041
3042 /* Fail if a refname appears more than once in the transaction: */
3043 for (i = 0; i < transaction->nr; i++)
3038 - string_list_append(&affected_refnames,
3039 - transaction->updates[i]->refname);
3044 + if (!(transaction->updates[i]->flags & REF_LOG_ONLY))
3045 + string_list_append(&affected_refnames,
3046 + transaction->updates[i]->refname);
3047 string_list_sort(&affected_refnames);
3048 if (ref_update_reject_duplicates(&affected_refnames, err)) {
3049 ret = TRANSACTION_GENERIC_ERROR;
refs/reftable-backend.c
+19 -3
@@ -990,8 +990,9 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
990 if (ret)
991 goto done;
992
993 - string_list_append(&affected_refnames,
994 - transaction->updates[i]->refname);
993 + if (!(transaction->updates[i]->flags & REF_LOG_ONLY))
994 + string_list_append(&affected_refnames,
995 + transaction->updates[i]->refname);
996 }
997
998 /*
@@ -1301,6 +1302,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
1302 struct reftable_log_record *logs = NULL;
1303 struct ident_split committer_ident = {0};
1304 size_t logs_nr = 0, logs_alloc = 0, i;
1305 + uint64_t max_update_index = ts;
1306 const char *committer_info;
1307 int ret = 0;
1308
@@ -1405,7 +1407,19 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
1407 }
1408
1409 fill_reftable_log_record(log, &c);
1408 - log->update_index = ts;
1410 +
1411 + /*
1412 + * Updates are sorted by the writer. So updates for the same
1413 + * refname need to contain different update indices.
1414 + */
1415 + log->update_index = ts + u->index;
1416 +
1417 + /*
1418 + * Note the max update_index so the limit can be set later on.
1419 + */
1420 + if (log->update_index > max_update_index)
1421 + max_update_index = log->update_index;
1422 +
1423 log->refname = xstrdup(u->refname);
1424 memcpy(log->value.update.new_hash,
1425 u->new_oid.hash, GIT_MAX_RAWSZ);
@@ -1469,6 +1483,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
1483 * and log blocks.
1484 */
1485 if (logs) {
1486 + reftable_writer_set_limits(writer, ts, max_update_index);
1487 +
1488 ret = reftable_writer_add_logs(writer, logs, logs_nr);
1489 if (ret < 0)
1490 goto done;