{"thread":{"id":"65517","subject":"[PATCH 0/8] refs: move some of the generic logic out of the backends","startedAt":"2026-04-20T10:12:22Z","lastAt":"2026-05-06T19:45:06Z","messageCount":68,"participants":["Karthik Nayak","Patrick Steinhardt","Toon Claes"],"isPatch":true,"patchVersion":1,"patchTotal":8},"messages":[{"id":"541953","messageId":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","threadId":"65517","inReplyTo":null,"subject":"[PATCH 0/8] refs: move some of the generic logic out of the backends","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:11:58Z","receivedAt":"2026-04-20T10:12:22Z","isPatch":true,"body":"This series came together while I was working on other reference related\ncode and realized that some of the individual logic implemented with the\nreference backends can be moved to the generic layer.\n\nMoving code to the generic layer, simplifies the responsibility of\nindividual backends and avoids deviation in logic between the backends.\n\nThe biggest changes are related to moving out usage of `parse_object()`\nand `peel_object()` from reference transactions. The former is used to\nvalidate that the OID provided points to a commit object. The latter is\nan optimization technique where the packed/reftable backend store the\npeeled OID whenever available, so reading such references provides the\npeeled OID without having to call the ODB.\n\nMoving object parsing to the generic layout involves moving it out of\nthe prepare stage of the transaction and into `ref_transaction_update()`\nwhere every added update is checked. As such, this also involves\nmodifying update-ref(1) and receive-pack(1) to follow this paradigm.\n\n---\n builtin/receive-pack.c  |  22 +++++---\n builtin/update-ref.c    | 143 ++++++++++++++++++++++++++++++------------------\n refs.c                  |  60 +++++++++++++++-----\n refs.h                  |  16 +++---\n refs/files-backend.c    |  58 +++++++-------------\n refs/packed-backend.c   |  10 ++--\n refs/packed-backend.h   |   3 +-\n refs/refs-internal.h    |  35 ++++++++++--\n refs/reftable-backend.c |  40 +++-----------\n 9 files changed, 223 insertions(+), 164 deletions(-)\n\nKarthik Nayak (8):\n      refs: remove unused typedef 'ref_transaction_commit_fn'\n      refs: extract out reflog config to generic layer\n      refs: return `ref_transaction_error` from `ref_transaction_update()`\n      update-ref: move `print_rejected_refs()` up\n      update-ref: handle rejections while adding updates\n      refs: move object parsing to the generic layer\n      refs: add peeled object ID to the `ref_update` struct\n      refs: use peeled tag values in reference backends\n\n\n\nbase-commit: f65aba1e87db64413b6d1ed5ae5a45b5a84a0997\nchange-id: 20260417-refs-move-to-generic-layer-f7525c5e8764\n\nThanks\n- Karthik\n\n"},{"id":"541954","messageId":"20260420-refs-move-to-generic-layer-v1-1-513e354f376b@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:11:59Z","receivedAt":"2026-04-20T10:12:26Z","isPatch":true,"body":"The typedef 'ref_transaction_commit_fn' is not used anywhere in our\ncode, let's remove it.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/refs-internal.h | 4 ----\n 1 file changed, 4 deletions(-)\n\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex d79e35fd26..2d963cc4f4 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,\n \t\t\t\t     struct ref_transaction *transaction,\n \t\t\t\t     struct strbuf *err);\n \n-typedef int ref_transaction_commit_fn(struct ref_store *refs,\n-\t\t\t\t      struct ref_transaction *transaction,\n-\t\t\t\t      struct strbuf *err);\n-\n typedef int optimize_fn(struct ref_store *ref_store,\n \t\t\tstruct refs_optimize_opts *opts);\n \n\n-- \n2.53.GIT\n\n"},{"id":"541955","messageId":"20260420-refs-move-to-generic-layer-v1-2-513e354f376b@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH 2/8] refs: extract out reflog config to generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:12:00Z","receivedAt":"2026-04-20T10:12:28Z","isPatch":true,"body":"The reference backends need to know when to create reflog entries, this\nis dictated by the 'core.logallrefupdates' config. Instead of relying on\nthe backends to call `repo_settings_get_log_all_ref_updates()` to obtain\nthis config value, let's do this in the generic layer and pass down the\nvalue to the backends.\n\nInstead of passing this in as a new argument, let's create a new\n`ref_init_options` structure which will house information required to\ninitialize a reference backend. Move the access flags here as well.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  7 ++++++-\n refs/files-backend.c    | 10 ++++++----\n refs/packed-backend.c   |  4 ++--\n refs/packed-backend.h   |  3 ++-\n refs/refs-internal.h    | 17 ++++++++++++++++-\n refs/reftable-backend.c |  6 +++---\n 6 files changed, 35 insertions(+), 12 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex bfcb9c7ac3..aa66c6b28e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2295,6 +2295,10 @@ static struct ref_store *ref_store_init(struct repository *repo,\n {\n \tconst struct ref_storage_be *be;\n \tstruct ref_store *refs;\n+\tstruct ref_store_init_options options = {\n+\t\t.access_flags = flags,\n+\t\t.log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),\n+\t};\n \n \tbe = find_ref_storage_backend(format);\n \tif (!be)\n@@ -2304,7 +2308,8 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \t * TODO Send in a 'struct worktree' instead of a 'gitdir', and\n \t * allow the backend to handle how it wants to deal with worktrees.\n \t */\n-\trefs = be->init(repo, repo->ref_storage_payload, gitdir, flags);\n+\trefs = be->init(repo, repo->ref_storage_payload, gitdir, &options);\n+\n \treturn refs;\n }\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex b3b0c25f84..407b97cc44 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -108,7 +108,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)\n static struct ref_store *files_ref_store_init(struct repository *repo,\n \t\t\t\t\t      const char *payload,\n \t\t\t\t\t      const char *gitdir,\n-\t\t\t\t\t      unsigned int flags)\n+\t\t\t\t\t      const struct ref_store_init_options *options)\n {\n \tstruct files_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n@@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \t\t\t\t\t &ref_common_dir);\n \n \tbase_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);\n-\trefs->store_flags = flags;\n+\n \trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n \trefs->packed_ref_store =\n-\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n-\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, options);\n+\trefs->store_flags = options->access_flags;\n+\trefs->log_all_ref_updates = options->log_all_ref_updates;\n+\n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \n \tchdir_notify_reparent(\"files-backend $GIT_DIR\", &refs->base.gitdir);\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 23ed62984b..195600cdad 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)\n struct ref_store *packed_ref_store_init(struct repository *repo,\n \t\t\t\t\tconst char *payload UNUSED,\n \t\t\t\t\tconst char *gitdir,\n-\t\t\t\t\tunsigned int store_flags)\n+\t\t\t\t\tconst struct ref_store_init_options *options)\n {\n \tstruct packed_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tbase_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);\n-\trefs->store_flags = store_flags;\n+\trefs->store_flags = options->access_flags;\n \n \tstrbuf_addf(&sb, \"%s/packed-refs\", gitdir);\n \trefs->path = strbuf_detach(&sb, NULL);\ndiff --git a/refs/packed-backend.h b/refs/packed-backend.h\nindex 2c2377a356..1db48e801d 100644\n--- a/refs/packed-backend.h\n+++ b/refs/packed-backend.h\n@@ -3,6 +3,7 @@\n \n struct repository;\n struct ref_transaction;\n+struct ref_store_init_options;\n \n /*\n  * Support for storing references in a `packed-refs` file.\n@@ -16,7 +17,7 @@ struct ref_transaction;\n struct ref_store *packed_ref_store_init(struct repository *repo,\n \t\t\t\t\tconst char *payload,\n \t\t\t\t\tconst char *gitdir,\n-\t\t\t\t\tunsigned int store_flags);\n+\t\t\t\t\tconst struct ref_store_init_options *options);\n \n /*\n  * Lock the packed-refs file for writing. Flags is passed to\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2d963cc4f4..eed13af4eb 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -385,6 +385,21 @@ struct ref_store;\n \t\t\t\t REF_STORE_ODB | \\\n \t\t\t\t REF_STORE_MAIN)\n \n+/*\n+ * Options for initializing the ref backend. All backend-agnostic information\n+ * which backends required will be held here.\n+ */\n+struct ref_store_init_options {\n+\t/* The kind of operations that the ref_store is allowed to perform. */\n+\tunsigned int access_flags;\n+\n+\t/*\n+\t * Denotes under what conditions reflogs should be created when updating\n+\t * references.\n+\t */\n+\tenum log_refs_config log_all_ref_updates;\n+};\n+\n /*\n  * Initialize the ref_store for the specified gitdir. These functions\n  * should call base_ref_store_init() to initialize the shared part of\n@@ -393,7 +408,7 @@ struct ref_store;\n typedef struct ref_store *ref_store_init_fn(struct repository *repo,\n \t\t\t\t\t    const char *payload,\n \t\t\t\t\t    const char *gitdir,\n-\t\t\t\t\t    unsigned int flags);\n+\t\t\t\t\t    const struct ref_store_init_options *options);\n /*\n  * Release all memory and resources associated with the ref store.\n  */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex daea30a5b4..bdc3e0aa19 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -369,7 +369,7 @@ static int reftable_be_config(const char *var, const char *value,\n static struct ref_store *reftable_be_init(struct repository *repo,\n \t\t\t\t\t  const char *payload,\n \t\t\t\t\t  const char *gitdir,\n-\t\t\t\t\t  unsigned int store_flags)\n+\t\t\t\t\t  const struct ref_store_init_options *options)\n {\n \tstruct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct strbuf ref_common_dir = STRBUF_INIT;\n@@ -386,8 +386,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n-\trefs->store_flags = store_flags;\n-\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->store_flags = options->access_flags;\n+\trefs->log_all_ref_updates = options->log_all_ref_updates;\n \n \tswitch (repo->hash_algo->format_id) {\n \tcase GIT_SHA1_FORMAT_ID:\n\n-- \n2.53.GIT\n\n"},{"id":"541956","messageId":"20260420-refs-move-to-generic-layer-v1-3-513e354f376b@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:12:01Z","receivedAt":"2026-04-20T10:12:28Z","isPatch":true,"body":"The `ref_transaction_update()` function is used to add updates to a\ngiven reference transactions. In the following commit, we'll add more\nvalidation to this function. As such, it would be more beneficial if the\nfunction returns specific error types, so callers can differentiate\nbetween different errors.\n\nTo facilitate this, return `enum ref_transaction_error` from the\nfunction and covert the existing '-1' returns to\n'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing\nbehavior, no changes are made to any of the callers but this sets the\nnecessary infrastructure for introduction of other errors.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c | 20 ++++++++++----------\n refs.h | 16 ++++++++--------\n 2 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex aa66c6b28e..39fef1cca0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1383,25 +1383,25 @@ static int transaction_refname_valid(const char *refname,\n \treturn 1;\n }\n \n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *new_oid,\n-\t\t\t   const struct object_id *old_oid,\n-\t\t\t   const char *new_target,\n-\t\t\t   const char *old_target,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err)\n+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t\t  const struct object_id *new_oid,\n+\t\t\t\t\t\t  const struct object_id *old_oid,\n+\t\t\t\t\t\t  const char *new_target,\n+\t\t\t\t\t\t  const char *old_target,\n+\t\t\t\t\t\t  unsigned int flags, const char *msg,\n+\t\t\t\t\t\t  struct strbuf *err)\n {\n \tassert(err);\n \n \tif ((flags & REF_FORCE_CREATE_REFLOG) &&\n \t    (flags & REF_SKIP_CREATE_REFLOG)) {\n \t\tstrbuf_addstr(err, _(\"refusing to force and skip creation of reflog\"));\n-\t\treturn -1;\n+\t\treturn REF_TRANSACTION_ERROR_GENERIC;\n \t}\n \n \tif (!transaction_refname_valid(refname, new_oid, flags, err))\n-\t\treturn -1;\n+\t\treturn REF_TRANSACTION_ERROR_GENERIC;\n \n \tif (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)\n \t\tBUG(\"illegal flags 0x%x passed to ref_transaction_update()\", flags);\ndiff --git a/refs.h b/refs.h\nindex d65de6ab5f..71d5c186d0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n  */\n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *new_oid,\n-\t\t\t   const struct object_id *old_oid,\n-\t\t\t   const char *new_target,\n-\t\t\t   const char *old_target,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err);\n+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t\t  const struct object_id *new_oid,\n+\t\t\t\t\t\t  const struct object_id *old_oid,\n+\t\t\t\t\t\t  const char *new_target,\n+\t\t\t\t\t\t  const char *old_target,\n+\t\t\t\t\t\t  unsigned int flags, const char *msg,\n+\t\t\t\t\t\t  struct strbuf *err);\n \n /*\n  * Similar to `ref_transaction_update`, but this function is only for adding\n\n-- \n2.53.GIT\n\n"},{"id":"541957","messageId":"20260420-refs-move-to-generic-layer-v1-4-513e354f376b@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH 4/8] update-ref: move `print_rejected_refs()` up","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:12:02Z","receivedAt":"2026-04-20T10:12:30Z","isPatch":true,"body":"The `print_rejected_refs()` is used to print any rejected refs when\nusing git-updated-ref(1) with the '--batch-updates' option. In the\nfollowing commit, we'll need to use this function in other places, so\nmove the function up to avoid a separate forward declaration.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 45 ++++++++++++++++++++++-----------------------\n 1 file changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 2d68c40ecb..5259cc7226 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -234,6 +234,28 @@ static int parse_next_oid(const char **next, const char *end,\n \t    command, refname);\n }\n \n+static void print_rejected_refs(const char *refname,\n+\t\t\t\tconst struct object_id *old_oid,\n+\t\t\t\tconst struct object_id *new_oid,\n+\t\t\t\tconst char *old_target,\n+\t\t\t\tconst char *new_target,\n+\t\t\t\tenum ref_transaction_error err,\n+\t\t\t\tconst char *details,\n+\t\t\t\tvoid *cb_data UNUSED)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (details && *details)\n+\t\terror(\"%s\", details);\n+\n+\tstrbuf_addf(&sb, \"rejected %s %s %s %s\\n\", refname,\n+\t\t    new_oid ? oid_to_hex(new_oid) : new_target,\n+\t\t    old_oid ? oid_to_hex(old_oid) : old_target,\n+\t\t    ref_transaction_error_msg(err));\n+\n+\tfwrite(sb.buf, sb.len, 1, stdout);\n+\tstrbuf_release(&sb);\n+}\n \n /*\n  * The following five parse_cmd_*() functions parse the corresponding\n@@ -567,29 +589,6 @@ static void parse_cmd_abort(struct ref_transaction *transaction,\n \treport_ok(\"abort\");\n }\n \n-static void print_rejected_refs(const char *refname,\n-\t\t\t\tconst struct object_id *old_oid,\n-\t\t\t\tconst struct object_id *new_oid,\n-\t\t\t\tconst char *old_target,\n-\t\t\t\tconst char *new_target,\n-\t\t\t\tenum ref_transaction_error err,\n-\t\t\t\tconst char *details,\n-\t\t\t\tvoid *cb_data UNUSED)\n-{\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tif (details && *details)\n-\t\terror(\"%s\", details);\n-\n-\tstrbuf_addf(&sb, \"rejected %s %s %s %s\\n\", refname,\n-\t\t    new_oid ? oid_to_hex(new_oid) : new_target,\n-\t\t    old_oid ? oid_to_hex(old_oid) : old_target,\n-\t\t    ref_transaction_error_msg(err));\n-\n-\tfwrite(sb.buf, sb.len, 1, stdout);\n-\tstrbuf_release(&sb);\n-}\n-\n static void parse_cmd_commit(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end UNUSED)\n {\n\n-- \n2.53.GIT\n\n"},{"id":"541958","messageId":"20260420-refs-move-to-generic-layer-v1-5-513e354f376b@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH 5/8] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:12:03Z","receivedAt":"2026-04-20T10:12:32Z","isPatch":true,"body":"When using git-update-ref(1) with the '--batch-updates' flag, updates\nrejected by the reference backend are displayed to the user while other\nupdates are applied. This only applies during the committing of the\ntransaction.\n\nIn the following commits, we'll also extend `ref_transaction_update()`\nto reject updates before even a transaction is prepared/committed. In\npreparation, modify the code in update-ref to also handle non-generic\nrejections from `ref_transaction_update()`. This involves propagating\ninformation to each of the commands on whether updates are allowed to be\nrejected, and also checking for rejections and only dying for generic\nfailures.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 98 ++++++++++++++++++++++++++++++++++++----------------\n 1 file changed, 69 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 5259cc7226..d1980c60c4 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -25,6 +25,15 @@ static unsigned int default_flags;\n static unsigned create_reflog_flag;\n static const char *msg;\n \n+struct command_options {\n+\t/*\n+\t * Individual updates are allowed to fail without causing\n+\t * update-ref to exit. This is set when using the\n+\t * '--batch-updates' flag.\n+\t */\n+\tbool allow_update_failures;\n+};\n+\n /*\n  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n  * and append the result to arg.  Return a pointer to the terminator.\n@@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,\n  */\n \n static void parse_cmd_update(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id new_oid, old_oid;\n+\tenum ref_transaction_error tx_err;\n \tint have_old;\n \n \trefname = parse_refname(&next);\n@@ -289,11 +300,18 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname,\n-\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n-\t\t\t\t   NULL, NULL,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n+\ttx_err = ref_transaction_update(transaction, refname,\n+\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t\tNULL, NULL,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\n+\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n+\t    opts->allow_update_failures)\n+\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n+\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n+\t\t\t\t    NULL);\n+\telse if (tx_err)\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\n@@ -302,9 +320,11 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_symref_update(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts)\n {\n \tchar *refname, *new_target, *old_arg;\n+\tenum ref_transaction_error tx_err;\n \tchar *old_target = NULL;\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct object_id old_oid;\n@@ -341,12 +361,19 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname, NULL,\n-\t\t\t\t   have_old_oid ? &old_oid : NULL,\n-\t\t\t\t   new_target,\n-\t\t\t\t   have_old_oid ? NULL : old_target,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n+\ttx_err = ref_transaction_update(transaction, refname, NULL,\n+\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n+\t\t\t\t\tnew_target,\n+\t\t\t\t\thave_old_oid ? NULL : old_target,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\n+\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n+\t    opts->allow_update_failures)\n+\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n+\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n+\t\t\t\t    new_target, tx_err, err.buf, NULL);\n+\telse if (tx_err)\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\n@@ -358,7 +385,8 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_create(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -387,9 +415,9 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_symref_create(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname, *new_target;\n@@ -417,7 +445,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_delete(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -450,9 +479,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname, *old_target;\n@@ -479,9 +508,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_verify(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -508,7 +537,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct object_id old_oid;\n@@ -550,7 +580,8 @@ static void report_ok(const char *command)\n }\n \n static void parse_cmd_option(struct ref_transaction *transaction UNUSED,\n-\t\t\t     const char *next, const char *end UNUSED)\n+\t\t\t     const char *next, const char *end UNUSED,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tconst char *rest;\n \tif (skip_prefix(next, \"no-deref\", &rest) && *rest == line_termination)\n@@ -560,7 +591,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED,\n }\n \n static void parse_cmd_start(struct ref_transaction *transaction UNUSED,\n-\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t    struct command_options *opts UNUSED)\n {\n \tif (*next != line_termination)\n \t\tdie(\"start: extra input: %s\", next);\n@@ -568,7 +600,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED,\n }\n \n static void parse_cmd_prepare(struct ref_transaction *transaction,\n-\t\t\t      const char *next, const char *end UNUSED)\n+\t\t\t      const char *next, const char *end UNUSED,\n+\t\t\t      struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -579,7 +612,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_abort(struct ref_transaction *transaction,\n-\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -590,7 +624,8 @@ static void parse_cmd_abort(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_commit(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end UNUSED)\n+\t\t\t     const char *next, const char *end UNUSED,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -618,7 +653,8 @@ enum update_refs_state {\n \n static const struct parse_cmd {\n \tconst char *prefix;\n-\tvoid (*fn)(struct ref_transaction *, const char *, const char *);\n+\tvoid (*fn)(struct ref_transaction *, const char *, const char *,\n+\t\t   struct command_options *);\n \tunsigned args;\n \tenum update_refs_state state;\n } command[] = {\n@@ -644,6 +680,10 @@ static void update_refs_stdin(unsigned int flags)\n \tstruct ref_transaction *transaction;\n \tint i, j;\n \n+\tstruct command_options opts = {\n+\t\t.allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,\n+\t};\n+\n \ttransaction = ref_store_transaction_begin(get_main_ref_store(the_repository),\n \t\t\t\t\t\t  flags, &err);\n \tif (!transaction)\n@@ -721,7 +761,7 @@ static void update_refs_stdin(unsigned int flags)\n \t\t}\n \n \t\tcmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,\n-\t\t\tinput.buf + input.len);\n+\t\t\tinput.buf + input.len, &opts);\n \t}\n \n \tswitch (state) {\n\n-- \n2.53.GIT\n\n"},{"id":"541959","messageId":"20260420-refs-move-to-generic-layer-v1-6-513e354f376b@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH 6/8] refs: move object parsing to the generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:12:04Z","receivedAt":"2026-04-20T10:12:34Z","isPatch":true,"body":"Regular reference updates made via reference transactions validate that\nthe provided object ID exists in the object database, this is done by\ncalling 'parse_object()'. This check is done independently by the\nbackends.\n\nLet's move this to the generic layer, ensuring the backends only have to\ncare about reference storage and not about validation of the object IDs.\nWith this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'\nerror type as its no longer used.\n\nSince we don't iterate over individual references in\n`ref_transaction_prepare()`, we add this check to\n`ref_transaction_update()`. This means that the validation is done as\nsoon as an update is queued, without needing to prepare the\ntransaction. It can be argued that this is more ideal, since this\nvalidation has no dependency on the reference transaction being\nprepared.\n\nIt must be noted that the change in behavior means that this error\ncannot be ignored even with usage of batched updates, since this happens\nwhen the update is being added to the transaction. But since the caller\ngets specific error codes, they can either abort the transaction or\ncontinue adding other updates to the transaction.\n\nModify 'builtin/receive-pack.c' to now capture the error type so that\nthe error propagated to the client stays the same. Also remove two of\nthe tests which validates batch-updates with invalid new_oid.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/receive-pack.c  | 22 +++++++++++++---------\n refs.c                  | 18 ++++++++++++++++++\n refs/files-backend.c    | 28 ++--------------------------\n refs/reftable-backend.c | 19 -------------------\n 4 files changed, 33 insertions(+), 54 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 878aa7f0ed..0fb3d57de8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\tret = NULL; /* good */\n \t\t}\n \t\tstrbuf_release(&err);\n-\t}\n-\telse {\n+\t} else {\n+\t\tenum ref_transaction_error err_type;\n \t\tstruct strbuf err = STRBUF_INIT;\n \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n \t\t    update_shallow_ref(cmd, si)) {\n@@ -1650,14 +1650,18 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (ref_transaction_update(transaction,\n-\t\t\t\t\t   namespaced_name,\n-\t\t\t\t\t   new_oid, old_oid,\n-\t\t\t\t\t   NULL, NULL,\n-\t\t\t\t\t   0, \"push\",\n-\t\t\t\t\t   &err)) {\n+\t\terr_type = ref_transaction_update(transaction,\n+\t\t\t\t\t\t  namespaced_name,\n+\t\t\t\t\t\t  new_oid, old_oid,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  0, \"push\",\n+\t\t\t\t\t\t  &err);\n+\t\tif (err_type) {\n \t\t\trp_error(\"%s\", err.buf);\n-\t\t\tret = \"failed to update ref\";\n+\t\t\tif (err_type == REF_TRANSACTION_ERROR_GENERIC)\n+\t\t\t\tret = \"failed to update ref\";\n+\t\t\telse\n+\t\t\t\tret = ref_transaction_error_msg(err_type);\n \t\t} else {\n \t\t\tret = NULL; /* good */\n \t\t}\ndiff --git a/refs.c b/refs.c\nindex 39fef1cca0..bbe19155f4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n+\tif ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&\n+\t    !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {\n+\t\tstruct object *o = parse_object(transaction->ref_store->repo, new_oid);\n+\n+\t\tif (!o) {\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    _(\"trying to write ref '%s' with nonexistent object %s\"),\n+\t\t\t\t    refname, oid_to_hex(new_oid));\n+\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n+\t\t}\n+\n+\t\tif (o->type != OBJ_COMMIT && is_branch(refname)) {\n+\t\t\tstrbuf_addf(err, _(\"trying to write non-commit object %s to branch '%s'\"),\n+\t\t\t\t    oid_to_hex(new_oid), refname);\n+\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n+\t\t}\n+\t}\n+\n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n \t\t\t\t   old_target, NULL, msg);\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 407b97cc44..0e2bbe37a0 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -19,7 +19,6 @@\n #include \"../iterator.h\"\n #include \"../dir-iterator.h\"\n #include \"../lockfile.h\"\n-#include \"../object.h\"\n #include \"../path.h\"\n #include \"../dir.h\"\n #include \"../chdir-notify.h\"\n@@ -1589,7 +1588,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)\n static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,\n \t\t\t\t\t\t\tstruct ref_lock *lock,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n-\t\t\t\t\t\t\tint skip_oid_verification,\n \t\t\t\t\t\t\tstruct strbuf *err);\n static int commit_ref_update(struct files_ref_store *refs,\n \t\t\t     struct ref_lock *lock,\n@@ -1737,7 +1735,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,\n \t}\n \toidcpy(&lock->old_oid, &orig_oid);\n \n-\tif (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||\n+\tif (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||\n \t    commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) {\n \t\terror(\"unable to write current sha1 into %s: %s\", newrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -1755,7 +1753,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,\n \t\tgoto rollbacklog;\n \t}\n \n-\tif (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||\n+\tif (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||\n \t    commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) {\n \t\terror(\"unable to write current sha1 into %s: %s\", oldrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -1999,32 +1997,11 @@ static int files_log_ref_write(struct files_ref_store *refs,\n static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,\n \t\t\t\t\t\t\tstruct ref_lock *lock,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n-\t\t\t\t\t\t\tint skip_oid_verification,\n \t\t\t\t\t\t\tstruct strbuf *err)\n {\n \tstatic char term = '\\n';\n-\tstruct object *o;\n \tint fd;\n \n-\tif (!skip_oid_verification) {\n-\t\to = parse_object(refs->base.repo, oid);\n-\t\tif (!o) {\n-\t\t\tstrbuf_addf(\n-\t\t\t\terr,\n-\t\t\t\t\"trying to write ref '%s' with nonexistent object %s\",\n-\t\t\t\tlock->ref_name, oid_to_hex(oid));\n-\t\t\tunlock_ref(lock);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t\tif (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {\n-\t\t\tstrbuf_addf(\n-\t\t\t\terr,\n-\t\t\t\t\"trying to write non-commit object %s to branch '%s'\",\n-\t\t\t\toid_to_hex(oid), lock->ref_name);\n-\t\t\tunlock_ref(lock);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t}\n \tfd = get_lock_file_fd(&lock->lk);\n \tif (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 ||\n \t    write_in_full(fd, &term, 1) < 0 ||\n@@ -2828,7 +2805,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re\n \t\t} else {\n \t\t\tret = write_ref_to_lockfile(\n \t\t\t\trefs, lock, &update->new_oid,\n-\t\t\t\tupdate->flags & REF_SKIP_OID_VERIFICATION,\n \t\t\t\terr);\n \t\t\tif (ret) {\n \t\t\t\tchar *write_err = strbuf_detach(err, NULL);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex bdc3e0aa19..fdf7336c0f 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1081,25 +1081,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\treturn 0;\n \t}\n \n-\t/* Verify that the new object ID is valid. */\n-\tif ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&\n-\t    !(u->flags & REF_SKIP_OID_VERIFICATION) &&\n-\t    !(u->flags & REF_LOG_ONLY)) {\n-\t\tstruct object *o = parse_object(refs->base.repo, &u->new_oid);\n-\t\tif (!o) {\n-\t\t\tstrbuf_addf(err,\n-\t\t\t\t    _(\"trying to write ref '%s' with nonexistent object %s\"),\n-\t\t\t\t    u->refname, oid_to_hex(&u->new_oid));\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\n-\t\tif (o->type != OBJ_COMMIT && is_branch(u->refname)) {\n-\t\t\tstrbuf_addf(err, _(\"trying to write non-commit object %s to branch '%s'\"),\n-\t\t\t\t    oid_to_hex(&u->new_oid), u->refname);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t}\n-\n \t/*\n \t * When we update the reference that HEAD points to we enqueue\n \t * a second log-only update for HEAD so that its reflog is\n\n-- \n2.53.GIT\n\n"},{"id":"541960","messageId":"20260420-refs-move-to-generic-layer-v1-8-513e354f376b@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH 8/8] refs: use peeled tag values in reference backends","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:12:06Z","receivedAt":"2026-04-20T10:12:37Z","isPatch":true,"body":"The reference backends peel tag objects when storing references to them.\nThis is to provide optimized reads which avoids hitting the odb. The\nprevious commits ensures that the peeled value is now propagated via the\ngeneric layer. So modify the packed and reftable backend to directly use\nthis value instead of calling `peel_object()` independently.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/packed-backend.c   | 6 ++----\n refs/reftable-backend.c | 9 ++-------\n 2 files changed, 4 insertions(+), 11 deletions(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 195600cdad..64d2e105e2 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\t */\n \t\t\ti++;\n \t\t} else {\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n-\t\t\t\t\t\t     &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);\n+\t\t\tbool peeled = update->flags & REF_HAVE_PEELED;\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\n-\t\t\t\t\t       peel_error ? NULL : &peeled))\n+\t\t\t\t\t       peeled ? &update->peeled : NULL))\n \t\t\t\tgoto write_error;\n \n \t\t\ti++;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 7416bb72fa..001be56e8c 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -12,7 +12,6 @@\n #include \"../hex.h\"\n #include \"../ident.h\"\n #include \"../iterator.h\"\n-#include \"../object.h\"\n #include \"../parse.h\"\n #include \"../path.h\"\n #include \"../refs.h\"\n@@ -1584,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t\tgoto done;\n \t\t} else if (u->flags & REF_HAVE_NEW) {\n \t\t\tstruct reftable_ref_record ref = {0};\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error;\n \n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,\n-\t\t\t\t\t\t PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);\n-\t\t\tif (!peel_error) {\n+\t\t\tif (u->flags & REF_HAVE_PEELED) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n-\t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tmemcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ);\n \t\t\t\tmemcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ);\n \t\t\t} else if (!is_null_oid(&u->new_oid)) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL1;\n\n-- \n2.53.GIT\n\n"},{"id":"541961","messageId":"20260420-refs-move-to-generic-layer-v1-7-513e354f376b@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH 7/8] refs: add peeled object ID to the `ref_update` struct","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-20T10:12:05Z","receivedAt":"2026-04-20T10:12:38Z","isPatch":true,"body":"Certain reference backend {packed, reftable}, have the ability to also\nstore the peeled object ID for a reference pointing to a tag object.\nThis has the added benefit that during retrieval of such references, we\nalso obtain the peeled object ID without having to use the ODB.\n\nTo provide this functionality, each backend independently calls the ODB\nto obtain the peeled OID. To move this functionality to the generic\nlayer, there must be support infrastructure to pass in a peeled OID for\nreference updates.\n\nAdd a `peeled` field to the `ref_update` structure and modify\n`ref_transaction_add_update()` to receive and copy this object ID to the\n`ref_update` structure. Finally, modify `ref_transaction_update()` to\npeel tag objects and pass the peeled OID to\n`ref_transaction_add_update()`.\n\nUpdate all callers of these functions with the new function parameters.\nCallers which only add reflog updates, need to only pass in NULL, since\nfor reflogs, we don't store peeled OIDs. Reference deletions also only\nneed to pass in NULL. For others, pass along the peeled OID if\navailable.\n\nIn a following commit, we'll modify the backends to use this peeled OID\ninstead of parsing it themselves.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 15 +++++++++++++--\n refs/files-backend.c    | 20 ++++++++++++--------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  6 +++---\n 4 files changed, 42 insertions(+), 13 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex bbe19155f4..63a54c460e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1307,6 +1307,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst struct object_id *peeled,\n \t\tconst char *new_target, const char *old_target,\n \t\tconst char *committer_info,\n \t\tconst char *msg)\n@@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update(\n \t\tupdate->committer_info = xstrdup_or_null(committer_info);\n \t\tupdate->msg = normalize_reflog_message(msg);\n \t}\n+\tif (flags & REF_HAVE_PEELED)\n+\t\toidcpy(&update->peeled, peeled);\n \n \t/*\n \t * This list is generally used by the backends to avoid duplicates.\n@@ -1392,6 +1395,8 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \t\t\t\t\t\t  unsigned int flags, const char *msg,\n \t\t\t\t\t\t  struct strbuf *err)\n {\n+\tstruct object_id peeled;\n+\n \tassert(err);\n \n \tif ((flags & REF_FORCE_CREATE_REFLOG) &&\n@@ -1432,10 +1437,16 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \t\t\t\t    oid_to_hex(new_oid), refname);\n \t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n \t\t}\n+\n+\t\tif (o->type == OBJ_TAG) {\n+\t\t\tif (!peel_object(transaction->ref_store->repo, new_oid, &peeled,\n+\t\t\t\t\t PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))\n+\t\t\t\tflags |= REF_HAVE_PEELED;\n+\t\t}\n \t}\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   new_oid, old_oid, &peeled, new_target,\n \t\t\t\t   old_target, NULL, msg);\n \n \treturn 0;\n@@ -1462,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction,\n \t\treturn -1;\n \n \tupdate = ref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t\t    new_oid, old_oid, NULL, NULL,\n+\t\t\t\t\t    new_oid, old_oid, NULL, NULL, NULL,\n \t\t\t\t\t    committer_info, msg);\n \tupdate->index = index;\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0e2bbe37a0..c7292a3c17 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1325,7 +1325,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);\n+\t\t\tnull_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,\n+\t\t\tNULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -2468,7 +2469,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,\n-\t\t\t&update->new_oid, &update->old_oid,\n+\t\t\t&update->new_oid, &update->old_oid, &update->peeled,\n \t\t\tNULL, NULL, update->committer_info, update->msg);\n \tnew_update->parent_update = update;\n \n@@ -2530,8 +2531,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,\n \t\t\ttransaction, referent, new_flags,\n \t\t\tupdate->new_target ? NULL : &update->new_oid,\n \t\t\tupdate->old_target ? NULL : &update->old_oid,\n-\t\t\tupdate->new_target, update->old_target, NULL,\n-\t\t\tupdate->msg);\n+\t\t\t&update->peeled, update->new_target, update->old_target,\n+\t\t\tNULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2994,7 +2995,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\tref_transaction_add_update(\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n-\t\t\t\t\t&update->new_oid, NULL,\n+\t\t\t\t\t&update->new_oid, NULL, NULL,\n \t\t\t\t\tNULL, NULL, NULL, NULL);\n \t\t}\n \t}\n@@ -3200,19 +3201,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,\n \t\t\tif (update->flags & REF_LOG_ONLY)\n \t\t\t\tref_transaction_add_update(loose_transaction, update->refname,\n \t\t\t\t\t\t\t   update->flags, &update->new_oid,\n-\t\t\t\t\t\t\t   &update->old_oid, NULL, NULL,\n+\t\t\t\t\t\t\t   &update->old_oid, &update->peeled,\n+\t\t\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t\t\t   update->committer_info, update->msg);\n \t\t\telse\n \t\t\t\tref_transaction_add_update(loose_transaction, update->refname,\n \t\t\t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t\t\t   update->new_target ? NULL : &update->new_oid, NULL,\n-\t\t\t\t\t\t\t   update->new_target, NULL, update->committer_info,\n+\t\t\t\t\t\t\t   &update->peeled, update->new_target,\n+\t\t\t\t\t\t\t   NULL, update->committer_info,\n \t\t\t\t\t\t\t   NULL);\n \t\t} else {\n \t\t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t\t   NULL, NULL, update->committer_info, NULL);\n+\t\t\t\t\t\t   &update->peeled, NULL, NULL,\n+\t\t\t\t\t\t   update->committer_info, NULL);\n \t\t}\n \t}\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex eed13af4eb..620aeb8320 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -39,6 +39,13 @@ struct ref_transaction;\n  */\n #define REF_LOG_ONLY (1 << 7)\n \n+/*\n+ * The reference contains a peeled object ID. This is used when the\n+ * new_oid is pointing to a tag object and the reference backend\n+ * wants to also store the peeled value for optimized retrieval.\n+ */\n+#define REF_HAVE_PEELED (1 << 15)\n+\n /*\n  * Return the length of time to retry acquiring a loose reference lock\n  * before giving up, in milliseconds:\n@@ -92,6 +99,12 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If the new_oid points to a tag object, set this to the peeled\n+\t * object ID for optimized retrieval without needed to hit the odb.\n+\t */\n+\tstruct object_id peeled;\n+\n \t/*\n \t * If set, point the reference to this value. This can also be\n \t * used to convert regular references to become symbolic refs.\n@@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst struct object_id *peeled,\n \t\tconst char *new_target, const char *old_target,\n \t\tconst char *committer_info,\n \t\tconst char *msg);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex fdf7336c0f..7416bb72fa 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1107,8 +1107,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\tref_transaction_add_update(\n \t\t\ttransaction, \"HEAD\",\n \t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, NULL,\n-\t\t\tu->msg);\n+\t\t\t&u->new_oid, &u->old_oid, &u->peeled, NULL, NULL,\n+\t\t\tNULL, u->msg);\n \t}\n \n \tret = reftable_backend_read_ref(be, rewritten_ref,\n@@ -1194,7 +1194,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\t\t\ttransaction, referent->buf, new_flags,\n \t\t\t\tu->new_target ? NULL : &u->new_oid,\n \t\t\t\tu->old_target ? NULL : &u->old_oid,\n-\t\t\t\tu->new_target, u->old_target,\n+\t\t\t\t&u->peeled, u->new_target, u->old_target,\n \t\t\t\tu->committer_info, u->msg);\n \n \t\t\tnew_update->parent_update = u;\n\n-- \n2.53.GIT\n\n"},{"id":"542107","messageId":"aeityL_05KDRZF98@pks.im","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-1-513e354f376b@gmail.com","subject":"Re: [PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn'","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-22T11:15:20Z","receivedAt":"2026-04-22T11:15:31Z","isPatch":true,"body":"On Mon, Apr 20, 2026 at 12:11:59PM +0200, Karthik Nayak wrote:\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index d79e35fd26..2d963cc4f4 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,\n>  \t\t\t\t     struct ref_transaction *transaction,\n>  \t\t\t\t     struct strbuf *err);\n>  \n> -typedef int ref_transaction_commit_fn(struct ref_store *refs,\n> -\t\t\t\t      struct ref_transaction *transaction,\n> -\t\t\t\t      struct strbuf *err);\n> -\n>  typedef int optimize_fn(struct ref_store *ref_store,\n>  \t\t\tstruct refs_optimize_opts *opts);\n>  \n\nI'm in general not much of a fan of these typedefs -- there's not really\nmuch of a point why we'd need them in the first place. We don't use them\nas a type anywhere but in the struct definition for the ref backend. So\nwe could just as well move them in there, which would also ensure that\nthey cannot become stale in the first place.\n\nPatrick\n"},{"id":"542108","messageId":"aeit0pw44IxBfc2J@pks.im","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-2-513e354f376b@gmail.com","subject":"Re: [PATCH 2/8] refs: extract out reflog config to generic layer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-22T11:15:30Z","receivedAt":"2026-04-22T11:15:35Z","isPatch":true,"body":"On Mon, Apr 20, 2026 at 12:12:00PM +0200, Karthik Nayak wrote:\n> The reference backends need to know when to create reflog entries, this\n\ns/this/which/\n\n> is dictated by the 'core.logallrefupdates' config. Instead of relying on\n\ns/dictated/controlled/\n\n> the backends to call `repo_settings_get_log_all_ref_updates()` to obtain\n> this config value, let's do this in the generic layer and pass down the\n> value to the backends.\n> \n> Instead of passing this in as a new argument, let's create a new\n> `ref_init_options` structure which will house information required to\n> initialize a reference backend. Move the access flags here as well.\n\nI agree with this direction. It's also something that I'm doing for many\ncallbacks in the ODB layer, and I'm moving more and more into that\ndirection.\n\n> diff --git a/refs.c b/refs.c\n> index bfcb9c7ac3..aa66c6b28e 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2295,6 +2295,10 @@ static struct ref_store *ref_store_init(struct repository *repo,\n>  {\n>  \tconst struct ref_storage_be *be;\n>  \tstruct ref_store *refs;\n> +\tstruct ref_store_init_options options = {\n> +\t\t.access_flags = flags,\n> +\t\t.log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),\n> +\t};\n>  \n>  \tbe = find_ref_storage_backend(format);\n>  \tif (!be)\n\nTiniest nit, please feel free to ignore: we often call the structure\nitself `_options`, but the variables just `opts`. May just be my own\npreference though.\n\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index 2d963cc4f4..eed13af4eb 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -385,6 +385,21 @@ struct ref_store;\n>  \t\t\t\t REF_STORE_ODB | \\\n>  \t\t\t\t REF_STORE_MAIN)\n>  \n> +/*\n> + * Options for initializing the ref backend. All backend-agnostic information\n> + * which backends required will be held here.\n> + */\n> +struct ref_store_init_options {\n> +\t/* The kind of operations that the ref_store is allowed to perform. */\n> +\tunsigned int access_flags;\n> +\n> +\t/*\n> +\t * Denotes under what conditions reflogs should be created when updating\n> +\t * references.\n> +\t */\n> +\tenum log_refs_config log_all_ref_updates;\n> +};\n\nNit: it might've made sense to split this up into two steps: the\nintroduction of the struct, and then moving the config in there.\n\nPatrick\n"},{"id":"542109","messageId":"aeit12h5l34Wkon-@pks.im","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-3-513e354f376b@gmail.com","subject":"Re: [PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-22T11:15:35Z","receivedAt":"2026-04-22T11:15:41Z","isPatch":true,"body":"On Mon, Apr 20, 2026 at 12:12:01PM +0200, Karthik Nayak wrote:\n> The `ref_transaction_update()` function is used to add updates to a\n> given reference transactions. In the following commit, we'll add more\n> validation to this function. As such, it would be more beneficial if the\n\ns/more beneficial/beneficial/\n\n> function returns specific error types, so callers can differentiate\n> between different errors.\n> \n> To facilitate this, return `enum ref_transaction_error` from the\n> function and covert the existing '-1' returns to\n> 'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing\n> behavior, no changes are made to any of the callers but this sets the\n> necessary infrastructure for introduction of other errors.\n\nYup, makes sense. This doesn't buy us anything yet, but will eventually.\n\nPatrick\n"},{"id":"542110","messageId":"aeit3RVFhX680NfZ@pks.im","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-4-513e354f376b@gmail.com","subject":"Re: [PATCH 4/8] update-ref: move `print_rejected_refs()` up","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-22T11:15:41Z","receivedAt":"2026-04-22T11:15:46Z","isPatch":true,"body":"On Mon, Apr 20, 2026 at 12:12:02PM +0200, Karthik Nayak wrote:\n> The `print_rejected_refs()` is used to print any rejected refs when\n\nSeems like a word is missing here. Maybe \"The `print_rejected_refs()`\nfunction\"?\n\nPatrick\n"},{"id":"542111","messageId":"aeit6Ic4fUa3Xx1Z@pks.im","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-5-513e354f376b@gmail.com","subject":"Re: [PATCH 5/8] update-ref: handle rejections while adding updates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-22T11:15:52Z","receivedAt":"2026-04-22T11:15:58Z","isPatch":true,"body":"On Mon, Apr 20, 2026 at 12:12:03PM +0200, Karthik Nayak wrote:\n> When using git-update-ref(1) with the '--batch-updates' flag, updates\n> rejected by the reference backend are displayed to the user while other\n> updates are applied. This only applies during the committing of the\n\nNit: s/committing/commit phase/\n\n> transaction.\n> \n> In the following commits, we'll also extend `ref_transaction_update()`\n> to reject updates before even a transaction is prepared/committed. In\n\nThe \"even\" reads like it's at the wrong position.\n\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index 5259cc7226..d1980c60c4 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,\n>   */\n>  \n>  static void parse_cmd_update(struct ref_transaction *transaction,\n> -\t\t\t     const char *next, const char *end)\n> +\t\t\t     const char *next, const char *end,\n> +\t\t\t     struct command_options *opts)\n\nHere you follow my earlier suggestion of using `_options` for the struct\nname, but `opts` for the variable :)\n\n> @@ -289,11 +300,18 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>  \tif (*next != line_termination)\n>  \t\tdie(\"update %s: extra input: %s\", refname, next);\n>  \n> -\tif (ref_transaction_update(transaction, refname,\n> -\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n> -\t\t\t\t   NULL, NULL,\n> -\t\t\t\t   update_flags | create_reflog_flag,\n> -\t\t\t\t   msg, &err))\n> +\ttx_err = ref_transaction_update(transaction, refname,\n> +\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n> +\t\t\t\t\tNULL, NULL,\n> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n> +\t\t\t\t\tmsg, &err);\n> +\n> +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n> +\t    opts->allow_update_failures)\n> +\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n> +\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n> +\t\t\t\t    NULL);\n> +\telse if (tx_err)\n>  \t\tdie(\"%s\", err.buf);\n>  \n>  \tupdate_flags = default_flags;\n\nI think this could use some curly braces to become easier to read, even\nif it's only single-line statements.\n\nDoes this change have an impact on the ordering the user sees for\nprinted errors? If so, it might make sense to point that out in the\ncommit message.\n\n> @@ -341,12 +361,19 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n>  \tif (*next != line_termination)\n>  \t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n>  \n> -\tif (ref_transaction_update(transaction, refname, NULL,\n> -\t\t\t\t   have_old_oid ? &old_oid : NULL,\n> -\t\t\t\t   new_target,\n> -\t\t\t\t   have_old_oid ? NULL : old_target,\n> -\t\t\t\t   update_flags | create_reflog_flag,\n> -\t\t\t\t   msg, &err))\n> +\ttx_err = ref_transaction_update(transaction, refname, NULL,\n> +\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n> +\t\t\t\t\tnew_target,\n> +\t\t\t\t\thave_old_oid ? NULL : old_target,\n> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n> +\t\t\t\t\tmsg, &err);\n> +\n> +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n> +\t    opts->allow_update_failures)\n> +\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n> +\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n> +\t\t\t\t    new_target, tx_err, err.buf, NULL);\n> +\telse if (tx_err)\n>  \t\tdie(\"%s\", err.buf);\n>  \n>  \tupdate_flags = default_flags;\n\nSame here, curly braces might help readability.\n\n> @@ -644,6 +680,10 @@ static void update_refs_stdin(unsigned int flags)\n>  \tstruct ref_transaction *transaction;\n>  \tint i, j;\n>  \n> +\tstruct command_options opts = {\n> +\t\t.allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,\n> +\t};\n> +\n>  \ttransaction = ref_store_transaction_begin(get_main_ref_store(the_repository),\n>  \t\t\t\t\t\t  flags, &err);\n>  \tif (!transaction)\n\nSuggestion, please feel free to ignore: we could also pass the flags\ndirectly instead.\n\nPatrick\n"},{"id":"542112","messageId":"aeit7X0Q_MlxvPas@pks.im","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-6-513e354f376b@gmail.com","subject":"Re: [PATCH 6/8] refs: move object parsing to the generic layer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-22T11:15:57Z","receivedAt":"2026-04-22T11:16:03Z","isPatch":true,"body":"On Mon, Apr 20, 2026 at 12:12:04PM +0200, Karthik Nayak wrote:\n> Regular reference updates made via reference transactions validate that\n> the provided object ID exists in the object database, this is done by\n\ns/this/which/\n\n> calling 'parse_object()'. This check is done independently by the\n> backends.\n\n..., which leads to duplicated logic.\n\n> Let's move this to the generic layer, ensuring the backends only have to\n> care about reference storage and not about validation of the object IDs.\n> With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'\n> error type as its no longer used.\n> \n> Since we don't iterate over individual references in\n> `ref_transaction_prepare()`, we add this check to\n> `ref_transaction_update()`. This means that the validation is done as\n> soon as an update is queued, without needing to prepare the\n> transaction. It can be argued that this is more ideal, since this\n> validation has no dependency on the reference transaction being\n> prepared.\n> \n> It must be noted that the change in behavior means that this error\n> cannot be ignored even with usage of batched updates, since this happens\n> when the update is being added to the transaction. But since the caller\n> gets specific error codes, they can either abort the transaction or\n> continue adding other updates to the transaction.\n\nRight, this is what the preceding commits have allowed us to do.\n\nI think this is a good step. Being less entangled with the object\ndatabase in the ref backends is a good thing.\n\nPatrick\n"},{"id":"542125","messageId":"CAOLa=ZRHVUYYLE6JHJ8hJBOp7teTAZgz2vB+chMA6kr0RNb7zw@mail.gmail.com","threadId":"65517","inReplyTo":"aeityL_05KDRZF98@pks.im","subject":"Re: [PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-22T12:20:20Z","receivedAt":"2026-04-22T12:20:22Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Apr 20, 2026 at 12:11:59PM +0200, Karthik Nayak wrote:\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index d79e35fd26..2d963cc4f4 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,\n>>  \t\t\t\t     struct ref_transaction *transaction,\n>>  \t\t\t\t     struct strbuf *err);\n>>\n>> -typedef int ref_transaction_commit_fn(struct ref_store *refs,\n>> -\t\t\t\t      struct ref_transaction *transaction,\n>> -\t\t\t\t      struct strbuf *err);\n>> -\n>>  typedef int optimize_fn(struct ref_store *ref_store,\n>>  \t\t\tstruct refs_optimize_opts *opts);\n>>\n>\n> I'm in general not much of a fan of these typedefs -- there's not really\n> much of a point why we'd need them in the first place. We don't use them\n> as a type anywhere but in the struct definition for the ref backend. So\n> we could just as well move them in there, which would also ensure that\n> they cannot become stale in the first place.\n>\n> Patrick\n\nI do like them, when reading `struct ref_storage_be {}` with these\ntypedefs, the interface is simplified and I only need to know what are\nthe different functions which need to be implemented without being\nexposed to the details of each of the functions. The details are\navailable in the typedefs.\n\nI'll leave it out of this series though.\n"},{"id":"542126","messageId":"CAOLa=ZTvYEzFrLCgJm6hVXUppHKCwPBws9eZeiBYbx2o8EaT_w@mail.gmail.com","threadId":"65517","inReplyTo":"aeit0pw44IxBfc2J@pks.im","subject":"Re: [PATCH 2/8] refs: extract out reflog config to generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-22T13:13:16Z","receivedAt":"2026-04-22T13:13:18Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Apr 20, 2026 at 12:12:00PM +0200, Karthik Nayak wrote:\n>> The reference backends need to know when to create reflog entries, this\n>\n> s/this/which/\n>\n\nBetter.\n\n>> is dictated by the 'core.logallrefupdates' config. Instead of relying on\n>\n> s/dictated/controlled/\n>\n\nBoth of those mean the same. I guess there is a negative connotation to\nusing the word 'dictated', but that's present with 'controlled' too.\nPerhaps 'determined'?\n\n>> the backends to call `repo_settings_get_log_all_ref_updates()` to obtain\n>> this config value, let's do this in the generic layer and pass down the\n>> value to the backends.\n>>\n>> Instead of passing this in as a new argument, let's create a new\n>> `ref_init_options` structure which will house information required to\n>> initialize a reference backend. Move the access flags here as well.\n>\n> I agree with this direction. It's also something that I'm doing for many\n> callbacks in the ODB layer, and I'm moving more and more into that\n> direction.\n>\n>> diff --git a/refs.c b/refs.c\n>> index bfcb9c7ac3..aa66c6b28e 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -2295,6 +2295,10 @@ static struct ref_store *ref_store_init(struct repository *repo,\n>>  {\n>>  \tconst struct ref_storage_be *be;\n>>  \tstruct ref_store *refs;\n>> +\tstruct ref_store_init_options options = {\n>> +\t\t.access_flags = flags,\n>> +\t\t.log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),\n>> +\t};\n>>\n>>  \tbe = find_ref_storage_backend(format);\n>>  \tif (!be)\n>\n> Tiniest nit, please feel free to ignore: we often call the structure\n> itself `_options`, but the variables just `opts`. May just be my own\n> preference though.\n>\n\nLet's make it consistent, I'll also start using `opts`.\n\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index 2d963cc4f4..eed13af4eb 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -385,6 +385,21 @@ struct ref_store;\n>>  \t\t\t\t REF_STORE_ODB | \\\n>>  \t\t\t\t REF_STORE_MAIN)\n>>\n>> +/*\n>> + * Options for initializing the ref backend. All backend-agnostic information\n>> + * which backends required will be held here.\n>> + */\n>> +struct ref_store_init_options {\n>> +\t/* The kind of operations that the ref_store is allowed to perform. */\n>> +\tunsigned int access_flags;\n>> +\n>> +\t/*\n>> +\t * Denotes under what conditions reflogs should be created when updating\n>> +\t * references.\n>> +\t */\n>> +\tenum log_refs_config log_all_ref_updates;\n>> +};\n>\n> Nit: it might've made sense to split this up into two steps: the\n> introduction of the struct, and then moving the config in there.\n>\n> Patrick\n\nYeah that might be better. I'll go ahead and do that.\n"},{"id":"542128","messageId":"CAOLa=ZT55_3433Si_dGy6U+XkOK6SiiyG3h1B5fk7SysphSsSg@mail.gmail.com","threadId":"65517","inReplyTo":"aeit12h5l34Wkon-@pks.im","subject":"Re: [PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-22T13:14:44Z","receivedAt":"2026-04-22T13:14:47Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Apr 20, 2026 at 12:12:01PM +0200, Karthik Nayak wrote:\n>> The `ref_transaction_update()` function is used to add updates to a\n>> given reference transactions. In the following commit, we'll add more\n>> validation to this function. As such, it would be more beneficial if the\n>\n> s/more beneficial/beneficial/\n>\n\nWill do.\n\n>> function returns specific error types, so callers can differentiate\n>> between different errors.\n>>\n>> To facilitate this, return `enum ref_transaction_error` from the\n>> function and covert the existing '-1' returns to\n>> 'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing\n>> behavior, no changes are made to any of the callers but this sets the\n>> necessary infrastructure for introduction of other errors.\n>\n> Yup, makes sense. This doesn't buy us anything yet, but will eventually.\n>\n> Patrick\n\nExactly!\n"},{"id":"542129","messageId":"CAOLa=ZSh1ciC-JD9M-dC9iiT=Q0up5UfodQ5VAGvNtTsZMUYww@mail.gmail.com","threadId":"65517","inReplyTo":"aeit3RVFhX680NfZ@pks.im","subject":"Re: [PATCH 4/8] update-ref: move `print_rejected_refs()` up","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-22T13:16:13Z","receivedAt":"2026-04-22T13:16:15Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Apr 20, 2026 at 12:12:02PM +0200, Karthik Nayak wrote:\n>> The `print_rejected_refs()` is used to print any rejected refs when\n>\n> Seems like a word is missing here. Maybe \"The `print_rejected_refs()`\n> function\"?\n>\n> Patrick\n\nYeah adding 'function' reads better. Thanks\n"},{"id":"542133","messageId":"CAOLa=ZT+JL5pbvKx44fkOMqx07TcZxbC--xCTAsUThf9a7RCjQ@mail.gmail.com","threadId":"65517","inReplyTo":"aeit6Ic4fUa3Xx1Z@pks.im","subject":"Re: [PATCH 5/8] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-22T14:13:05Z","receivedAt":"2026-04-22T14:13:08Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Apr 20, 2026 at 12:12:03PM +0200, Karthik Nayak wrote:\n>> When using git-update-ref(1) with the '--batch-updates' flag, updates\n>> rejected by the reference backend are displayed to the user while other\n>> updates are applied. This only applies during the committing of the\n>\n> Nit: s/committing/commit phase/\n>\n\nWill do.\n\n>> transaction.\n>>\n>> In the following commits, we'll also extend `ref_transaction_update()`\n>> to reject updates before even a transaction is prepared/committed. In\n>\n> The \"even\" reads like it's at the wrong position.\n>\n\nYeah seems like a filler, will remove.\n\n>> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n>> index 5259cc7226..d1980c60c4 100644\n>> --- a/builtin/update-ref.c\n>> +++ b/builtin/update-ref.c\n>> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,\n>>   */\n>>\n>>  static void parse_cmd_update(struct ref_transaction *transaction,\n>> -\t\t\t     const char *next, const char *end)\n>> +\t\t\t     const char *next, const char *end,\n>> +\t\t\t     struct command_options *opts)\n>\n> Here you follow my earlier suggestion of using `_options` for the struct\n> name, but `opts` for the variable :)\n>\n\nHaha, indeed, seems like I learn very quickly.\n\n>> @@ -289,11 +300,18 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>>  \tif (*next != line_termination)\n>>  \t\tdie(\"update %s: extra input: %s\", refname, next);\n>>\n>> -\tif (ref_transaction_update(transaction, refname,\n>> -\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n>> -\t\t\t\t   NULL, NULL,\n>> -\t\t\t\t   update_flags | create_reflog_flag,\n>> -\t\t\t\t   msg, &err))\n>> +\ttx_err = ref_transaction_update(transaction, refname,\n>> +\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n>> +\t\t\t\t\tNULL, NULL,\n>> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n>> +\t\t\t\t\tmsg, &err);\n>> +\n>> +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n>> +\t    opts->allow_update_failures)\n>> +\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n>> +\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n>> +\t\t\t\t    NULL);\n>> +\telse if (tx_err)\n>>  \t\tdie(\"%s\", err.buf);\n>>\n>>  \tupdate_flags = default_flags;\n>\n> I think this could use some curly braces to become easier to read, even\n> if it's only single-line statements.\n\nThat's fair.\n\n\n> Does this change have an impact on the ordering the user sees for\n> printed errors? If so, it might make sense to point that out in the\n> commit message.\n>\n\nI didn't consider that, it will be different since we already output to\nthe user before the transaction is prepared/committed.\n\nWill add.\n\n>> @@ -341,12 +361,19 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n>>  \tif (*next != line_termination)\n>>  \t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n>>\n>> -\tif (ref_transaction_update(transaction, refname, NULL,\n>> -\t\t\t\t   have_old_oid ? &old_oid : NULL,\n>> -\t\t\t\t   new_target,\n>> -\t\t\t\t   have_old_oid ? NULL : old_target,\n>> -\t\t\t\t   update_flags | create_reflog_flag,\n>> -\t\t\t\t   msg, &err))\n>> +\ttx_err = ref_transaction_update(transaction, refname, NULL,\n>> +\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n>> +\t\t\t\t\tnew_target,\n>> +\t\t\t\t\thave_old_oid ? NULL : old_target,\n>> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n>> +\t\t\t\t\tmsg, &err);\n>> +\n>> +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n>> +\t    opts->allow_update_failures)\n>> +\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n>> +\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n>> +\t\t\t\t    new_target, tx_err, err.buf, NULL);\n>> +\telse if (tx_err)\n>>  \t\tdie(\"%s\", err.buf);\n>>\n>>  \tupdate_flags = default_flags;\n>\n> Same here, curly braces might help readability.\n>\n\nWill do.\n\n>> @@ -644,6 +680,10 @@ static void update_refs_stdin(unsigned int flags)\n>>  \tstruct ref_transaction *transaction;\n>>  \tint i, j;\n>>\n>> +\tstruct command_options opts = {\n>> +\t\t.allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,\n>> +\t};\n>> +\n>>  \ttransaction = ref_store_transaction_begin(get_main_ref_store(the_repository),\n>>  \t\t\t\t\t\t  flags, &err);\n>>  \tif (!transaction)\n>\n> Suggestion, please feel free to ignore: we could also pass the flags\n> directly instead.\n>\n> Patrick\n\nYeah we could, I deliberately avoided it to make the easily extensible\nin the future. If you feel strongly, happy to make the change.\n"},{"id":"542137","messageId":"CAOLa=ZQnxNgsLbp9J-T8eP5S-mW9xgr=viDfwo-VkbqQcjXU+A@mail.gmail.com","threadId":"65517","inReplyTo":"aeit7X0Q_MlxvPas@pks.im","subject":"Re: [PATCH 6/8] refs: move object parsing to the generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-22T15:03:10Z","receivedAt":"2026-04-22T15:03:14Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Apr 20, 2026 at 12:12:04PM +0200, Karthik Nayak wrote:\n>> Regular reference updates made via reference transactions validate that\n>> the provided object ID exists in the object database, this is done by\n>\n> s/this/which/\n>\n>> calling 'parse_object()'. This check is done independently by the\n>> backends.\n>\n> ..., which leads to duplicated logic.\n\nWill add in.\n\n>\n>> Let's move this to the generic layer, ensuring the backends only have to\n>> care about reference storage and not about validation of the object IDs.\n>> With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'\n>> error type as its no longer used.\n>>\n>> Since we don't iterate over individual references in\n>> `ref_transaction_prepare()`, we add this check to\n>> `ref_transaction_update()`. This means that the validation is done as\n>> soon as an update is queued, without needing to prepare the\n>> transaction. It can be argued that this is more ideal, since this\n>> validation has no dependency on the reference transaction being\n>> prepared.\n>>\n>> It must be noted that the change in behavior means that this error\n>> cannot be ignored even with usage of batched updates, since this happens\n>> when the update is being added to the transaction. But since the caller\n>> gets specific error codes, they can either abort the transaction or\n>> continue adding other updates to the transaction.\n>\n> Right, this is what the preceding commits have allowed us to do.\n>\n> I think this is a good step. Being less entangled with the object\n> database in the ref backends is a good thing.\n>\n> Patrick\n\nAgreed. Thanks for the review.\n"},{"id":"542180","messageId":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH v2 0/9] refs: move some of the generic logic out of the backends","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:29Z","receivedAt":"2026-04-23T08:40:40Z","isPatch":true,"body":"This series came together while I was working on other reference related\ncode and realized that some of the individual logic implemented with the\nreference backends can be moved to the generic layer.\n\nMoving code to the generic layer, simplifies the responsibility of\nindividual backends and avoids deviation in logic between the backends.\n\nThe biggest changes are related to moving out usage of `parse_object()`\nand `peel_object()` from reference transactions. The former is used to\nvalidate that the OID provided points to a commit object. The latter is\nan optimization technique where the packed/reftable backend store the\npeeled OID whenever available, so reading such references provides the\npeeled OID without having to call the ODB.\n\nMoving object parsing to the generic layout involves moving it out of\nthe prepare stage of the transaction and into `ref_transaction_update()`\nwhere every added update is checked. As such, this also involves\nmodifying update-ref(1) and receive-pack(1) to follow this paradigm.\n\n---\nChanges in v2:\n- Split the second commit into two: one introducing\n  `ref_store_init_options` and the second to use it for reflog config.\n- Use opts as the variable name consistently.\n- A bunch of grammar fixes.\n- Link to v1: https://patch.msgid.link/20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com\n\n---\n builtin/receive-pack.c  |  22 +++++---\n builtin/update-ref.c    | 145 +++++++++++++++++++++++++++++++-----------------\n refs.c                  |  60 +++++++++++++++-----\n refs.h                  |  16 +++---\n refs/files-backend.c    |  58 +++++++------------\n refs/packed-backend.c   |  10 ++--\n refs/packed-backend.h   |   3 +-\n refs/refs-internal.h    |  35 ++++++++++--\n refs/reftable-backend.c |  40 +++----------\n 9 files changed, 225 insertions(+), 164 deletions(-)\n\nKarthik Nayak (9):\n      refs: remove unused typedef 'ref_transaction_commit_fn'\n      refs: introduce `ref_store_init_options`\n      refs: extract out reflog config to generic layer\n      refs: return `ref_transaction_error` from `ref_transaction_update()`\n      update-ref: move `print_rejected_refs()` up\n      update-ref: handle rejections while adding updates\n      refs: move object parsing to the generic layer\n      refs: add peeled object ID to the `ref_update` struct\n      refs: use peeled tag values in reference backends\n\nRange-diff versus v1:\n\n 1:  bbcc7bff38 =  1:  807f23ee66 refs: remove unused typedef 'ref_transaction_commit_fn'\n 2:  57a66ae8d5 !  2:  f63583d8b0 refs: extract out reflog config to generic layer\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    refs: extract out reflog config to generic layer\n    +    refs: introduce `ref_store_init_options`\n     \n    -    The reference backends need to know when to create reflog entries, this\n    -    is dictated by the 'core.logallrefupdates' config. Instead of relying on\n    -    the backends to call `repo_settings_get_log_all_ref_updates()` to obtain\n    -    this config value, let's do this in the generic layer and pass down the\n    -    value to the backends.\n    +    Reference backends are initiated via the `init()` function. When\n    +    initiating the function, the backend is also provided flags which denote\n    +    the access levels of the initiator. Create a new structure\n    +    `ref_store_init_options` to house such options and move the access flags\n    +    to this structure.\n     \n    -    Instead of passing this in as a new argument, let's create a new\n    -    `ref_init_options` structure which will house information required to\n    -    initialize a reference backend. Move the access flags here as well.\n    +    This allows easier extension of providing further options to the\n    +    backends. In the following commit, we'll also provide config around\n    +    reflog creation to the backends via the same structure.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ refs.c: static struct ref_store *ref_store_init(struct repository *repo,\n      {\n      \tconst struct ref_storage_be *be;\n      \tstruct ref_store *refs;\n    -+\tstruct ref_store_init_options options = {\n    ++\tstruct ref_store_init_options opts = {\n     +\t\t.access_flags = flags,\n    -+\t\t.log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),\n     +\t};\n      \n      \tbe = find_ref_storage_backend(format);\n    @@ refs.c: static struct ref_store *ref_store_init(struct repository *repo,\n      \t * allow the backend to handle how it wants to deal with worktrees.\n      \t */\n     -\trefs = be->init(repo, repo->ref_storage_payload, gitdir, flags);\n    -+\trefs = be->init(repo, repo->ref_storage_payload, gitdir, &options);\n    ++\trefs = be->init(repo, repo->ref_storage_payload, gitdir, &opts);\n     +\n      \treturn refs;\n      }\n    @@ refs/files-backend.c: static void clear_loose_ref_cache(struct files_ref_store *\n      \t\t\t\t\t      const char *payload,\n      \t\t\t\t\t      const char *gitdir,\n     -\t\t\t\t\t      unsigned int flags)\n    -+\t\t\t\t\t      const struct ref_store_init_options *options)\n    ++\t\t\t\t\t      const struct ref_store_init_options *opts)\n      {\n      \tstruct files_ref_store *refs = xcalloc(1, sizeof(*refs));\n      \tstruct ref_store *ref_store = (struct ref_store *)refs;\n    @@ refs/files-backend.c: static struct ref_store *files_ref_store_init(struct repos\n      \trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n      \trefs->packed_ref_store =\n     -\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n    --\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n    -+\t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, options);\n    -+\trefs->store_flags = options->access_flags;\n    -+\trefs->log_all_ref_updates = options->log_all_ref_updates;\n    ++\t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, opts);\n    ++\trefs->store_flags = opts->access_flags;\n    + \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n     +\n      \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n      \n    @@ refs/packed-backend.c: static size_t snapshot_hexsz(const struct snapshot *snaps\n      \t\t\t\t\tconst char *payload UNUSED,\n      \t\t\t\t\tconst char *gitdir,\n     -\t\t\t\t\tunsigned int store_flags)\n    -+\t\t\t\t\tconst struct ref_store_init_options *options)\n    ++\t\t\t\t\tconst struct ref_store_init_options *opts)\n      {\n      \tstruct packed_ref_store *refs = xcalloc(1, sizeof(*refs));\n      \tstruct ref_store *ref_store = (struct ref_store *)refs;\n    @@ refs/packed-backend.c: static size_t snapshot_hexsz(const struct snapshot *snaps\n      \n      \tbase_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);\n     -\trefs->store_flags = store_flags;\n    -+\trefs->store_flags = options->access_flags;\n    ++\trefs->store_flags = opts->access_flags;\n      \n      \tstrbuf_addf(&sb, \"%s/packed-refs\", gitdir);\n      \trefs->path = strbuf_detach(&sb, NULL);\n    @@ refs/refs-internal.h: struct ref_store;\n     +struct ref_store_init_options {\n     +\t/* The kind of operations that the ref_store is allowed to perform. */\n     +\tunsigned int access_flags;\n    -+\n    -+\t/*\n    -+\t * Denotes under what conditions reflogs should be created when updating\n    -+\t * references.\n    -+\t */\n    -+\tenum log_refs_config log_all_ref_updates;\n     +};\n     +\n      /*\n    @@ refs/refs-internal.h: struct ref_store;\n      \t\t\t\t\t    const char *payload,\n      \t\t\t\t\t    const char *gitdir,\n     -\t\t\t\t\t    unsigned int flags);\n    -+\t\t\t\t\t    const struct ref_store_init_options *options);\n    ++\t\t\t\t\t    const struct ref_store_init_options *opts);\n      /*\n       * Release all memory and resources associated with the ref store.\n       */\n    @@ refs/reftable-backend.c: static int reftable_be_config(const char *var, const ch\n      \t\t\t\t\t  const char *payload,\n      \t\t\t\t\t  const char *gitdir,\n     -\t\t\t\t\t  unsigned int store_flags)\n    -+\t\t\t\t\t  const struct ref_store_init_options *options)\n    ++\t\t\t\t\t  const struct ref_store_init_options *opts)\n      {\n      \tstruct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));\n      \tstruct strbuf ref_common_dir = STRBUF_INIT;\n    @@ refs/reftable-backend.c: static struct ref_store *reftable_be_init(struct reposi\n      \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n      \tstrmap_init(&refs->worktree_backends);\n     -\trefs->store_flags = store_flags;\n    --\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n    -+\trefs->store_flags = options->access_flags;\n    -+\trefs->log_all_ref_updates = options->log_all_ref_updates;\n    + \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n    ++\trefs->store_flags = opts->access_flags;\n      \n      \tswitch (repo->hash_algo->format_id) {\n      \tcase GIT_SHA1_FORMAT_ID:\n -:  ---------- >  3:  15fd026e85 refs: extract out reflog config to generic layer\n 3:  1c05642914 !  4:  a389011125 refs: return `ref_transaction_error` from `ref_transaction_update()`\n    @@ Commit message\n     \n         The `ref_transaction_update()` function is used to add updates to a\n         given reference transactions. In the following commit, we'll add more\n    -    validation to this function. As such, it would be more beneficial if the\n    +    validation to this function. As such, it would be beneficial if the\n         function returns specific error types, so callers can differentiate\n         between different errors.\n     \n 4:  6d9544a834 !  5:  3a203ace83 update-ref: move `print_rejected_refs()` up\n    @@ Metadata\n      ## Commit message ##\n         update-ref: move `print_rejected_refs()` up\n     \n    -    The `print_rejected_refs()` is used to print any rejected refs when\n    -    using git-updated-ref(1) with the '--batch-updates' option. In the\n    -    following commit, we'll need to use this function in other places, so\n    +    The `print_rejected_refs()` function is used to print any rejected refs\n    +    when using git-updated-ref(1) with the '--batch-updates' option. In the\n    +    following commit, we'll need to use this function in another place, so\n         move the function up to avoid a separate forward declaration.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n 5:  fd3b66ec45 !  6:  e6b2c7b523 update-ref: handle rejections while adding updates\n    @@ Commit message\n     \n         When using git-update-ref(1) with the '--batch-updates' flag, updates\n         rejected by the reference backend are displayed to the user while other\n    -    updates are applied. This only applies during the committing of the\n    +    updates are applied. This only applies during the commit phase of the\n         transaction.\n     \n         In the following commits, we'll also extend `ref_transaction_update()`\n    -    to reject updates before even a transaction is prepared/committed. In\n    +    to reject updates before a transaction is prepared/committed. In\n         preparation, modify the code in update-ref to also handle non-generic\n         rejections from `ref_transaction_update()`. This involves propagating\n         information to each of the commands on whether updates are allowed to be\n    @@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *trans\n     +\t\t\t\t\tmsg, &err);\n     +\n     +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n    -+\t    opts->allow_update_failures)\n    ++\t    opts->allow_update_failures) {\n     +\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n     +\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n     +\t\t\t\t    NULL);\n    -+\telse if (tx_err)\n    ++\t} else if (tx_err) {\n      \t\tdie(\"%s\", err.buf);\n    ++\t}\n      \n      \tupdate_flags = default_flags;\n    + \tfree(refname);\n     @@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *transaction,\n      }\n      \n    @@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction\n     +\t\t\t\t\tmsg, &err);\n     +\n     +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n    -+\t    opts->allow_update_failures)\n    ++\t    opts->allow_update_failures) {\n     +\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n     +\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n     +\t\t\t\t    new_target, tx_err, err.buf, NULL);\n    -+\telse if (tx_err)\n    ++\t} else if (tx_err) {\n      \t\tdie(\"%s\", err.buf);\n    ++\t}\n      \n      \tupdate_flags = default_flags;\n    + \tfree(refname);\n     @@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction *transaction,\n      }\n      \n 6:  3be9566bf9 !  7:  36a6284ac1 refs: move object parsing to the generic layer\n    @@ Commit message\n         refs: move object parsing to the generic layer\n     \n         Regular reference updates made via reference transactions validate that\n    -    the provided object ID exists in the object database, this is done by\n    +    the provided object ID exists in the object database, which is done by\n         calling 'parse_object()'. This check is done independently by the\n    -    backends.\n    +    backends which leads to duplicated logic.\n     \n         Let's move this to the generic layer, ensuring the backends only have to\n         care about reference storage and not about validation of the object IDs.\n 7:  5e6a26567a =  8:  32f3bc52f1 refs: add peeled object ID to the `ref_update` struct\n 8:  27914d0556 =  9:  bbcdbd3d18 refs: use peeled tag values in reference backends\n\n\nbase-commit: f65aba1e87db64413b6d1ed5ae5a45b5a84a0997\nchange-id: 20260417-refs-move-to-generic-layer-f7525c5e8764\n\nThanks\n- Karthik\n\n"},{"id":"542181","messageId":"20260423-refs-move-to-generic-layer-v2-1-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 1/9] refs: remove unused typedef 'ref_transaction_commit_fn'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:30Z","receivedAt":"2026-04-23T08:40:42Z","isPatch":true,"body":"The typedef 'ref_transaction_commit_fn' is not used anywhere in our\ncode, let's remove it.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/refs-internal.h | 4 ----\n 1 file changed, 4 deletions(-)\n\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex d79e35fd26..2d963cc4f4 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,\n \t\t\t\t     struct ref_transaction *transaction,\n \t\t\t\t     struct strbuf *err);\n \n-typedef int ref_transaction_commit_fn(struct ref_store *refs,\n-\t\t\t\t      struct ref_transaction *transaction,\n-\t\t\t\t      struct strbuf *err);\n-\n typedef int optimize_fn(struct ref_store *ref_store,\n \t\t\tstruct refs_optimize_opts *opts);\n \n\n-- \n2.53.GIT\n\n"},{"id":"542182","messageId":"20260423-refs-move-to-generic-layer-v2-2-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 2/9] refs: introduce `ref_store_init_options`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:31Z","receivedAt":"2026-04-23T08:40:43Z","isPatch":true,"body":"Reference backends are initiated via the `init()` function. When\ninitiating the function, the backend is also provided flags which denote\nthe access levels of the initiator. Create a new structure\n`ref_store_init_options` to house such options and move the access flags\nto this structure.\n\nThis allows easier extension of providing further options to the\nbackends. In the following commit, we'll also provide config around\nreflog creation to the backends via the same structure.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  6 +++++-\n refs/files-backend.c    |  8 +++++---\n refs/packed-backend.c   |  4 ++--\n refs/packed-backend.h   |  3 ++-\n refs/refs-internal.h    | 11 ++++++++++-\n refs/reftable-backend.c |  4 ++--\n 6 files changed, 26 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex bfcb9c7ac3..8992dd6ae8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2295,6 +2295,9 @@ static struct ref_store *ref_store_init(struct repository *repo,\n {\n \tconst struct ref_storage_be *be;\n \tstruct ref_store *refs;\n+\tstruct ref_store_init_options opts = {\n+\t\t.access_flags = flags,\n+\t};\n \n \tbe = find_ref_storage_backend(format);\n \tif (!be)\n@@ -2304,7 +2307,8 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \t * TODO Send in a 'struct worktree' instead of a 'gitdir', and\n \t * allow the backend to handle how it wants to deal with worktrees.\n \t */\n-\trefs = be->init(repo, repo->ref_storage_payload, gitdir, flags);\n+\trefs = be->init(repo, repo->ref_storage_payload, gitdir, &opts);\n+\n \treturn refs;\n }\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex b3b0c25f84..78150ad209 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -108,7 +108,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)\n static struct ref_store *files_ref_store_init(struct repository *repo,\n \t\t\t\t\t      const char *payload,\n \t\t\t\t\t      const char *gitdir,\n-\t\t\t\t\t      unsigned int flags)\n+\t\t\t\t\t      const struct ref_store_init_options *opts)\n {\n \tstruct files_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n@@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \t\t\t\t\t &ref_common_dir);\n \n \tbase_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);\n-\trefs->store_flags = flags;\n+\n \trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n \trefs->packed_ref_store =\n-\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n+\t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, opts);\n+\trefs->store_flags = opts->access_flags;\n \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \n \tchdir_notify_reparent(\"files-backend $GIT_DIR\", &refs->base.gitdir);\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 23ed62984b..35a0f32e1c 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)\n struct ref_store *packed_ref_store_init(struct repository *repo,\n \t\t\t\t\tconst char *payload UNUSED,\n \t\t\t\t\tconst char *gitdir,\n-\t\t\t\t\tunsigned int store_flags)\n+\t\t\t\t\tconst struct ref_store_init_options *opts)\n {\n \tstruct packed_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tbase_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);\n-\trefs->store_flags = store_flags;\n+\trefs->store_flags = opts->access_flags;\n \n \tstrbuf_addf(&sb, \"%s/packed-refs\", gitdir);\n \trefs->path = strbuf_detach(&sb, NULL);\ndiff --git a/refs/packed-backend.h b/refs/packed-backend.h\nindex 2c2377a356..1db48e801d 100644\n--- a/refs/packed-backend.h\n+++ b/refs/packed-backend.h\n@@ -3,6 +3,7 @@\n \n struct repository;\n struct ref_transaction;\n+struct ref_store_init_options;\n \n /*\n  * Support for storing references in a `packed-refs` file.\n@@ -16,7 +17,7 @@ struct ref_transaction;\n struct ref_store *packed_ref_store_init(struct repository *repo,\n \t\t\t\t\tconst char *payload,\n \t\t\t\t\tconst char *gitdir,\n-\t\t\t\t\tunsigned int store_flags);\n+\t\t\t\t\tconst struct ref_store_init_options *options);\n \n /*\n  * Lock the packed-refs file for writing. Flags is passed to\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2d963cc4f4..f49b3807bf 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -385,6 +385,15 @@ struct ref_store;\n \t\t\t\t REF_STORE_ODB | \\\n \t\t\t\t REF_STORE_MAIN)\n \n+/*\n+ * Options for initializing the ref backend. All backend-agnostic information\n+ * which backends required will be held here.\n+ */\n+struct ref_store_init_options {\n+\t/* The kind of operations that the ref_store is allowed to perform. */\n+\tunsigned int access_flags;\n+};\n+\n /*\n  * Initialize the ref_store for the specified gitdir. These functions\n  * should call base_ref_store_init() to initialize the shared part of\n@@ -393,7 +402,7 @@ struct ref_store;\n typedef struct ref_store *ref_store_init_fn(struct repository *repo,\n \t\t\t\t\t    const char *payload,\n \t\t\t\t\t    const char *gitdir,\n-\t\t\t\t\t    unsigned int flags);\n+\t\t\t\t\t    const struct ref_store_init_options *opts);\n /*\n  * Release all memory and resources associated with the ref store.\n  */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex daea30a5b4..ad4ee2627c 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -369,7 +369,7 @@ static int reftable_be_config(const char *var, const char *value,\n static struct ref_store *reftable_be_init(struct repository *repo,\n \t\t\t\t\t  const char *payload,\n \t\t\t\t\t  const char *gitdir,\n-\t\t\t\t\t  unsigned int store_flags)\n+\t\t\t\t\t  const struct ref_store_init_options *opts)\n {\n \tstruct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct strbuf ref_common_dir = STRBUF_INIT;\n@@ -386,8 +386,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n-\trefs->store_flags = store_flags;\n \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->store_flags = opts->access_flags;\n \n \tswitch (repo->hash_algo->format_id) {\n \tcase GIT_SHA1_FORMAT_ID:\n\n-- \n2.53.GIT\n\n"},{"id":"542183","messageId":"20260423-refs-move-to-generic-layer-v2-3-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 3/9] refs: extract out reflog config to generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:32Z","receivedAt":"2026-04-23T08:40:44Z","isPatch":true,"body":"The reference backends need to know when to create reflog entries, this\nis dictated by the 'core.logallrefupdates' config. Instead of relying on\nthe backends to call `repo_settings_get_log_all_ref_updates()` to obtain\nthis config value, let's do this in the generic layer and pass down the\nvalue to the backends.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 1 +\n refs/files-backend.c    | 2 +-\n refs/refs-internal.h    | 6 ++++++\n refs/reftable-backend.c | 2 +-\n 4 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 8992dd6ae8..6b506aeea3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2297,6 +2297,7 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \tstruct ref_store *refs;\n \tstruct ref_store_init_options opts = {\n \t\t.access_flags = flags,\n+\t\t.log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),\n \t};\n \n \tbe = find_ref_storage_backend(format);\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 78150ad209..9b1fa955f8 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -125,7 +125,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \trefs->packed_ref_store =\n \t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, opts);\n \trefs->store_flags = opts->access_flags;\n-\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n \n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex f49b3807bf..d103387ebf 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -392,6 +392,12 @@ struct ref_store;\n struct ref_store_init_options {\n \t/* The kind of operations that the ref_store is allowed to perform. */\n \tunsigned int access_flags;\n+\n+\t/*\n+\t * Denotes under what conditions reflogs should be created when updating\n+\t * references.\n+\t */\n+\tenum log_refs_config log_all_ref_updates;\n };\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex ad4ee2627c..93374d25c2 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -386,7 +386,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n-\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n \trefs->store_flags = opts->access_flags;\n \n \tswitch (repo->hash_algo->format_id) {\n\n-- \n2.53.GIT\n\n"},{"id":"542184","messageId":"20260423-refs-move-to-generic-layer-v2-4-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:33Z","receivedAt":"2026-04-23T08:40:44Z","isPatch":true,"body":"The `ref_transaction_update()` function is used to add updates to a\ngiven reference transactions. In the following commit, we'll add more\nvalidation to this function. As such, it would be beneficial if the\nfunction returns specific error types, so callers can differentiate\nbetween different errors.\n\nTo facilitate this, return `enum ref_transaction_error` from the\nfunction and covert the existing '-1' returns to\n'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing\nbehavior, no changes are made to any of the callers but this sets the\nnecessary infrastructure for introduction of other errors.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c | 20 ++++++++++----------\n refs.h | 16 ++++++++--------\n 2 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 6b506aeea3..efa16b739d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1383,25 +1383,25 @@ static int transaction_refname_valid(const char *refname,\n \treturn 1;\n }\n \n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *new_oid,\n-\t\t\t   const struct object_id *old_oid,\n-\t\t\t   const char *new_target,\n-\t\t\t   const char *old_target,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err)\n+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t\t  const struct object_id *new_oid,\n+\t\t\t\t\t\t  const struct object_id *old_oid,\n+\t\t\t\t\t\t  const char *new_target,\n+\t\t\t\t\t\t  const char *old_target,\n+\t\t\t\t\t\t  unsigned int flags, const char *msg,\n+\t\t\t\t\t\t  struct strbuf *err)\n {\n \tassert(err);\n \n \tif ((flags & REF_FORCE_CREATE_REFLOG) &&\n \t    (flags & REF_SKIP_CREATE_REFLOG)) {\n \t\tstrbuf_addstr(err, _(\"refusing to force and skip creation of reflog\"));\n-\t\treturn -1;\n+\t\treturn REF_TRANSACTION_ERROR_GENERIC;\n \t}\n \n \tif (!transaction_refname_valid(refname, new_oid, flags, err))\n-\t\treturn -1;\n+\t\treturn REF_TRANSACTION_ERROR_GENERIC;\n \n \tif (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)\n \t\tBUG(\"illegal flags 0x%x passed to ref_transaction_update()\", flags);\ndiff --git a/refs.h b/refs.h\nindex d65de6ab5f..71d5c186d0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n  */\n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *new_oid,\n-\t\t\t   const struct object_id *old_oid,\n-\t\t\t   const char *new_target,\n-\t\t\t   const char *old_target,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err);\n+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t\t  const struct object_id *new_oid,\n+\t\t\t\t\t\t  const struct object_id *old_oid,\n+\t\t\t\t\t\t  const char *new_target,\n+\t\t\t\t\t\t  const char *old_target,\n+\t\t\t\t\t\t  unsigned int flags, const char *msg,\n+\t\t\t\t\t\t  struct strbuf *err);\n \n /*\n  * Similar to `ref_transaction_update`, but this function is only for adding\n\n-- \n2.53.GIT\n\n"},{"id":"542185","messageId":"20260423-refs-move-to-generic-layer-v2-5-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 5/9] update-ref: move `print_rejected_refs()` up","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:34Z","receivedAt":"2026-04-23T08:40:46Z","isPatch":true,"body":"The `print_rejected_refs()` function is used to print any rejected refs\nwhen using git-updated-ref(1) with the '--batch-updates' option. In the\nfollowing commit, we'll need to use this function in another place, so\nmove the function up to avoid a separate forward declaration.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 45 ++++++++++++++++++++++-----------------------\n 1 file changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 2d68c40ecb..5259cc7226 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -234,6 +234,28 @@ static int parse_next_oid(const char **next, const char *end,\n \t    command, refname);\n }\n \n+static void print_rejected_refs(const char *refname,\n+\t\t\t\tconst struct object_id *old_oid,\n+\t\t\t\tconst struct object_id *new_oid,\n+\t\t\t\tconst char *old_target,\n+\t\t\t\tconst char *new_target,\n+\t\t\t\tenum ref_transaction_error err,\n+\t\t\t\tconst char *details,\n+\t\t\t\tvoid *cb_data UNUSED)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (details && *details)\n+\t\terror(\"%s\", details);\n+\n+\tstrbuf_addf(&sb, \"rejected %s %s %s %s\\n\", refname,\n+\t\t    new_oid ? oid_to_hex(new_oid) : new_target,\n+\t\t    old_oid ? oid_to_hex(old_oid) : old_target,\n+\t\t    ref_transaction_error_msg(err));\n+\n+\tfwrite(sb.buf, sb.len, 1, stdout);\n+\tstrbuf_release(&sb);\n+}\n \n /*\n  * The following five parse_cmd_*() functions parse the corresponding\n@@ -567,29 +589,6 @@ static void parse_cmd_abort(struct ref_transaction *transaction,\n \treport_ok(\"abort\");\n }\n \n-static void print_rejected_refs(const char *refname,\n-\t\t\t\tconst struct object_id *old_oid,\n-\t\t\t\tconst struct object_id *new_oid,\n-\t\t\t\tconst char *old_target,\n-\t\t\t\tconst char *new_target,\n-\t\t\t\tenum ref_transaction_error err,\n-\t\t\t\tconst char *details,\n-\t\t\t\tvoid *cb_data UNUSED)\n-{\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tif (details && *details)\n-\t\terror(\"%s\", details);\n-\n-\tstrbuf_addf(&sb, \"rejected %s %s %s %s\\n\", refname,\n-\t\t    new_oid ? oid_to_hex(new_oid) : new_target,\n-\t\t    old_oid ? oid_to_hex(old_oid) : old_target,\n-\t\t    ref_transaction_error_msg(err));\n-\n-\tfwrite(sb.buf, sb.len, 1, stdout);\n-\tstrbuf_release(&sb);\n-}\n-\n static void parse_cmd_commit(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end UNUSED)\n {\n\n-- \n2.53.GIT\n\n"},{"id":"542186","messageId":"20260423-refs-move-to-generic-layer-v2-6-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 6/9] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:35Z","receivedAt":"2026-04-23T08:40:46Z","isPatch":true,"body":"When using git-update-ref(1) with the '--batch-updates' flag, updates\nrejected by the reference backend are displayed to the user while other\nupdates are applied. This only applies during the commit phase of the\ntransaction.\n\nIn the following commits, we'll also extend `ref_transaction_update()`\nto reject updates before a transaction is prepared/committed. In\npreparation, modify the code in update-ref to also handle non-generic\nrejections from `ref_transaction_update()`. This involves propagating\ninformation to each of the commands on whether updates are allowed to be\nrejected, and also checking for rejections and only dying for generic\nfailures.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 100 ++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 71 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 5259cc7226..99deaac6db 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -25,6 +25,15 @@ static unsigned int default_flags;\n static unsigned create_reflog_flag;\n static const char *msg;\n \n+struct command_options {\n+\t/*\n+\t * Individual updates are allowed to fail without causing\n+\t * update-ref to exit. This is set when using the\n+\t * '--batch-updates' flag.\n+\t */\n+\tbool allow_update_failures;\n+};\n+\n /*\n  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n  * and append the result to arg.  Return a pointer to the terminator.\n@@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,\n  */\n \n static void parse_cmd_update(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id new_oid, old_oid;\n+\tenum ref_transaction_error tx_err;\n \tint have_old;\n \n \trefname = parse_refname(&next);\n@@ -289,12 +300,20 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname,\n-\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n-\t\t\t\t   NULL, NULL,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n+\ttx_err = ref_transaction_update(transaction, refname,\n+\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t\tNULL, NULL,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\n+\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n+\t    opts->allow_update_failures) {\n+\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n+\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n+\t\t\t\t    NULL);\n+\t} else if (tx_err) {\n \t\tdie(\"%s\", err.buf);\n+\t}\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n@@ -302,9 +321,11 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_symref_update(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts)\n {\n \tchar *refname, *new_target, *old_arg;\n+\tenum ref_transaction_error tx_err;\n \tchar *old_target = NULL;\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct object_id old_oid;\n@@ -341,13 +362,21 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname, NULL,\n-\t\t\t\t   have_old_oid ? &old_oid : NULL,\n-\t\t\t\t   new_target,\n-\t\t\t\t   have_old_oid ? NULL : old_target,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n+\ttx_err = ref_transaction_update(transaction, refname, NULL,\n+\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n+\t\t\t\t\tnew_target,\n+\t\t\t\t\thave_old_oid ? NULL : old_target,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\n+\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n+\t    opts->allow_update_failures) {\n+\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n+\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n+\t\t\t\t    new_target, tx_err, err.buf, NULL);\n+\t} else if (tx_err) {\n \t\tdie(\"%s\", err.buf);\n+\t}\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n@@ -358,7 +387,8 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_create(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -387,9 +417,9 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_symref_create(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname, *new_target;\n@@ -417,7 +447,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_delete(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -450,9 +481,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname, *old_target;\n@@ -479,9 +510,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_verify(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -508,7 +539,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct object_id old_oid;\n@@ -550,7 +582,8 @@ static void report_ok(const char *command)\n }\n \n static void parse_cmd_option(struct ref_transaction *transaction UNUSED,\n-\t\t\t     const char *next, const char *end UNUSED)\n+\t\t\t     const char *next, const char *end UNUSED,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tconst char *rest;\n \tif (skip_prefix(next, \"no-deref\", &rest) && *rest == line_termination)\n@@ -560,7 +593,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED,\n }\n \n static void parse_cmd_start(struct ref_transaction *transaction UNUSED,\n-\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t    struct command_options *opts UNUSED)\n {\n \tif (*next != line_termination)\n \t\tdie(\"start: extra input: %s\", next);\n@@ -568,7 +602,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED,\n }\n \n static void parse_cmd_prepare(struct ref_transaction *transaction,\n-\t\t\t      const char *next, const char *end UNUSED)\n+\t\t\t      const char *next, const char *end UNUSED,\n+\t\t\t      struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -579,7 +614,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_abort(struct ref_transaction *transaction,\n-\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -590,7 +626,8 @@ static void parse_cmd_abort(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_commit(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end UNUSED)\n+\t\t\t     const char *next, const char *end UNUSED,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -618,7 +655,8 @@ enum update_refs_state {\n \n static const struct parse_cmd {\n \tconst char *prefix;\n-\tvoid (*fn)(struct ref_transaction *, const char *, const char *);\n+\tvoid (*fn)(struct ref_transaction *, const char *, const char *,\n+\t\t   struct command_options *);\n \tunsigned args;\n \tenum update_refs_state state;\n } command[] = {\n@@ -644,6 +682,10 @@ static void update_refs_stdin(unsigned int flags)\n \tstruct ref_transaction *transaction;\n \tint i, j;\n \n+\tstruct command_options opts = {\n+\t\t.allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,\n+\t};\n+\n \ttransaction = ref_store_transaction_begin(get_main_ref_store(the_repository),\n \t\t\t\t\t\t  flags, &err);\n \tif (!transaction)\n@@ -721,7 +763,7 @@ static void update_refs_stdin(unsigned int flags)\n \t\t}\n \n \t\tcmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,\n-\t\t\tinput.buf + input.len);\n+\t\t\tinput.buf + input.len, &opts);\n \t}\n \n \tswitch (state) {\n\n-- \n2.53.GIT\n\n"},{"id":"542187","messageId":"20260423-refs-move-to-generic-layer-v2-7-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 7/9] refs: move object parsing to the generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:36Z","receivedAt":"2026-04-23T08:40:48Z","isPatch":true,"body":"Regular reference updates made via reference transactions validate that\nthe provided object ID exists in the object database, which is done by\ncalling 'parse_object()'. This check is done independently by the\nbackends which leads to duplicated logic.\n\nLet's move this to the generic layer, ensuring the backends only have to\ncare about reference storage and not about validation of the object IDs.\nWith this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'\nerror type as its no longer used.\n\nSince we don't iterate over individual references in\n`ref_transaction_prepare()`, we add this check to\n`ref_transaction_update()`. This means that the validation is done as\nsoon as an update is queued, without needing to prepare the\ntransaction. It can be argued that this is more ideal, since this\nvalidation has no dependency on the reference transaction being\nprepared.\n\nIt must be noted that the change in behavior means that this error\ncannot be ignored even with usage of batched updates, since this happens\nwhen the update is being added to the transaction. But since the caller\ngets specific error codes, they can either abort the transaction or\ncontinue adding other updates to the transaction.\n\nModify 'builtin/receive-pack.c' to now capture the error type so that\nthe error propagated to the client stays the same. Also remove two of\nthe tests which validates batch-updates with invalid new_oid.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/receive-pack.c  | 22 +++++++++++++---------\n refs.c                  | 18 ++++++++++++++++++\n refs/files-backend.c    | 28 ++--------------------------\n refs/reftable-backend.c | 19 -------------------\n 4 files changed, 33 insertions(+), 54 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 878aa7f0ed..0fb3d57de8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\tret = NULL; /* good */\n \t\t}\n \t\tstrbuf_release(&err);\n-\t}\n-\telse {\n+\t} else {\n+\t\tenum ref_transaction_error err_type;\n \t\tstruct strbuf err = STRBUF_INIT;\n \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n \t\t    update_shallow_ref(cmd, si)) {\n@@ -1650,14 +1650,18 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (ref_transaction_update(transaction,\n-\t\t\t\t\t   namespaced_name,\n-\t\t\t\t\t   new_oid, old_oid,\n-\t\t\t\t\t   NULL, NULL,\n-\t\t\t\t\t   0, \"push\",\n-\t\t\t\t\t   &err)) {\n+\t\terr_type = ref_transaction_update(transaction,\n+\t\t\t\t\t\t  namespaced_name,\n+\t\t\t\t\t\t  new_oid, old_oid,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  0, \"push\",\n+\t\t\t\t\t\t  &err);\n+\t\tif (err_type) {\n \t\t\trp_error(\"%s\", err.buf);\n-\t\t\tret = \"failed to update ref\";\n+\t\t\tif (err_type == REF_TRANSACTION_ERROR_GENERIC)\n+\t\t\t\tret = \"failed to update ref\";\n+\t\t\telse\n+\t\t\t\tret = ref_transaction_error_msg(err_type);\n \t\t} else {\n \t\t\tret = NULL; /* good */\n \t\t}\ndiff --git a/refs.c b/refs.c\nindex efa16b739d..662a9e6f9e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n+\tif ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&\n+\t    !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {\n+\t\tstruct object *o = parse_object(transaction->ref_store->repo, new_oid);\n+\n+\t\tif (!o) {\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    _(\"trying to write ref '%s' with nonexistent object %s\"),\n+\t\t\t\t    refname, oid_to_hex(new_oid));\n+\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n+\t\t}\n+\n+\t\tif (o->type != OBJ_COMMIT && is_branch(refname)) {\n+\t\t\tstrbuf_addf(err, _(\"trying to write non-commit object %s to branch '%s'\"),\n+\t\t\t\t    oid_to_hex(new_oid), refname);\n+\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n+\t\t}\n+\t}\n+\n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n \t\t\t\t   old_target, NULL, msg);\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 9b1fa955f8..6da0cb4f1b 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -19,7 +19,6 @@\n #include \"../iterator.h\"\n #include \"../dir-iterator.h\"\n #include \"../lockfile.h\"\n-#include \"../object.h\"\n #include \"../path.h\"\n #include \"../dir.h\"\n #include \"../chdir-notify.h\"\n@@ -1589,7 +1588,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)\n static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,\n \t\t\t\t\t\t\tstruct ref_lock *lock,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n-\t\t\t\t\t\t\tint skip_oid_verification,\n \t\t\t\t\t\t\tstruct strbuf *err);\n static int commit_ref_update(struct files_ref_store *refs,\n \t\t\t     struct ref_lock *lock,\n@@ -1737,7 +1735,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,\n \t}\n \toidcpy(&lock->old_oid, &orig_oid);\n \n-\tif (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||\n+\tif (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||\n \t    commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) {\n \t\terror(\"unable to write current sha1 into %s: %s\", newrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -1755,7 +1753,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,\n \t\tgoto rollbacklog;\n \t}\n \n-\tif (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||\n+\tif (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||\n \t    commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) {\n \t\terror(\"unable to write current sha1 into %s: %s\", oldrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -1999,32 +1997,11 @@ static int files_log_ref_write(struct files_ref_store *refs,\n static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,\n \t\t\t\t\t\t\tstruct ref_lock *lock,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n-\t\t\t\t\t\t\tint skip_oid_verification,\n \t\t\t\t\t\t\tstruct strbuf *err)\n {\n \tstatic char term = '\\n';\n-\tstruct object *o;\n \tint fd;\n \n-\tif (!skip_oid_verification) {\n-\t\to = parse_object(refs->base.repo, oid);\n-\t\tif (!o) {\n-\t\t\tstrbuf_addf(\n-\t\t\t\terr,\n-\t\t\t\t\"trying to write ref '%s' with nonexistent object %s\",\n-\t\t\t\tlock->ref_name, oid_to_hex(oid));\n-\t\t\tunlock_ref(lock);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t\tif (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {\n-\t\t\tstrbuf_addf(\n-\t\t\t\terr,\n-\t\t\t\t\"trying to write non-commit object %s to branch '%s'\",\n-\t\t\t\toid_to_hex(oid), lock->ref_name);\n-\t\t\tunlock_ref(lock);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t}\n \tfd = get_lock_file_fd(&lock->lk);\n \tif (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 ||\n \t    write_in_full(fd, &term, 1) < 0 ||\n@@ -2828,7 +2805,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re\n \t\t} else {\n \t\t\tret = write_ref_to_lockfile(\n \t\t\t\trefs, lock, &update->new_oid,\n-\t\t\t\tupdate->flags & REF_SKIP_OID_VERIFICATION,\n \t\t\t\terr);\n \t\t\tif (ret) {\n \t\t\t\tchar *write_err = strbuf_detach(err, NULL);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 93374d25c2..444b0c24e5 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1081,25 +1081,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\treturn 0;\n \t}\n \n-\t/* Verify that the new object ID is valid. */\n-\tif ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&\n-\t    !(u->flags & REF_SKIP_OID_VERIFICATION) &&\n-\t    !(u->flags & REF_LOG_ONLY)) {\n-\t\tstruct object *o = parse_object(refs->base.repo, &u->new_oid);\n-\t\tif (!o) {\n-\t\t\tstrbuf_addf(err,\n-\t\t\t\t    _(\"trying to write ref '%s' with nonexistent object %s\"),\n-\t\t\t\t    u->refname, oid_to_hex(&u->new_oid));\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\n-\t\tif (o->type != OBJ_COMMIT && is_branch(u->refname)) {\n-\t\t\tstrbuf_addf(err, _(\"trying to write non-commit object %s to branch '%s'\"),\n-\t\t\t\t    oid_to_hex(&u->new_oid), u->refname);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t}\n-\n \t/*\n \t * When we update the reference that HEAD points to we enqueue\n \t * a second log-only update for HEAD so that its reflog is\n\n-- \n2.53.GIT\n\n"},{"id":"542188","messageId":"20260423-refs-move-to-generic-layer-v2-8-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:37Z","receivedAt":"2026-04-23T08:40:49Z","isPatch":true,"body":"Certain reference backend {packed, reftable}, have the ability to also\nstore the peeled object ID for a reference pointing to a tag object.\nThis has the added benefit that during retrieval of such references, we\nalso obtain the peeled object ID without having to use the ODB.\n\nTo provide this functionality, each backend independently calls the ODB\nto obtain the peeled OID. To move this functionality to the generic\nlayer, there must be support infrastructure to pass in a peeled OID for\nreference updates.\n\nAdd a `peeled` field to the `ref_update` structure and modify\n`ref_transaction_add_update()` to receive and copy this object ID to the\n`ref_update` structure. Finally, modify `ref_transaction_update()` to\npeel tag objects and pass the peeled OID to\n`ref_transaction_add_update()`.\n\nUpdate all callers of these functions with the new function parameters.\nCallers which only add reflog updates, need to only pass in NULL, since\nfor reflogs, we don't store peeled OIDs. Reference deletions also only\nneed to pass in NULL. For others, pass along the peeled OID if\navailable.\n\nIn a following commit, we'll modify the backends to use this peeled OID\ninstead of parsing it themselves.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 15 +++++++++++++--\n refs/files-backend.c    | 20 ++++++++++++--------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  6 +++---\n 4 files changed, 42 insertions(+), 13 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 662a9e6f9e..0648df2b6c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1307,6 +1307,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst struct object_id *peeled,\n \t\tconst char *new_target, const char *old_target,\n \t\tconst char *committer_info,\n \t\tconst char *msg)\n@@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update(\n \t\tupdate->committer_info = xstrdup_or_null(committer_info);\n \t\tupdate->msg = normalize_reflog_message(msg);\n \t}\n+\tif (flags & REF_HAVE_PEELED)\n+\t\toidcpy(&update->peeled, peeled);\n \n \t/*\n \t * This list is generally used by the backends to avoid duplicates.\n@@ -1392,6 +1395,8 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \t\t\t\t\t\t  unsigned int flags, const char *msg,\n \t\t\t\t\t\t  struct strbuf *err)\n {\n+\tstruct object_id peeled;\n+\n \tassert(err);\n \n \tif ((flags & REF_FORCE_CREATE_REFLOG) &&\n@@ -1432,10 +1437,16 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \t\t\t\t    oid_to_hex(new_oid), refname);\n \t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n \t\t}\n+\n+\t\tif (o->type == OBJ_TAG) {\n+\t\t\tif (!peel_object(transaction->ref_store->repo, new_oid, &peeled,\n+\t\t\t\t\t PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))\n+\t\t\t\tflags |= REF_HAVE_PEELED;\n+\t\t}\n \t}\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   new_oid, old_oid, &peeled, new_target,\n \t\t\t\t   old_target, NULL, msg);\n \n \treturn 0;\n@@ -1462,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction,\n \t\treturn -1;\n \n \tupdate = ref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t\t    new_oid, old_oid, NULL, NULL,\n+\t\t\t\t\t    new_oid, old_oid, NULL, NULL, NULL,\n \t\t\t\t\t    committer_info, msg);\n \tupdate->index = index;\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 6da0cb4f1b..2ed1082d56 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1325,7 +1325,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);\n+\t\t\tnull_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,\n+\t\t\tNULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -2468,7 +2469,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,\n-\t\t\t&update->new_oid, &update->old_oid,\n+\t\t\t&update->new_oid, &update->old_oid, &update->peeled,\n \t\t\tNULL, NULL, update->committer_info, update->msg);\n \tnew_update->parent_update = update;\n \n@@ -2530,8 +2531,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,\n \t\t\ttransaction, referent, new_flags,\n \t\t\tupdate->new_target ? NULL : &update->new_oid,\n \t\t\tupdate->old_target ? NULL : &update->old_oid,\n-\t\t\tupdate->new_target, update->old_target, NULL,\n-\t\t\tupdate->msg);\n+\t\t\t&update->peeled, update->new_target, update->old_target,\n+\t\t\tNULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2994,7 +2995,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\tref_transaction_add_update(\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n-\t\t\t\t\t&update->new_oid, NULL,\n+\t\t\t\t\t&update->new_oid, NULL, NULL,\n \t\t\t\t\tNULL, NULL, NULL, NULL);\n \t\t}\n \t}\n@@ -3200,19 +3201,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,\n \t\t\tif (update->flags & REF_LOG_ONLY)\n \t\t\t\tref_transaction_add_update(loose_transaction, update->refname,\n \t\t\t\t\t\t\t   update->flags, &update->new_oid,\n-\t\t\t\t\t\t\t   &update->old_oid, NULL, NULL,\n+\t\t\t\t\t\t\t   &update->old_oid, &update->peeled,\n+\t\t\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t\t\t   update->committer_info, update->msg);\n \t\t\telse\n \t\t\t\tref_transaction_add_update(loose_transaction, update->refname,\n \t\t\t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t\t\t   update->new_target ? NULL : &update->new_oid, NULL,\n-\t\t\t\t\t\t\t   update->new_target, NULL, update->committer_info,\n+\t\t\t\t\t\t\t   &update->peeled, update->new_target,\n+\t\t\t\t\t\t\t   NULL, update->committer_info,\n \t\t\t\t\t\t\t   NULL);\n \t\t} else {\n \t\t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t\t   NULL, NULL, update->committer_info, NULL);\n+\t\t\t\t\t\t   &update->peeled, NULL, NULL,\n+\t\t\t\t\t\t   update->committer_info, NULL);\n \t\t}\n \t}\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex d103387ebf..307dcb277b 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -39,6 +39,13 @@ struct ref_transaction;\n  */\n #define REF_LOG_ONLY (1 << 7)\n \n+/*\n+ * The reference contains a peeled object ID. This is used when the\n+ * new_oid is pointing to a tag object and the reference backend\n+ * wants to also store the peeled value for optimized retrieval.\n+ */\n+#define REF_HAVE_PEELED (1 << 15)\n+\n /*\n  * Return the length of time to retry acquiring a loose reference lock\n  * before giving up, in milliseconds:\n@@ -92,6 +99,12 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If the new_oid points to a tag object, set this to the peeled\n+\t * object ID for optimized retrieval without needed to hit the odb.\n+\t */\n+\tstruct object_id peeled;\n+\n \t/*\n \t * If set, point the reference to this value. This can also be\n \t * used to convert regular references to become symbolic refs.\n@@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst struct object_id *peeled,\n \t\tconst char *new_target, const char *old_target,\n \t\tconst char *committer_info,\n \t\tconst char *msg);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 444b0c24e5..b0c010387d 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1107,8 +1107,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\tref_transaction_add_update(\n \t\t\ttransaction, \"HEAD\",\n \t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, NULL,\n-\t\t\tu->msg);\n+\t\t\t&u->new_oid, &u->old_oid, &u->peeled, NULL, NULL,\n+\t\t\tNULL, u->msg);\n \t}\n \n \tret = reftable_backend_read_ref(be, rewritten_ref,\n@@ -1194,7 +1194,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\t\t\ttransaction, referent->buf, new_flags,\n \t\t\t\tu->new_target ? NULL : &u->new_oid,\n \t\t\t\tu->old_target ? NULL : &u->old_oid,\n-\t\t\t\tu->new_target, u->old_target,\n+\t\t\t\t&u->peeled, u->new_target, u->old_target,\n \t\t\t\tu->committer_info, u->msg);\n \n \t\t\tnew_update->parent_update = u;\n\n-- \n2.53.GIT\n\n"},{"id":"542189","messageId":"20260423-refs-move-to-generic-layer-v2-9-ae5a4f146d7d@gmail.com","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com","subject":"[PATCH v2 9/9] refs: use peeled tag values in reference backends","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-23T08:40:38Z","receivedAt":"2026-04-23T08:40:50Z","isPatch":true,"body":"The reference backends peel tag objects when storing references to them.\nThis is to provide optimized reads which avoids hitting the odb. The\nprevious commits ensures that the peeled value is now propagated via the\ngeneric layer. So modify the packed and reftable backend to directly use\nthis value instead of calling `peel_object()` independently.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/packed-backend.c   | 6 ++----\n refs/reftable-backend.c | 9 ++-------\n 2 files changed, 4 insertions(+), 11 deletions(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 35a0f32e1c..0acde48c45 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\t */\n \t\t\ti++;\n \t\t} else {\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n-\t\t\t\t\t\t     &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);\n+\t\t\tbool peeled = update->flags & REF_HAVE_PEELED;\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\n-\t\t\t\t\t       peel_error ? NULL : &peeled))\n+\t\t\t\t\t       peeled ? &update->peeled : NULL))\n \t\t\t\tgoto write_error;\n \n \t\t\ti++;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex b0c010387d..8b4ac2e618 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -12,7 +12,6 @@\n #include \"../hex.h\"\n #include \"../ident.h\"\n #include \"../iterator.h\"\n-#include \"../object.h\"\n #include \"../parse.h\"\n #include \"../path.h\"\n #include \"../refs.h\"\n@@ -1584,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t\tgoto done;\n \t\t} else if (u->flags & REF_HAVE_NEW) {\n \t\t\tstruct reftable_ref_record ref = {0};\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error;\n \n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,\n-\t\t\t\t\t\t PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);\n-\t\t\tif (!peel_error) {\n+\t\t\tif (u->flags & REF_HAVE_PEELED) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n-\t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tmemcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ);\n \t\t\t\tmemcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ);\n \t\t\t} else if (!is_null_oid(&u->new_oid)) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL1;\n\n-- \n2.53.GIT\n\n"},{"id":"542190","messageId":"aendvlxDYMHNn4Sq@pks.im","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-2-ae5a4f146d7d@gmail.com","subject":"Re: [PATCH v2 2/9] refs: introduce `ref_store_init_options`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-23T08:52:14Z","receivedAt":"2026-04-23T08:52:20Z","isPatch":true,"body":"On Thu, Apr 23, 2026 at 10:40:31AM +0200, Karthik Nayak wrote:\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index b3b0c25f84..78150ad209 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n>  \t\t\t\t\t &ref_common_dir);\n>  \n>  \tbase_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);\n> -\trefs->store_flags = flags;\n> +\n>  \trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n>  \trefs->packed_ref_store =\n> -\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n> +\t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, opts);\n\nIs this change here intentional? Doesn't seem to be related to any of\nthe other changes here.\n\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index 2d963cc4f4..f49b3807bf 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -385,6 +385,15 @@ struct ref_store;\n>  \t\t\t\t REF_STORE_ODB | \\\n>  \t\t\t\t REF_STORE_MAIN)\n>  \n> +/*\n> + * Options for initializing the ref backend. All backend-agnostic information\n> + * which backends required will be held here.\n> + */\n> +struct ref_store_init_options {\n> +\t/* The kind of operations that the ref_store is allowed to perform. */\n> +\tunsigned int access_flags;\n\nSmells like something that should be converted into an enum eventually,\nbut that definitely is out of scope for this patch series.\n\nPatrick\n"},{"id":"542191","messageId":"aendxRyd9kTc2WC1@pks.im","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-3-ae5a4f146d7d@gmail.com","subject":"Re: [PATCH v2 3/9] refs: extract out reflog config to generic layer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-23T08:52:21Z","receivedAt":"2026-04-23T08:52:25Z","isPatch":true,"body":"On Thu, Apr 23, 2026 at 10:40:32AM +0200, Karthik Nayak wrote:\n> The reference backends need to know when to create reflog entries, this\n> is dictated by the 'core.logallrefupdates' config. Instead of relying on\n> the backends to call `repo_settings_get_log_all_ref_updates()` to obtain\n> this config value, let's do this in the generic layer and pass down the\n> value to the backends.\n\nGreat, this is now a ton easier to review. Thanks!\n\nPatrick\n"},{"id":"542192","messageId":"aendymWafXrTblbQ@pks.im","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-6-ae5a4f146d7d@gmail.com","subject":"Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-23T08:52:26Z","receivedAt":"2026-04-23T08:52:30Z","isPatch":true,"body":"On Thu, Apr 23, 2026 at 10:40:35AM +0200, Karthik Nayak wrote:\n> When using git-update-ref(1) with the '--batch-updates' flag, updates\n> rejected by the reference backend are displayed to the user while other\n> updates are applied. This only applies during the commit phase of the\n> transaction.\n> \n> In the following commits, we'll also extend `ref_transaction_update()`\n> to reject updates before a transaction is prepared/committed. In\n> preparation, modify the code in update-ref to also handle non-generic\n> rejections from `ref_transaction_update()`. This involves propagating\n> information to each of the commands on whether updates are allowed to be\n> rejected, and also checking for rejections and only dying for generic\n> failures.\n\nI noticed that you didn't address my feedback on the changed ordering of\nerrors I posted on the preceding version. Was this intentional?\n\nPatrick\n"},{"id":"542239","messageId":"CAOLa=ZQ=UdVvFVNCt8s-jqx1Aai1KmfzbEF1quHS3OG09vT5Vg@mail.gmail.com","threadId":"65517","inReplyTo":"aendvlxDYMHNn4Sq@pks.im","subject":"Re: [PATCH v2 2/9] refs: introduce `ref_store_init_options`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-24T09:34:01Z","receivedAt":"2026-04-24T09:34:03Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, Apr 23, 2026 at 10:40:31AM +0200, Karthik Nayak wrote:\n>> diff --git a/refs/files-backend.c b/refs/files-backend.c\n>> index b3b0c25f84..78150ad209 100644\n>> --- a/refs/files-backend.c\n>> +++ b/refs/files-backend.c\n>> @@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n>>  \t\t\t\t\t &ref_common_dir);\n>>\n>>  \tbase_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);\n>> -\trefs->store_flags = flags;\n>> +\n>>  \trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n>>  \trefs->packed_ref_store =\n>> -\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n>> +\t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, opts);\n>\n> Is this change here intentional? Doesn't seem to be related to any of\n> the other changes here.\n>\n\nDefinitely not, I think this was a messy rebase.\n\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index 2d963cc4f4..f49b3807bf 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -385,6 +385,15 @@ struct ref_store;\n>>  \t\t\t\t REF_STORE_ODB | \\\n>>  \t\t\t\t REF_STORE_MAIN)\n>>\n>> +/*\n>> + * Options for initializing the ref backend. All backend-agnostic information\n>> + * which backends required will be held here.\n>> + */\n>> +struct ref_store_init_options {\n>> +\t/* The kind of operations that the ref_store is allowed to perform. */\n>> +\tunsigned int access_flags;\n>\n> Smells like something that should be converted into an enum eventually,\n> but that definitely is out of scope for this patch series.\n>\n> Patrick\n\nYeah, this is good #leftoverbits task\n"},{"id":"542240","messageId":"CAOLa=ZSY6KhGJz0-fjzW6WWbMNFi02UaFPPU6Df6uSDQ8+2rsQ@mail.gmail.com","threadId":"65517","inReplyTo":"aendymWafXrTblbQ@pks.im","subject":"Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-24T09:35:57Z","receivedAt":"2026-04-24T09:35:58Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, Apr 23, 2026 at 10:40:35AM +0200, Karthik Nayak wrote:\n>> When using git-update-ref(1) with the '--batch-updates' flag, updates\n>> rejected by the reference backend are displayed to the user while other\n>> updates are applied. This only applies during the commit phase of the\n>> transaction.\n>>\n>> In the following commits, we'll also extend `ref_transaction_update()`\n>> to reject updates before a transaction is prepared/committed. In\n>> preparation, modify the code in update-ref to also handle non-generic\n>> rejections from `ref_transaction_update()`. This involves propagating\n>> information to each of the commands on whether updates are allowed to be\n>> rejected, and also checking for rejections and only dying for generic\n>> failures.\n>\n> I noticed that you didn't address my feedback on the changed ordering of\n> errors I posted on the preceding version. Was this intentional?\n>\n> Patrick\n\nHuh. I do remember adding it in. I'll check my reflog and fix this.\nThanks for checking again.\n"},{"id":"542242","messageId":"875x5gei62.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-4-ae5a4f146d7d@gmail.com","subject":"Re: [PATCH v2 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-04-24T11:01:25Z","receivedAt":"2026-04-24T11:01:33Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> The `ref_transaction_update()` function is used to add updates to a\n> given reference transactions. In the following commit, we'll add more\n> validation to this function. As such, it would be beneficial if the\n> function returns specific error types, so callers can differentiate\n> between different errors.\n>\n> To facilitate this, return `enum ref_transaction_error` from the\n> function and covert the existing '-1' returns to\n> 'REF_TRANSACTION_ERROR_GENERIC'.\n\nI had to look it up, but it seems this enum was introduced not long ago\nin 76e760b999 (refs: introduce enum-based transaction error types,\n2025-04-08). I'm happy to see it's reused here.\n\n> Since this retains the existing\n> behavior, no changes are made to any of the callers but this sets the\n> necessary infrastructure for introduction of other errors.\n\nMakes sense.\n\n-- Toon\n"},{"id":"542243","messageId":"87340keh6h.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-6-ae5a4f146d7d@gmail.com","subject":"Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-04-24T11:22:46Z","receivedAt":"2026-04-24T11:22:56Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> When using git-update-ref(1) with the '--batch-updates' flag, updates\n> rejected by the reference backend are displayed to the user while other\n> updates are applied. This only applies during the commit phase of the\n> transaction.\n>\n> In the following commits, we'll also extend `ref_transaction_update()`\n> to reject updates before a transaction is prepared/committed. In\n> preparation, modify the code in update-ref to also handle non-generic\n> rejections from `ref_transaction_update()`. This involves propagating\n> information to each of the commands on whether updates are allowed to be\n> rejected, and also checking for rejections and only dying for generic\n> failures.\n>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  builtin/update-ref.c | 100 ++++++++++++++++++++++++++++++++++++---------------\n>  1 file changed, 71 insertions(+), 29 deletions(-)\n>\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index 5259cc7226..99deaac6db 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -25,6 +25,15 @@ static unsigned int default_flags;\n>  static unsigned create_reflog_flag;\n>  static const char *msg;\n>  \n> +struct command_options {\n> +\t/*\n> +\t * Individual updates are allowed to fail without causing\n> +\t * update-ref to exit. This is set when using the\n> +\t * '--batch-updates' flag.\n> +\t */\n> +\tbool allow_update_failures;\n> +};\n> +\n>  /*\n>   * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n>   * and append the result to arg.  Return a pointer to the terminator.\n> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,\n>   */\n>  \n>  static void parse_cmd_update(struct ref_transaction *transaction,\n> -\t\t\t     const char *next, const char *end)\n> +\t\t\t     const char *next, const char *end,\n> +\t\t\t     struct command_options *opts)\n>  {\n>  \tstruct strbuf err = STRBUF_INIT;\n>  \tchar *refname;\n>  \tstruct object_id new_oid, old_oid;\n> +\tenum ref_transaction_error tx_err;\n>  \tint have_old;\n>  \n>  \trefname = parse_refname(&next);\n> @@ -289,12 +300,20 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>  \tif (*next != line_termination)\n>  \t\tdie(\"update %s: extra input: %s\", refname, next);\n>  \n> -\tif (ref_transaction_update(transaction, refname,\n> -\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n> -\t\t\t\t   NULL, NULL,\n> -\t\t\t\t   update_flags | create_reflog_flag,\n> -\t\t\t\t   msg, &err))\n> +\ttx_err = ref_transaction_update(transaction, refname,\n> +\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n> +\t\t\t\t\tNULL, NULL,\n> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n> +\t\t\t\t\tmsg, &err);\n> +\n> +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n> +\t    opts->allow_update_failures) {\n> +\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n> +\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n> +\t\t\t\t    NULL);\n> +\t} else if (tx_err) {\n>  \t\tdie(\"%s\", err.buf);\n\nWhy die() only on an ERROR_GENERIC? Is GENERIC the only error that stops\nprocessing of other refs? Why? Would there be more errors in the future\nthat could be added to the pile of \"fatal\" errors like GENERIC?\n\nI would rather see something that gives a more clear indication this\ncurrent ref is rejected. Maybe have a range in the enum:\n\nenum ref_transaction_error {\n\t/* Default error code */\n\tREF_TRANSACTION_ERROR_GENERIC = -1,\n\n        /* Ref rejected error range start */\n\tREF_TRANSACTION_REF_REJECTED = -100,\n\n\t/* Ref name conflict like A vs A/B */\n\tREF_TRANSACTION_ERROR_NAME_CONFLICT = -101,\n\t/* Ref to be created already exists */\n\tREF_TRANSACTION_ERROR_CREATE_EXISTS = -102,\n\t/* ref expected but doesn't exist */\n\tREF_TRANSACTION_ERROR_NONEXISTENT_REF = -103,\n\t/* Provided old_oid or old_target of reference doesn't match actual */\n\tREF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE = -104,\n\t/* Provided new_oid or new_target is invalid */\n\tREF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -105,\n\t/* Expected ref to be symref, but is a regular ref */\n\tREF_TRANSACTION_ERROR_EXPECTED_SYMREF = -106,\n\t/* Cannot create ref due to case-insensitive filesystem */\n\tREF_TRANSACTION_ERROR_CASE_CONFLICT = -107,\n};\n\nstatis inline bool ref_rejected(enum ref_transaction_error err)\n{\n\treturn err < REF_TRANSACTION_REF_REJECTED;\n}\n\nThe thing is, now you have this checking on GENERIC in two places, I'm\nworried one or the other might be forgotten in the future.\n\nNow maybe this is a bit of an overkill, so feel free to reject that\nsuggestion. But if you want to keep looking at GENERIC, how do you feel\nabout this version:\n\n\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n\t\tdie(\"%s\", err.buf);\n\n\tif (tx_err && opts->allow_update_failures)\n\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n\t\t\t\t    NULL);\n\nAnd a little line of comment saying why to die() on GENERIC wouldn't\nhurt I think.\n\n> @@ -341,13 +362,21 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n>  \tif (*next != line_termination)\n>  \t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n>  \n> -\tif (ref_transaction_update(transaction, refname, NULL,\n> -\t\t\t\t   have_old_oid ? &old_oid : NULL,\n> -\t\t\t\t   new_target,\n> -\t\t\t\t   have_old_oid ? NULL : old_target,\n> -\t\t\t\t   update_flags | create_reflog_flag,\n> -\t\t\t\t   msg, &err))\n> +\ttx_err = ref_transaction_update(transaction, refname, NULL,\n> +\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n> +\t\t\t\t\tnew_target,\n> +\t\t\t\t\thave_old_oid ? NULL : old_target,\n> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n> +\t\t\t\t\tmsg, &err);\n> +\n> +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n> +\t    opts->allow_update_failures) {\n> +\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n> +\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n> +\t\t\t\t    new_target, tx_err, err.buf, NULL);\n> +\t} else if (tx_err) {\n>  \t\tdie(\"%s\", err.buf);\n> +\t}\n\nObviously the same suggestion could be applied here.\n\n-- \nCheers,\nToon\n"},{"id":"542245","messageId":"87zf2sd0lb.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-7-ae5a4f146d7d@gmail.com","subject":"Re: [PATCH v2 7/9] refs: move object parsing to the generic layer","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-04-24T12:06:24Z","receivedAt":"2026-04-24T12:06:40Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Regular reference updates made via reference transactions validate that\n> the provided object ID exists in the object database, which is done by\n> calling 'parse_object()'. This check is done independently by the\n> backends which leads to duplicated logic.\n>\n> Let's move this to the generic layer, ensuring the backends only have to\n> care about reference storage and not about validation of the object IDs.\n> With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'\n> error type as its no longer used.\n>\n> Since we don't iterate over individual references in\n> `ref_transaction_prepare()`, we add this check to\n> `ref_transaction_update()`. This means that the validation is done as\n> soon as an update is queued, without needing to prepare the\n> transaction. It can be argued that this is more ideal, since this\n> validation has no dependency on the reference transaction being\n> prepared.\n>\n> It must be noted that the change in behavior means that this error\n> cannot be ignored even with usage of batched updates, since this happens\n> when the update is being added to the transaction. But since the caller\n> gets specific error codes, they can either abort the transaction or\n> continue adding other updates to the transaction.\n>\n> Modify 'builtin/receive-pack.c' to now capture the error type so that\n> the error propagated to the client stays the same. Also remove two of\n> the tests which validates batch-updates with invalid new_oid.\n>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  builtin/receive-pack.c  | 22 +++++++++++++---------\n>  refs.c                  | 18 ++++++++++++++++++\n>  refs/files-backend.c    | 28 ++--------------------------\n>  refs/reftable-backend.c | 19 -------------------\n>  4 files changed, 33 insertions(+), 54 deletions(-)\n>\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index 878aa7f0ed..0fb3d57de8 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n>  \t\t\tret = NULL; /* good */\n>  \t\t}\n>  \t\tstrbuf_release(&err);\n> -\t}\n> -\telse {\n> +\t} else {\n> +\t\tenum ref_transaction_error err_type;\n\nShall we also use `tx_err` like in builtin/update-ref.c?\n\n> diff --git a/refs.c b/refs.c\n> index efa16b739d..662a9e6f9e 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n>  \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n>  \tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n>  \n> +\tif ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&\n> +\t    !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {\n\nCompared to the version we used to have in refs/reftable-backend.c,\nyou've added `!new_target`. Why is that? If I understand correctly, that\nonly happens when `new_oid` is set. Wouldn't `!is_null_oid(new_oid)`\nguard for that?\n\n-- \nCheers,\nToon\n"},{"id":"542265","messageId":"87v7dgcnp6.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","threadId":"65517","inReplyTo":"20260423-refs-move-to-generic-layer-v2-8-ae5a4f146d7d@gmail.com","subject":"Re: [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-04-24T16:44:53Z","receivedAt":"2026-04-24T16:45:00Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Certain reference backend {packed, reftable}, have the ability to also\n\nShouldn't it be:\n\n  Certain reference backends {packed, reftable} have the ability to also\n\n> store the peeled object ID for a reference pointing to a tag object.\n> This has the added benefit that during retrieval of such references, we\n> also obtain the peeled object ID without having to use the ODB.\n>\n> To provide this functionality, each backend independently calls the ODB\n> to obtain the peeled OID. To move this functionality to the generic\n> layer, there must be support infrastructure to pass in a peeled OID for\n> reference updates.\n>\n> Add a `peeled` field to the `ref_update` structure and modify\n> `ref_transaction_add_update()` to receive and copy this object ID to the\n> `ref_update` structure. Finally, modify `ref_transaction_update()` to\n> peel tag objects and pass the peeled OID to\n> `ref_transaction_add_update()`.\n>\n> Update all callers of these functions with the new function parameters.\n> Callers which only add reflog updates, need to only pass in NULL, since\n> for reflogs, we don't store peeled OIDs. Reference deletions also only\n> need to pass in NULL. For others, pass along the peeled OID if\n> available.\n>\n> In a following commit, we'll modify the backends to use this peeled OID\n> instead of parsing it themselves.\n>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  refs.c                  | 15 +++++++++++++--\n>  refs/files-backend.c    | 20 ++++++++++++--------\n>  refs/refs-internal.h    | 14 ++++++++++++++\n>  refs/reftable-backend.c |  6 +++---\n>  4 files changed, 42 insertions(+), 13 deletions(-)\n>\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index d103387ebf..307dcb277b 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -39,6 +39,13 @@ struct ref_transaction;\n>   */\n>  #define REF_LOG_ONLY (1 << 7)\n>  \n> +/*\n> + * The reference contains a peeled object ID. This is used when the\n> + * new_oid is pointing to a tag object and the reference backend\n> + * wants to also store the peeled value for optimized retrieval.\n> + */\n> +#define REF_HAVE_PEELED (1 << 15)\n\nHow did you end up picking this value?\n\nI did some grepping to figure out if it would conflict with anything:\n\n    git grep -h '#define REF_' -- '*.h' '*.c' |\n      awk '/0x/{n=strtonum($3);b=0;while(n>1){n/=2;b++};$3=\"(1 << \"b\")\"} 1' |\n      sort -t'<' -k3 -n |\n      column -t\n\n(Yeah I got some help from AI to write the `awk` command)\n\nResulting in:\n\n    #define  REF_EXCLUSIONS_INIT                   {                \\\n    #define  REF_FILTER_H\n    #define  REF_FILTER_INIT                       {                \\\n    #define  REF_FORMAT_INIT                       {                \\\n    #define  REF_FORMATTING_STATE_INIT             {                0   }\n    #define  REF_NO_DEREF                          (1               <<  0)\n    #define  REF_NORMAL                            (1u              <<  0)\n    #define  REF_STATES_INIT                       {                \\\n    #define  REF_STORE_ALL_CAPS                    (REF_STORE_READ  |   \\\n    #define  REF_STORE_CREATE_ON_DISK_IS_WORKTREE  (1               <<  0)\n    #define  REF_STORE_READ                        (1               <<  0)\n    #define  REF_TRANSACTION_UPDATE_ALLOWED_FLAGS  \\\n    #define  REF_BRANCHES                          (1u              <<  1)\n    #define  REF_FORCE_CREATE_REFLOG               (1               <<  1)\n    #define  REF_STORE_WRITE                       (1               <<  1)   /*  can  perform  update  operations  */\n    #define  REF_HAVE_NEW                          (1               <<  2)\n    #define  REF_STORE_ODB                         (1               <<  2)   /*  has  access   to      object      database  */\n    #define  REF_TAGS                              (1u              <<  2)\n    #define  REF_HAVE_OLD                          (1               <<  3)\n    #define  REF_STORE_MAIN                        (1               <<  3)\n    #define  REF_DIR                               (1               <<  4)\n    #define  REF_IS_PRUNING                        (1               <<  4)\n    #define  REF_DELETING                          (1               <<  5)\n    #define  REF_INCOMPLETE                        (1               <<  5)\n    #define  REF_KNOWS_PEELED                      (1               <<  6)\n    #define  REF_NEEDS_COMMIT                      (1               <<  6)\n    #define  REF_LOG_ONLY                          (1               <<  7)\n    #define  REF_UPDATE_VIA_HEAD                   (1               <<  8)\n    #define  REF_UPDATE_VIA_HEAD                   (1               <<  8)\n    #define  REF_DELETED_RMDIR                     (1               <<  9)\n    #define  REF_SKIP_OID_VERIFICATION             (1               <<  10)\n    #define  REF_SKIP_REFNAME_VERIFICATION         (1               <<  11)\n    #define  REF_SKIP_CREATE_REFLOG                (1               <<  12)\n    #define  REF_LOG_USE_PROVIDED_OIDS             (1               <<  13)\n    #define  REF_LOG_VIA_SPLIT                     (1               <<  14)\n    #define  REF_HAVE_PEELED                       (1               <<  15)\n\nSo I guess it makes sense to use `(1 << 15)`.\n\n-- \nCheers,\nToon\n"},{"id":"542350","messageId":"CAOLa=ZShiWmaTrdK+ReoBO-kgsrmB6ykQ7DDrTG2qj8P=tkFwg@mail.gmail.com","threadId":"65517","inReplyTo":"87340keh6h.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","subject":"Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T08:47:33Z","receivedAt":"2026-04-27T08:47:35Z","isPatch":true,"body":"Toon Claes <toon@iotcl.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> When using git-update-ref(1) with the '--batch-updates' flag, updates\n>> rejected by the reference backend are displayed to the user while other\n>> updates are applied. This only applies during the commit phase of the\n>> transaction.\n>>\n>> In the following commits, we'll also extend `ref_transaction_update()`\n>> to reject updates before a transaction is prepared/committed. In\n>> preparation, modify the code in update-ref to also handle non-generic\n>> rejections from `ref_transaction_update()`. This involves propagating\n>> information to each of the commands on whether updates are allowed to be\n>> rejected, and also checking for rejections and only dying for generic\n>> failures.\n>>\n>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n>> ---\n>>  builtin/update-ref.c | 100 ++++++++++++++++++++++++++++++++++++---------------\n>>  1 file changed, 71 insertions(+), 29 deletions(-)\n>>\n>> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n>> index 5259cc7226..99deaac6db 100644\n>> --- a/builtin/update-ref.c\n>> +++ b/builtin/update-ref.c\n>> @@ -25,6 +25,15 @@ static unsigned int default_flags;\n>>  static unsigned create_reflog_flag;\n>>  static const char *msg;\n>>\n>> +struct command_options {\n>> +\t/*\n>> +\t * Individual updates are allowed to fail without causing\n>> +\t * update-ref to exit. This is set when using the\n>> +\t * '--batch-updates' flag.\n>> +\t */\n>> +\tbool allow_update_failures;\n>> +};\n>> +\n>>  /*\n>>   * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n>>   * and append the result to arg.  Return a pointer to the terminator.\n>> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,\n>>   */\n>>\n>>  static void parse_cmd_update(struct ref_transaction *transaction,\n>> -\t\t\t     const char *next, const char *end)\n>> +\t\t\t     const char *next, const char *end,\n>> +\t\t\t     struct command_options *opts)\n>>  {\n>>  \tstruct strbuf err = STRBUF_INIT;\n>>  \tchar *refname;\n>>  \tstruct object_id new_oid, old_oid;\n>> +\tenum ref_transaction_error tx_err;\n>>  \tint have_old;\n>>\n>>  \trefname = parse_refname(&next);\n>> @@ -289,12 +300,20 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>>  \tif (*next != line_termination)\n>>  \t\tdie(\"update %s: extra input: %s\", refname, next);\n>>\n>> -\tif (ref_transaction_update(transaction, refname,\n>> -\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n>> -\t\t\t\t   NULL, NULL,\n>> -\t\t\t\t   update_flags | create_reflog_flag,\n>> -\t\t\t\t   msg, &err))\n>> +\ttx_err = ref_transaction_update(transaction, refname,\n>> +\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n>> +\t\t\t\t\tNULL, NULL,\n>> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n>> +\t\t\t\t\tmsg, &err);\n>> +\n>> +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n>> +\t    opts->allow_update_failures) {\n>> +\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n>> +\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n>> +\t\t\t\t    NULL);\n>> +\t} else if (tx_err) {\n>>  \t\tdie(\"%s\", err.buf);\n>\n> Why die() only on an ERROR_GENERIC? Is GENERIC the only error that stops\n> processing of other refs? Why? Would there be more errors in the future\n> that could be added to the pile of \"fatal\" errors like GENERIC?\n>\n> I would rather see something that gives a more clear indication this\n> current ref is rejected. Maybe have a range in the enum:\n>\n> enum ref_transaction_error {\n> \t/* Default error code */\n> \tREF_TRANSACTION_ERROR_GENERIC = -1,\n>\n>         /* Ref rejected error range start */\n> \tREF_TRANSACTION_REF_REJECTED = -100,\n>\n> \t/* Ref name conflict like A vs A/B */\n> \tREF_TRANSACTION_ERROR_NAME_CONFLICT = -101,\n> \t/* Ref to be created already exists */\n> \tREF_TRANSACTION_ERROR_CREATE_EXISTS = -102,\n> \t/* ref expected but doesn't exist */\n> \tREF_TRANSACTION_ERROR_NONEXISTENT_REF = -103,\n> \t/* Provided old_oid or old_target of reference doesn't match actual */\n> \tREF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE = -104,\n> \t/* Provided new_oid or new_target is invalid */\n> \tREF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -105,\n> \t/* Expected ref to be symref, but is a regular ref */\n> \tREF_TRANSACTION_ERROR_EXPECTED_SYMREF = -106,\n> \t/* Cannot create ref due to case-insensitive filesystem */\n> \tREF_TRANSACTION_ERROR_CASE_CONFLICT = -107,\n> };\n>\n> statis inline bool ref_rejected(enum ref_transaction_error err)\n> {\n> \treturn err < REF_TRANSACTION_REF_REJECTED;\n> }\n>\n> The thing is, now you have this checking on GENERIC in two places, I'm\n> worried one or the other might be forgotten in the future.\n\nBut it's the same no? We moved from using the error to using a function\nwhich checks the same error. This function will now be used in two\nplaces and one of them could be forgotten.\n\nAnyways, the GENERIC error is the default error code, this is to state\nthat this isn't a specific recoverable error type.\n\n>\n> Now maybe this is a bit of an overkill, so feel free to reject that\n> suggestion. But if you want to keep looking at GENERIC, how do you feel\n> about this version:\n>\n> \tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n> \t\tdie(\"%s\", err.buf);\n>\n> \tif (tx_err && opts->allow_update_failures)\n> \t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n> \t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n> \t\t\t\t    NULL);\n>\n> And a little line of comment saying why to die() on GENERIC wouldn't\n> hurt I think.\n>\n\nThis is nicer, I can modify to this :)\n\n>> @@ -341,13 +362,21 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n>>  \tif (*next != line_termination)\n>>  \t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n>>\n>> -\tif (ref_transaction_update(transaction, refname, NULL,\n>> -\t\t\t\t   have_old_oid ? &old_oid : NULL,\n>> -\t\t\t\t   new_target,\n>> -\t\t\t\t   have_old_oid ? NULL : old_target,\n>> -\t\t\t\t   update_flags | create_reflog_flag,\n>> -\t\t\t\t   msg, &err))\n>> +\ttx_err = ref_transaction_update(transaction, refname, NULL,\n>> +\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n>> +\t\t\t\t\tnew_target,\n>> +\t\t\t\t\thave_old_oid ? NULL : old_target,\n>> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n>> +\t\t\t\t\tmsg, &err);\n>> +\n>> +\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n>> +\t    opts->allow_update_failures) {\n>> +\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n>> +\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n>> +\t\t\t\t    new_target, tx_err, err.buf, NULL);\n>> +\t} else if (tx_err) {\n>>  \t\tdie(\"%s\", err.buf);\n>> +\t}\n>\n> Obviously the same suggestion could be applied here.\n>\n> --\n> Cheers,\n> Toon\n"},{"id":"542351","messageId":"CAOLa=ZTZhVVgE1e4UN2urO46+cgwY2TratHRcrc3ZQtPsZU=Kw@mail.gmail.com","threadId":"65517","inReplyTo":"87zf2sd0lb.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","subject":"Re: [PATCH v2 7/9] refs: move object parsing to the generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T09:32:15Z","receivedAt":"2026-04-27T09:32:17Z","isPatch":true,"body":"Toon Claes <toon@iotcl.com> writes:\n\n[snip]\n\n>> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n>> index 878aa7f0ed..0fb3d57de8 100644\n>> --- a/builtin/receive-pack.c\n>> +++ b/builtin/receive-pack.c\n>> @@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n>>  \t\t\tret = NULL; /* good */\n>>  \t\t}\n>>  \t\tstrbuf_release(&err);\n>> -\t}\n>> -\telse {\n>> +\t} else {\n>> +\t\tenum ref_transaction_error err_type;\n>\n> Shall we also use `tx_err` like in builtin/update-ref.c?\n>\n\nMakes sense, let's do that.\n\n>> diff --git a/refs.c b/refs.c\n>> index efa16b739d..662a9e6f9e 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n>>  \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n>>  \tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n>>\n>> +\tif ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&\n>> +\t    !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {\n>\n> Compared to the version we used to have in refs/reftable-backend.c,\n> you've added `!new_target`. Why is that? If I understand correctly, that\n> only happens when `new_oid` is set. Wouldn't `!is_null_oid(new_oid)`\n> guard for that?\n>\n\nSince `new_oid` is a pointer here, we don't know if it's NULL or not.\n`is_null_oid()` doesn't validate NULL values, so the check must come\nbefore it.\n\nIf `REF_HAVE_NEW` is set, it either means that either `new_target` is\nset or `new_oid` is set. So we check that `new_target` is NULL.\n\nThe reftable check worked as there we operate on top of `struct\nref_update`, which contains `struct object_id new_oid;`, since it is not\na pointer, the value is 0 when `new_target` is set. This works with the\n`is_null_oid()` check.\n\n> --\n> Cheers,\n> Toon\n"},{"id":"542352","messageId":"CAOLa=ZSwupDzWKk0bq=N+O8U0NV9y3h3hFsAeN8WJF-Nwj5vVw@mail.gmail.com","threadId":"65517","inReplyTo":"87v7dgcnp6.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","subject":"Re: [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T09:33:57Z","receivedAt":"2026-04-27T09:33:59Z","isPatch":true,"body":"Toon Claes <toon@iotcl.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> Certain reference backend {packed, reftable}, have the ability to also\n>\n> Shouldn't it be:\n>\n>   Certain reference backends {packed, reftable} have the ability to also\n>\n\nOh yeah, thanks!\n\n>> store the peeled object ID for a reference pointing to a tag object.\n>> This has the added benefit that during retrieval of such references, we\n>> also obtain the peeled object ID without having to use the ODB.\n>>\n>> To provide this functionality, each backend independently calls the ODB\n>> to obtain the peeled OID. To move this functionality to the generic\n>> layer, there must be support infrastructure to pass in a peeled OID for\n>> reference updates.\n>>\n>> Add a `peeled` field to the `ref_update` structure and modify\n>> `ref_transaction_add_update()` to receive and copy this object ID to the\n>> `ref_update` structure. Finally, modify `ref_transaction_update()` to\n>> peel tag objects and pass the peeled OID to\n>> `ref_transaction_add_update()`.\n>>\n>> Update all callers of these functions with the new function parameters.\n>> Callers which only add reflog updates, need to only pass in NULL, since\n>> for reflogs, we don't store peeled OIDs. Reference deletions also only\n>> need to pass in NULL. For others, pass along the peeled OID if\n>> available.\n>>\n>> In a following commit, we'll modify the backends to use this peeled OID\n>> instead of parsing it themselves.\n>>\n>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n>> ---\n>>  refs.c                  | 15 +++++++++++++--\n>>  refs/files-backend.c    | 20 ++++++++++++--------\n>>  refs/refs-internal.h    | 14 ++++++++++++++\n>>  refs/reftable-backend.c |  6 +++---\n>>  4 files changed, 42 insertions(+), 13 deletions(-)\n>>\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index d103387ebf..307dcb277b 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -39,6 +39,13 @@ struct ref_transaction;\n>>   */\n>>  #define REF_LOG_ONLY (1 << 7)\n>>\n>> +/*\n>> + * The reference contains a peeled object ID. This is used when the\n>> + * new_oid is pointing to a tag object and the reference backend\n>> + * wants to also store the peeled value for optimized retrieval.\n>> + */\n>> +#define REF_HAVE_PEELED (1 << 15)\n>\n> How did you end up picking this value?\n>\n> I did some grepping to figure out if it would conflict with anything:\n>\n>     git grep -h '#define REF_' -- '*.h' '*.c' |\n>       awk '/0x/{n=strtonum($3);b=0;while(n>1){n/=2;b++};$3=\"(1 << \"b\")\"} 1' |\n>       sort -t'<' -k3 -n |\n>       column -t\n>\n> (Yeah I got some help from AI to write the `awk` command)\n>\n> Resulting in:\n>\n>     #define  REF_EXCLUSIONS_INIT                   {                \\\n>     #define  REF_FILTER_H\n>     #define  REF_FILTER_INIT                       {                \\\n>     #define  REF_FORMAT_INIT                       {                \\\n>     #define  REF_FORMATTING_STATE_INIT             {                0   }\n>     #define  REF_NO_DEREF                          (1               <<  0)\n>     #define  REF_NORMAL                            (1u              <<  0)\n>     #define  REF_STATES_INIT                       {                \\\n>     #define  REF_STORE_ALL_CAPS                    (REF_STORE_READ  |   \\\n>     #define  REF_STORE_CREATE_ON_DISK_IS_WORKTREE  (1               <<  0)\n>     #define  REF_STORE_READ                        (1               <<  0)\n>     #define  REF_TRANSACTION_UPDATE_ALLOWED_FLAGS  \\\n>     #define  REF_BRANCHES                          (1u              <<  1)\n>     #define  REF_FORCE_CREATE_REFLOG               (1               <<  1)\n>     #define  REF_STORE_WRITE                       (1               <<  1)   /*  can  perform  update  operations  */\n>     #define  REF_HAVE_NEW                          (1               <<  2)\n>     #define  REF_STORE_ODB                         (1               <<  2)   /*  has  access   to      object      database  */\n>     #define  REF_TAGS                              (1u              <<  2)\n>     #define  REF_HAVE_OLD                          (1               <<  3)\n>     #define  REF_STORE_MAIN                        (1               <<  3)\n>     #define  REF_DIR                               (1               <<  4)\n>     #define  REF_IS_PRUNING                        (1               <<  4)\n>     #define  REF_DELETING                          (1               <<  5)\n>     #define  REF_INCOMPLETE                        (1               <<  5)\n>     #define  REF_KNOWS_PEELED                      (1               <<  6)\n>     #define  REF_NEEDS_COMMIT                      (1               <<  6)\n>     #define  REF_LOG_ONLY                          (1               <<  7)\n>     #define  REF_UPDATE_VIA_HEAD                   (1               <<  8)\n>     #define  REF_UPDATE_VIA_HEAD                   (1               <<  8)\n>     #define  REF_DELETED_RMDIR                     (1               <<  9)\n>     #define  REF_SKIP_OID_VERIFICATION             (1               <<  10)\n>     #define  REF_SKIP_REFNAME_VERIFICATION         (1               <<  11)\n>     #define  REF_SKIP_CREATE_REFLOG                (1               <<  12)\n>     #define  REF_LOG_USE_PROVIDED_OIDS             (1               <<  13)\n>     #define  REF_LOG_VIA_SPLIT                     (1               <<  14)\n>     #define  REF_HAVE_PEELED                       (1               <<  15)\n>\n> So I guess it makes sense to use `(1 << 15)`.\n\nI wish I has this command, I manually went through the individual files.\nI think it is messy though, hopefully something to cleanup next.\n\n#leftoverbits\n\n> --\n> Cheers,\n> Toon\n\nThanks for the review, appreciate it..\n"},{"id":"542356","messageId":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH v3 0/9] refs: move some of the generic logic out of the backends","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:01Z","receivedAt":"2026-04-27T10:42:12Z","isPatch":true,"body":"This series came together while I was working on other reference related\ncode and realized that some of the individual logic implemented with the\nreference backends can be moved to the generic layer.\n\nMoving code to the generic layer, simplifies the responsibility of\nindividual backends and avoids deviation in logic between the backends.\n\nThe biggest changes are related to moving out usage of `parse_object()`\nand `peel_object()` from reference transactions. The former is used to\nvalidate that the OID provided points to a commit object. The latter is\nan optimization technique where the packed/reftable backend store the\npeeled OID whenever available, so reading such references provides the\npeeled OID without having to call the ODB.\n\nMoving object parsing to the generic layout involves moving it out of\nthe prepare stage of the transaction and into `ref_transaction_update()`\nwhere every added update is checked. As such, this also involves\nmodifying update-ref(1) and receive-pack(1) to follow this paradigm.\n\n---\nChanges in v3:\n- Remove an unwanted change which creeped up during a rebase.\n- Add information in the commit message around how the order of errors\n  in git-update-ref(1) will change while maintaining functionality.\n- Change up the order of an `if..else` to make it clearer.\n- Other small typos and fixes.\n- Link to v2: https://patch.msgid.link/20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com\n\nChanges in v2:\n- Split the second commit into two: one introducing\n  `ref_store_init_options` and the second to use it for reflog config.\n- Use opts as the variable name consistently.\n- A bunch of grammar fixes.\n- Link to v1: https://patch.msgid.link/20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com\n\n---\n builtin/receive-pack.c  |  22 ++++---\n builtin/update-ref.c    | 151 +++++++++++++++++++++++++++++++-----------------\n refs.c                  |  60 ++++++++++++++-----\n refs.h                  |  16 ++---\n refs/files-backend.c    |  58 +++++++------------\n refs/packed-backend.c   |  10 ++--\n refs/packed-backend.h   |   3 +-\n refs/refs-internal.h    |  35 +++++++++--\n refs/reftable-backend.c |  40 +++----------\n 9 files changed, 231 insertions(+), 164 deletions(-)\n\nKarthik Nayak (9):\n      refs: remove unused typedef 'ref_transaction_commit_fn'\n      refs: introduce `ref_store_init_options`\n      refs: extract out reflog config to generic layer\n      refs: return `ref_transaction_error` from `ref_transaction_update()`\n      update-ref: move `print_rejected_refs()` up\n      update-ref: handle rejections while adding updates\n      refs: move object parsing to the generic layer\n      refs: add peeled object ID to the `ref_update` struct\n      refs: use peeled tag values in reference backends\n\nRange-diff versus v2:\n\n 1:  b8ab8a6c8b =  1:  704a218bce refs: remove unused typedef 'ref_transaction_commit_fn'\n 2:  dd419614e0 !  2:  f3e0caa8e4 refs: introduce `ref_store_init_options`\n    @@ refs/files-backend.c: static struct ref_store *files_ref_store_init(struct repos\n      \trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n      \trefs->packed_ref_store =\n     -\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n    -+\t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, opts);\n    ++\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, opts);\n     +\trefs->store_flags = opts->access_flags;\n      \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n     +\n 3:  337e5c8c5b !  3:  48ebcc2438 refs: extract out reflog config to generic layer\n    @@ refs.c: static struct ref_store *ref_store_init(struct repository *repo,\n      ## refs/files-backend.c ##\n     @@ refs/files-backend.c: static struct ref_store *files_ref_store_init(struct repository *repo,\n      \trefs->packed_ref_store =\n    - \t\tpacked_ref_store_init(repo, payload, refs->gitcommondir, opts);\n    + \t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, opts);\n      \trefs->store_flags = opts->access_flags;\n     -\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n     +\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n 4:  cdb4aad11e =  4:  d4c120ab28 refs: return `ref_transaction_error` from `ref_transaction_update()`\n 5:  454549240a =  5:  6e3b16258c update-ref: move `print_rejected_refs()` up\n 6:  1f224fb868 !  6:  505c5b8edb update-ref: handle rejections while adding updates\n    @@ Commit message\n         rejected, and also checking for rejections and only dying for generic\n         failures.\n     \n    +    Errors encountered during updates will be shown to the user immediately\n    +    unlike other errors encountered only when the transaction is\n    +    prepared/committed. As the verification of object IDs and peeled tag\n    +    objects will move into `ref_transaction_update()` in the following\n    +    commit, this means that those errors will be shown to the user before\n    +    other errors, this changes the order of errors, but the functionality\n    +    remains the same.\n    +\n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## builtin/update-ref.c ##\n    @@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *trans\n     +\t\t\t\t\tupdate_flags | create_reflog_flag,\n     +\t\t\t\t\tmsg, &err);\n     +\n    -+\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n    -+\t    opts->allow_update_failures) {\n    ++\t/*\n    ++\t * Generic errors are non-recoverable, so we cannot skip the update\n    ++\t * or mark it as rejected.\n    ++\t */\n    ++\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n    + \t\tdie(\"%s\", err.buf);\n    + \n    ++\tif (tx_err && opts->allow_update_failures)\n     +\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n     +\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n     +\t\t\t\t    NULL);\n    -+\t} else if (tx_err) {\n    - \t\tdie(\"%s\", err.buf);\n    -+\t}\n    - \n    ++\n      \tupdate_flags = default_flags;\n      \tfree(refname);\n    -@@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *transaction,\n    + \tstrbuf_release(&err);\n      }\n      \n      static void parse_cmd_symref_update(struct ref_transaction *transaction,\n    @@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction\n     +\t\t\t\t\tupdate_flags | create_reflog_flag,\n     +\t\t\t\t\tmsg, &err);\n     +\n    -+\tif (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&\n    -+\t    opts->allow_update_failures) {\n    ++\t/*\n    ++\t * Generic errors are non-recoverable, so we cannot skip the update\n    ++\t * or mark it as rejected.\n    ++\t */\n    ++\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n    + \t\tdie(\"%s\", err.buf);\n    + \n    ++\tif (tx_err && opts->allow_update_failures)\n     +\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n     +\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n     +\t\t\t\t    new_target, tx_err, err.buf, NULL);\n    -+\t} else if (tx_err) {\n    - \t\tdie(\"%s\", err.buf);\n    -+\t}\n    - \n    ++\n      \tupdate_flags = default_flags;\n      \tfree(refname);\n    + \tfree(old_arg);\n     @@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction *transaction,\n      }\n      \n 7:  a52b019afa !  7:  ad1aae6b35 refs: move object parsing to the generic layer\n    @@ builtin/receive-pack.c: static const char *update(struct command *cmd, struct sh\n     -\t}\n     -\telse {\n     +\t} else {\n    -+\t\tenum ref_transaction_error err_type;\n    ++\t\tenum ref_transaction_error tx_err;\n      \t\tstruct strbuf err = STRBUF_INIT;\n      \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n      \t\t    update_shallow_ref(cmd, si)) {\n    @@ builtin/receive-pack.c: static const char *update(struct command *cmd, struct sh\n     -\t\t\t\t\t   NULL, NULL,\n     -\t\t\t\t\t   0, \"push\",\n     -\t\t\t\t\t   &err)) {\n    -+\t\terr_type = ref_transaction_update(transaction,\n    ++\t\ttx_err = ref_transaction_update(transaction,\n     +\t\t\t\t\t\t  namespaced_name,\n     +\t\t\t\t\t\t  new_oid, old_oid,\n     +\t\t\t\t\t\t  NULL, NULL,\n     +\t\t\t\t\t\t  0, \"push\",\n     +\t\t\t\t\t\t  &err);\n    -+\t\tif (err_type) {\n    ++\t\tif (tx_err) {\n      \t\t\trp_error(\"%s\", err.buf);\n     -\t\t\tret = \"failed to update ref\";\n    -+\t\t\tif (err_type == REF_TRANSACTION_ERROR_GENERIC)\n    ++\t\t\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n     +\t\t\t\tret = \"failed to update ref\";\n     +\t\t\telse\n    -+\t\t\t\tret = ref_transaction_error_msg(err_type);\n    ++\t\t\t\tret = ref_transaction_error_msg(tx_err);\n      \t\t} else {\n      \t\t\tret = NULL; /* good */\n      \t\t}\n 8:  aa5fd09831 !  8:  5cb7ee9853 refs: add peeled object ID to the `ref_update` struct\n    @@ Metadata\n      ## Commit message ##\n         refs: add peeled object ID to the `ref_update` struct\n     \n    -    Certain reference backend {packed, reftable}, have the ability to also\n    +    Certain reference backends {packed, reftable}, have the ability to also\n         store the peeled object ID for a reference pointing to a tag object.\n         This has the added benefit that during retrieval of such references, we\n         also obtain the peeled object ID without having to use the ODB.\n 9:  96146b5083 =  9:  ba34e10548 refs: use peeled tag values in reference backends\n\n\nbase-commit: f65aba1e87db64413b6d1ed5ae5a45b5a84a0997\nchange-id: 20260417-refs-move-to-generic-layer-f7525c5e8764\n\nThanks\n- Karthik\n\n"},{"id":"542357","messageId":"20260427-refs-move-to-generic-layer-v3-1-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 1/9] refs: remove unused typedef 'ref_transaction_commit_fn'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:02Z","receivedAt":"2026-04-27T10:42:13Z","isPatch":true,"body":"The typedef 'ref_transaction_commit_fn' is not used anywhere in our\ncode, let's remove it.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/refs-internal.h | 4 ----\n 1 file changed, 4 deletions(-)\n\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex d79e35fd26..2d963cc4f4 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,\n \t\t\t\t     struct ref_transaction *transaction,\n \t\t\t\t     struct strbuf *err);\n \n-typedef int ref_transaction_commit_fn(struct ref_store *refs,\n-\t\t\t\t      struct ref_transaction *transaction,\n-\t\t\t\t      struct strbuf *err);\n-\n typedef int optimize_fn(struct ref_store *ref_store,\n \t\t\tstruct refs_optimize_opts *opts);\n \n\n-- \n2.53.GIT\n\n"},{"id":"542358","messageId":"20260427-refs-move-to-generic-layer-v3-2-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 2/9] refs: introduce `ref_store_init_options`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:03Z","receivedAt":"2026-04-27T10:42:14Z","isPatch":true,"body":"Reference backends are initiated via the `init()` function. When\ninitiating the function, the backend is also provided flags which denote\nthe access levels of the initiator. Create a new structure\n`ref_store_init_options` to house such options and move the access flags\nto this structure.\n\nThis allows easier extension of providing further options to the\nbackends. In the following commit, we'll also provide config around\nreflog creation to the backends via the same structure.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  6 +++++-\n refs/files-backend.c    |  8 +++++---\n refs/packed-backend.c   |  4 ++--\n refs/packed-backend.h   |  3 ++-\n refs/refs-internal.h    | 11 ++++++++++-\n refs/reftable-backend.c |  4 ++--\n 6 files changed, 26 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex bfcb9c7ac3..8992dd6ae8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2295,6 +2295,9 @@ static struct ref_store *ref_store_init(struct repository *repo,\n {\n \tconst struct ref_storage_be *be;\n \tstruct ref_store *refs;\n+\tstruct ref_store_init_options opts = {\n+\t\t.access_flags = flags,\n+\t};\n \n \tbe = find_ref_storage_backend(format);\n \tif (!be)\n@@ -2304,7 +2307,8 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \t * TODO Send in a 'struct worktree' instead of a 'gitdir', and\n \t * allow the backend to handle how it wants to deal with worktrees.\n \t */\n-\trefs = be->init(repo, repo->ref_storage_payload, gitdir, flags);\n+\trefs = be->init(repo, repo->ref_storage_payload, gitdir, &opts);\n+\n \treturn refs;\n }\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex b3b0c25f84..72afe62cee 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -108,7 +108,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)\n static struct ref_store *files_ref_store_init(struct repository *repo,\n \t\t\t\t\t      const char *payload,\n \t\t\t\t\t      const char *gitdir,\n-\t\t\t\t\t      unsigned int flags)\n+\t\t\t\t\t      const struct ref_store_init_options *opts)\n {\n \tstruct files_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n@@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \t\t\t\t\t &ref_common_dir);\n \n \tbase_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);\n-\trefs->store_flags = flags;\n+\n \trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n \trefs->packed_ref_store =\n-\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n+\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, opts);\n+\trefs->store_flags = opts->access_flags;\n \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \n \tchdir_notify_reparent(\"files-backend $GIT_DIR\", &refs->base.gitdir);\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 23ed62984b..35a0f32e1c 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)\n struct ref_store *packed_ref_store_init(struct repository *repo,\n \t\t\t\t\tconst char *payload UNUSED,\n \t\t\t\t\tconst char *gitdir,\n-\t\t\t\t\tunsigned int store_flags)\n+\t\t\t\t\tconst struct ref_store_init_options *opts)\n {\n \tstruct packed_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tbase_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);\n-\trefs->store_flags = store_flags;\n+\trefs->store_flags = opts->access_flags;\n \n \tstrbuf_addf(&sb, \"%s/packed-refs\", gitdir);\n \trefs->path = strbuf_detach(&sb, NULL);\ndiff --git a/refs/packed-backend.h b/refs/packed-backend.h\nindex 2c2377a356..1db48e801d 100644\n--- a/refs/packed-backend.h\n+++ b/refs/packed-backend.h\n@@ -3,6 +3,7 @@\n \n struct repository;\n struct ref_transaction;\n+struct ref_store_init_options;\n \n /*\n  * Support for storing references in a `packed-refs` file.\n@@ -16,7 +17,7 @@ struct ref_transaction;\n struct ref_store *packed_ref_store_init(struct repository *repo,\n \t\t\t\t\tconst char *payload,\n \t\t\t\t\tconst char *gitdir,\n-\t\t\t\t\tunsigned int store_flags);\n+\t\t\t\t\tconst struct ref_store_init_options *options);\n \n /*\n  * Lock the packed-refs file for writing. Flags is passed to\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2d963cc4f4..f49b3807bf 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -385,6 +385,15 @@ struct ref_store;\n \t\t\t\t REF_STORE_ODB | \\\n \t\t\t\t REF_STORE_MAIN)\n \n+/*\n+ * Options for initializing the ref backend. All backend-agnostic information\n+ * which backends required will be held here.\n+ */\n+struct ref_store_init_options {\n+\t/* The kind of operations that the ref_store is allowed to perform. */\n+\tunsigned int access_flags;\n+};\n+\n /*\n  * Initialize the ref_store for the specified gitdir. These functions\n  * should call base_ref_store_init() to initialize the shared part of\n@@ -393,7 +402,7 @@ struct ref_store;\n typedef struct ref_store *ref_store_init_fn(struct repository *repo,\n \t\t\t\t\t    const char *payload,\n \t\t\t\t\t    const char *gitdir,\n-\t\t\t\t\t    unsigned int flags);\n+\t\t\t\t\t    const struct ref_store_init_options *opts);\n /*\n  * Release all memory and resources associated with the ref store.\n  */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex daea30a5b4..ad4ee2627c 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -369,7 +369,7 @@ static int reftable_be_config(const char *var, const char *value,\n static struct ref_store *reftable_be_init(struct repository *repo,\n \t\t\t\t\t  const char *payload,\n \t\t\t\t\t  const char *gitdir,\n-\t\t\t\t\t  unsigned int store_flags)\n+\t\t\t\t\t  const struct ref_store_init_options *opts)\n {\n \tstruct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct strbuf ref_common_dir = STRBUF_INIT;\n@@ -386,8 +386,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n-\trefs->store_flags = store_flags;\n \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->store_flags = opts->access_flags;\n \n \tswitch (repo->hash_algo->format_id) {\n \tcase GIT_SHA1_FORMAT_ID:\n\n-- \n2.53.GIT\n\n"},{"id":"542359","messageId":"20260427-refs-move-to-generic-layer-v3-3-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 3/9] refs: extract out reflog config to generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:04Z","receivedAt":"2026-04-27T10:42:15Z","isPatch":true,"body":"The reference backends need to know when to create reflog entries, this\nis dictated by the 'core.logallrefupdates' config. Instead of relying on\nthe backends to call `repo_settings_get_log_all_ref_updates()` to obtain\nthis config value, let's do this in the generic layer and pass down the\nvalue to the backends.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 1 +\n refs/files-backend.c    | 2 +-\n refs/refs-internal.h    | 6 ++++++\n refs/reftable-backend.c | 2 +-\n 4 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 8992dd6ae8..6b506aeea3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2297,6 +2297,7 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \tstruct ref_store *refs;\n \tstruct ref_store_init_options opts = {\n \t\t.access_flags = flags,\n+\t\t.log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),\n \t};\n \n \tbe = find_ref_storage_backend(format);\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 72afe62cee..4b2faf4777 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -125,7 +125,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \trefs->packed_ref_store =\n \t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, opts);\n \trefs->store_flags = opts->access_flags;\n-\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n \n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex f49b3807bf..d103387ebf 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -392,6 +392,12 @@ struct ref_store;\n struct ref_store_init_options {\n \t/* The kind of operations that the ref_store is allowed to perform. */\n \tunsigned int access_flags;\n+\n+\t/*\n+\t * Denotes under what conditions reflogs should be created when updating\n+\t * references.\n+\t */\n+\tenum log_refs_config log_all_ref_updates;\n };\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex ad4ee2627c..93374d25c2 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -386,7 +386,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n-\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n \trefs->store_flags = opts->access_flags;\n \n \tswitch (repo->hash_algo->format_id) {\n\n-- \n2.53.GIT\n\n"},{"id":"542360","messageId":"20260427-refs-move-to-generic-layer-v3-4-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:05Z","receivedAt":"2026-04-27T10:42:16Z","isPatch":true,"body":"The `ref_transaction_update()` function is used to add updates to a\ngiven reference transactions. In the following commit, we'll add more\nvalidation to this function. As such, it would be beneficial if the\nfunction returns specific error types, so callers can differentiate\nbetween different errors.\n\nTo facilitate this, return `enum ref_transaction_error` from the\nfunction and covert the existing '-1' returns to\n'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing\nbehavior, no changes are made to any of the callers but this sets the\nnecessary infrastructure for introduction of other errors.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c | 20 ++++++++++----------\n refs.h | 16 ++++++++--------\n 2 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 6b506aeea3..efa16b739d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1383,25 +1383,25 @@ static int transaction_refname_valid(const char *refname,\n \treturn 1;\n }\n \n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *new_oid,\n-\t\t\t   const struct object_id *old_oid,\n-\t\t\t   const char *new_target,\n-\t\t\t   const char *old_target,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err)\n+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t\t  const struct object_id *new_oid,\n+\t\t\t\t\t\t  const struct object_id *old_oid,\n+\t\t\t\t\t\t  const char *new_target,\n+\t\t\t\t\t\t  const char *old_target,\n+\t\t\t\t\t\t  unsigned int flags, const char *msg,\n+\t\t\t\t\t\t  struct strbuf *err)\n {\n \tassert(err);\n \n \tif ((flags & REF_FORCE_CREATE_REFLOG) &&\n \t    (flags & REF_SKIP_CREATE_REFLOG)) {\n \t\tstrbuf_addstr(err, _(\"refusing to force and skip creation of reflog\"));\n-\t\treturn -1;\n+\t\treturn REF_TRANSACTION_ERROR_GENERIC;\n \t}\n \n \tif (!transaction_refname_valid(refname, new_oid, flags, err))\n-\t\treturn -1;\n+\t\treturn REF_TRANSACTION_ERROR_GENERIC;\n \n \tif (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)\n \t\tBUG(\"illegal flags 0x%x passed to ref_transaction_update()\", flags);\ndiff --git a/refs.h b/refs.h\nindex d65de6ab5f..71d5c186d0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n  */\n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *new_oid,\n-\t\t\t   const struct object_id *old_oid,\n-\t\t\t   const char *new_target,\n-\t\t\t   const char *old_target,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err);\n+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t\t  const struct object_id *new_oid,\n+\t\t\t\t\t\t  const struct object_id *old_oid,\n+\t\t\t\t\t\t  const char *new_target,\n+\t\t\t\t\t\t  const char *old_target,\n+\t\t\t\t\t\t  unsigned int flags, const char *msg,\n+\t\t\t\t\t\t  struct strbuf *err);\n \n /*\n  * Similar to `ref_transaction_update`, but this function is only for adding\n\n-- \n2.53.GIT\n\n"},{"id":"542361","messageId":"20260427-refs-move-to-generic-layer-v3-5-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 5/9] update-ref: move `print_rejected_refs()` up","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:06Z","receivedAt":"2026-04-27T10:42:18Z","isPatch":true,"body":"The `print_rejected_refs()` function is used to print any rejected refs\nwhen using git-updated-ref(1) with the '--batch-updates' option. In the\nfollowing commit, we'll need to use this function in another place, so\nmove the function up to avoid a separate forward declaration.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 45 ++++++++++++++++++++++-----------------------\n 1 file changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 2d68c40ecb..5259cc7226 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -234,6 +234,28 @@ static int parse_next_oid(const char **next, const char *end,\n \t    command, refname);\n }\n \n+static void print_rejected_refs(const char *refname,\n+\t\t\t\tconst struct object_id *old_oid,\n+\t\t\t\tconst struct object_id *new_oid,\n+\t\t\t\tconst char *old_target,\n+\t\t\t\tconst char *new_target,\n+\t\t\t\tenum ref_transaction_error err,\n+\t\t\t\tconst char *details,\n+\t\t\t\tvoid *cb_data UNUSED)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (details && *details)\n+\t\terror(\"%s\", details);\n+\n+\tstrbuf_addf(&sb, \"rejected %s %s %s %s\\n\", refname,\n+\t\t    new_oid ? oid_to_hex(new_oid) : new_target,\n+\t\t    old_oid ? oid_to_hex(old_oid) : old_target,\n+\t\t    ref_transaction_error_msg(err));\n+\n+\tfwrite(sb.buf, sb.len, 1, stdout);\n+\tstrbuf_release(&sb);\n+}\n \n /*\n  * The following five parse_cmd_*() functions parse the corresponding\n@@ -567,29 +589,6 @@ static void parse_cmd_abort(struct ref_transaction *transaction,\n \treport_ok(\"abort\");\n }\n \n-static void print_rejected_refs(const char *refname,\n-\t\t\t\tconst struct object_id *old_oid,\n-\t\t\t\tconst struct object_id *new_oid,\n-\t\t\t\tconst char *old_target,\n-\t\t\t\tconst char *new_target,\n-\t\t\t\tenum ref_transaction_error err,\n-\t\t\t\tconst char *details,\n-\t\t\t\tvoid *cb_data UNUSED)\n-{\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tif (details && *details)\n-\t\terror(\"%s\", details);\n-\n-\tstrbuf_addf(&sb, \"rejected %s %s %s %s\\n\", refname,\n-\t\t    new_oid ? oid_to_hex(new_oid) : new_target,\n-\t\t    old_oid ? oid_to_hex(old_oid) : old_target,\n-\t\t    ref_transaction_error_msg(err));\n-\n-\tfwrite(sb.buf, sb.len, 1, stdout);\n-\tstrbuf_release(&sb);\n-}\n-\n static void parse_cmd_commit(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end UNUSED)\n {\n\n-- \n2.53.GIT\n\n"},{"id":"542362","messageId":"20260427-refs-move-to-generic-layer-v3-6-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 6/9] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:07Z","receivedAt":"2026-04-27T10:42:20Z","isPatch":true,"body":"When using git-update-ref(1) with the '--batch-updates' flag, updates\nrejected by the reference backend are displayed to the user while other\nupdates are applied. This only applies during the commit phase of the\ntransaction.\n\nIn the following commits, we'll also extend `ref_transaction_update()`\nto reject updates before a transaction is prepared/committed. In\npreparation, modify the code in update-ref to also handle non-generic\nrejections from `ref_transaction_update()`. This involves propagating\ninformation to each of the commands on whether updates are allowed to be\nrejected, and also checking for rejections and only dying for generic\nfailures.\n\nErrors encountered during updates will be shown to the user immediately\nunlike other errors encountered only when the transaction is\nprepared/committed. As the verification of object IDs and peeled tag\nobjects will move into `ref_transaction_update()` in the following\ncommit, this means that those errors will be shown to the user before\nother errors, this changes the order of errors, but the functionality\nremains the same.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 106 +++++++++++++++++++++++++++++++++++++--------------\n 1 file changed, 77 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 5259cc7226..348b7fec94 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -25,6 +25,15 @@ static unsigned int default_flags;\n static unsigned create_reflog_flag;\n static const char *msg;\n \n+struct command_options {\n+\t/*\n+\t * Individual updates are allowed to fail without causing\n+\t * update-ref to exit. This is set when using the\n+\t * '--batch-updates' flag.\n+\t */\n+\tbool allow_update_failures;\n+};\n+\n /*\n  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n  * and append the result to arg.  Return a pointer to the terminator.\n@@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,\n  */\n \n static void parse_cmd_update(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id new_oid, old_oid;\n+\tenum ref_transaction_error tx_err;\n \tint have_old;\n \n \trefname = parse_refname(&next);\n@@ -289,22 +300,35 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname,\n-\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n-\t\t\t\t   NULL, NULL,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n+\ttx_err = ref_transaction_update(transaction, refname,\n+\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t\tNULL, NULL,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\n+\t/*\n+\t * Generic errors are non-recoverable, so we cannot skip the update\n+\t * or mark it as rejected.\n+\t */\n+\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n \t\tdie(\"%s\", err.buf);\n \n+\tif (tx_err && opts->allow_update_failures)\n+\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n+\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n+\t\t\t\t    NULL);\n+\n \tupdate_flags = default_flags;\n \tfree(refname);\n \tstrbuf_release(&err);\n }\n \n static void parse_cmd_symref_update(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts)\n {\n \tchar *refname, *new_target, *old_arg;\n+\tenum ref_transaction_error tx_err;\n \tchar *old_target = NULL;\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct object_id old_oid;\n@@ -341,14 +365,25 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname, NULL,\n-\t\t\t\t   have_old_oid ? &old_oid : NULL,\n-\t\t\t\t   new_target,\n-\t\t\t\t   have_old_oid ? NULL : old_target,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n+\ttx_err = ref_transaction_update(transaction, refname, NULL,\n+\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n+\t\t\t\t\tnew_target,\n+\t\t\t\t\thave_old_oid ? NULL : old_target,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\n+\t/*\n+\t * Generic errors are non-recoverable, so we cannot skip the update\n+\t * or mark it as rejected.\n+\t */\n+\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n \t\tdie(\"%s\", err.buf);\n \n+\tif (tx_err && opts->allow_update_failures)\n+\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n+\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n+\t\t\t\t    new_target, tx_err, err.buf, NULL);\n+\n \tupdate_flags = default_flags;\n \tfree(refname);\n \tfree(old_arg);\n@@ -358,7 +393,8 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_create(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -387,9 +423,9 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_symref_create(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname, *new_target;\n@@ -417,7 +453,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_delete(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -450,9 +487,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname, *old_target;\n@@ -479,9 +516,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_verify(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -508,7 +545,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct object_id old_oid;\n@@ -550,7 +588,8 @@ static void report_ok(const char *command)\n }\n \n static void parse_cmd_option(struct ref_transaction *transaction UNUSED,\n-\t\t\t     const char *next, const char *end UNUSED)\n+\t\t\t     const char *next, const char *end UNUSED,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tconst char *rest;\n \tif (skip_prefix(next, \"no-deref\", &rest) && *rest == line_termination)\n@@ -560,7 +599,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED,\n }\n \n static void parse_cmd_start(struct ref_transaction *transaction UNUSED,\n-\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t    struct command_options *opts UNUSED)\n {\n \tif (*next != line_termination)\n \t\tdie(\"start: extra input: %s\", next);\n@@ -568,7 +608,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED,\n }\n \n static void parse_cmd_prepare(struct ref_transaction *transaction,\n-\t\t\t      const char *next, const char *end UNUSED)\n+\t\t\t      const char *next, const char *end UNUSED,\n+\t\t\t      struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -579,7 +620,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_abort(struct ref_transaction *transaction,\n-\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -590,7 +632,8 @@ static void parse_cmd_abort(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_commit(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end UNUSED)\n+\t\t\t     const char *next, const char *end UNUSED,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -618,7 +661,8 @@ enum update_refs_state {\n \n static const struct parse_cmd {\n \tconst char *prefix;\n-\tvoid (*fn)(struct ref_transaction *, const char *, const char *);\n+\tvoid (*fn)(struct ref_transaction *, const char *, const char *,\n+\t\t   struct command_options *);\n \tunsigned args;\n \tenum update_refs_state state;\n } command[] = {\n@@ -644,6 +688,10 @@ static void update_refs_stdin(unsigned int flags)\n \tstruct ref_transaction *transaction;\n \tint i, j;\n \n+\tstruct command_options opts = {\n+\t\t.allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,\n+\t};\n+\n \ttransaction = ref_store_transaction_begin(get_main_ref_store(the_repository),\n \t\t\t\t\t\t  flags, &err);\n \tif (!transaction)\n@@ -721,7 +769,7 @@ static void update_refs_stdin(unsigned int flags)\n \t\t}\n \n \t\tcmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,\n-\t\t\tinput.buf + input.len);\n+\t\t\tinput.buf + input.len, &opts);\n \t}\n \n \tswitch (state) {\n\n-- \n2.53.GIT\n\n"},{"id":"542363","messageId":"20260427-refs-move-to-generic-layer-v3-7-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 7/9] refs: move object parsing to the generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:08Z","receivedAt":"2026-04-27T10:42:20Z","isPatch":true,"body":"Regular reference updates made via reference transactions validate that\nthe provided object ID exists in the object database, which is done by\ncalling 'parse_object()'. This check is done independently by the\nbackends which leads to duplicated logic.\n\nLet's move this to the generic layer, ensuring the backends only have to\ncare about reference storage and not about validation of the object IDs.\nWith this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'\nerror type as its no longer used.\n\nSince we don't iterate over individual references in\n`ref_transaction_prepare()`, we add this check to\n`ref_transaction_update()`. This means that the validation is done as\nsoon as an update is queued, without needing to prepare the\ntransaction. It can be argued that this is more ideal, since this\nvalidation has no dependency on the reference transaction being\nprepared.\n\nIt must be noted that the change in behavior means that this error\ncannot be ignored even with usage of batched updates, since this happens\nwhen the update is being added to the transaction. But since the caller\ngets specific error codes, they can either abort the transaction or\ncontinue adding other updates to the transaction.\n\nModify 'builtin/receive-pack.c' to now capture the error type so that\nthe error propagated to the client stays the same. Also remove two of\nthe tests which validates batch-updates with invalid new_oid.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/receive-pack.c  | 22 +++++++++++++---------\n refs.c                  | 18 ++++++++++++++++++\n refs/files-backend.c    | 28 ++--------------------------\n refs/reftable-backend.c | 19 -------------------\n 4 files changed, 33 insertions(+), 54 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 878aa7f0ed..376e755e97 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\tret = NULL; /* good */\n \t\t}\n \t\tstrbuf_release(&err);\n-\t}\n-\telse {\n+\t} else {\n+\t\tenum ref_transaction_error tx_err;\n \t\tstruct strbuf err = STRBUF_INIT;\n \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n \t\t    update_shallow_ref(cmd, si)) {\n@@ -1650,14 +1650,18 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (ref_transaction_update(transaction,\n-\t\t\t\t\t   namespaced_name,\n-\t\t\t\t\t   new_oid, old_oid,\n-\t\t\t\t\t   NULL, NULL,\n-\t\t\t\t\t   0, \"push\",\n-\t\t\t\t\t   &err)) {\n+\t\ttx_err = ref_transaction_update(transaction,\n+\t\t\t\t\t\t  namespaced_name,\n+\t\t\t\t\t\t  new_oid, old_oid,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  0, \"push\",\n+\t\t\t\t\t\t  &err);\n+\t\tif (tx_err) {\n \t\t\trp_error(\"%s\", err.buf);\n-\t\t\tret = \"failed to update ref\";\n+\t\t\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n+\t\t\t\tret = \"failed to update ref\";\n+\t\t\telse\n+\t\t\t\tret = ref_transaction_error_msg(tx_err);\n \t\t} else {\n \t\t\tret = NULL; /* good */\n \t\t}\ndiff --git a/refs.c b/refs.c\nindex efa16b739d..662a9e6f9e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n+\tif ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&\n+\t    !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {\n+\t\tstruct object *o = parse_object(transaction->ref_store->repo, new_oid);\n+\n+\t\tif (!o) {\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    _(\"trying to write ref '%s' with nonexistent object %s\"),\n+\t\t\t\t    refname, oid_to_hex(new_oid));\n+\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n+\t\t}\n+\n+\t\tif (o->type != OBJ_COMMIT && is_branch(refname)) {\n+\t\t\tstrbuf_addf(err, _(\"trying to write non-commit object %s to branch '%s'\"),\n+\t\t\t\t    oid_to_hex(new_oid), refname);\n+\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n+\t\t}\n+\t}\n+\n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n \t\t\t\t   old_target, NULL, msg);\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 4b2faf4777..f20f580fbc 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -19,7 +19,6 @@\n #include \"../iterator.h\"\n #include \"../dir-iterator.h\"\n #include \"../lockfile.h\"\n-#include \"../object.h\"\n #include \"../path.h\"\n #include \"../dir.h\"\n #include \"../chdir-notify.h\"\n@@ -1589,7 +1588,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)\n static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,\n \t\t\t\t\t\t\tstruct ref_lock *lock,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n-\t\t\t\t\t\t\tint skip_oid_verification,\n \t\t\t\t\t\t\tstruct strbuf *err);\n static int commit_ref_update(struct files_ref_store *refs,\n \t\t\t     struct ref_lock *lock,\n@@ -1737,7 +1735,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,\n \t}\n \toidcpy(&lock->old_oid, &orig_oid);\n \n-\tif (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||\n+\tif (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||\n \t    commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) {\n \t\terror(\"unable to write current sha1 into %s: %s\", newrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -1755,7 +1753,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,\n \t\tgoto rollbacklog;\n \t}\n \n-\tif (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||\n+\tif (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||\n \t    commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) {\n \t\terror(\"unable to write current sha1 into %s: %s\", oldrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -1999,32 +1997,11 @@ static int files_log_ref_write(struct files_ref_store *refs,\n static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,\n \t\t\t\t\t\t\tstruct ref_lock *lock,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n-\t\t\t\t\t\t\tint skip_oid_verification,\n \t\t\t\t\t\t\tstruct strbuf *err)\n {\n \tstatic char term = '\\n';\n-\tstruct object *o;\n \tint fd;\n \n-\tif (!skip_oid_verification) {\n-\t\to = parse_object(refs->base.repo, oid);\n-\t\tif (!o) {\n-\t\t\tstrbuf_addf(\n-\t\t\t\terr,\n-\t\t\t\t\"trying to write ref '%s' with nonexistent object %s\",\n-\t\t\t\tlock->ref_name, oid_to_hex(oid));\n-\t\t\tunlock_ref(lock);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t\tif (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {\n-\t\t\tstrbuf_addf(\n-\t\t\t\terr,\n-\t\t\t\t\"trying to write non-commit object %s to branch '%s'\",\n-\t\t\t\toid_to_hex(oid), lock->ref_name);\n-\t\t\tunlock_ref(lock);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t}\n \tfd = get_lock_file_fd(&lock->lk);\n \tif (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 ||\n \t    write_in_full(fd, &term, 1) < 0 ||\n@@ -2828,7 +2805,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re\n \t\t} else {\n \t\t\tret = write_ref_to_lockfile(\n \t\t\t\trefs, lock, &update->new_oid,\n-\t\t\t\tupdate->flags & REF_SKIP_OID_VERIFICATION,\n \t\t\t\terr);\n \t\t\tif (ret) {\n \t\t\t\tchar *write_err = strbuf_detach(err, NULL);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 93374d25c2..444b0c24e5 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1081,25 +1081,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\treturn 0;\n \t}\n \n-\t/* Verify that the new object ID is valid. */\n-\tif ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&\n-\t    !(u->flags & REF_SKIP_OID_VERIFICATION) &&\n-\t    !(u->flags & REF_LOG_ONLY)) {\n-\t\tstruct object *o = parse_object(refs->base.repo, &u->new_oid);\n-\t\tif (!o) {\n-\t\t\tstrbuf_addf(err,\n-\t\t\t\t    _(\"trying to write ref '%s' with nonexistent object %s\"),\n-\t\t\t\t    u->refname, oid_to_hex(&u->new_oid));\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\n-\t\tif (o->type != OBJ_COMMIT && is_branch(u->refname)) {\n-\t\t\tstrbuf_addf(err, _(\"trying to write non-commit object %s to branch '%s'\"),\n-\t\t\t\t    oid_to_hex(&u->new_oid), u->refname);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t}\n-\n \t/*\n \t * When we update the reference that HEAD points to we enqueue\n \t * a second log-only update for HEAD so that its reflog is\n\n-- \n2.53.GIT\n\n"},{"id":"542364","messageId":"20260427-refs-move-to-generic-layer-v3-8-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 8/9] refs: add peeled object ID to the `ref_update` struct","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:09Z","receivedAt":"2026-04-27T10:42:22Z","isPatch":true,"body":"Certain reference backends {packed, reftable}, have the ability to also\nstore the peeled object ID for a reference pointing to a tag object.\nThis has the added benefit that during retrieval of such references, we\nalso obtain the peeled object ID without having to use the ODB.\n\nTo provide this functionality, each backend independently calls the ODB\nto obtain the peeled OID. To move this functionality to the generic\nlayer, there must be support infrastructure to pass in a peeled OID for\nreference updates.\n\nAdd a `peeled` field to the `ref_update` structure and modify\n`ref_transaction_add_update()` to receive and copy this object ID to the\n`ref_update` structure. Finally, modify `ref_transaction_update()` to\npeel tag objects and pass the peeled OID to\n`ref_transaction_add_update()`.\n\nUpdate all callers of these functions with the new function parameters.\nCallers which only add reflog updates, need to only pass in NULL, since\nfor reflogs, we don't store peeled OIDs. Reference deletions also only\nneed to pass in NULL. For others, pass along the peeled OID if\navailable.\n\nIn a following commit, we'll modify the backends to use this peeled OID\ninstead of parsing it themselves.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 15 +++++++++++++--\n refs/files-backend.c    | 20 ++++++++++++--------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  6 +++---\n 4 files changed, 42 insertions(+), 13 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 662a9e6f9e..0648df2b6c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1307,6 +1307,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst struct object_id *peeled,\n \t\tconst char *new_target, const char *old_target,\n \t\tconst char *committer_info,\n \t\tconst char *msg)\n@@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update(\n \t\tupdate->committer_info = xstrdup_or_null(committer_info);\n \t\tupdate->msg = normalize_reflog_message(msg);\n \t}\n+\tif (flags & REF_HAVE_PEELED)\n+\t\toidcpy(&update->peeled, peeled);\n \n \t/*\n \t * This list is generally used by the backends to avoid duplicates.\n@@ -1392,6 +1395,8 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \t\t\t\t\t\t  unsigned int flags, const char *msg,\n \t\t\t\t\t\t  struct strbuf *err)\n {\n+\tstruct object_id peeled;\n+\n \tassert(err);\n \n \tif ((flags & REF_FORCE_CREATE_REFLOG) &&\n@@ -1432,10 +1437,16 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \t\t\t\t    oid_to_hex(new_oid), refname);\n \t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n \t\t}\n+\n+\t\tif (o->type == OBJ_TAG) {\n+\t\t\tif (!peel_object(transaction->ref_store->repo, new_oid, &peeled,\n+\t\t\t\t\t PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))\n+\t\t\t\tflags |= REF_HAVE_PEELED;\n+\t\t}\n \t}\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   new_oid, old_oid, &peeled, new_target,\n \t\t\t\t   old_target, NULL, msg);\n \n \treturn 0;\n@@ -1462,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction,\n \t\treturn -1;\n \n \tupdate = ref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t\t    new_oid, old_oid, NULL, NULL,\n+\t\t\t\t\t    new_oid, old_oid, NULL, NULL, NULL,\n \t\t\t\t\t    committer_info, msg);\n \tupdate->index = index;\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex f20f580fbc..d0896d0e37 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1325,7 +1325,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);\n+\t\t\tnull_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,\n+\t\t\tNULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -2468,7 +2469,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,\n-\t\t\t&update->new_oid, &update->old_oid,\n+\t\t\t&update->new_oid, &update->old_oid, &update->peeled,\n \t\t\tNULL, NULL, update->committer_info, update->msg);\n \tnew_update->parent_update = update;\n \n@@ -2530,8 +2531,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,\n \t\t\ttransaction, referent, new_flags,\n \t\t\tupdate->new_target ? NULL : &update->new_oid,\n \t\t\tupdate->old_target ? NULL : &update->old_oid,\n-\t\t\tupdate->new_target, update->old_target, NULL,\n-\t\t\tupdate->msg);\n+\t\t\t&update->peeled, update->new_target, update->old_target,\n+\t\t\tNULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2994,7 +2995,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\tref_transaction_add_update(\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n-\t\t\t\t\t&update->new_oid, NULL,\n+\t\t\t\t\t&update->new_oid, NULL, NULL,\n \t\t\t\t\tNULL, NULL, NULL, NULL);\n \t\t}\n \t}\n@@ -3200,19 +3201,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,\n \t\t\tif (update->flags & REF_LOG_ONLY)\n \t\t\t\tref_transaction_add_update(loose_transaction, update->refname,\n \t\t\t\t\t\t\t   update->flags, &update->new_oid,\n-\t\t\t\t\t\t\t   &update->old_oid, NULL, NULL,\n+\t\t\t\t\t\t\t   &update->old_oid, &update->peeled,\n+\t\t\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t\t\t   update->committer_info, update->msg);\n \t\t\telse\n \t\t\t\tref_transaction_add_update(loose_transaction, update->refname,\n \t\t\t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t\t\t   update->new_target ? NULL : &update->new_oid, NULL,\n-\t\t\t\t\t\t\t   update->new_target, NULL, update->committer_info,\n+\t\t\t\t\t\t\t   &update->peeled, update->new_target,\n+\t\t\t\t\t\t\t   NULL, update->committer_info,\n \t\t\t\t\t\t\t   NULL);\n \t\t} else {\n \t\t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t\t   NULL, NULL, update->committer_info, NULL);\n+\t\t\t\t\t\t   &update->peeled, NULL, NULL,\n+\t\t\t\t\t\t   update->committer_info, NULL);\n \t\t}\n \t}\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex d103387ebf..307dcb277b 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -39,6 +39,13 @@ struct ref_transaction;\n  */\n #define REF_LOG_ONLY (1 << 7)\n \n+/*\n+ * The reference contains a peeled object ID. This is used when the\n+ * new_oid is pointing to a tag object and the reference backend\n+ * wants to also store the peeled value for optimized retrieval.\n+ */\n+#define REF_HAVE_PEELED (1 << 15)\n+\n /*\n  * Return the length of time to retry acquiring a loose reference lock\n  * before giving up, in milliseconds:\n@@ -92,6 +99,12 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If the new_oid points to a tag object, set this to the peeled\n+\t * object ID for optimized retrieval without needed to hit the odb.\n+\t */\n+\tstruct object_id peeled;\n+\n \t/*\n \t * If set, point the reference to this value. This can also be\n \t * used to convert regular references to become symbolic refs.\n@@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst struct object_id *peeled,\n \t\tconst char *new_target, const char *old_target,\n \t\tconst char *committer_info,\n \t\tconst char *msg);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 444b0c24e5..b0c010387d 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1107,8 +1107,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\tref_transaction_add_update(\n \t\t\ttransaction, \"HEAD\",\n \t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, NULL,\n-\t\t\tu->msg);\n+\t\t\t&u->new_oid, &u->old_oid, &u->peeled, NULL, NULL,\n+\t\t\tNULL, u->msg);\n \t}\n \n \tret = reftable_backend_read_ref(be, rewritten_ref,\n@@ -1194,7 +1194,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\t\t\ttransaction, referent->buf, new_flags,\n \t\t\t\tu->new_target ? NULL : &u->new_oid,\n \t\t\t\tu->old_target ? NULL : &u->old_oid,\n-\t\t\t\tu->new_target, u->old_target,\n+\t\t\t\t&u->peeled, u->new_target, u->old_target,\n \t\t\t\tu->committer_info, u->msg);\n \n \t\t\tnew_update->parent_update = u;\n\n-- \n2.53.GIT\n\n"},{"id":"542365","messageId":"20260427-refs-move-to-generic-layer-v3-9-e4638dfb7897@gmail.com","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com","subject":"[PATCH v3 9/9] refs: use peeled tag values in reference backends","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-27T10:42:10Z","receivedAt":"2026-04-27T10:42:22Z","isPatch":true,"body":"The reference backends peel tag objects when storing references to them.\nThis is to provide optimized reads which avoids hitting the odb. The\nprevious commits ensures that the peeled value is now propagated via the\ngeneric layer. So modify the packed and reftable backend to directly use\nthis value instead of calling `peel_object()` independently.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/packed-backend.c   | 6 ++----\n refs/reftable-backend.c | 9 ++-------\n 2 files changed, 4 insertions(+), 11 deletions(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 35a0f32e1c..0acde48c45 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\t */\n \t\t\ti++;\n \t\t} else {\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n-\t\t\t\t\t\t     &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);\n+\t\t\tbool peeled = update->flags & REF_HAVE_PEELED;\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\n-\t\t\t\t\t       peel_error ? NULL : &peeled))\n+\t\t\t\t\t       peeled ? &update->peeled : NULL))\n \t\t\t\tgoto write_error;\n \n \t\t\ti++;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex b0c010387d..8b4ac2e618 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -12,7 +12,6 @@\n #include \"../hex.h\"\n #include \"../ident.h\"\n #include \"../iterator.h\"\n-#include \"../object.h\"\n #include \"../parse.h\"\n #include \"../path.h\"\n #include \"../refs.h\"\n@@ -1584,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t\tgoto done;\n \t\t} else if (u->flags & REF_HAVE_NEW) {\n \t\t\tstruct reftable_ref_record ref = {0};\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error;\n \n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,\n-\t\t\t\t\t\t PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);\n-\t\t\tif (!peel_error) {\n+\t\t\tif (u->flags & REF_HAVE_PEELED) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n-\t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tmemcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ);\n \t\t\t\tmemcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ);\n \t\t\t} else if (!is_null_oid(&u->new_oid)) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL1;\n\n-- \n2.53.GIT\n\n"},{"id":"542471","messageId":"87v7dagdjk.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","threadId":"65517","inReplyTo":"20260427-refs-move-to-generic-layer-v3-6-e4638dfb7897@gmail.com","subject":"Re: [PATCH v3 6/9] update-ref: handle rejections while adding updates","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-04-29T12:24:15Z","receivedAt":"2026-04-29T12:24:23Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> @@ -289,22 +300,35 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>  \tif (*next != line_termination)\n>  \t\tdie(\"update %s: extra input: %s\", refname, next);\n>  \n> -\tif (ref_transaction_update(transaction, refname,\n> -\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n> -\t\t\t\t   NULL, NULL,\n> -\t\t\t\t   update_flags | create_reflog_flag,\n> -\t\t\t\t   msg, &err))\n> +\ttx_err = ref_transaction_update(transaction, refname,\n> +\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n> +\t\t\t\t\tNULL, NULL,\n> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n> +\t\t\t\t\tmsg, &err);\n> +\n> +\t/*\n> +\t * Generic errors are non-recoverable, so we cannot skip the update\n> +\t * or mark it as rejected.\n> +\t */\n> +\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n>  \t\tdie(\"%s\", err.buf);\n>  \n> +\tif (tx_err && opts->allow_update_failures)\n> +\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n> +\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n> +\t\t\t\t    NULL);\n\nI realize I've made this suggestion, but I think I've made a mistake.\nWhen opts->allow_update_failures is falsey and tx_err is truthy we\nshould die also. Don't we?\n\nI'm not sure what the nicest way is to write this, but maybe:\n\n        if (tx_err) {\n            if (tx_err == REF_TRANSACTION_ERROR_GENERIC || !opts->allow_update_failures)\n                die(\"%s\", err.buf);\n\n            print_rejected_refs(refname, have_old ? &old_oid : NULL,\n                                &new_oid, NULL, NULL, tx_err, err.buf,\n                                NULL);\n        }\n\nHow did test coverage not find this?\n\n-- \nCheers,\nToon\n"},{"id":"542522","messageId":"CAOLa=ZQ3P=K0iX9w4GvMPPBXrUMuZrBkt=MxYg+nDt5_-vyMRw@mail.gmail.com","threadId":"65517","inReplyTo":"87v7dagdjk.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","subject":"Re: [PATCH v3 6/9] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-04-30T09:52:16Z","receivedAt":"2026-04-30T09:52:18Z","isPatch":true,"body":"Toon Claes <toon@iotcl.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> @@ -289,22 +300,35 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>>  \tif (*next != line_termination)\n>>  \t\tdie(\"update %s: extra input: %s\", refname, next);\n>>\n>> -\tif (ref_transaction_update(transaction, refname,\n>> -\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n>> -\t\t\t\t   NULL, NULL,\n>> -\t\t\t\t   update_flags | create_reflog_flag,\n>> -\t\t\t\t   msg, &err))\n>> +\ttx_err = ref_transaction_update(transaction, refname,\n>> +\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n>> +\t\t\t\t\tNULL, NULL,\n>> +\t\t\t\t\tupdate_flags | create_reflog_flag,\n>> +\t\t\t\t\tmsg, &err);\n>> +\n>> +\t/*\n>> +\t * Generic errors are non-recoverable, so we cannot skip the update\n>> +\t * or mark it as rejected.\n>> +\t */\n>> +\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n>>  \t\tdie(\"%s\", err.buf);\n>>\n>> +\tif (tx_err && opts->allow_update_failures)\n>> +\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n>> +\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n>> +\t\t\t\t    NULL);\n>\n> I realize I've made this suggestion, but I think I've made a mistake.\n> When opts->allow_update_failures is falsey and tx_err is truthy we\n> should die also. Don't we?\n>\n\nNice. I didn't think of that either.\n\n> I'm not sure what the nicest way is to write this, but maybe:\n>\n>         if (tx_err) {\n>             if (tx_err == REF_TRANSACTION_ERROR_GENERIC || !opts->allow_update_failures)\n>                 die(\"%s\", err.buf);\n>\n>             print_rejected_refs(refname, have_old ? &old_oid : NULL,\n>                                 &new_oid, NULL, NULL, tx_err, err.buf,\n>                                 NULL);\n>         }\n>\n> How did test coverage not find this?\n>\n\nBecause:\n1. The function only returns `REF_TRANSACTION_ERROR_GENERIC` as of this\ncommit.\n2. We only seem to be testing this scenario for batched updates.\n3. I'll fix this and add some tests.\n\n> --\n> Cheers,\n> Toon\n"},{"id":"542696","messageId":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com","subject":"[PATCH v4 0/9] refs: move some of the generic logic out of the backends","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:04Z","receivedAt":"2026-05-04T17:44:14Z","isPatch":true,"body":"This series came together while I was working on other reference related\ncode and realized that some of the individual logic implemented with the\nreference backends can be moved to the generic layer.\n\nMoving code to the generic layer, simplifies the responsibility of\nindividual backends and avoids deviation in logic between the backends.\n\nThe biggest changes are related to moving out usage of `parse_object()`\nand `peel_object()` from reference transactions. The former is used to\nvalidate that the OID provided points to a commit object. The latter is\nan optimization technique where the packed/reftable backend store the\npeeled OID whenever available, so reading such references provides the\npeeled OID without having to call the ODB.\n\nMoving object parsing to the generic layout involves moving it out of\nthe prepare stage of the transaction and into `ref_transaction_update()`\nwhere every added update is checked. As such, this also involves\nmodifying update-ref(1) and receive-pack(1) to follow this paradigm.\n\n---\nChanges in v4:\n- Fix a bug in the error handling code, move it to a function and also\n  add missing call sites. Add tests to ensure we catch this.\n- Link to v3: https://patch.msgid.link/20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com\n\nChanges in v3:\n- Remove an unwanted change which creeped up during a rebase.\n- Add information in the commit message around how the order of errors\n  in git-update-ref(1) will change while maintaining functionality.\n- Change up the order of an `if..else` to make it clearer.\n- Other small typos and fixes.\n- Link to v2: https://patch.msgid.link/20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com\n\nChanges in v2:\n- Split the second commit into two: one introducing\n  `ref_store_init_options` and the second to use it for reflog config.\n- Use opts as the variable name consistently.\n- A bunch of grammar fixes.\n- Link to v1: https://patch.msgid.link/20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com\n\n---\n builtin/receive-pack.c  |  22 +++---\n builtin/update-ref.c    | 182 +++++++++++++++++++++++++++++++-----------------\n refs.c                  |  60 ++++++++++++----\n refs.h                  |  16 ++---\n refs/files-backend.c    |  58 ++++++---------\n refs/packed-backend.c   |  10 ++-\n refs/packed-backend.h   |   3 +-\n refs/refs-internal.h    |  35 ++++++++--\n refs/reftable-backend.c |  40 +++--------\n t/t1400-update-ref.sh   |  14 ++++\n 10 files changed, 266 insertions(+), 174 deletions(-)\n\nKarthik Nayak (9):\n      refs: remove unused typedef 'ref_transaction_commit_fn'\n      refs: introduce `ref_store_init_options`\n      refs: extract out reflog config to generic layer\n      refs: return `ref_transaction_error` from `ref_transaction_update()`\n      update-ref: move `print_rejected_refs()` up\n      update-ref: handle rejections while adding updates\n      refs: move object parsing to the generic layer\n      refs: add peeled object ID to the `ref_update` struct\n      refs: use peeled tag values in reference backends\n\nRange-diff versus v3:\n\n 1:  b89e273583 =  1:  cf5fa53f34 refs: remove unused typedef 'ref_transaction_commit_fn'\n 2:  8f7f8dadc8 =  2:  afd10fd7a3 refs: introduce `ref_store_init_options`\n 3:  9cdc35f5b6 =  3:  4eb6950cdc refs: extract out reflog config to generic layer\n 4:  36c0c86a31 =  4:  dd8177a243 refs: return `ref_transaction_error` from `ref_transaction_update()`\n 5:  1b7eca9353 =  5:  eb86178ae5 update-ref: move `print_rejected_refs()` up\n 6:  aef1529054 !  6:  464c6371a4 update-ref: handle rejections while adding updates\n    @@ builtin/update-ref.c: static unsigned int default_flags;\n      /*\n       * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n       * and append the result to arg.  Return a pointer to the terminator.\n    +@@ builtin/update-ref.c: static void print_rejected_refs(const char *refname,\n    + \tstrbuf_release(&sb);\n    + }\n    + \n    ++/*\n    ++ * Handle transaction errors. If we're using batches updates, we want to only\n    ++ * die for generic errors and print the remaining to the user.\n    ++ */\n    ++static void handle_ref_transaction_error(const char *refname,\n    ++\t\t\t\t\t struct object_id *new_oid,\n    ++\t\t\t\t\t struct object_id *old_oid,\n    ++\t\t\t\t\t const char *new_target,\n    ++\t\t\t\t\t const char *old_target,\n    ++\t\t\t\t\t enum ref_transaction_error tx_err,\n    ++\t\t\t\t\t struct strbuf *err,\n    ++\t\t\t\t\t struct command_options *opts)\n    ++{\n    ++\tif (!tx_err)\n    ++\t\treturn;\n    ++\n    ++\tif (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) {\n    ++\t\tprint_rejected_refs(refname, old_oid, new_oid, old_target,\n    ++\t\t\t\t    new_target, tx_err, err->buf, NULL);\n    ++\t\treturn;\n    ++\t}\n    ++\n    ++\tdie(\"%s\", err->buf);\n    ++}\n    ++\n    + /*\n    +  * The following five parse_cmd_*() functions parse the corresponding\n    +  * command.  In each case, next points at the character following the\n     @@ builtin/update-ref.c: static void print_rejected_refs(const char *refname,\n       */\n      \n    @@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *trans\n     -\t\t\t\t   NULL, NULL,\n     -\t\t\t\t   update_flags | create_reflog_flag,\n     -\t\t\t\t   msg, &err))\n    +-\t\tdie(\"%s\", err.buf);\n     +\ttx_err = ref_transaction_update(transaction, refname,\n     +\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n     +\t\t\t\t\tNULL, NULL,\n     +\t\t\t\t\tupdate_flags | create_reflog_flag,\n     +\t\t\t\t\tmsg, &err);\n    ++\thandle_ref_transaction_error(refname, &new_oid, have_old ? &old_oid : NULL,\n    ++\t\t\t\t     NULL, NULL, tx_err, &err, opts);\n     +\n    -+\t/*\n    -+\t * Generic errors are non-recoverable, so we cannot skip the update\n    -+\t * or mark it as rejected.\n    -+\t */\n    -+\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n    - \t\tdie(\"%s\", err.buf);\n      \n    -+\tif (tx_err && opts->allow_update_failures)\n    -+\t\tprint_rejected_refs(refname, have_old ? &old_oid : NULL,\n    -+\t\t\t\t    &new_oid, NULL, NULL, tx_err, err.buf,\n    -+\t\t\t\t    NULL);\n    -+\n      \tupdate_flags = default_flags;\n      \tfree(refname);\n    - \tstrbuf_release(&err);\n    +@@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *transaction,\n      }\n      \n      static void parse_cmd_symref_update(struct ref_transaction *transaction,\n    @@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction\n     -\t\t\t\t   have_old_oid ? NULL : old_target,\n     -\t\t\t\t   update_flags | create_reflog_flag,\n     -\t\t\t\t   msg, &err))\n    +-\t\tdie(\"%s\", err.buf);\n     +\ttx_err = ref_transaction_update(transaction, refname, NULL,\n     +\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n     +\t\t\t\t\tnew_target,\n     +\t\t\t\t\thave_old_oid ? NULL : old_target,\n     +\t\t\t\t\tupdate_flags | create_reflog_flag,\n     +\t\t\t\t\tmsg, &err);\n    -+\n    -+\t/*\n    -+\t * Generic errors are non-recoverable, so we cannot skip the update\n    -+\t * or mark it as rejected.\n    -+\t */\n    -+\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n    - \t\tdie(\"%s\", err.buf);\n    ++\thandle_ref_transaction_error(refname, NULL, have_old_oid ? &old_oid : NULL,\n    ++\t\t\t\t     new_target, have_old_oid ? NULL : old_target,\n    ++\t\t\t\t     tx_err, &err, opts);\n      \n    -+\tif (tx_err && opts->allow_update_failures)\n    -+\t\tprint_rejected_refs(refname, have_old_oid ? &old_oid : NULL,\n    -+\t\t\t\t    NULL, have_old_oid ? NULL : old_target,\n    -+\t\t\t\t    new_target, tx_err, err.buf, NULL);\n    -+\n      \tupdate_flags = default_flags;\n      \tfree(refname);\n    - \tfree(old_arg);\n     @@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction *transaction,\n      }\n      \n      static void parse_cmd_create(struct ref_transaction *transaction,\n     -\t\t\t     const char *next, const char *end)\n     +\t\t\t     const char *next, const char *end,\n    -+\t\t\t     struct command_options *opts UNUSED)\n    ++\t\t\t     struct command_options *opts)\n      {\n      \tstruct strbuf err = STRBUF_INIT;\n      \tchar *refname;\n    + \tstruct object_id new_oid;\n    ++\tenum ref_transaction_error tx_err;\n    + \n    + \trefname = parse_refname(&next);\n    + \tif (!refname)\n     @@ builtin/update-ref.c: static void parse_cmd_create(struct ref_transaction *transaction,\n    + \tif (*next != line_termination)\n    + \t\tdie(\"create %s: extra input: %s\", refname, next);\n    + \n    +-\tif (ref_transaction_create(transaction, refname, &new_oid, NULL,\n    +-\t\t\t\t   update_flags | create_reflog_flag,\n    +-\t\t\t\t   msg, &err))\n    +-\t\tdie(\"%s\", err.buf);\n    ++\ttx_err = ref_transaction_create(transaction, refname, &new_oid, NULL,\n    ++\t\t\t\t\tupdate_flags | create_reflog_flag,\n    ++\t\t\t\t\tmsg, &err);\n    ++\thandle_ref_transaction_error(refname, &new_oid, NULL, NULL, NULL, tx_err,\n    ++\t\t\t\t     &err, opts);\n    + \n    + \tupdate_flags = default_flags;\n    + \tfree(refname);\n      \tstrbuf_release(&err);\n      }\n      \n    @@ builtin/update-ref.c: static void parse_cmd_create(struct ref_transaction *trans\n      static void parse_cmd_symref_create(struct ref_transaction *transaction,\n     -\t\t\t\t    const char *next, const char *end UNUSED)\n     +\t\t\t\t    const char *next, const char *end UNUSED,\n    -+\t\t\t\t    struct command_options *opts UNUSED)\n    ++\t\t\t\t    struct command_options *opts)\n      {\n      \tstruct strbuf err = STRBUF_INIT;\n      \tchar *refname, *new_target;\n    ++\tenum ref_transaction_error tx_err;\n    + \n    + \trefname = parse_refname(&next);\n    + \tif (!refname)\n    +@@ builtin/update-ref.c: static void parse_cmd_symref_create(struct ref_transaction *transaction,\n    + \tif (*next != line_termination)\n    + \t\tdie(\"symref-create %s: extra input: %s\", refname, next);\n    + \n    +-\tif (ref_transaction_create(transaction, refname, NULL, new_target,\n    +-\t\t\t\t   update_flags | create_reflog_flag,\n    +-\t\t\t\t   msg, &err))\n    +-\t\tdie(\"%s\", err.buf);\n    ++\ttx_err = ref_transaction_create(transaction, refname, NULL, new_target,\n    ++\t\t\t\t\tupdate_flags | create_reflog_flag,\n    ++\t\t\t\t\tmsg, &err);\n    ++\thandle_ref_transaction_error(refname, NULL, NULL, new_target, NULL,\n    ++\t\t\t\t     tx_err, &err, opts);\n    + \n    + \tupdate_flags = default_flags;\n    + \tfree(refname);\n     @@ builtin/update-ref.c: static void parse_cmd_symref_create(struct ref_transaction *transaction,\n      }\n      \n 7:  4b225f4f4d !  7:  f971226d89 refs: move object parsing to the generic layer\n    @@ refs/reftable-backend.c: static enum ref_transaction_error prepare_single_update\n      \t/*\n      \t * When we update the reference that HEAD points to we enqueue\n      \t * a second log-only update for HEAD so that its reflog is\n    +\n    + ## t/t1400-update-ref.sh ##\n    +@@ t/t1400-update-ref.sh: test_expect_success 'stdin -z create ref fails with empty new value' '\n    + \ttest_must_fail git rev-parse --verify -q $c\n    + '\n    + \n    ++test_expect_success 'stdin -z create ref fails with non commit object' '\n    ++\tprintf $F \"create $c\" \"$(test_oid 001)\" >stdin &&\n    ++\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n    ++\tgrep \"fatal: trying to write ref ${SQ}$c${SQ} with nonexistent object\" err &&\n    ++\ttest_must_fail git rev-parse --verify -q $c\n    ++'\n    ++\n    ++test_expect_success 'stdin -z update ref fails with non commit object' '\n    ++\tprintf $F \"update $b\" \"$(test_oid 001)\" \"\" >stdin &&\n    ++\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n    ++\tgrep \"fatal: trying to write ref ${SQ}$b${SQ} with nonexistent object\" err &&\n    ++\ttest_must_fail git rev-parse --verify -q $c\n    ++'\n    ++\n    + test_expect_success 'stdin -z update ref works with right old value' '\n    + \tprintf $F \"update $b\" \"$m~1\" \"$m\" >stdin &&\n    + \tgit update-ref -z --stdin <stdin &&\n 8:  944af6a454 =  8:  d271167077 refs: add peeled object ID to the `ref_update` struct\n 9:  47653fdde9 =  9:  ed88b9e2ce refs: use peeled tag values in reference backends\n\n\nbase-commit: f65aba1e87db64413b6d1ed5ae5a45b5a84a0997\nchange-id: 20260417-refs-move-to-generic-layer-f7525c5e8764\n\nThanks\n- Karthik\n\n"},{"id":"542697","messageId":"20260504-refs-move-to-generic-layer-v4-1-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 1/9] refs: remove unused typedef 'ref_transaction_commit_fn'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:05Z","receivedAt":"2026-05-04T17:44:14Z","isPatch":true,"body":"The typedef 'ref_transaction_commit_fn' is not used anywhere in our\ncode, let's remove it.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/refs-internal.h | 4 ----\n 1 file changed, 4 deletions(-)\n\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex d79e35fd26..2d963cc4f4 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,\n \t\t\t\t     struct ref_transaction *transaction,\n \t\t\t\t     struct strbuf *err);\n \n-typedef int ref_transaction_commit_fn(struct ref_store *refs,\n-\t\t\t\t      struct ref_transaction *transaction,\n-\t\t\t\t      struct strbuf *err);\n-\n typedef int optimize_fn(struct ref_store *ref_store,\n \t\t\tstruct refs_optimize_opts *opts);\n \n\n-- \n2.53.GIT\n\n"},{"id":"542698","messageId":"20260504-refs-move-to-generic-layer-v4-2-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 2/9] refs: introduce `ref_store_init_options`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:06Z","receivedAt":"2026-05-04T17:44:16Z","isPatch":true,"body":"Reference backends are initiated via the `init()` function. When\ninitiating the function, the backend is also provided flags which denote\nthe access levels of the initiator. Create a new structure\n`ref_store_init_options` to house such options and move the access flags\nto this structure.\n\nThis allows easier extension of providing further options to the\nbackends. In the following commit, we'll also provide config around\nreflog creation to the backends via the same structure.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  6 +++++-\n refs/files-backend.c    |  8 +++++---\n refs/packed-backend.c   |  4 ++--\n refs/packed-backend.h   |  3 ++-\n refs/refs-internal.h    | 11 ++++++++++-\n refs/reftable-backend.c |  4 ++--\n 6 files changed, 26 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex bfcb9c7ac3..8992dd6ae8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2295,6 +2295,9 @@ static struct ref_store *ref_store_init(struct repository *repo,\n {\n \tconst struct ref_storage_be *be;\n \tstruct ref_store *refs;\n+\tstruct ref_store_init_options opts = {\n+\t\t.access_flags = flags,\n+\t};\n \n \tbe = find_ref_storage_backend(format);\n \tif (!be)\n@@ -2304,7 +2307,8 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \t * TODO Send in a 'struct worktree' instead of a 'gitdir', and\n \t * allow the backend to handle how it wants to deal with worktrees.\n \t */\n-\trefs = be->init(repo, repo->ref_storage_payload, gitdir, flags);\n+\trefs = be->init(repo, repo->ref_storage_payload, gitdir, &opts);\n+\n \treturn refs;\n }\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex b3b0c25f84..72afe62cee 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -108,7 +108,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)\n static struct ref_store *files_ref_store_init(struct repository *repo,\n \t\t\t\t\t      const char *payload,\n \t\t\t\t\t      const char *gitdir,\n-\t\t\t\t\t      unsigned int flags)\n+\t\t\t\t\t      const struct ref_store_init_options *opts)\n {\n \tstruct files_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n@@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \t\t\t\t\t &ref_common_dir);\n \n \tbase_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);\n-\trefs->store_flags = flags;\n+\n \trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n \trefs->packed_ref_store =\n-\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n+\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, opts);\n+\trefs->store_flags = opts->access_flags;\n \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \n \tchdir_notify_reparent(\"files-backend $GIT_DIR\", &refs->base.gitdir);\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 23ed62984b..35a0f32e1c 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)\n struct ref_store *packed_ref_store_init(struct repository *repo,\n \t\t\t\t\tconst char *payload UNUSED,\n \t\t\t\t\tconst char *gitdir,\n-\t\t\t\t\tunsigned int store_flags)\n+\t\t\t\t\tconst struct ref_store_init_options *opts)\n {\n \tstruct packed_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n \tstruct strbuf sb = STRBUF_INIT;\n \n \tbase_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);\n-\trefs->store_flags = store_flags;\n+\trefs->store_flags = opts->access_flags;\n \n \tstrbuf_addf(&sb, \"%s/packed-refs\", gitdir);\n \trefs->path = strbuf_detach(&sb, NULL);\ndiff --git a/refs/packed-backend.h b/refs/packed-backend.h\nindex 2c2377a356..1db48e801d 100644\n--- a/refs/packed-backend.h\n+++ b/refs/packed-backend.h\n@@ -3,6 +3,7 @@\n \n struct repository;\n struct ref_transaction;\n+struct ref_store_init_options;\n \n /*\n  * Support for storing references in a `packed-refs` file.\n@@ -16,7 +17,7 @@ struct ref_transaction;\n struct ref_store *packed_ref_store_init(struct repository *repo,\n \t\t\t\t\tconst char *payload,\n \t\t\t\t\tconst char *gitdir,\n-\t\t\t\t\tunsigned int store_flags);\n+\t\t\t\t\tconst struct ref_store_init_options *options);\n \n /*\n  * Lock the packed-refs file for writing. Flags is passed to\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2d963cc4f4..f49b3807bf 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -385,6 +385,15 @@ struct ref_store;\n \t\t\t\t REF_STORE_ODB | \\\n \t\t\t\t REF_STORE_MAIN)\n \n+/*\n+ * Options for initializing the ref backend. All backend-agnostic information\n+ * which backends required will be held here.\n+ */\n+struct ref_store_init_options {\n+\t/* The kind of operations that the ref_store is allowed to perform. */\n+\tunsigned int access_flags;\n+};\n+\n /*\n  * Initialize the ref_store for the specified gitdir. These functions\n  * should call base_ref_store_init() to initialize the shared part of\n@@ -393,7 +402,7 @@ struct ref_store;\n typedef struct ref_store *ref_store_init_fn(struct repository *repo,\n \t\t\t\t\t    const char *payload,\n \t\t\t\t\t    const char *gitdir,\n-\t\t\t\t\t    unsigned int flags);\n+\t\t\t\t\t    const struct ref_store_init_options *opts);\n /*\n  * Release all memory and resources associated with the ref store.\n  */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex daea30a5b4..ad4ee2627c 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -369,7 +369,7 @@ static int reftable_be_config(const char *var, const char *value,\n static struct ref_store *reftable_be_init(struct repository *repo,\n \t\t\t\t\t  const char *payload,\n \t\t\t\t\t  const char *gitdir,\n-\t\t\t\t\t  unsigned int store_flags)\n+\t\t\t\t\t  const struct ref_store_init_options *opts)\n {\n \tstruct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct strbuf ref_common_dir = STRBUF_INIT;\n@@ -386,8 +386,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n-\trefs->store_flags = store_flags;\n \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->store_flags = opts->access_flags;\n \n \tswitch (repo->hash_algo->format_id) {\n \tcase GIT_SHA1_FORMAT_ID:\n\n-- \n2.53.GIT\n\n"},{"id":"542699","messageId":"20260504-refs-move-to-generic-layer-v4-3-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 3/9] refs: extract out reflog config to generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:07Z","receivedAt":"2026-05-04T17:44:17Z","isPatch":true,"body":"The reference backends need to know when to create reflog entries, this\nis dictated by the 'core.logallrefupdates' config. Instead of relying on\nthe backends to call `repo_settings_get_log_all_ref_updates()` to obtain\nthis config value, let's do this in the generic layer and pass down the\nvalue to the backends.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 1 +\n refs/files-backend.c    | 2 +-\n refs/refs-internal.h    | 6 ++++++\n refs/reftable-backend.c | 2 +-\n 4 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 8992dd6ae8..6b506aeea3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2297,6 +2297,7 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \tstruct ref_store *refs;\n \tstruct ref_store_init_options opts = {\n \t\t.access_flags = flags,\n+\t\t.log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),\n \t};\n \n \tbe = find_ref_storage_backend(format);\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 72afe62cee..4b2faf4777 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -125,7 +125,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \trefs->packed_ref_store =\n \t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, opts);\n \trefs->store_flags = opts->access_flags;\n-\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n \n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex f49b3807bf..d103387ebf 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -392,6 +392,12 @@ struct ref_store;\n struct ref_store_init_options {\n \t/* The kind of operations that the ref_store is allowed to perform. */\n \tunsigned int access_flags;\n+\n+\t/*\n+\t * Denotes under what conditions reflogs should be created when updating\n+\t * references.\n+\t */\n+\tenum log_refs_config log_all_ref_updates;\n };\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex ad4ee2627c..93374d25c2 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -386,7 +386,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n \tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n-\trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n+\trefs->log_all_ref_updates = opts->log_all_ref_updates;\n \trefs->store_flags = opts->access_flags;\n \n \tswitch (repo->hash_algo->format_id) {\n\n-- \n2.53.GIT\n\n"},{"id":"542700","messageId":"20260504-refs-move-to-generic-layer-v4-4-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:08Z","receivedAt":"2026-05-04T17:44:18Z","isPatch":true,"body":"The `ref_transaction_update()` function is used to add updates to a\ngiven reference transactions. In the following commit, we'll add more\nvalidation to this function. As such, it would be beneficial if the\nfunction returns specific error types, so callers can differentiate\nbetween different errors.\n\nTo facilitate this, return `enum ref_transaction_error` from the\nfunction and covert the existing '-1' returns to\n'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing\nbehavior, no changes are made to any of the callers but this sets the\nnecessary infrastructure for introduction of other errors.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c | 20 ++++++++++----------\n refs.h | 16 ++++++++--------\n 2 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 6b506aeea3..efa16b739d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1383,25 +1383,25 @@ static int transaction_refname_valid(const char *refname,\n \treturn 1;\n }\n \n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *new_oid,\n-\t\t\t   const struct object_id *old_oid,\n-\t\t\t   const char *new_target,\n-\t\t\t   const char *old_target,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err)\n+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t\t  const struct object_id *new_oid,\n+\t\t\t\t\t\t  const struct object_id *old_oid,\n+\t\t\t\t\t\t  const char *new_target,\n+\t\t\t\t\t\t  const char *old_target,\n+\t\t\t\t\t\t  unsigned int flags, const char *msg,\n+\t\t\t\t\t\t  struct strbuf *err)\n {\n \tassert(err);\n \n \tif ((flags & REF_FORCE_CREATE_REFLOG) &&\n \t    (flags & REF_SKIP_CREATE_REFLOG)) {\n \t\tstrbuf_addstr(err, _(\"refusing to force and skip creation of reflog\"));\n-\t\treturn -1;\n+\t\treturn REF_TRANSACTION_ERROR_GENERIC;\n \t}\n \n \tif (!transaction_refname_valid(refname, new_oid, flags, err))\n-\t\treturn -1;\n+\t\treturn REF_TRANSACTION_ERROR_GENERIC;\n \n \tif (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)\n \t\tBUG(\"illegal flags 0x%x passed to ref_transaction_update()\", flags);\ndiff --git a/refs.h b/refs.h\nindex d65de6ab5f..71d5c186d0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n  */\n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *new_oid,\n-\t\t\t   const struct object_id *old_oid,\n-\t\t\t   const char *new_target,\n-\t\t\t   const char *old_target,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err);\n+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t\t  const struct object_id *new_oid,\n+\t\t\t\t\t\t  const struct object_id *old_oid,\n+\t\t\t\t\t\t  const char *new_target,\n+\t\t\t\t\t\t  const char *old_target,\n+\t\t\t\t\t\t  unsigned int flags, const char *msg,\n+\t\t\t\t\t\t  struct strbuf *err);\n \n /*\n  * Similar to `ref_transaction_update`, but this function is only for adding\n\n-- \n2.53.GIT\n\n"},{"id":"542701","messageId":"20260504-refs-move-to-generic-layer-v4-5-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 5/9] update-ref: move `print_rejected_refs()` up","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:09Z","receivedAt":"2026-05-04T17:44:19Z","isPatch":true,"body":"The `print_rejected_refs()` function is used to print any rejected refs\nwhen using git-updated-ref(1) with the '--batch-updates' option. In the\nfollowing commit, we'll need to use this function in another place, so\nmove the function up to avoid a separate forward declaration.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 45 ++++++++++++++++++++++-----------------------\n 1 file changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 2d68c40ecb..5259cc7226 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -234,6 +234,28 @@ static int parse_next_oid(const char **next, const char *end,\n \t    command, refname);\n }\n \n+static void print_rejected_refs(const char *refname,\n+\t\t\t\tconst struct object_id *old_oid,\n+\t\t\t\tconst struct object_id *new_oid,\n+\t\t\t\tconst char *old_target,\n+\t\t\t\tconst char *new_target,\n+\t\t\t\tenum ref_transaction_error err,\n+\t\t\t\tconst char *details,\n+\t\t\t\tvoid *cb_data UNUSED)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (details && *details)\n+\t\terror(\"%s\", details);\n+\n+\tstrbuf_addf(&sb, \"rejected %s %s %s %s\\n\", refname,\n+\t\t    new_oid ? oid_to_hex(new_oid) : new_target,\n+\t\t    old_oid ? oid_to_hex(old_oid) : old_target,\n+\t\t    ref_transaction_error_msg(err));\n+\n+\tfwrite(sb.buf, sb.len, 1, stdout);\n+\tstrbuf_release(&sb);\n+}\n \n /*\n  * The following five parse_cmd_*() functions parse the corresponding\n@@ -567,29 +589,6 @@ static void parse_cmd_abort(struct ref_transaction *transaction,\n \treport_ok(\"abort\");\n }\n \n-static void print_rejected_refs(const char *refname,\n-\t\t\t\tconst struct object_id *old_oid,\n-\t\t\t\tconst struct object_id *new_oid,\n-\t\t\t\tconst char *old_target,\n-\t\t\t\tconst char *new_target,\n-\t\t\t\tenum ref_transaction_error err,\n-\t\t\t\tconst char *details,\n-\t\t\t\tvoid *cb_data UNUSED)\n-{\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tif (details && *details)\n-\t\terror(\"%s\", details);\n-\n-\tstrbuf_addf(&sb, \"rejected %s %s %s %s\\n\", refname,\n-\t\t    new_oid ? oid_to_hex(new_oid) : new_target,\n-\t\t    old_oid ? oid_to_hex(old_oid) : old_target,\n-\t\t    ref_transaction_error_msg(err));\n-\n-\tfwrite(sb.buf, sb.len, 1, stdout);\n-\tstrbuf_release(&sb);\n-}\n-\n static void parse_cmd_commit(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end UNUSED)\n {\n\n-- \n2.53.GIT\n\n"},{"id":"542702","messageId":"20260504-refs-move-to-generic-layer-v4-6-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 6/9] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:10Z","receivedAt":"2026-05-04T17:44:20Z","isPatch":true,"body":"When using git-update-ref(1) with the '--batch-updates' flag, updates\nrejected by the reference backend are displayed to the user while other\nupdates are applied. This only applies during the commit phase of the\ntransaction.\n\nIn the following commits, we'll also extend `ref_transaction_update()`\nto reject updates before a transaction is prepared/committed. In\npreparation, modify the code in update-ref to also handle non-generic\nrejections from `ref_transaction_update()`. This involves propagating\ninformation to each of the commands on whether updates are allowed to be\nrejected, and also checking for rejections and only dying for generic\nfailures.\n\nErrors encountered during updates will be shown to the user immediately\nunlike other errors encountered only when the transaction is\nprepared/committed. As the verification of object IDs and peeled tag\nobjects will move into `ref_transaction_update()` in the following\ncommit, this means that those errors will be shown to the user before\nother errors, this changes the order of errors, but the functionality\nremains the same.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 137 ++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 98 insertions(+), 39 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 5259cc7226..6355c3dd3e 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -25,6 +25,15 @@ static unsigned int default_flags;\n static unsigned create_reflog_flag;\n static const char *msg;\n \n+struct command_options {\n+\t/*\n+\t * Individual updates are allowed to fail without causing\n+\t * update-ref to exit. This is set when using the\n+\t * '--batch-updates' flag.\n+\t */\n+\tbool allow_update_failures;\n+};\n+\n /*\n  * Parse one whitespace- or NUL-terminated, possibly C-quoted argument\n  * and append the result to arg.  Return a pointer to the terminator.\n@@ -257,6 +266,31 @@ static void print_rejected_refs(const char *refname,\n \tstrbuf_release(&sb);\n }\n \n+/*\n+ * Handle transaction errors. If we're using batches updates, we want to only\n+ * die for generic errors and print the remaining to the user.\n+ */\n+static void handle_ref_transaction_error(const char *refname,\n+\t\t\t\t\t struct object_id *new_oid,\n+\t\t\t\t\t struct object_id *old_oid,\n+\t\t\t\t\t const char *new_target,\n+\t\t\t\t\t const char *old_target,\n+\t\t\t\t\t enum ref_transaction_error tx_err,\n+\t\t\t\t\t struct strbuf *err,\n+\t\t\t\t\t struct command_options *opts)\n+{\n+\tif (!tx_err)\n+\t\treturn;\n+\n+\tif (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) {\n+\t\tprint_rejected_refs(refname, old_oid, new_oid, old_target,\n+\t\t\t\t    new_target, tx_err, err->buf, NULL);\n+\t\treturn;\n+\t}\n+\n+\tdie(\"%s\", err->buf);\n+}\n+\n /*\n  * The following five parse_cmd_*() functions parse the corresponding\n  * command.  In each case, next points at the character following the\n@@ -268,11 +302,13 @@ static void print_rejected_refs(const char *refname,\n  */\n \n static void parse_cmd_update(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id new_oid, old_oid;\n+\tenum ref_transaction_error tx_err;\n \tint have_old;\n \n \trefname = parse_refname(&next);\n@@ -289,12 +325,14 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname,\n-\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n-\t\t\t\t   NULL, NULL,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n-\t\tdie(\"%s\", err.buf);\n+\ttx_err = ref_transaction_update(transaction, refname,\n+\t\t\t\t\t&new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t\tNULL, NULL,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\thandle_ref_transaction_error(refname, &new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t     NULL, NULL, tx_err, &err, opts);\n+\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n@@ -302,9 +340,11 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_symref_update(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts)\n {\n \tchar *refname, *new_target, *old_arg;\n+\tenum ref_transaction_error tx_err;\n \tchar *old_target = NULL;\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct object_id old_oid;\n@@ -341,13 +381,15 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname, NULL,\n-\t\t\t\t   have_old_oid ? &old_oid : NULL,\n-\t\t\t\t   new_target,\n-\t\t\t\t   have_old_oid ? NULL : old_target,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n-\t\tdie(\"%s\", err.buf);\n+\ttx_err = ref_transaction_update(transaction, refname, NULL,\n+\t\t\t\t\thave_old_oid ? &old_oid : NULL,\n+\t\t\t\t\tnew_target,\n+\t\t\t\t\thave_old_oid ? NULL : old_target,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\thandle_ref_transaction_error(refname, NULL, have_old_oid ? &old_oid : NULL,\n+\t\t\t\t     new_target, have_old_oid ? NULL : old_target,\n+\t\t\t\t     tx_err, &err, opts);\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n@@ -358,11 +400,13 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_create(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id new_oid;\n+\tenum ref_transaction_error tx_err;\n \n \trefname = parse_refname(&next);\n \tif (!refname)\n@@ -377,22 +421,24 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"create %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_create(transaction, refname, &new_oid, NULL,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n-\t\tdie(\"%s\", err.buf);\n+\ttx_err = ref_transaction_create(transaction, refname, &new_oid, NULL,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\thandle_ref_transaction_error(refname, &new_oid, NULL, NULL, NULL, tx_err,\n+\t\t\t\t     &err, opts);\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_symref_create(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname, *new_target;\n+\tenum ref_transaction_error tx_err;\n \n \trefname = parse_refname(&next);\n \tif (!refname)\n@@ -405,10 +451,11 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"symref-create %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_create(transaction, refname, NULL, new_target,\n-\t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n-\t\tdie(\"%s\", err.buf);\n+\ttx_err = ref_transaction_create(transaction, refname, NULL, new_target,\n+\t\t\t\t\tupdate_flags | create_reflog_flag,\n+\t\t\t\t\tmsg, &err);\n+\thandle_ref_transaction_error(refname, NULL, NULL, new_target, NULL,\n+\t\t\t\t     tx_err, &err, opts);\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n@@ -417,7 +464,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_delete(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -450,9 +498,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname, *old_target;\n@@ -479,9 +527,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n-\n static void parse_cmd_verify(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end)\n+\t\t\t     const char *next, const char *end,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *refname;\n@@ -508,7 +556,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n-\t\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct object_id old_oid;\n@@ -550,7 +599,8 @@ static void report_ok(const char *command)\n }\n \n static void parse_cmd_option(struct ref_transaction *transaction UNUSED,\n-\t\t\t     const char *next, const char *end UNUSED)\n+\t\t\t     const char *next, const char *end UNUSED,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tconst char *rest;\n \tif (skip_prefix(next, \"no-deref\", &rest) && *rest == line_termination)\n@@ -560,7 +610,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED,\n }\n \n static void parse_cmd_start(struct ref_transaction *transaction UNUSED,\n-\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t    struct command_options *opts UNUSED)\n {\n \tif (*next != line_termination)\n \t\tdie(\"start: extra input: %s\", next);\n@@ -568,7 +619,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED,\n }\n \n static void parse_cmd_prepare(struct ref_transaction *transaction,\n-\t\t\t      const char *next, const char *end UNUSED)\n+\t\t\t      const char *next, const char *end UNUSED,\n+\t\t\t      struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -579,7 +631,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_abort(struct ref_transaction *transaction,\n-\t\t\t    const char *next, const char *end UNUSED)\n+\t\t\t    const char *next, const char *end UNUSED,\n+\t\t\t    struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -590,7 +643,8 @@ static void parse_cmd_abort(struct ref_transaction *transaction,\n }\n \n static void parse_cmd_commit(struct ref_transaction *transaction,\n-\t\t\t     const char *next, const char *end UNUSED)\n+\t\t\t     const char *next, const char *end UNUSED,\n+\t\t\t     struct command_options *opts UNUSED)\n {\n \tstruct strbuf error = STRBUF_INIT;\n \tif (*next != line_termination)\n@@ -618,7 +672,8 @@ enum update_refs_state {\n \n static const struct parse_cmd {\n \tconst char *prefix;\n-\tvoid (*fn)(struct ref_transaction *, const char *, const char *);\n+\tvoid (*fn)(struct ref_transaction *, const char *, const char *,\n+\t\t   struct command_options *);\n \tunsigned args;\n \tenum update_refs_state state;\n } command[] = {\n@@ -644,6 +699,10 @@ static void update_refs_stdin(unsigned int flags)\n \tstruct ref_transaction *transaction;\n \tint i, j;\n \n+\tstruct command_options opts = {\n+\t\t.allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,\n+\t};\n+\n \ttransaction = ref_store_transaction_begin(get_main_ref_store(the_repository),\n \t\t\t\t\t\t  flags, &err);\n \tif (!transaction)\n@@ -721,7 +780,7 @@ static void update_refs_stdin(unsigned int flags)\n \t\t}\n \n \t\tcmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,\n-\t\t\tinput.buf + input.len);\n+\t\t\tinput.buf + input.len, &opts);\n \t}\n \n \tswitch (state) {\n\n-- \n2.53.GIT\n\n"},{"id":"542703","messageId":"20260504-refs-move-to-generic-layer-v4-7-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 7/9] refs: move object parsing to the generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:11Z","receivedAt":"2026-05-04T17:44:21Z","isPatch":true,"body":"Regular reference updates made via reference transactions validate that\nthe provided object ID exists in the object database, which is done by\ncalling 'parse_object()'. This check is done independently by the\nbackends which leads to duplicated logic.\n\nLet's move this to the generic layer, ensuring the backends only have to\ncare about reference storage and not about validation of the object IDs.\nWith this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'\nerror type as its no longer used.\n\nSince we don't iterate over individual references in\n`ref_transaction_prepare()`, we add this check to\n`ref_transaction_update()`. This means that the validation is done as\nsoon as an update is queued, without needing to prepare the\ntransaction. It can be argued that this is more ideal, since this\nvalidation has no dependency on the reference transaction being\nprepared.\n\nIt must be noted that the change in behavior means that this error\ncannot be ignored even with usage of batched updates, since this happens\nwhen the update is being added to the transaction. But since the caller\ngets specific error codes, they can either abort the transaction or\ncontinue adding other updates to the transaction.\n\nModify 'builtin/receive-pack.c' to now capture the error type so that\nthe error propagated to the client stays the same. Also remove two of\nthe tests which validates batch-updates with invalid new_oid.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/receive-pack.c  | 22 +++++++++++++---------\n refs.c                  | 18 ++++++++++++++++++\n refs/files-backend.c    | 28 ++--------------------------\n refs/reftable-backend.c | 19 -------------------\n t/t1400-update-ref.sh   | 14 ++++++++++++++\n 5 files changed, 47 insertions(+), 54 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 878aa7f0ed..376e755e97 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\tret = NULL; /* good */\n \t\t}\n \t\tstrbuf_release(&err);\n-\t}\n-\telse {\n+\t} else {\n+\t\tenum ref_transaction_error tx_err;\n \t\tstruct strbuf err = STRBUF_INIT;\n \t\tif (shallow_update && si->shallow_ref[cmd->index] &&\n \t\t    update_shallow_ref(cmd, si)) {\n@@ -1650,14 +1650,18 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (ref_transaction_update(transaction,\n-\t\t\t\t\t   namespaced_name,\n-\t\t\t\t\t   new_oid, old_oid,\n-\t\t\t\t\t   NULL, NULL,\n-\t\t\t\t\t   0, \"push\",\n-\t\t\t\t\t   &err)) {\n+\t\ttx_err = ref_transaction_update(transaction,\n+\t\t\t\t\t\t  namespaced_name,\n+\t\t\t\t\t\t  new_oid, old_oid,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  0, \"push\",\n+\t\t\t\t\t\t  &err);\n+\t\tif (tx_err) {\n \t\t\trp_error(\"%s\", err.buf);\n-\t\t\tret = \"failed to update ref\";\n+\t\t\tif (tx_err == REF_TRANSACTION_ERROR_GENERIC)\n+\t\t\t\tret = \"failed to update ref\";\n+\t\t\telse\n+\t\t\t\tret = ref_transaction_error_msg(tx_err);\n \t\t} else {\n \t\t\tret = NULL; /* good */\n \t\t}\ndiff --git a/refs.c b/refs.c\nindex efa16b739d..662a9e6f9e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n+\tif ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&\n+\t    !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {\n+\t\tstruct object *o = parse_object(transaction->ref_store->repo, new_oid);\n+\n+\t\tif (!o) {\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    _(\"trying to write ref '%s' with nonexistent object %s\"),\n+\t\t\t\t    refname, oid_to_hex(new_oid));\n+\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n+\t\t}\n+\n+\t\tif (o->type != OBJ_COMMIT && is_branch(refname)) {\n+\t\t\tstrbuf_addf(err, _(\"trying to write non-commit object %s to branch '%s'\"),\n+\t\t\t\t    oid_to_hex(new_oid), refname);\n+\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n+\t\t}\n+\t}\n+\n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n \t\t\t\t   old_target, NULL, msg);\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 4b2faf4777..f20f580fbc 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -19,7 +19,6 @@\n #include \"../iterator.h\"\n #include \"../dir-iterator.h\"\n #include \"../lockfile.h\"\n-#include \"../object.h\"\n #include \"../path.h\"\n #include \"../dir.h\"\n #include \"../chdir-notify.h\"\n@@ -1589,7 +1588,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)\n static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,\n \t\t\t\t\t\t\tstruct ref_lock *lock,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n-\t\t\t\t\t\t\tint skip_oid_verification,\n \t\t\t\t\t\t\tstruct strbuf *err);\n static int commit_ref_update(struct files_ref_store *refs,\n \t\t\t     struct ref_lock *lock,\n@@ -1737,7 +1735,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,\n \t}\n \toidcpy(&lock->old_oid, &orig_oid);\n \n-\tif (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||\n+\tif (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||\n \t    commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) {\n \t\terror(\"unable to write current sha1 into %s: %s\", newrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -1755,7 +1753,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,\n \t\tgoto rollbacklog;\n \t}\n \n-\tif (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||\n+\tif (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||\n \t    commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) {\n \t\terror(\"unable to write current sha1 into %s: %s\", oldrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -1999,32 +1997,11 @@ static int files_log_ref_write(struct files_ref_store *refs,\n static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,\n \t\t\t\t\t\t\tstruct ref_lock *lock,\n \t\t\t\t\t\t\tconst struct object_id *oid,\n-\t\t\t\t\t\t\tint skip_oid_verification,\n \t\t\t\t\t\t\tstruct strbuf *err)\n {\n \tstatic char term = '\\n';\n-\tstruct object *o;\n \tint fd;\n \n-\tif (!skip_oid_verification) {\n-\t\to = parse_object(refs->base.repo, oid);\n-\t\tif (!o) {\n-\t\t\tstrbuf_addf(\n-\t\t\t\terr,\n-\t\t\t\t\"trying to write ref '%s' with nonexistent object %s\",\n-\t\t\t\tlock->ref_name, oid_to_hex(oid));\n-\t\t\tunlock_ref(lock);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t\tif (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {\n-\t\t\tstrbuf_addf(\n-\t\t\t\terr,\n-\t\t\t\t\"trying to write non-commit object %s to branch '%s'\",\n-\t\t\t\toid_to_hex(oid), lock->ref_name);\n-\t\t\tunlock_ref(lock);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t}\n \tfd = get_lock_file_fd(&lock->lk);\n \tif (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 ||\n \t    write_in_full(fd, &term, 1) < 0 ||\n@@ -2828,7 +2805,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re\n \t\t} else {\n \t\t\tret = write_ref_to_lockfile(\n \t\t\t\trefs, lock, &update->new_oid,\n-\t\t\t\tupdate->flags & REF_SKIP_OID_VERIFICATION,\n \t\t\t\terr);\n \t\t\tif (ret) {\n \t\t\t\tchar *write_err = strbuf_detach(err, NULL);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 93374d25c2..444b0c24e5 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1081,25 +1081,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\treturn 0;\n \t}\n \n-\t/* Verify that the new object ID is valid. */\n-\tif ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&\n-\t    !(u->flags & REF_SKIP_OID_VERIFICATION) &&\n-\t    !(u->flags & REF_LOG_ONLY)) {\n-\t\tstruct object *o = parse_object(refs->base.repo, &u->new_oid);\n-\t\tif (!o) {\n-\t\t\tstrbuf_addf(err,\n-\t\t\t\t    _(\"trying to write ref '%s' with nonexistent object %s\"),\n-\t\t\t\t    u->refname, oid_to_hex(&u->new_oid));\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\n-\t\tif (o->type != OBJ_COMMIT && is_branch(u->refname)) {\n-\t\t\tstrbuf_addf(err, _(\"trying to write non-commit object %s to branch '%s'\"),\n-\t\t\t\t    oid_to_hex(&u->new_oid), u->refname);\n-\t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n-\t\t}\n-\t}\n-\n \t/*\n \t * When we update the reference that HEAD points to we enqueue\n \t * a second log-only update for HEAD so that its reflog is\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex b2858a9061..1015f335e3 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1196,6 +1196,20 @@ test_expect_success 'stdin -z create ref fails with empty new value' '\n \ttest_must_fail git rev-parse --verify -q $c\n '\n \n+test_expect_success 'stdin -z create ref fails with non commit object' '\n+\tprintf $F \"create $c\" \"$(test_oid 001)\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: trying to write ref ${SQ}$c${SQ} with nonexistent object\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update ref fails with non commit object' '\n+\tprintf $F \"update $b\" \"$(test_oid 001)\" \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: trying to write ref ${SQ}$b${SQ} with nonexistent object\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n test_expect_success 'stdin -z update ref works with right old value' '\n \tprintf $F \"update $b\" \"$m~1\" \"$m\" >stdin &&\n \tgit update-ref -z --stdin <stdin &&\n\n-- \n2.53.GIT\n\n"},{"id":"542704","messageId":"20260504-refs-move-to-generic-layer-v4-8-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 8/9] refs: add peeled object ID to the `ref_update` struct","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:12Z","receivedAt":"2026-05-04T17:44:22Z","isPatch":true,"body":"Certain reference backends {packed, reftable}, have the ability to also\nstore the peeled object ID for a reference pointing to a tag object.\nThis has the added benefit that during retrieval of such references, we\nalso obtain the peeled object ID without having to use the ODB.\n\nTo provide this functionality, each backend independently calls the ODB\nto obtain the peeled OID. To move this functionality to the generic\nlayer, there must be support infrastructure to pass in a peeled OID for\nreference updates.\n\nAdd a `peeled` field to the `ref_update` structure and modify\n`ref_transaction_add_update()` to receive and copy this object ID to the\n`ref_update` structure. Finally, modify `ref_transaction_update()` to\npeel tag objects and pass the peeled OID to\n`ref_transaction_add_update()`.\n\nUpdate all callers of these functions with the new function parameters.\nCallers which only add reflog updates, need to only pass in NULL, since\nfor reflogs, we don't store peeled OIDs. Reference deletions also only\nneed to pass in NULL. For others, pass along the peeled OID if\navailable.\n\nIn a following commit, we'll modify the backends to use this peeled OID\ninstead of parsing it themselves.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 15 +++++++++++++--\n refs/files-backend.c    | 20 ++++++++++++--------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  6 +++---\n 4 files changed, 42 insertions(+), 13 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 662a9e6f9e..0648df2b6c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1307,6 +1307,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst struct object_id *peeled,\n \t\tconst char *new_target, const char *old_target,\n \t\tconst char *committer_info,\n \t\tconst char *msg)\n@@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update(\n \t\tupdate->committer_info = xstrdup_or_null(committer_info);\n \t\tupdate->msg = normalize_reflog_message(msg);\n \t}\n+\tif (flags & REF_HAVE_PEELED)\n+\t\toidcpy(&update->peeled, peeled);\n \n \t/*\n \t * This list is generally used by the backends to avoid duplicates.\n@@ -1392,6 +1395,8 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \t\t\t\t\t\t  unsigned int flags, const char *msg,\n \t\t\t\t\t\t  struct strbuf *err)\n {\n+\tstruct object_id peeled;\n+\n \tassert(err);\n \n \tif ((flags & REF_FORCE_CREATE_REFLOG) &&\n@@ -1432,10 +1437,16 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa\n \t\t\t\t    oid_to_hex(new_oid), refname);\n \t\t\treturn REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;\n \t\t}\n+\n+\t\tif (o->type == OBJ_TAG) {\n+\t\t\tif (!peel_object(transaction->ref_store->repo, new_oid, &peeled,\n+\t\t\t\t\t PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))\n+\t\t\t\tflags |= REF_HAVE_PEELED;\n+\t\t}\n \t}\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   new_oid, old_oid, &peeled, new_target,\n \t\t\t\t   old_target, NULL, msg);\n \n \treturn 0;\n@@ -1462,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction,\n \t\treturn -1;\n \n \tupdate = ref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t\t    new_oid, old_oid, NULL, NULL,\n+\t\t\t\t\t    new_oid, old_oid, NULL, NULL, NULL,\n \t\t\t\t\t    committer_info, msg);\n \tupdate->index = index;\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex f20f580fbc..d0896d0e37 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1325,7 +1325,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);\n+\t\t\tnull_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,\n+\t\t\tNULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -2468,7 +2469,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,\n-\t\t\t&update->new_oid, &update->old_oid,\n+\t\t\t&update->new_oid, &update->old_oid, &update->peeled,\n \t\t\tNULL, NULL, update->committer_info, update->msg);\n \tnew_update->parent_update = update;\n \n@@ -2530,8 +2531,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,\n \t\t\ttransaction, referent, new_flags,\n \t\t\tupdate->new_target ? NULL : &update->new_oid,\n \t\t\tupdate->old_target ? NULL : &update->old_oid,\n-\t\t\tupdate->new_target, update->old_target, NULL,\n-\t\t\tupdate->msg);\n+\t\t\t&update->peeled, update->new_target, update->old_target,\n+\t\t\tNULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2994,7 +2995,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\tref_transaction_add_update(\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n-\t\t\t\t\t&update->new_oid, NULL,\n+\t\t\t\t\t&update->new_oid, NULL, NULL,\n \t\t\t\t\tNULL, NULL, NULL, NULL);\n \t\t}\n \t}\n@@ -3200,19 +3201,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,\n \t\t\tif (update->flags & REF_LOG_ONLY)\n \t\t\t\tref_transaction_add_update(loose_transaction, update->refname,\n \t\t\t\t\t\t\t   update->flags, &update->new_oid,\n-\t\t\t\t\t\t\t   &update->old_oid, NULL, NULL,\n+\t\t\t\t\t\t\t   &update->old_oid, &update->peeled,\n+\t\t\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t\t\t   update->committer_info, update->msg);\n \t\t\telse\n \t\t\t\tref_transaction_add_update(loose_transaction, update->refname,\n \t\t\t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t\t\t   update->new_target ? NULL : &update->new_oid, NULL,\n-\t\t\t\t\t\t\t   update->new_target, NULL, update->committer_info,\n+\t\t\t\t\t\t\t   &update->peeled, update->new_target,\n+\t\t\t\t\t\t\t   NULL, update->committer_info,\n \t\t\t\t\t\t\t   NULL);\n \t\t} else {\n \t\t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t\t   NULL, NULL, update->committer_info, NULL);\n+\t\t\t\t\t\t   &update->peeled, NULL, NULL,\n+\t\t\t\t\t\t   update->committer_info, NULL);\n \t\t}\n \t}\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex d103387ebf..307dcb277b 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -39,6 +39,13 @@ struct ref_transaction;\n  */\n #define REF_LOG_ONLY (1 << 7)\n \n+/*\n+ * The reference contains a peeled object ID. This is used when the\n+ * new_oid is pointing to a tag object and the reference backend\n+ * wants to also store the peeled value for optimized retrieval.\n+ */\n+#define REF_HAVE_PEELED (1 << 15)\n+\n /*\n  * Return the length of time to retry acquiring a loose reference lock\n  * before giving up, in milliseconds:\n@@ -92,6 +99,12 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If the new_oid points to a tag object, set this to the peeled\n+\t * object ID for optimized retrieval without needed to hit the odb.\n+\t */\n+\tstruct object_id peeled;\n+\n \t/*\n \t * If set, point the reference to this value. This can also be\n \t * used to convert regular references to become symbolic refs.\n@@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst struct object_id *peeled,\n \t\tconst char *new_target, const char *old_target,\n \t\tconst char *committer_info,\n \t\tconst char *msg);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 444b0c24e5..b0c010387d 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1107,8 +1107,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\tref_transaction_add_update(\n \t\t\ttransaction, \"HEAD\",\n \t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, NULL,\n-\t\t\tu->msg);\n+\t\t\t&u->new_oid, &u->old_oid, &u->peeled, NULL, NULL,\n+\t\t\tNULL, u->msg);\n \t}\n \n \tret = reftable_backend_read_ref(be, rewritten_ref,\n@@ -1194,7 +1194,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor\n \t\t\t\ttransaction, referent->buf, new_flags,\n \t\t\t\tu->new_target ? NULL : &u->new_oid,\n \t\t\t\tu->old_target ? NULL : &u->old_oid,\n-\t\t\t\tu->new_target, u->old_target,\n+\t\t\t\t&u->peeled, u->new_target, u->old_target,\n \t\t\t\tu->committer_info, u->msg);\n \n \t\t\tnew_update->parent_update = u;\n\n-- \n2.53.GIT\n\n"},{"id":"542705","messageId":"20260504-refs-move-to-generic-layer-v4-9-936ac2f0b1a3@gmail.com","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-0-936ac2f0b1a3@gmail.com","subject":"[PATCH v4 9/9] refs: use peeled tag values in reference backends","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-04T17:44:13Z","receivedAt":"2026-05-04T17:44:23Z","isPatch":true,"body":"The reference backends peel tag objects when storing references to them.\nThis is to provide optimized reads which avoids hitting the odb. The\nprevious commits ensures that the peeled value is now propagated via the\ngeneric layer. So modify the packed and reftable backend to directly use\nthis value instead of calling `peel_object()` independently.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/packed-backend.c   | 6 ++----\n refs/reftable-backend.c | 9 ++-------\n 2 files changed, 4 insertions(+), 11 deletions(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 35a0f32e1c..0acde48c45 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\t */\n \t\t\ti++;\n \t\t} else {\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n-\t\t\t\t\t\t     &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);\n+\t\t\tbool peeled = update->flags & REF_HAVE_PEELED;\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\n-\t\t\t\t\t       peel_error ? NULL : &peeled))\n+\t\t\t\t\t       peeled ? &update->peeled : NULL))\n \t\t\t\tgoto write_error;\n \n \t\t\ti++;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex b0c010387d..8b4ac2e618 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -12,7 +12,6 @@\n #include \"../hex.h\"\n #include \"../ident.h\"\n #include \"../iterator.h\"\n-#include \"../object.h\"\n #include \"../parse.h\"\n #include \"../path.h\"\n #include \"../refs.h\"\n@@ -1584,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t\tgoto done;\n \t\t} else if (u->flags & REF_HAVE_NEW) {\n \t\t\tstruct reftable_ref_record ref = {0};\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error;\n \n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,\n-\t\t\t\t\t\t PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);\n-\t\t\tif (!peel_error) {\n+\t\t\tif (u->flags & REF_HAVE_PEELED) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n-\t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tmemcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ);\n \t\t\t\tmemcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ);\n \t\t\t} else if (!is_null_oid(&u->new_oid)) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL1;\n\n-- \n2.53.GIT\n\n"},{"id":"542739","messageId":"afmFmGo_Sg33Rv6V@pks.im","threadId":"65517","inReplyTo":"20260504-refs-move-to-generic-layer-v4-6-936ac2f0b1a3@gmail.com","subject":"Re: [PATCH v4 6/9] update-ref: handle rejections while adding updates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-05T05:52:24Z","receivedAt":"2026-05-05T05:52:31Z","isPatch":true,"body":"On Mon, May 04, 2026 at 07:44:10PM +0200, Karthik Nayak wrote:\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index 5259cc7226..6355c3dd3e 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -257,6 +266,31 @@ static void print_rejected_refs(const char *refname,\n>  \tstrbuf_release(&sb);\n>  }\n>  \n> +/*\n> + * Handle transaction errors. If we're using batches updates, we want to only\n> + * die for generic errors and print the remaining to the user.\n> + */\n> +static void handle_ref_transaction_error(const char *refname,\n> +\t\t\t\t\t struct object_id *new_oid,\n> +\t\t\t\t\t struct object_id *old_oid,\n> +\t\t\t\t\t const char *new_target,\n> +\t\t\t\t\t const char *old_target,\n> +\t\t\t\t\t enum ref_transaction_error tx_err,\n> +\t\t\t\t\t struct strbuf *err,\n> +\t\t\t\t\t struct command_options *opts)\n> +{\n> +\tif (!tx_err)\n> +\t\treturn;\n> +\n> +\tif (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) {\n> +\t\tprint_rejected_refs(refname, old_oid, new_oid, old_target,\n> +\t\t\t\t    new_target, tx_err, err->buf, NULL);\n> +\t\treturn;\n> +\t}\n> +\n> +\tdie(\"%s\", err->buf);\n> +}\n\nIt's a bit weird that we pass in the error message as a strbuf given\nthat we really only care about the actual message.\n\n> @@ -644,6 +699,10 @@ static void update_refs_stdin(unsigned int flags)\n>  \tstruct ref_transaction *transaction;\n>  \tint i, j;\n>  \n> +\tstruct command_options opts = {\n> +\t\t.allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,\n> +\t};\n> +\n\nNit: stray empty line between the variable declarations.\n\nOther than that this patch looks good to me, thanks!\n\nPatrick\n"},{"id":"542752","messageId":"CAOLa=ZRj11QW16-E6dY2YxDWZ+3moV1h_-S1DfbFPJeOGTjHgg@mail.gmail.com","threadId":"65517","inReplyTo":"afmFmGo_Sg33Rv6V@pks.im","subject":"Re: [PATCH v4 6/9] update-ref: handle rejections while adding updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-05-05T08:23:39Z","receivedAt":"2026-05-05T08:23:41Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, May 04, 2026 at 07:44:10PM +0200, Karthik Nayak wrote:\n>> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n>> index 5259cc7226..6355c3dd3e 100644\n>> --- a/builtin/update-ref.c\n>> +++ b/builtin/update-ref.c\n>> @@ -257,6 +266,31 @@ static void print_rejected_refs(const char *refname,\n>>  \tstrbuf_release(&sb);\n>>  }\n>>\n>> +/*\n>> + * Handle transaction errors. If we're using batches updates, we want to only\n>> + * die for generic errors and print the remaining to the user.\n>> + */\n>> +static void handle_ref_transaction_error(const char *refname,\n>> +\t\t\t\t\t struct object_id *new_oid,\n>> +\t\t\t\t\t struct object_id *old_oid,\n>> +\t\t\t\t\t const char *new_target,\n>> +\t\t\t\t\t const char *old_target,\n>> +\t\t\t\t\t enum ref_transaction_error tx_err,\n>> +\t\t\t\t\t struct strbuf *err,\n>> +\t\t\t\t\t struct command_options *opts)\n>> +{\n>> +\tif (!tx_err)\n>> +\t\treturn;\n>> +\n>> +\tif (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) {\n>> +\t\tprint_rejected_refs(refname, old_oid, new_oid, old_target,\n>> +\t\t\t\t    new_target, tx_err, err->buf, NULL);\n>> +\t\treturn;\n>> +\t}\n>> +\n>> +\tdie(\"%s\", err->buf);\n>> +}\n>\n> It's a bit weird that we pass in the error message as a strbuf given\n> that we really only care about the actual message.\n>\n\nThat's fair. I will add this locally but I think it is also not worth a\nre-roll.\n\n>> @@ -644,6 +699,10 @@ static void update_refs_stdin(unsigned int flags)\n>>  \tstruct ref_transaction *transaction;\n>>  \tint i, j;\n>>\n>> +\tstruct command_options opts = {\n>> +\t\t.allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,\n>> +\t};\n>> +\n>\n> Nit: stray empty line between the variable declarations.\n>\n\nSame here.\n\n> Other than that this patch looks good to me, thanks!\n>\n> Patrick\n>\n\nI'll hold off on a re-roll unless needed.\n\nThanks for the review again!\n"},{"id":"542814","messageId":"87o6isqq4q.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","threadId":"65517","inReplyTo":"CAOLa=ZRj11QW16-E6dY2YxDWZ+3moV1h_-S1DfbFPJeOGTjHgg@mail.gmail.com","subject":"Re: [PATCH v4 6/9] update-ref: handle rejections while adding updates","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-05-06T19:44:53Z","receivedAt":"2026-05-06T19:45:06Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> I'll hold off on a re-roll unless needed.\n\nWell, from my side there aren't any extra comments. I've reviewed the\nrange-diff and this patch, and glanced through the other patches (but\nthey didn't change compared to previous version) and all looks good to\nme.\n\n-- \nCheers,\nToon\n"}]}