* [PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn'
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
@ 2026-04-20 10:11 ` Karthik Nayak
2026-04-22 11:15 ` Patrick Steinhardt
2026-04-20 10:12 ` [PATCH 2/8] refs: extract out reflog config to generic layer Karthik Nayak
` (9 subsequent siblings)
10 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-20 10:11 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The typedef 'ref_transaction_commit_fn' is not used anywhere in our
code, let's remove it.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/refs-internal.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index d79e35fd26..2d963cc4f4 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,
struct ref_transaction *transaction,
struct strbuf *err);
-typedef int ref_transaction_commit_fn(struct ref_store *refs,
- struct ref_transaction *transaction,
- struct strbuf *err);
-
typedef int optimize_fn(struct ref_store *ref_store,
struct refs_optimize_opts *opts);
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn'
2026-04-20 10:11 ` [PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
@ 2026-04-22 11:15 ` Patrick Steinhardt
2026-04-22 12:20 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Patrick Steinhardt @ 2026-04-22 11:15 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Apr 20, 2026 at 12:11:59PM +0200, Karthik Nayak wrote:
> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
> index d79e35fd26..2d963cc4f4 100644
> --- a/refs/refs-internal.h
> +++ b/refs/refs-internal.h
> @@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,
> struct ref_transaction *transaction,
> struct strbuf *err);
>
> -typedef int ref_transaction_commit_fn(struct ref_store *refs,
> - struct ref_transaction *transaction,
> - struct strbuf *err);
> -
> typedef int optimize_fn(struct ref_store *ref_store,
> struct refs_optimize_opts *opts);
>
I'm in general not much of a fan of these typedefs -- there's not really
much of a point why we'd need them in the first place. We don't use them
as a type anywhere but in the struct definition for the ref backend. So
we could just as well move them in there, which would also ensure that
they cannot become stale in the first place.
Patrick
^ permalink raw reply [flat|nested] 68+ messages in thread
* Re: [PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn'
2026-04-22 11:15 ` Patrick Steinhardt
@ 2026-04-22 12:20 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-22 12:20 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
[-- Attachment #1: Type: text/plain, Size: 1392 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Mon, Apr 20, 2026 at 12:11:59PM +0200, Karthik Nayak wrote:
>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
>> index d79e35fd26..2d963cc4f4 100644
>> --- a/refs/refs-internal.h
>> +++ b/refs/refs-internal.h
>> @@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,
>> struct ref_transaction *transaction,
>> struct strbuf *err);
>>
>> -typedef int ref_transaction_commit_fn(struct ref_store *refs,
>> - struct ref_transaction *transaction,
>> - struct strbuf *err);
>> -
>> typedef int optimize_fn(struct ref_store *ref_store,
>> struct refs_optimize_opts *opts);
>>
>
> I'm in general not much of a fan of these typedefs -- there's not really
> much of a point why we'd need them in the first place. We don't use them
> as a type anywhere but in the struct definition for the ref backend. So
> we could just as well move them in there, which would also ensure that
> they cannot become stale in the first place.
>
> Patrick
I do like them, when reading `struct ref_storage_be {}` with these
typedefs, the interface is simplified and I only need to know what are
the different functions which need to be implemented without being
exposed to the details of each of the functions. The details are
available in the typedefs.
I'll leave it out of this series though.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH 2/8] refs: extract out reflog config to generic layer
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
2026-04-20 10:11 ` [PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
@ 2026-04-20 10:12 ` Karthik Nayak
2026-04-22 11:15 ` Patrick Steinhardt
2026-04-20 10:12 ` [PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
` (8 subsequent siblings)
10 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-20 10:12 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The reference backends need to know when to create reflog entries, this
is dictated by the 'core.logallrefupdates' config. Instead of relying on
the backends to call `repo_settings_get_log_all_ref_updates()` to obtain
this config value, let's do this in the generic layer and pass down the
value to the backends.
Instead of passing this in as a new argument, let's create a new
`ref_init_options` structure which will house information required to
initialize a reference backend. Move the access flags here as well.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 7 ++++++-
refs/files-backend.c | 10 ++++++----
refs/packed-backend.c | 4 ++--
refs/packed-backend.h | 3 ++-
refs/refs-internal.h | 17 ++++++++++++++++-
refs/reftable-backend.c | 6 +++---
6 files changed, 35 insertions(+), 12 deletions(-)
diff --git a/refs.c b/refs.c
index bfcb9c7ac3..aa66c6b28e 100644
--- a/refs.c
+++ b/refs.c
@@ -2295,6 +2295,10 @@ static struct ref_store *ref_store_init(struct repository *repo,
{
const struct ref_storage_be *be;
struct ref_store *refs;
+ struct ref_store_init_options options = {
+ .access_flags = flags,
+ .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),
+ };
be = find_ref_storage_backend(format);
if (!be)
@@ -2304,7 +2308,8 @@ static struct ref_store *ref_store_init(struct repository *repo,
* TODO Send in a 'struct worktree' instead of a 'gitdir', and
* allow the backend to handle how it wants to deal with worktrees.
*/
- refs = be->init(repo, repo->ref_storage_payload, gitdir, flags);
+ refs = be->init(repo, repo->ref_storage_payload, gitdir, &options);
+
return refs;
}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index b3b0c25f84..407b97cc44 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -108,7 +108,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
static struct ref_store *files_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int flags)
+ const struct ref_store_init_options *options)
{
struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
@@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
&ref_common_dir);
base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);
- refs->store_flags = flags;
+
refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
refs->packed_ref_store =
- packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ packed_ref_store_init(repo, payload, refs->gitcommondir, options);
+ refs->store_flags = options->access_flags;
+ refs->log_all_ref_updates = options->log_all_ref_updates;
+
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 23ed62984b..195600cdad 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload UNUSED,
const char *gitdir,
- unsigned int store_flags)
+ const struct ref_store_init_options *options)
{
struct packed_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
struct strbuf sb = STRBUF_INIT;
base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);
- refs->store_flags = store_flags;
+ refs->store_flags = options->access_flags;
strbuf_addf(&sb, "%s/packed-refs", gitdir);
refs->path = strbuf_detach(&sb, NULL);
diff --git a/refs/packed-backend.h b/refs/packed-backend.h
index 2c2377a356..1db48e801d 100644
--- a/refs/packed-backend.h
+++ b/refs/packed-backend.h
@@ -3,6 +3,7 @@
struct repository;
struct ref_transaction;
+struct ref_store_init_options;
/*
* Support for storing references in a `packed-refs` file.
@@ -16,7 +17,7 @@ struct ref_transaction;
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int store_flags);
+ const struct ref_store_init_options *options);
/*
* Lock the packed-refs file for writing. Flags is passed to
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2d963cc4f4..eed13af4eb 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -385,6 +385,21 @@ struct ref_store;
REF_STORE_ODB | \
REF_STORE_MAIN)
+/*
+ * Options for initializing the ref backend. All backend-agnostic information
+ * which backends required will be held here.
+ */
+struct ref_store_init_options {
+ /* The kind of operations that the ref_store is allowed to perform. */
+ unsigned int access_flags;
+
+ /*
+ * Denotes under what conditions reflogs should be created when updating
+ * references.
+ */
+ enum log_refs_config log_all_ref_updates;
+};
+
/*
* Initialize the ref_store for the specified gitdir. These functions
* should call base_ref_store_init() to initialize the shared part of
@@ -393,7 +408,7 @@ struct ref_store;
typedef struct ref_store *ref_store_init_fn(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int flags);
+ const struct ref_store_init_options *options);
/*
* Release all memory and resources associated with the ref store.
*/
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index daea30a5b4..bdc3e0aa19 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -369,7 +369,7 @@ static int reftable_be_config(const char *var, const char *value,
static struct ref_store *reftable_be_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int store_flags)
+ const struct ref_store_init_options *options)
{
struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
struct strbuf ref_common_dir = STRBUF_INIT;
@@ -386,8 +386,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
- refs->store_flags = store_flags;
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->store_flags = options->access_flags;
+ refs->log_all_ref_updates = options->log_all_ref_updates;
switch (repo->hash_algo->format_id) {
case GIT_SHA1_FORMAT_ID:
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH 2/8] refs: extract out reflog config to generic layer
2026-04-20 10:12 ` [PATCH 2/8] refs: extract out reflog config to generic layer Karthik Nayak
@ 2026-04-22 11:15 ` Patrick Steinhardt
2026-04-22 13:13 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Patrick Steinhardt @ 2026-04-22 11:15 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Apr 20, 2026 at 12:12:00PM +0200, Karthik Nayak wrote:
> The reference backends need to know when to create reflog entries, this
s/this/which/
> is dictated by the 'core.logallrefupdates' config. Instead of relying on
s/dictated/controlled/
> the backends to call `repo_settings_get_log_all_ref_updates()` to obtain
> this config value, let's do this in the generic layer and pass down the
> value to the backends.
>
> Instead of passing this in as a new argument, let's create a new
> `ref_init_options` structure which will house information required to
> initialize a reference backend. Move the access flags here as well.
I agree with this direction. It's also something that I'm doing for many
callbacks in the ODB layer, and I'm moving more and more into that
direction.
> diff --git a/refs.c b/refs.c
> index bfcb9c7ac3..aa66c6b28e 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2295,6 +2295,10 @@ static struct ref_store *ref_store_init(struct repository *repo,
> {
> const struct ref_storage_be *be;
> struct ref_store *refs;
> + struct ref_store_init_options options = {
> + .access_flags = flags,
> + .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),
> + };
>
> be = find_ref_storage_backend(format);
> if (!be)
Tiniest nit, please feel free to ignore: we often call the structure
itself `_options`, but the variables just `opts`. May just be my own
preference though.
> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
> index 2d963cc4f4..eed13af4eb 100644
> --- a/refs/refs-internal.h
> +++ b/refs/refs-internal.h
> @@ -385,6 +385,21 @@ struct ref_store;
> REF_STORE_ODB | \
> REF_STORE_MAIN)
>
> +/*
> + * Options for initializing the ref backend. All backend-agnostic information
> + * which backends required will be held here.
> + */
> +struct ref_store_init_options {
> + /* The kind of operations that the ref_store is allowed to perform. */
> + unsigned int access_flags;
> +
> + /*
> + * Denotes under what conditions reflogs should be created when updating
> + * references.
> + */
> + enum log_refs_config log_all_ref_updates;
> +};
Nit: it might've made sense to split this up into two steps: the
introduction of the struct, and then moving the config in there.
Patrick
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH 2/8] refs: extract out reflog config to generic layer
2026-04-22 11:15 ` Patrick Steinhardt
@ 2026-04-22 13:13 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-22 13:13 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
[-- Attachment #1: Type: text/plain, Size: 2668 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Mon, Apr 20, 2026 at 12:12:00PM +0200, Karthik Nayak wrote:
>> The reference backends need to know when to create reflog entries, this
>
> s/this/which/
>
Better.
>> is dictated by the 'core.logallrefupdates' config. Instead of relying on
>
> s/dictated/controlled/
>
Both of those mean the same. I guess there is a negative connotation to
using the word 'dictated', but that's present with 'controlled' too.
Perhaps 'determined'?
>> the backends to call `repo_settings_get_log_all_ref_updates()` to obtain
>> this config value, let's do this in the generic layer and pass down the
>> value to the backends.
>>
>> Instead of passing this in as a new argument, let's create a new
>> `ref_init_options` structure which will house information required to
>> initialize a reference backend. Move the access flags here as well.
>
> I agree with this direction. It's also something that I'm doing for many
> callbacks in the ODB layer, and I'm moving more and more into that
> direction.
>
>> diff --git a/refs.c b/refs.c
>> index bfcb9c7ac3..aa66c6b28e 100644
>> --- a/refs.c
>> +++ b/refs.c
>> @@ -2295,6 +2295,10 @@ static struct ref_store *ref_store_init(struct repository *repo,
>> {
>> const struct ref_storage_be *be;
>> struct ref_store *refs;
>> + struct ref_store_init_options options = {
>> + .access_flags = flags,
>> + .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),
>> + };
>>
>> be = find_ref_storage_backend(format);
>> if (!be)
>
> Tiniest nit, please feel free to ignore: we often call the structure
> itself `_options`, but the variables just `opts`. May just be my own
> preference though.
>
Let's make it consistent, I'll also start using `opts`.
>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
>> index 2d963cc4f4..eed13af4eb 100644
>> --- a/refs/refs-internal.h
>> +++ b/refs/refs-internal.h
>> @@ -385,6 +385,21 @@ struct ref_store;
>> REF_STORE_ODB | \
>> REF_STORE_MAIN)
>>
>> +/*
>> + * Options for initializing the ref backend. All backend-agnostic information
>> + * which backends required will be held here.
>> + */
>> +struct ref_store_init_options {
>> + /* The kind of operations that the ref_store is allowed to perform. */
>> + unsigned int access_flags;
>> +
>> + /*
>> + * Denotes under what conditions reflogs should be created when updating
>> + * references.
>> + */
>> + enum log_refs_config log_all_ref_updates;
>> +};
>
> Nit: it might've made sense to split this up into two steps: the
> introduction of the struct, and then moving the config in there.
>
> Patrick
Yeah that might be better. I'll go ahead and do that.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()`
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
2026-04-20 10:11 ` [PATCH 1/8] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
2026-04-20 10:12 ` [PATCH 2/8] refs: extract out reflog config to generic layer Karthik Nayak
@ 2026-04-20 10:12 ` Karthik Nayak
2026-04-22 11:15 ` Patrick Steinhardt
2026-04-20 10:12 ` [PATCH 4/8] update-ref: move `print_rejected_refs()` up Karthik Nayak
` (7 subsequent siblings)
10 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-20 10:12 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The `ref_transaction_update()` function is used to add updates to a
given reference transactions. In the following commit, we'll add more
validation to this function. As such, it would be more beneficial if the
function returns specific error types, so callers can differentiate
between different errors.
To facilitate this, return `enum ref_transaction_error` from the
function and covert the existing '-1' returns to
'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing
behavior, no changes are made to any of the callers but this sets the
necessary infrastructure for introduction of other errors.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 20 ++++++++++----------
refs.h | 16 ++++++++--------
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/refs.c b/refs.c
index aa66c6b28e..39fef1cca0 100644
--- a/refs.c
+++ b/refs.c
@@ -1383,25 +1383,25 @@ static int transaction_refname_valid(const char *refname,
return 1;
}
-int ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- const char *new_target,
- const char *old_target,
- unsigned int flags, const char *msg,
- struct strbuf *err)
+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ unsigned int flags, const char *msg,
+ struct strbuf *err)
{
assert(err);
if ((flags & REF_FORCE_CREATE_REFLOG) &&
(flags & REF_SKIP_CREATE_REFLOG)) {
strbuf_addstr(err, _("refusing to force and skip creation of reflog"));
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
if (!transaction_refname_valid(refname, new_oid, flags, err))
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
diff --git a/refs.h b/refs.h
index d65de6ab5f..71d5c186d0 100644
--- a/refs.h
+++ b/refs.h
@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
* See the above comment "Reference transaction updates" for more
* information.
*/
-int ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- const char *new_target,
- const char *old_target,
- unsigned int flags, const char *msg,
- struct strbuf *err);
+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ unsigned int flags, const char *msg,
+ struct strbuf *err);
/*
* Similar to `ref_transaction_update`, but this function is only for adding
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()`
2026-04-20 10:12 ` [PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
@ 2026-04-22 11:15 ` Patrick Steinhardt
2026-04-22 13:14 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Patrick Steinhardt @ 2026-04-22 11:15 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Apr 20, 2026 at 12:12:01PM +0200, Karthik Nayak wrote:
> The `ref_transaction_update()` function is used to add updates to a
> given reference transactions. In the following commit, we'll add more
> validation to this function. As such, it would be more beneficial if the
s/more beneficial/beneficial/
> function returns specific error types, so callers can differentiate
> between different errors.
>
> To facilitate this, return `enum ref_transaction_error` from the
> function and covert the existing '-1' returns to
> 'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing
> behavior, no changes are made to any of the callers but this sets the
> necessary infrastructure for introduction of other errors.
Yup, makes sense. This doesn't buy us anything yet, but will eventually.
Patrick
^ permalink raw reply [flat|nested] 68+ messages in thread
* Re: [PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()`
2026-04-22 11:15 ` Patrick Steinhardt
@ 2026-04-22 13:14 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-22 13:14 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
[-- Attachment #1: Type: text/plain, Size: 897 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Mon, Apr 20, 2026 at 12:12:01PM +0200, Karthik Nayak wrote:
>> The `ref_transaction_update()` function is used to add updates to a
>> given reference transactions. In the following commit, we'll add more
>> validation to this function. As such, it would be more beneficial if the
>
> s/more beneficial/beneficial/
>
Will do.
>> function returns specific error types, so callers can differentiate
>> between different errors.
>>
>> To facilitate this, return `enum ref_transaction_error` from the
>> function and covert the existing '-1' returns to
>> 'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing
>> behavior, no changes are made to any of the callers but this sets the
>> necessary infrastructure for introduction of other errors.
>
> Yup, makes sense. This doesn't buy us anything yet, but will eventually.
>
> Patrick
Exactly!
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH 4/8] update-ref: move `print_rejected_refs()` up
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
` (2 preceding siblings ...)
2026-04-20 10:12 ` [PATCH 3/8] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
@ 2026-04-20 10:12 ` Karthik Nayak
2026-04-22 11:15 ` Patrick Steinhardt
2026-04-20 10:12 ` [PATCH 5/8] update-ref: handle rejections while adding updates Karthik Nayak
` (6 subsequent siblings)
10 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-20 10:12 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The `print_rejected_refs()` is used to print any rejected refs when
using git-updated-ref(1) with the '--batch-updates' option. In the
following commit, we'll need to use this function in other places, so
move the function up to avoid a separate forward declaration.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 45 ++++++++++++++++++++++-----------------------
1 file changed, 22 insertions(+), 23 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 2d68c40ecb..5259cc7226 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -234,6 +234,28 @@ static int parse_next_oid(const char **next, const char *end,
command, refname);
}
+static void print_rejected_refs(const char *refname,
+ const struct object_id *old_oid,
+ const struct object_id *new_oid,
+ const char *old_target,
+ const char *new_target,
+ enum ref_transaction_error err,
+ const char *details,
+ void *cb_data UNUSED)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ if (details && *details)
+ error("%s", details);
+
+ strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
+ new_oid ? oid_to_hex(new_oid) : new_target,
+ old_oid ? oid_to_hex(old_oid) : old_target,
+ ref_transaction_error_msg(err));
+
+ fwrite(sb.buf, sb.len, 1, stdout);
+ strbuf_release(&sb);
+}
/*
* The following five parse_cmd_*() functions parse the corresponding
@@ -567,29 +589,6 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
report_ok("abort");
}
-static void print_rejected_refs(const char *refname,
- const struct object_id *old_oid,
- const struct object_id *new_oid,
- const char *old_target,
- const char *new_target,
- enum ref_transaction_error err,
- const char *details,
- void *cb_data UNUSED)
-{
- struct strbuf sb = STRBUF_INIT;
-
- if (details && *details)
- error("%s", details);
-
- strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
- new_oid ? oid_to_hex(new_oid) : new_target,
- old_oid ? oid_to_hex(old_oid) : old_target,
- ref_transaction_error_msg(err));
-
- fwrite(sb.buf, sb.len, 1, stdout);
- strbuf_release(&sb);
-}
-
static void parse_cmd_commit(struct ref_transaction *transaction,
const char *next, const char *end UNUSED)
{
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH 5/8] update-ref: handle rejections while adding updates
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
` (3 preceding siblings ...)
2026-04-20 10:12 ` [PATCH 4/8] update-ref: move `print_rejected_refs()` up Karthik Nayak
@ 2026-04-20 10:12 ` Karthik Nayak
2026-04-22 11:15 ` Patrick Steinhardt
2026-04-20 10:12 ` [PATCH 6/8] refs: move object parsing to the generic layer Karthik Nayak
` (5 subsequent siblings)
10 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-20 10:12 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
When using git-update-ref(1) with the '--batch-updates' flag, updates
rejected by the reference backend are displayed to the user while other
updates are applied. This only applies during the committing of the
transaction.
In the following commits, we'll also extend `ref_transaction_update()`
to reject updates before even a transaction is prepared/committed. In
preparation, modify the code in update-ref to also handle non-generic
rejections from `ref_transaction_update()`. This involves propagating
information to each of the commands on whether updates are allowed to be
rejected, and also checking for rejections and only dying for generic
failures.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 98 ++++++++++++++++++++++++++++++++++++----------------
1 file changed, 69 insertions(+), 29 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 5259cc7226..d1980c60c4 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -25,6 +25,15 @@ static unsigned int default_flags;
static unsigned create_reflog_flag;
static const char *msg;
+struct command_options {
+ /*
+ * Individual updates are allowed to fail without causing
+ * update-ref to exit. This is set when using the
+ * '--batch-updates' flag.
+ */
+ bool allow_update_failures;
+};
+
/*
* Parse one whitespace- or NUL-terminated, possibly C-quoted argument
* and append the result to arg. Return a pointer to the terminator.
@@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,
*/
static void parse_cmd_update(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid, old_oid;
+ enum ref_transaction_error tx_err;
int have_old;
refname = parse_refname(&next);
@@ -289,11 +300,18 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname,
- &new_oid, have_old ? &old_oid : NULL,
- NULL, NULL,
- update_flags | create_reflog_flag,
- msg, &err))
+ tx_err = ref_transaction_update(transaction, refname,
+ &new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
+ update_flags | create_reflog_flag,
+ msg, &err);
+
+ if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
+ opts->allow_update_failures)
+ print_rejected_refs(refname, have_old ? &old_oid : NULL,
+ &new_oid, NULL, NULL, tx_err, err.buf,
+ NULL);
+ else if (tx_err)
die("%s", err.buf);
update_flags = default_flags;
@@ -302,9 +320,11 @@ static void parse_cmd_update(struct ref_transaction *transaction,
}
static void parse_cmd_symref_update(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts)
{
char *refname, *new_target, *old_arg;
+ enum ref_transaction_error tx_err;
char *old_target = NULL;
struct strbuf err = STRBUF_INIT;
struct object_id old_oid;
@@ -341,12 +361,19 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("symref-update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname, NULL,
- have_old_oid ? &old_oid : NULL,
- new_target,
- have_old_oid ? NULL : old_target,
- update_flags | create_reflog_flag,
- msg, &err))
+ tx_err = ref_transaction_update(transaction, refname, NULL,
+ have_old_oid ? &old_oid : NULL,
+ new_target,
+ have_old_oid ? NULL : old_target,
+ update_flags | create_reflog_flag,
+ msg, &err);
+
+ if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
+ opts->allow_update_failures)
+ print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
+ NULL, have_old_oid ? NULL : old_target,
+ new_target, tx_err, err.buf, NULL);
+ else if (tx_err)
die("%s", err.buf);
update_flags = default_flags;
@@ -358,7 +385,8 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
}
static void parse_cmd_create(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -387,9 +415,9 @@ static void parse_cmd_create(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_symref_create(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname, *new_target;
@@ -417,7 +445,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,
}
static void parse_cmd_delete(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -450,9 +479,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_symref_delete(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname, *old_target;
@@ -479,9 +508,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_verify(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -508,7 +537,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
}
static void parse_cmd_symref_verify(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
struct object_id old_oid;
@@ -550,7 +580,8 @@ static void report_ok(const char *command)
}
static void parse_cmd_option(struct ref_transaction *transaction UNUSED,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
const char *rest;
if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination)
@@ -560,7 +591,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED,
}
static void parse_cmd_start(struct ref_transaction *transaction UNUSED,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
if (*next != line_termination)
die("start: extra input: %s", next);
@@ -568,7 +600,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED,
}
static void parse_cmd_prepare(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -579,7 +612,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,
}
static void parse_cmd_abort(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -590,7 +624,8 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
}
static void parse_cmd_commit(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -618,7 +653,8 @@ enum update_refs_state {
static const struct parse_cmd {
const char *prefix;
- void (*fn)(struct ref_transaction *, const char *, const char *);
+ void (*fn)(struct ref_transaction *, const char *, const char *,
+ struct command_options *);
unsigned args;
enum update_refs_state state;
} command[] = {
@@ -644,6 +680,10 @@ static void update_refs_stdin(unsigned int flags)
struct ref_transaction *transaction;
int i, j;
+ struct command_options opts = {
+ .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,
+ };
+
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
flags, &err);
if (!transaction)
@@ -721,7 +761,7 @@ static void update_refs_stdin(unsigned int flags)
}
cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,
- input.buf + input.len);
+ input.buf + input.len, &opts);
}
switch (state) {
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH 5/8] update-ref: handle rejections while adding updates
2026-04-20 10:12 ` [PATCH 5/8] update-ref: handle rejections while adding updates Karthik Nayak
@ 2026-04-22 11:15 ` Patrick Steinhardt
2026-04-22 14:13 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Patrick Steinhardt @ 2026-04-22 11:15 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Apr 20, 2026 at 12:12:03PM +0200, Karthik Nayak wrote:
> When using git-update-ref(1) with the '--batch-updates' flag, updates
> rejected by the reference backend are displayed to the user while other
> updates are applied. This only applies during the committing of the
Nit: s/committing/commit phase/
> transaction.
>
> In the following commits, we'll also extend `ref_transaction_update()`
> to reject updates before even a transaction is prepared/committed. In
The "even" reads like it's at the wrong position.
> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
> index 5259cc7226..d1980c60c4 100644
> --- a/builtin/update-ref.c
> +++ b/builtin/update-ref.c
> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,
> */
>
> static void parse_cmd_update(struct ref_transaction *transaction,
> - const char *next, const char *end)
> + const char *next, const char *end,
> + struct command_options *opts)
Here you follow my earlier suggestion of using `_options` for the struct
name, but `opts` for the variable :)
> @@ -289,11 +300,18 @@ static void parse_cmd_update(struct ref_transaction *transaction,
> if (*next != line_termination)
> die("update %s: extra input: %s", refname, next);
>
> - if (ref_transaction_update(transaction, refname,
> - &new_oid, have_old ? &old_oid : NULL,
> - NULL, NULL,
> - update_flags | create_reflog_flag,
> - msg, &err))
> + tx_err = ref_transaction_update(transaction, refname,
> + &new_oid, have_old ? &old_oid : NULL,
> + NULL, NULL,
> + update_flags | create_reflog_flag,
> + msg, &err);
> +
> + if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
> + opts->allow_update_failures)
> + print_rejected_refs(refname, have_old ? &old_oid : NULL,
> + &new_oid, NULL, NULL, tx_err, err.buf,
> + NULL);
> + else if (tx_err)
> die("%s", err.buf);
>
> update_flags = default_flags;
I think this could use some curly braces to become easier to read, even
if it's only single-line statements.
Does this change have an impact on the ordering the user sees for
printed errors? If so, it might make sense to point that out in the
commit message.
> @@ -341,12 +361,19 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
> if (*next != line_termination)
> die("symref-update %s: extra input: %s", refname, next);
>
> - if (ref_transaction_update(transaction, refname, NULL,
> - have_old_oid ? &old_oid : NULL,
> - new_target,
> - have_old_oid ? NULL : old_target,
> - update_flags | create_reflog_flag,
> - msg, &err))
> + tx_err = ref_transaction_update(transaction, refname, NULL,
> + have_old_oid ? &old_oid : NULL,
> + new_target,
> + have_old_oid ? NULL : old_target,
> + update_flags | create_reflog_flag,
> + msg, &err);
> +
> + if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
> + opts->allow_update_failures)
> + print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
> + NULL, have_old_oid ? NULL : old_target,
> + new_target, tx_err, err.buf, NULL);
> + else if (tx_err)
> die("%s", err.buf);
>
> update_flags = default_flags;
Same here, curly braces might help readability.
> @@ -644,6 +680,10 @@ static void update_refs_stdin(unsigned int flags)
> struct ref_transaction *transaction;
> int i, j;
>
> + struct command_options opts = {
> + .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,
> + };
> +
> transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
> flags, &err);
> if (!transaction)
Suggestion, please feel free to ignore: we could also pass the flags
directly instead.
Patrick
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH 5/8] update-ref: handle rejections while adding updates
2026-04-22 11:15 ` Patrick Steinhardt
@ 2026-04-22 14:13 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-22 14:13 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
[-- Attachment #1: Type: text/plain, Size: 4307 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Mon, Apr 20, 2026 at 12:12:03PM +0200, Karthik Nayak wrote:
>> When using git-update-ref(1) with the '--batch-updates' flag, updates
>> rejected by the reference backend are displayed to the user while other
>> updates are applied. This only applies during the committing of the
>
> Nit: s/committing/commit phase/
>
Will do.
>> transaction.
>>
>> In the following commits, we'll also extend `ref_transaction_update()`
>> to reject updates before even a transaction is prepared/committed. In
>
> The "even" reads like it's at the wrong position.
>
Yeah seems like a filler, will remove.
>> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
>> index 5259cc7226..d1980c60c4 100644
>> --- a/builtin/update-ref.c
>> +++ b/builtin/update-ref.c
>> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,
>> */
>>
>> static void parse_cmd_update(struct ref_transaction *transaction,
>> - const char *next, const char *end)
>> + const char *next, const char *end,
>> + struct command_options *opts)
>
> Here you follow my earlier suggestion of using `_options` for the struct
> name, but `opts` for the variable :)
>
Haha, indeed, seems like I learn very quickly.
>> @@ -289,11 +300,18 @@ static void parse_cmd_update(struct ref_transaction *transaction,
>> if (*next != line_termination)
>> die("update %s: extra input: %s", refname, next);
>>
>> - if (ref_transaction_update(transaction, refname,
>> - &new_oid, have_old ? &old_oid : NULL,
>> - NULL, NULL,
>> - update_flags | create_reflog_flag,
>> - msg, &err))
>> + tx_err = ref_transaction_update(transaction, refname,
>> + &new_oid, have_old ? &old_oid : NULL,
>> + NULL, NULL,
>> + update_flags | create_reflog_flag,
>> + msg, &err);
>> +
>> + if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
>> + opts->allow_update_failures)
>> + print_rejected_refs(refname, have_old ? &old_oid : NULL,
>> + &new_oid, NULL, NULL, tx_err, err.buf,
>> + NULL);
>> + else if (tx_err)
>> die("%s", err.buf);
>>
>> update_flags = default_flags;
>
> I think this could use some curly braces to become easier to read, even
> if it's only single-line statements.
That's fair.
> Does this change have an impact on the ordering the user sees for
> printed errors? If so, it might make sense to point that out in the
> commit message.
>
I didn't consider that, it will be different since we already output to
the user before the transaction is prepared/committed.
Will add.
>> @@ -341,12 +361,19 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
>> if (*next != line_termination)
>> die("symref-update %s: extra input: %s", refname, next);
>>
>> - if (ref_transaction_update(transaction, refname, NULL,
>> - have_old_oid ? &old_oid : NULL,
>> - new_target,
>> - have_old_oid ? NULL : old_target,
>> - update_flags | create_reflog_flag,
>> - msg, &err))
>> + tx_err = ref_transaction_update(transaction, refname, NULL,
>> + have_old_oid ? &old_oid : NULL,
>> + new_target,
>> + have_old_oid ? NULL : old_target,
>> + update_flags | create_reflog_flag,
>> + msg, &err);
>> +
>> + if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
>> + opts->allow_update_failures)
>> + print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
>> + NULL, have_old_oid ? NULL : old_target,
>> + new_target, tx_err, err.buf, NULL);
>> + else if (tx_err)
>> die("%s", err.buf);
>>
>> update_flags = default_flags;
>
> Same here, curly braces might help readability.
>
Will do.
>> @@ -644,6 +680,10 @@ static void update_refs_stdin(unsigned int flags)
>> struct ref_transaction *transaction;
>> int i, j;
>>
>> + struct command_options opts = {
>> + .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,
>> + };
>> +
>> transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
>> flags, &err);
>> if (!transaction)
>
> Suggestion, please feel free to ignore: we could also pass the flags
> directly instead.
>
> Patrick
Yeah we could, I deliberately avoided it to make the easily extensible
in the future. If you feel strongly, happy to make the change.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH 6/8] refs: move object parsing to the generic layer
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
` (4 preceding siblings ...)
2026-04-20 10:12 ` [PATCH 5/8] update-ref: handle rejections while adding updates Karthik Nayak
@ 2026-04-20 10:12 ` Karthik Nayak
2026-04-22 11:15 ` Patrick Steinhardt
2026-04-20 10:12 ` [PATCH 7/8] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
` (4 subsequent siblings)
10 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-20 10:12 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Regular reference updates made via reference transactions validate that
the provided object ID exists in the object database, this is done by
calling 'parse_object()'. This check is done independently by the
backends.
Let's move this to the generic layer, ensuring the backends only have to
care about reference storage and not about validation of the object IDs.
With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'
error type as its no longer used.
Since we don't iterate over individual references in
`ref_transaction_prepare()`, we add this check to
`ref_transaction_update()`. This means that the validation is done as
soon as an update is queued, without needing to prepare the
transaction. It can be argued that this is more ideal, since this
validation has no dependency on the reference transaction being
prepared.
It must be noted that the change in behavior means that this error
cannot be ignored even with usage of batched updates, since this happens
when the update is being added to the transaction. But since the caller
gets specific error codes, they can either abort the transaction or
continue adding other updates to the transaction.
Modify 'builtin/receive-pack.c' to now capture the error type so that
the error propagated to the client stays the same. Also remove two of
the tests which validates batch-updates with invalid new_oid.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/receive-pack.c | 22 +++++++++++++---------
refs.c | 18 ++++++++++++++++++
refs/files-backend.c | 28 ++--------------------------
refs/reftable-backend.c | 19 -------------------
4 files changed, 33 insertions(+), 54 deletions(-)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 878aa7f0ed..0fb3d57de8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
ret = NULL; /* good */
}
strbuf_release(&err);
- }
- else {
+ } else {
+ enum ref_transaction_error err_type;
struct strbuf err = STRBUF_INIT;
if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si)) {
@@ -1650,14 +1650,18 @@ static const char *update(struct command *cmd, struct shallow_info *si)
goto out;
}
- if (ref_transaction_update(transaction,
- namespaced_name,
- new_oid, old_oid,
- NULL, NULL,
- 0, "push",
- &err)) {
+ err_type = ref_transaction_update(transaction,
+ namespaced_name,
+ new_oid, old_oid,
+ NULL, NULL,
+ 0, "push",
+ &err);
+ if (err_type) {
rp_error("%s", err.buf);
- ret = "failed to update ref";
+ if (err_type == REF_TRANSACTION_ERROR_GENERIC)
+ ret = "failed to update ref";
+ else
+ ret = ref_transaction_error_msg(err_type);
} else {
ret = NULL; /* good */
}
diff --git a/refs.c b/refs.c
index 39fef1cca0..bbe19155f4 100644
--- a/refs.c
+++ b/refs.c
@@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
+ if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&
+ !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {
+ struct object *o = parse_object(transaction->ref_store->repo, new_oid);
+
+ if (!o) {
+ strbuf_addf(err,
+ _("trying to write ref '%s' with nonexistent object %s"),
+ refname, oid_to_hex(new_oid));
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+
+ if (o->type != OBJ_COMMIT && is_branch(refname)) {
+ strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
+ oid_to_hex(new_oid), refname);
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+ }
+
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
old_target, NULL, msg);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 407b97cc44..0e2bbe37a0 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -19,7 +19,6 @@
#include "../iterator.h"
#include "../dir-iterator.h"
#include "../lockfile.h"
-#include "../object.h"
#include "../path.h"
#include "../dir.h"
#include "../chdir-notify.h"
@@ -1589,7 +1588,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid,
- int skip_oid_verification,
struct strbuf *err);
static int commit_ref_update(struct files_ref_store *refs,
struct ref_lock *lock,
@@ -1737,7 +1735,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
}
oidcpy(&lock->old_oid, &orig_oid);
- if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||
+ if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||
commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) {
error("unable to write current sha1 into %s: %s", newrefname, err.buf);
strbuf_release(&err);
@@ -1755,7 +1753,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
goto rollbacklog;
}
- if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||
+ if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||
commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) {
error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
strbuf_release(&err);
@@ -1999,32 +1997,11 @@ static int files_log_ref_write(struct files_ref_store *refs,
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid,
- int skip_oid_verification,
struct strbuf *err)
{
static char term = '\n';
- struct object *o;
int fd;
- if (!skip_oid_verification) {
- o = parse_object(refs->base.repo, oid);
- if (!o) {
- strbuf_addf(
- err,
- "trying to write ref '%s' with nonexistent object %s",
- lock->ref_name, oid_to_hex(oid));
- unlock_ref(lock);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
- strbuf_addf(
- err,
- "trying to write non-commit object %s to branch '%s'",
- oid_to_hex(oid), lock->ref_name);
- unlock_ref(lock);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- }
fd = get_lock_file_fd(&lock->lk);
if (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 ||
write_in_full(fd, &term, 1) < 0 ||
@@ -2828,7 +2805,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
} else {
ret = write_ref_to_lockfile(
refs, lock, &update->new_oid,
- update->flags & REF_SKIP_OID_VERIFICATION,
err);
if (ret) {
char *write_err = strbuf_detach(err, NULL);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index bdc3e0aa19..fdf7336c0f 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1081,25 +1081,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
return 0;
}
- /* Verify that the new object ID is valid. */
- if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&
- !(u->flags & REF_SKIP_OID_VERIFICATION) &&
- !(u->flags & REF_LOG_ONLY)) {
- struct object *o = parse_object(refs->base.repo, &u->new_oid);
- if (!o) {
- strbuf_addf(err,
- _("trying to write ref '%s' with nonexistent object %s"),
- u->refname, oid_to_hex(&u->new_oid));
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
-
- if (o->type != OBJ_COMMIT && is_branch(u->refname)) {
- strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
- oid_to_hex(&u->new_oid), u->refname);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- }
-
/*
* When we update the reference that HEAD points to we enqueue
* a second log-only update for HEAD so that its reflog is
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH 6/8] refs: move object parsing to the generic layer
2026-04-20 10:12 ` [PATCH 6/8] refs: move object parsing to the generic layer Karthik Nayak
@ 2026-04-22 11:15 ` Patrick Steinhardt
2026-04-22 15:03 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Patrick Steinhardt @ 2026-04-22 11:15 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
On Mon, Apr 20, 2026 at 12:12:04PM +0200, Karthik Nayak wrote:
> Regular reference updates made via reference transactions validate that
> the provided object ID exists in the object database, this is done by
s/this/which/
> calling 'parse_object()'. This check is done independently by the
> backends.
..., which leads to duplicated logic.
> Let's move this to the generic layer, ensuring the backends only have to
> care about reference storage and not about validation of the object IDs.
> With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'
> error type as its no longer used.
>
> Since we don't iterate over individual references in
> `ref_transaction_prepare()`, we add this check to
> `ref_transaction_update()`. This means that the validation is done as
> soon as an update is queued, without needing to prepare the
> transaction. It can be argued that this is more ideal, since this
> validation has no dependency on the reference transaction being
> prepared.
>
> It must be noted that the change in behavior means that this error
> cannot be ignored even with usage of batched updates, since this happens
> when the update is being added to the transaction. But since the caller
> gets specific error codes, they can either abort the transaction or
> continue adding other updates to the transaction.
Right, this is what the preceding commits have allowed us to do.
I think this is a good step. Being less entangled with the object
database in the ref backends is a good thing.
Patrick
^ permalink raw reply [flat|nested] 68+ messages in thread
* Re: [PATCH 6/8] refs: move object parsing to the generic layer
2026-04-22 11:15 ` Patrick Steinhardt
@ 2026-04-22 15:03 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-22 15:03 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
[-- Attachment #1: Type: text/plain, Size: 1650 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Mon, Apr 20, 2026 at 12:12:04PM +0200, Karthik Nayak wrote:
>> Regular reference updates made via reference transactions validate that
>> the provided object ID exists in the object database, this is done by
>
> s/this/which/
>
>> calling 'parse_object()'. This check is done independently by the
>> backends.
>
> ..., which leads to duplicated logic.
Will add in.
>
>> Let's move this to the generic layer, ensuring the backends only have to
>> care about reference storage and not about validation of the object IDs.
>> With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'
>> error type as its no longer used.
>>
>> Since we don't iterate over individual references in
>> `ref_transaction_prepare()`, we add this check to
>> `ref_transaction_update()`. This means that the validation is done as
>> soon as an update is queued, without needing to prepare the
>> transaction. It can be argued that this is more ideal, since this
>> validation has no dependency on the reference transaction being
>> prepared.
>>
>> It must be noted that the change in behavior means that this error
>> cannot be ignored even with usage of batched updates, since this happens
>> when the update is being added to the transaction. But since the caller
>> gets specific error codes, they can either abort the transaction or
>> continue adding other updates to the transaction.
>
> Right, this is what the preceding commits have allowed us to do.
>
> I think this is a good step. Being less entangled with the object
> database in the ref backends is a good thing.
>
> Patrick
Agreed. Thanks for the review.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH 7/8] refs: add peeled object ID to the `ref_update` struct
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
` (5 preceding siblings ...)
2026-04-20 10:12 ` [PATCH 6/8] refs: move object parsing to the generic layer Karthik Nayak
@ 2026-04-20 10:12 ` Karthik Nayak
2026-04-20 10:12 ` [PATCH 8/8] refs: use peeled tag values in reference backends Karthik Nayak
` (3 subsequent siblings)
10 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-20 10:12 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
Certain reference backend {packed, reftable}, have the ability to also
store the peeled object ID for a reference pointing to a tag object.
This has the added benefit that during retrieval of such references, we
also obtain the peeled object ID without having to use the ODB.
To provide this functionality, each backend independently calls the ODB
to obtain the peeled OID. To move this functionality to the generic
layer, there must be support infrastructure to pass in a peeled OID for
reference updates.
Add a `peeled` field to the `ref_update` structure and modify
`ref_transaction_add_update()` to receive and copy this object ID to the
`ref_update` structure. Finally, modify `ref_transaction_update()` to
peel tag objects and pass the peeled OID to
`ref_transaction_add_update()`.
Update all callers of these functions with the new function parameters.
Callers which only add reflog updates, need to only pass in NULL, since
for reflogs, we don't store peeled OIDs. Reference deletions also only
need to pass in NULL. For others, pass along the peeled OID if
available.
In a following commit, we'll modify the backends to use this peeled OID
instead of parsing it themselves.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 15 +++++++++++++--
refs/files-backend.c | 20 ++++++++++++--------
refs/refs-internal.h | 14 ++++++++++++++
refs/reftable-backend.c | 6 +++---
4 files changed, 42 insertions(+), 13 deletions(-)
diff --git a/refs.c b/refs.c
index bbe19155f4..63a54c460e 100644
--- a/refs.c
+++ b/refs.c
@@ -1307,6 +1307,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const struct object_id *peeled,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg)
@@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update(
update->committer_info = xstrdup_or_null(committer_info);
update->msg = normalize_reflog_message(msg);
}
+ if (flags & REF_HAVE_PEELED)
+ oidcpy(&update->peeled, peeled);
/*
* This list is generally used by the backends to avoid duplicates.
@@ -1392,6 +1395,8 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
unsigned int flags, const char *msg,
struct strbuf *err)
{
+ struct object_id peeled;
+
assert(err);
if ((flags & REF_FORCE_CREATE_REFLOG) &&
@@ -1432,10 +1437,16 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
oid_to_hex(new_oid), refname);
return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
}
+
+ if (o->type == OBJ_TAG) {
+ if (!peel_object(transaction->ref_store->repo, new_oid, &peeled,
+ PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))
+ flags |= REF_HAVE_PEELED;
+ }
}
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, new_target,
+ new_oid, old_oid, &peeled, new_target,
old_target, NULL, msg);
return 0;
@@ -1462,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction,
return -1;
update = ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, NULL, NULL,
+ new_oid, old_oid, NULL, NULL, NULL,
committer_info, msg);
update->index = index;
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 0e2bbe37a0..c7292a3c17 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1325,7 +1325,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);
+ null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,
+ NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -2468,7 +2469,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,
- &update->new_oid, &update->old_oid,
+ &update->new_oid, &update->old_oid, &update->peeled,
NULL, NULL, update->committer_info, update->msg);
new_update->parent_update = update;
@@ -2530,8 +2531,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
transaction, referent, new_flags,
update->new_target ? NULL : &update->new_oid,
update->old_target ? NULL : &update->old_oid,
- update->new_target, update->old_target, NULL,
- update->msg);
+ &update->peeled, update->new_target, update->old_target,
+ NULL, update->msg);
new_update->parent_update = update;
@@ -2994,7 +2995,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
ref_transaction_add_update(
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
- &update->new_oid, NULL,
+ &update->new_oid, NULL, NULL,
NULL, NULL, NULL, NULL);
}
}
@@ -3200,19 +3201,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
if (update->flags & REF_LOG_ONLY)
ref_transaction_add_update(loose_transaction, update->refname,
update->flags, &update->new_oid,
- &update->old_oid, NULL, NULL,
+ &update->old_oid, &update->peeled,
+ NULL, NULL,
update->committer_info, update->msg);
else
ref_transaction_add_update(loose_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
update->new_target ? NULL : &update->new_oid, NULL,
- update->new_target, NULL, update->committer_info,
+ &update->peeled, update->new_target,
+ NULL, update->committer_info,
NULL);
} else {
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL, NULL, update->committer_info, NULL);
+ &update->peeled, NULL, NULL,
+ update->committer_info, NULL);
}
}
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index eed13af4eb..620aeb8320 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -39,6 +39,13 @@ struct ref_transaction;
*/
#define REF_LOG_ONLY (1 << 7)
+/*
+ * The reference contains a peeled object ID. This is used when the
+ * new_oid is pointing to a tag object and the reference backend
+ * wants to also store the peeled value for optimized retrieval.
+ */
+#define REF_HAVE_PEELED (1 << 15)
+
/*
* Return the length of time to retry acquiring a loose reference lock
* before giving up, in milliseconds:
@@ -92,6 +99,12 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If the new_oid points to a tag object, set this to the peeled
+ * object ID for optimized retrieval without needed to hit the odb.
+ */
+ struct object_id peeled;
+
/*
* If set, point the reference to this value. This can also be
* used to convert regular references to become symbolic refs.
@@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const struct object_id *peeled,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index fdf7336c0f..7416bb72fa 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1107,8 +1107,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, NULL, NULL, NULL,
- u->msg);
+ &u->new_oid, &u->old_oid, &u->peeled, NULL, NULL,
+ NULL, u->msg);
}
ret = reftable_backend_read_ref(be, rewritten_ref,
@@ -1194,7 +1194,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
transaction, referent->buf, new_flags,
u->new_target ? NULL : &u->new_oid,
u->old_target ? NULL : &u->old_oid,
- u->new_target, u->old_target,
+ &u->peeled, u->new_target, u->old_target,
u->committer_info, u->msg);
new_update->parent_update = u;
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH 8/8] refs: use peeled tag values in reference backends
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
` (6 preceding siblings ...)
2026-04-20 10:12 ` [PATCH 7/8] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
@ 2026-04-20 10:12 ` Karthik Nayak
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (2 subsequent siblings)
10 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-20 10:12 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak
The reference backends peel tag objects when storing references to them.
This is to provide optimized reads which avoids hitting the odb. The
previous commits ensures that the peeled value is now propagated via the
generic layer. So modify the packed and reftable backend to directly use
this value instead of calling `peel_object()` independently.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/packed-backend.c | 6 ++----
refs/reftable-backend.c | 9 ++-------
2 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 195600cdad..64d2e105e2 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
*/
i++;
} else {
- struct object_id peeled;
- int peel_error = peel_object(refs->base.repo, &update->new_oid,
- &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
+ bool peeled = update->flags & REF_HAVE_PEELED;
if (write_packed_entry(out, update->refname,
&update->new_oid,
- peel_error ? NULL : &peeled))
+ peeled ? &update->peeled : NULL))
goto write_error;
i++;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 7416bb72fa..001be56e8c 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -12,7 +12,6 @@
#include "../hex.h"
#include "../ident.h"
#include "../iterator.h"
-#include "../object.h"
#include "../parse.h"
#include "../path.h"
#include "../refs.h"
@@ -1584,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
goto done;
} else if (u->flags & REF_HAVE_NEW) {
struct reftable_ref_record ref = {0};
- struct object_id peeled;
- int peel_error;
ref.refname = (char *)u->refname;
ref.update_index = ts;
- peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,
- PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
- if (!peel_error) {
+ if (u->flags & REF_HAVE_PEELED) {
ref.value_type = REFTABLE_REF_VAL2;
- memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);
+ memcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ);
memcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ);
} else if (!is_null_oid(&u->new_oid)) {
ref.value_type = REFTABLE_REF_VAL1;
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v2 0/9] refs: move some of the generic logic out of the backends
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
` (7 preceding siblings ...)
2026-04-20 10:12 ` [PATCH 8/8] refs: use peeled tag values in reference backends Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-23 8:40 ` [PATCH v2 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
` (8 more replies)
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
10 siblings, 9 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
This series came together while I was working on other reference related
code and realized that some of the individual logic implemented with the
reference backends can be moved to the generic layer.
Moving code to the generic layer, simplifies the responsibility of
individual backends and avoids deviation in logic between the backends.
The biggest changes are related to moving out usage of `parse_object()`
and `peel_object()` from reference transactions. The former is used to
validate that the OID provided points to a commit object. The latter is
an optimization technique where the packed/reftable backend store the
peeled OID whenever available, so reading such references provides the
peeled OID without having to call the ODB.
Moving object parsing to the generic layout involves moving it out of
the prepare stage of the transaction and into `ref_transaction_update()`
where every added update is checked. As such, this also involves
modifying update-ref(1) and receive-pack(1) to follow this paradigm.
---
Changes in v2:
- Split the second commit into two: one introducing
`ref_store_init_options` and the second to use it for reflog config.
- Use opts as the variable name consistently.
- A bunch of grammar fixes.
- Link to v1: https://patch.msgid.link/20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com
---
builtin/receive-pack.c | 22 +++++---
builtin/update-ref.c | 145 +++++++++++++++++++++++++++++++-----------------
refs.c | 60 +++++++++++++++-----
refs.h | 16 +++---
refs/files-backend.c | 58 +++++++------------
refs/packed-backend.c | 10 ++--
refs/packed-backend.h | 3 +-
refs/refs-internal.h | 35 ++++++++++--
refs/reftable-backend.c | 40 +++----------
9 files changed, 225 insertions(+), 164 deletions(-)
Karthik Nayak (9):
refs: remove unused typedef 'ref_transaction_commit_fn'
refs: introduce `ref_store_init_options`
refs: extract out reflog config to generic layer
refs: return `ref_transaction_error` from `ref_transaction_update()`
update-ref: move `print_rejected_refs()` up
update-ref: handle rejections while adding updates
refs: move object parsing to the generic layer
refs: add peeled object ID to the `ref_update` struct
refs: use peeled tag values in reference backends
Range-diff versus v1:
1: bbcc7bff38 = 1: 807f23ee66 refs: remove unused typedef 'ref_transaction_commit_fn'
2: 57a66ae8d5 ! 2: f63583d8b0 refs: extract out reflog config to generic layer
@@ Metadata
Author: Karthik Nayak <karthik.188@gmail.com>
## Commit message ##
- refs: extract out reflog config to generic layer
+ refs: introduce `ref_store_init_options`
- The reference backends need to know when to create reflog entries, this
- is dictated by the 'core.logallrefupdates' config. Instead of relying on
- the backends to call `repo_settings_get_log_all_ref_updates()` to obtain
- this config value, let's do this in the generic layer and pass down the
- value to the backends.
+ Reference backends are initiated via the `init()` function. When
+ initiating the function, the backend is also provided flags which denote
+ the access levels of the initiator. Create a new structure
+ `ref_store_init_options` to house such options and move the access flags
+ to this structure.
- Instead of passing this in as a new argument, let's create a new
- `ref_init_options` structure which will house information required to
- initialize a reference backend. Move the access flags here as well.
+ This allows easier extension of providing further options to the
+ backends. In the following commit, we'll also provide config around
+ reflog creation to the backends via the same structure.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ refs.c: static struct ref_store *ref_store_init(struct repository *repo,
{
const struct ref_storage_be *be;
struct ref_store *refs;
-+ struct ref_store_init_options options = {
++ struct ref_store_init_options opts = {
+ .access_flags = flags,
-+ .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),
+ };
be = find_ref_storage_backend(format);
@@ refs.c: static struct ref_store *ref_store_init(struct repository *repo,
* allow the backend to handle how it wants to deal with worktrees.
*/
- refs = be->init(repo, repo->ref_storage_payload, gitdir, flags);
-+ refs = be->init(repo, repo->ref_storage_payload, gitdir, &options);
++ refs = be->init(repo, repo->ref_storage_payload, gitdir, &opts);
+
return refs;
}
@@ refs/files-backend.c: static void clear_loose_ref_cache(struct files_ref_store *
const char *payload,
const char *gitdir,
- unsigned int flags)
-+ const struct ref_store_init_options *options)
++ const struct ref_store_init_options *opts)
{
struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
@@ refs/files-backend.c: static struct ref_store *files_ref_store_init(struct repos
refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
refs->packed_ref_store =
- packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
-- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
-+ packed_ref_store_init(repo, payload, refs->gitcommondir, options);
-+ refs->store_flags = options->access_flags;
-+ refs->log_all_ref_updates = options->log_all_ref_updates;
++ packed_ref_store_init(repo, payload, refs->gitcommondir, opts);
++ refs->store_flags = opts->access_flags;
+ refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
@@ refs/packed-backend.c: static size_t snapshot_hexsz(const struct snapshot *snaps
const char *payload UNUSED,
const char *gitdir,
- unsigned int store_flags)
-+ const struct ref_store_init_options *options)
++ const struct ref_store_init_options *opts)
{
struct packed_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
@@ refs/packed-backend.c: static size_t snapshot_hexsz(const struct snapshot *snaps
base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);
- refs->store_flags = store_flags;
-+ refs->store_flags = options->access_flags;
++ refs->store_flags = opts->access_flags;
strbuf_addf(&sb, "%s/packed-refs", gitdir);
refs->path = strbuf_detach(&sb, NULL);
@@ refs/refs-internal.h: struct ref_store;
+struct ref_store_init_options {
+ /* The kind of operations that the ref_store is allowed to perform. */
+ unsigned int access_flags;
-+
-+ /*
-+ * Denotes under what conditions reflogs should be created when updating
-+ * references.
-+ */
-+ enum log_refs_config log_all_ref_updates;
+};
+
/*
@@ refs/refs-internal.h: struct ref_store;
const char *payload,
const char *gitdir,
- unsigned int flags);
-+ const struct ref_store_init_options *options);
++ const struct ref_store_init_options *opts);
/*
* Release all memory and resources associated with the ref store.
*/
@@ refs/reftable-backend.c: static int reftable_be_config(const char *var, const ch
const char *payload,
const char *gitdir,
- unsigned int store_flags)
-+ const struct ref_store_init_options *options)
++ const struct ref_store_init_options *opts)
{
struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
struct strbuf ref_common_dir = STRBUF_INIT;
@@ refs/reftable-backend.c: static struct ref_store *reftable_be_init(struct reposi
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
- refs->store_flags = store_flags;
-- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
-+ refs->store_flags = options->access_flags;
-+ refs->log_all_ref_updates = options->log_all_ref_updates;
+ refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
++ refs->store_flags = opts->access_flags;
switch (repo->hash_algo->format_id) {
case GIT_SHA1_FORMAT_ID:
-: ---------- > 3: 15fd026e85 refs: extract out reflog config to generic layer
3: 1c05642914 ! 4: a389011125 refs: return `ref_transaction_error` from `ref_transaction_update()`
@@ Commit message
The `ref_transaction_update()` function is used to add updates to a
given reference transactions. In the following commit, we'll add more
- validation to this function. As such, it would be more beneficial if the
+ validation to this function. As such, it would be beneficial if the
function returns specific error types, so callers can differentiate
between different errors.
4: 6d9544a834 ! 5: 3a203ace83 update-ref: move `print_rejected_refs()` up
@@ Metadata
## Commit message ##
update-ref: move `print_rejected_refs()` up
- The `print_rejected_refs()` is used to print any rejected refs when
- using git-updated-ref(1) with the '--batch-updates' option. In the
- following commit, we'll need to use this function in other places, so
+ The `print_rejected_refs()` function is used to print any rejected refs
+ when using git-updated-ref(1) with the '--batch-updates' option. In the
+ following commit, we'll need to use this function in another place, so
move the function up to avoid a separate forward declaration.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
5: fd3b66ec45 ! 6: e6b2c7b523 update-ref: handle rejections while adding updates
@@ Commit message
When using git-update-ref(1) with the '--batch-updates' flag, updates
rejected by the reference backend are displayed to the user while other
- updates are applied. This only applies during the committing of the
+ updates are applied. This only applies during the commit phase of the
transaction.
In the following commits, we'll also extend `ref_transaction_update()`
- to reject updates before even a transaction is prepared/committed. In
+ to reject updates before a transaction is prepared/committed. In
preparation, modify the code in update-ref to also handle non-generic
rejections from `ref_transaction_update()`. This involves propagating
information to each of the commands on whether updates are allowed to be
@@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *trans
+ msg, &err);
+
+ if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
-+ opts->allow_update_failures)
++ opts->allow_update_failures) {
+ print_rejected_refs(refname, have_old ? &old_oid : NULL,
+ &new_oid, NULL, NULL, tx_err, err.buf,
+ NULL);
-+ else if (tx_err)
++ } else if (tx_err) {
die("%s", err.buf);
++ }
update_flags = default_flags;
+ free(refname);
@@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *transaction,
}
@@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction
+ msg, &err);
+
+ if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
-+ opts->allow_update_failures)
++ opts->allow_update_failures) {
+ print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
+ NULL, have_old_oid ? NULL : old_target,
+ new_target, tx_err, err.buf, NULL);
-+ else if (tx_err)
++ } else if (tx_err) {
die("%s", err.buf);
++ }
update_flags = default_flags;
+ free(refname);
@@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction *transaction,
}
6: 3be9566bf9 ! 7: 36a6284ac1 refs: move object parsing to the generic layer
@@ Commit message
refs: move object parsing to the generic layer
Regular reference updates made via reference transactions validate that
- the provided object ID exists in the object database, this is done by
+ the provided object ID exists in the object database, which is done by
calling 'parse_object()'. This check is done independently by the
- backends.
+ backends which leads to duplicated logic.
Let's move this to the generic layer, ensuring the backends only have to
care about reference storage and not about validation of the object IDs.
7: 5e6a26567a = 8: 32f3bc52f1 refs: add peeled object ID to the `ref_update` struct
8: 27914d0556 = 9: bbcdbd3d18 refs: use peeled tag values in reference backends
base-commit: f65aba1e87db64413b6d1ed5ae5a45b5a84a0997
change-id: 20260417-refs-move-to-generic-layer-f7525c5e8764
Thanks
- Karthik
^ permalink raw reply [flat|nested] 68+ messages in thread* [PATCH v2 1/9] refs: remove unused typedef 'ref_transaction_commit_fn'
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-23 8:40 ` [PATCH v2 2/9] refs: introduce `ref_store_init_options` Karthik Nayak
` (7 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
The typedef 'ref_transaction_commit_fn' is not used anywhere in our
code, let's remove it.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/refs-internal.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index d79e35fd26..2d963cc4f4 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,
struct ref_transaction *transaction,
struct strbuf *err);
-typedef int ref_transaction_commit_fn(struct ref_store *refs,
- struct ref_transaction *transaction,
- struct strbuf *err);
-
typedef int optimize_fn(struct ref_store *ref_store,
struct refs_optimize_opts *opts);
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v2 2/9] refs: introduce `ref_store_init_options`
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
2026-04-23 8:40 ` [PATCH v2 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-23 8:52 ` Patrick Steinhardt
2026-04-23 8:40 ` [PATCH v2 3/9] refs: extract out reflog config to generic layer Karthik Nayak
` (6 subsequent siblings)
8 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
Reference backends are initiated via the `init()` function. When
initiating the function, the backend is also provided flags which denote
the access levels of the initiator. Create a new structure
`ref_store_init_options` to house such options and move the access flags
to this structure.
This allows easier extension of providing further options to the
backends. In the following commit, we'll also provide config around
reflog creation to the backends via the same structure.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 6 +++++-
refs/files-backend.c | 8 +++++---
refs/packed-backend.c | 4 ++--
refs/packed-backend.h | 3 ++-
refs/refs-internal.h | 11 ++++++++++-
refs/reftable-backend.c | 4 ++--
6 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/refs.c b/refs.c
index bfcb9c7ac3..8992dd6ae8 100644
--- a/refs.c
+++ b/refs.c
@@ -2295,6 +2295,9 @@ static struct ref_store *ref_store_init(struct repository *repo,
{
const struct ref_storage_be *be;
struct ref_store *refs;
+ struct ref_store_init_options opts = {
+ .access_flags = flags,
+ };
be = find_ref_storage_backend(format);
if (!be)
@@ -2304,7 +2307,8 @@ static struct ref_store *ref_store_init(struct repository *repo,
* TODO Send in a 'struct worktree' instead of a 'gitdir', and
* allow the backend to handle how it wants to deal with worktrees.
*/
- refs = be->init(repo, repo->ref_storage_payload, gitdir, flags);
+ refs = be->init(repo, repo->ref_storage_payload, gitdir, &opts);
+
return refs;
}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index b3b0c25f84..78150ad209 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -108,7 +108,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
static struct ref_store *files_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int flags)
+ const struct ref_store_init_options *opts)
{
struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
@@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
&ref_common_dir);
base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);
- refs->store_flags = flags;
+
refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
refs->packed_ref_store =
- packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
+ packed_ref_store_init(repo, payload, refs->gitcommondir, opts);
+ refs->store_flags = opts->access_flags;
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 23ed62984b..35a0f32e1c 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload UNUSED,
const char *gitdir,
- unsigned int store_flags)
+ const struct ref_store_init_options *opts)
{
struct packed_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
struct strbuf sb = STRBUF_INIT;
base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);
- refs->store_flags = store_flags;
+ refs->store_flags = opts->access_flags;
strbuf_addf(&sb, "%s/packed-refs", gitdir);
refs->path = strbuf_detach(&sb, NULL);
diff --git a/refs/packed-backend.h b/refs/packed-backend.h
index 2c2377a356..1db48e801d 100644
--- a/refs/packed-backend.h
+++ b/refs/packed-backend.h
@@ -3,6 +3,7 @@
struct repository;
struct ref_transaction;
+struct ref_store_init_options;
/*
* Support for storing references in a `packed-refs` file.
@@ -16,7 +17,7 @@ struct ref_transaction;
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int store_flags);
+ const struct ref_store_init_options *options);
/*
* Lock the packed-refs file for writing. Flags is passed to
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2d963cc4f4..f49b3807bf 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -385,6 +385,15 @@ struct ref_store;
REF_STORE_ODB | \
REF_STORE_MAIN)
+/*
+ * Options for initializing the ref backend. All backend-agnostic information
+ * which backends required will be held here.
+ */
+struct ref_store_init_options {
+ /* The kind of operations that the ref_store is allowed to perform. */
+ unsigned int access_flags;
+};
+
/*
* Initialize the ref_store for the specified gitdir. These functions
* should call base_ref_store_init() to initialize the shared part of
@@ -393,7 +402,7 @@ struct ref_store;
typedef struct ref_store *ref_store_init_fn(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int flags);
+ const struct ref_store_init_options *opts);
/*
* Release all memory and resources associated with the ref store.
*/
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index daea30a5b4..ad4ee2627c 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -369,7 +369,7 @@ static int reftable_be_config(const char *var, const char *value,
static struct ref_store *reftable_be_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int store_flags)
+ const struct ref_store_init_options *opts)
{
struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
struct strbuf ref_common_dir = STRBUF_INIT;
@@ -386,8 +386,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
- refs->store_flags = store_flags;
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->store_flags = opts->access_flags;
switch (repo->hash_algo->format_id) {
case GIT_SHA1_FORMAT_ID:
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH v2 2/9] refs: introduce `ref_store_init_options`
2026-04-23 8:40 ` [PATCH v2 2/9] refs: introduce `ref_store_init_options` Karthik Nayak
@ 2026-04-23 8:52 ` Patrick Steinhardt
2026-04-24 9:34 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Patrick Steinhardt @ 2026-04-23 8:52 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, gitster
On Thu, Apr 23, 2026 at 10:40:31AM +0200, Karthik Nayak wrote:
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index b3b0c25f84..78150ad209 100644
> --- a/refs/files-backend.c
> +++ b/refs/files-backend.c
> @@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
> &ref_common_dir);
>
> base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);
> - refs->store_flags = flags;
> +
> refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
> refs->packed_ref_store =
> - packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
> + packed_ref_store_init(repo, payload, refs->gitcommondir, opts);
Is this change here intentional? Doesn't seem to be related to any of
the other changes here.
> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
> index 2d963cc4f4..f49b3807bf 100644
> --- a/refs/refs-internal.h
> +++ b/refs/refs-internal.h
> @@ -385,6 +385,15 @@ struct ref_store;
> REF_STORE_ODB | \
> REF_STORE_MAIN)
>
> +/*
> + * Options for initializing the ref backend. All backend-agnostic information
> + * which backends required will be held here.
> + */
> +struct ref_store_init_options {
> + /* The kind of operations that the ref_store is allowed to perform. */
> + unsigned int access_flags;
Smells like something that should be converted into an enum eventually,
but that definitely is out of scope for this patch series.
Patrick
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH v2 2/9] refs: introduce `ref_store_init_options`
2026-04-23 8:52 ` Patrick Steinhardt
@ 2026-04-24 9:34 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-24 9:34 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, gitster
[-- Attachment #1: Type: text/plain, Size: 1619 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Thu, Apr 23, 2026 at 10:40:31AM +0200, Karthik Nayak wrote:
>> diff --git a/refs/files-backend.c b/refs/files-backend.c
>> index b3b0c25f84..78150ad209 100644
>> --- a/refs/files-backend.c
>> +++ b/refs/files-backend.c
>> @@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
>> &ref_common_dir);
>>
>> base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);
>> - refs->store_flags = flags;
>> +
>> refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
>> refs->packed_ref_store =
>> - packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
>> + packed_ref_store_init(repo, payload, refs->gitcommondir, opts);
>
> Is this change here intentional? Doesn't seem to be related to any of
> the other changes here.
>
Definitely not, I think this was a messy rebase.
>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
>> index 2d963cc4f4..f49b3807bf 100644
>> --- a/refs/refs-internal.h
>> +++ b/refs/refs-internal.h
>> @@ -385,6 +385,15 @@ struct ref_store;
>> REF_STORE_ODB | \
>> REF_STORE_MAIN)
>>
>> +/*
>> + * Options for initializing the ref backend. All backend-agnostic information
>> + * which backends required will be held here.
>> + */
>> +struct ref_store_init_options {
>> + /* The kind of operations that the ref_store is allowed to perform. */
>> + unsigned int access_flags;
>
> Smells like something that should be converted into an enum eventually,
> but that definitely is out of scope for this patch series.
>
> Patrick
Yeah, this is good #leftoverbits task
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH v2 3/9] refs: extract out reflog config to generic layer
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
2026-04-23 8:40 ` [PATCH v2 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
2026-04-23 8:40 ` [PATCH v2 2/9] refs: introduce `ref_store_init_options` Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-23 8:52 ` Patrick Steinhardt
2026-04-23 8:40 ` [PATCH v2 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
` (5 subsequent siblings)
8 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
The reference backends need to know when to create reflog entries, this
is dictated by the 'core.logallrefupdates' config. Instead of relying on
the backends to call `repo_settings_get_log_all_ref_updates()` to obtain
this config value, let's do this in the generic layer and pass down the
value to the backends.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 1 +
refs/files-backend.c | 2 +-
refs/refs-internal.h | 6 ++++++
refs/reftable-backend.c | 2 +-
4 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 8992dd6ae8..6b506aeea3 100644
--- a/refs.c
+++ b/refs.c
@@ -2297,6 +2297,7 @@ static struct ref_store *ref_store_init(struct repository *repo,
struct ref_store *refs;
struct ref_store_init_options opts = {
.access_flags = flags,
+ .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),
};
be = find_ref_storage_backend(format);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 78150ad209..9b1fa955f8 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -125,7 +125,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
refs->packed_ref_store =
packed_ref_store_init(repo, payload, refs->gitcommondir, opts);
refs->store_flags = opts->access_flags;
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->log_all_ref_updates = opts->log_all_ref_updates;
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index f49b3807bf..d103387ebf 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -392,6 +392,12 @@ struct ref_store;
struct ref_store_init_options {
/* The kind of operations that the ref_store is allowed to perform. */
unsigned int access_flags;
+
+ /*
+ * Denotes under what conditions reflogs should be created when updating
+ * references.
+ */
+ enum log_refs_config log_all_ref_updates;
};
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index ad4ee2627c..93374d25c2 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -386,7 +386,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->log_all_ref_updates = opts->log_all_ref_updates;
refs->store_flags = opts->access_flags;
switch (repo->hash_algo->format_id) {
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v2 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (2 preceding siblings ...)
2026-04-23 8:40 ` [PATCH v2 3/9] refs: extract out reflog config to generic layer Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-24 11:01 ` Toon Claes
2026-04-23 8:40 ` [PATCH v2 5/9] update-ref: move `print_rejected_refs()` up Karthik Nayak
` (4 subsequent siblings)
8 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
The `ref_transaction_update()` function is used to add updates to a
given reference transactions. In the following commit, we'll add more
validation to this function. As such, it would be beneficial if the
function returns specific error types, so callers can differentiate
between different errors.
To facilitate this, return `enum ref_transaction_error` from the
function and covert the existing '-1' returns to
'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing
behavior, no changes are made to any of the callers but this sets the
necessary infrastructure for introduction of other errors.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 20 ++++++++++----------
refs.h | 16 ++++++++--------
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/refs.c b/refs.c
index 6b506aeea3..efa16b739d 100644
--- a/refs.c
+++ b/refs.c
@@ -1383,25 +1383,25 @@ static int transaction_refname_valid(const char *refname,
return 1;
}
-int ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- const char *new_target,
- const char *old_target,
- unsigned int flags, const char *msg,
- struct strbuf *err)
+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ unsigned int flags, const char *msg,
+ struct strbuf *err)
{
assert(err);
if ((flags & REF_FORCE_CREATE_REFLOG) &&
(flags & REF_SKIP_CREATE_REFLOG)) {
strbuf_addstr(err, _("refusing to force and skip creation of reflog"));
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
if (!transaction_refname_valid(refname, new_oid, flags, err))
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
diff --git a/refs.h b/refs.h
index d65de6ab5f..71d5c186d0 100644
--- a/refs.h
+++ b/refs.h
@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
* See the above comment "Reference transaction updates" for more
* information.
*/
-int ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- const char *new_target,
- const char *old_target,
- unsigned int flags, const char *msg,
- struct strbuf *err);
+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ unsigned int flags, const char *msg,
+ struct strbuf *err);
/*
* Similar to `ref_transaction_update`, but this function is only for adding
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH v2 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`
2026-04-23 8:40 ` [PATCH v2 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
@ 2026-04-24 11:01 ` Toon Claes
0 siblings, 0 replies; 68+ messages in thread
From: Toon Claes @ 2026-04-24 11:01 UTC (permalink / raw)
To: Karthik Nayak, git; +Cc: Karthik Nayak, gitster, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> The `ref_transaction_update()` function is used to add updates to a
> given reference transactions. In the following commit, we'll add more
> validation to this function. As such, it would be beneficial if the
> function returns specific error types, so callers can differentiate
> between different errors.
>
> To facilitate this, return `enum ref_transaction_error` from the
> function and covert the existing '-1' returns to
> 'REF_TRANSACTION_ERROR_GENERIC'.
I had to look it up, but it seems this enum was introduced not long ago
in 76e760b999 (refs: introduce enum-based transaction error types,
2025-04-08). I'm happy to see it's reused here.
> Since this retains the existing
> behavior, no changes are made to any of the callers but this sets the
> necessary infrastructure for introduction of other errors.
Makes sense.
-- Toon
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH v2 5/9] update-ref: move `print_rejected_refs()` up
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (3 preceding siblings ...)
2026-04-23 8:40 ` [PATCH v2 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-23 8:40 ` [PATCH v2 6/9] update-ref: handle rejections while adding updates Karthik Nayak
` (3 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
The `print_rejected_refs()` function is used to print any rejected refs
when using git-updated-ref(1) with the '--batch-updates' option. In the
following commit, we'll need to use this function in another place, so
move the function up to avoid a separate forward declaration.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 45 ++++++++++++++++++++++-----------------------
1 file changed, 22 insertions(+), 23 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 2d68c40ecb..5259cc7226 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -234,6 +234,28 @@ static int parse_next_oid(const char **next, const char *end,
command, refname);
}
+static void print_rejected_refs(const char *refname,
+ const struct object_id *old_oid,
+ const struct object_id *new_oid,
+ const char *old_target,
+ const char *new_target,
+ enum ref_transaction_error err,
+ const char *details,
+ void *cb_data UNUSED)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ if (details && *details)
+ error("%s", details);
+
+ strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
+ new_oid ? oid_to_hex(new_oid) : new_target,
+ old_oid ? oid_to_hex(old_oid) : old_target,
+ ref_transaction_error_msg(err));
+
+ fwrite(sb.buf, sb.len, 1, stdout);
+ strbuf_release(&sb);
+}
/*
* The following five parse_cmd_*() functions parse the corresponding
@@ -567,29 +589,6 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
report_ok("abort");
}
-static void print_rejected_refs(const char *refname,
- const struct object_id *old_oid,
- const struct object_id *new_oid,
- const char *old_target,
- const char *new_target,
- enum ref_transaction_error err,
- const char *details,
- void *cb_data UNUSED)
-{
- struct strbuf sb = STRBUF_INIT;
-
- if (details && *details)
- error("%s", details);
-
- strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
- new_oid ? oid_to_hex(new_oid) : new_target,
- old_oid ? oid_to_hex(old_oid) : old_target,
- ref_transaction_error_msg(err));
-
- fwrite(sb.buf, sb.len, 1, stdout);
- strbuf_release(&sb);
-}
-
static void parse_cmd_commit(struct ref_transaction *transaction,
const char *next, const char *end UNUSED)
{
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v2 6/9] update-ref: handle rejections while adding updates
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (4 preceding siblings ...)
2026-04-23 8:40 ` [PATCH v2 5/9] update-ref: move `print_rejected_refs()` up Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-23 8:52 ` Patrick Steinhardt
2026-04-24 11:22 ` Toon Claes
2026-04-23 8:40 ` [PATCH v2 7/9] refs: move object parsing to the generic layer Karthik Nayak
` (2 subsequent siblings)
8 siblings, 2 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
When using git-update-ref(1) with the '--batch-updates' flag, updates
rejected by the reference backend are displayed to the user while other
updates are applied. This only applies during the commit phase of the
transaction.
In the following commits, we'll also extend `ref_transaction_update()`
to reject updates before a transaction is prepared/committed. In
preparation, modify the code in update-ref to also handle non-generic
rejections from `ref_transaction_update()`. This involves propagating
information to each of the commands on whether updates are allowed to be
rejected, and also checking for rejections and only dying for generic
failures.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 100 ++++++++++++++++++++++++++++++++++++---------------
1 file changed, 71 insertions(+), 29 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 5259cc7226..99deaac6db 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -25,6 +25,15 @@ static unsigned int default_flags;
static unsigned create_reflog_flag;
static const char *msg;
+struct command_options {
+ /*
+ * Individual updates are allowed to fail without causing
+ * update-ref to exit. This is set when using the
+ * '--batch-updates' flag.
+ */
+ bool allow_update_failures;
+};
+
/*
* Parse one whitespace- or NUL-terminated, possibly C-quoted argument
* and append the result to arg. Return a pointer to the terminator.
@@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,
*/
static void parse_cmd_update(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid, old_oid;
+ enum ref_transaction_error tx_err;
int have_old;
refname = parse_refname(&next);
@@ -289,12 +300,20 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname,
- &new_oid, have_old ? &old_oid : NULL,
- NULL, NULL,
- update_flags | create_reflog_flag,
- msg, &err))
+ tx_err = ref_transaction_update(transaction, refname,
+ &new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
+ update_flags | create_reflog_flag,
+ msg, &err);
+
+ if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
+ opts->allow_update_failures) {
+ print_rejected_refs(refname, have_old ? &old_oid : NULL,
+ &new_oid, NULL, NULL, tx_err, err.buf,
+ NULL);
+ } else if (tx_err) {
die("%s", err.buf);
+ }
update_flags = default_flags;
free(refname);
@@ -302,9 +321,11 @@ static void parse_cmd_update(struct ref_transaction *transaction,
}
static void parse_cmd_symref_update(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts)
{
char *refname, *new_target, *old_arg;
+ enum ref_transaction_error tx_err;
char *old_target = NULL;
struct strbuf err = STRBUF_INIT;
struct object_id old_oid;
@@ -341,13 +362,21 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("symref-update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname, NULL,
- have_old_oid ? &old_oid : NULL,
- new_target,
- have_old_oid ? NULL : old_target,
- update_flags | create_reflog_flag,
- msg, &err))
+ tx_err = ref_transaction_update(transaction, refname, NULL,
+ have_old_oid ? &old_oid : NULL,
+ new_target,
+ have_old_oid ? NULL : old_target,
+ update_flags | create_reflog_flag,
+ msg, &err);
+
+ if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
+ opts->allow_update_failures) {
+ print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
+ NULL, have_old_oid ? NULL : old_target,
+ new_target, tx_err, err.buf, NULL);
+ } else if (tx_err) {
die("%s", err.buf);
+ }
update_flags = default_flags;
free(refname);
@@ -358,7 +387,8 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
}
static void parse_cmd_create(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -387,9 +417,9 @@ static void parse_cmd_create(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_symref_create(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname, *new_target;
@@ -417,7 +447,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,
}
static void parse_cmd_delete(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -450,9 +481,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_symref_delete(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname, *old_target;
@@ -479,9 +510,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_verify(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -508,7 +539,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
}
static void parse_cmd_symref_verify(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
struct object_id old_oid;
@@ -550,7 +582,8 @@ static void report_ok(const char *command)
}
static void parse_cmd_option(struct ref_transaction *transaction UNUSED,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
const char *rest;
if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination)
@@ -560,7 +593,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED,
}
static void parse_cmd_start(struct ref_transaction *transaction UNUSED,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
if (*next != line_termination)
die("start: extra input: %s", next);
@@ -568,7 +602,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED,
}
static void parse_cmd_prepare(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -579,7 +614,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,
}
static void parse_cmd_abort(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -590,7 +626,8 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
}
static void parse_cmd_commit(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -618,7 +655,8 @@ enum update_refs_state {
static const struct parse_cmd {
const char *prefix;
- void (*fn)(struct ref_transaction *, const char *, const char *);
+ void (*fn)(struct ref_transaction *, const char *, const char *,
+ struct command_options *);
unsigned args;
enum update_refs_state state;
} command[] = {
@@ -644,6 +682,10 @@ static void update_refs_stdin(unsigned int flags)
struct ref_transaction *transaction;
int i, j;
+ struct command_options opts = {
+ .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,
+ };
+
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
flags, &err);
if (!transaction)
@@ -721,7 +763,7 @@ static void update_refs_stdin(unsigned int flags)
}
cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,
- input.buf + input.len);
+ input.buf + input.len, &opts);
}
switch (state) {
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates
2026-04-23 8:40 ` [PATCH v2 6/9] update-ref: handle rejections while adding updates Karthik Nayak
@ 2026-04-23 8:52 ` Patrick Steinhardt
2026-04-24 9:35 ` Karthik Nayak
2026-04-24 11:22 ` Toon Claes
1 sibling, 1 reply; 68+ messages in thread
From: Patrick Steinhardt @ 2026-04-23 8:52 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, gitster
On Thu, Apr 23, 2026 at 10:40:35AM +0200, Karthik Nayak wrote:
> When using git-update-ref(1) with the '--batch-updates' flag, updates
> rejected by the reference backend are displayed to the user while other
> updates are applied. This only applies during the commit phase of the
> transaction.
>
> In the following commits, we'll also extend `ref_transaction_update()`
> to reject updates before a transaction is prepared/committed. In
> preparation, modify the code in update-ref to also handle non-generic
> rejections from `ref_transaction_update()`. This involves propagating
> information to each of the commands on whether updates are allowed to be
> rejected, and also checking for rejections and only dying for generic
> failures.
I noticed that you didn't address my feedback on the changed ordering of
errors I posted on the preceding version. Was this intentional?
Patrick
^ permalink raw reply [flat|nested] 68+ messages in thread
* Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates
2026-04-23 8:52 ` Patrick Steinhardt
@ 2026-04-24 9:35 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-24 9:35 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, gitster
[-- Attachment #1: Type: text/plain, Size: 1046 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Thu, Apr 23, 2026 at 10:40:35AM +0200, Karthik Nayak wrote:
>> When using git-update-ref(1) with the '--batch-updates' flag, updates
>> rejected by the reference backend are displayed to the user while other
>> updates are applied. This only applies during the commit phase of the
>> transaction.
>>
>> In the following commits, we'll also extend `ref_transaction_update()`
>> to reject updates before a transaction is prepared/committed. In
>> preparation, modify the code in update-ref to also handle non-generic
>> rejections from `ref_transaction_update()`. This involves propagating
>> information to each of the commands on whether updates are allowed to be
>> rejected, and also checking for rejections and only dying for generic
>> failures.
>
> I noticed that you didn't address my feedback on the changed ordering of
> errors I posted on the preceding version. Was this intentional?
>
> Patrick
Huh. I do remember adding it in. I'll check my reflog and fix this.
Thanks for checking again.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates
2026-04-23 8:40 ` [PATCH v2 6/9] update-ref: handle rejections while adding updates Karthik Nayak
2026-04-23 8:52 ` Patrick Steinhardt
@ 2026-04-24 11:22 ` Toon Claes
2026-04-27 8:47 ` Karthik Nayak
1 sibling, 1 reply; 68+ messages in thread
From: Toon Claes @ 2026-04-24 11:22 UTC (permalink / raw)
To: Karthik Nayak, git; +Cc: Karthik Nayak, gitster, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> When using git-update-ref(1) with the '--batch-updates' flag, updates
> rejected by the reference backend are displayed to the user while other
> updates are applied. This only applies during the commit phase of the
> transaction.
>
> In the following commits, we'll also extend `ref_transaction_update()`
> to reject updates before a transaction is prepared/committed. In
> preparation, modify the code in update-ref to also handle non-generic
> rejections from `ref_transaction_update()`. This involves propagating
> information to each of the commands on whether updates are allowed to be
> rejected, and also checking for rejections and only dying for generic
> failures.
>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
> builtin/update-ref.c | 100 ++++++++++++++++++++++++++++++++++++---------------
> 1 file changed, 71 insertions(+), 29 deletions(-)
>
> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
> index 5259cc7226..99deaac6db 100644
> --- a/builtin/update-ref.c
> +++ b/builtin/update-ref.c
> @@ -25,6 +25,15 @@ static unsigned int default_flags;
> static unsigned create_reflog_flag;
> static const char *msg;
>
> +struct command_options {
> + /*
> + * Individual updates are allowed to fail without causing
> + * update-ref to exit. This is set when using the
> + * '--batch-updates' flag.
> + */
> + bool allow_update_failures;
> +};
> +
> /*
> * Parse one whitespace- or NUL-terminated, possibly C-quoted argument
> * and append the result to arg. Return a pointer to the terminator.
> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,
> */
>
> static void parse_cmd_update(struct ref_transaction *transaction,
> - const char *next, const char *end)
> + const char *next, const char *end,
> + struct command_options *opts)
> {
> struct strbuf err = STRBUF_INIT;
> char *refname;
> struct object_id new_oid, old_oid;
> + enum ref_transaction_error tx_err;
> int have_old;
>
> refname = parse_refname(&next);
> @@ -289,12 +300,20 @@ static void parse_cmd_update(struct ref_transaction *transaction,
> if (*next != line_termination)
> die("update %s: extra input: %s", refname, next);
>
> - if (ref_transaction_update(transaction, refname,
> - &new_oid, have_old ? &old_oid : NULL,
> - NULL, NULL,
> - update_flags | create_reflog_flag,
> - msg, &err))
> + tx_err = ref_transaction_update(transaction, refname,
> + &new_oid, have_old ? &old_oid : NULL,
> + NULL, NULL,
> + update_flags | create_reflog_flag,
> + msg, &err);
> +
> + if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
> + opts->allow_update_failures) {
> + print_rejected_refs(refname, have_old ? &old_oid : NULL,
> + &new_oid, NULL, NULL, tx_err, err.buf,
> + NULL);
> + } else if (tx_err) {
> die("%s", err.buf);
Why die() only on an ERROR_GENERIC? Is GENERIC the only error that stops
processing of other refs? Why? Would there be more errors in the future
that could be added to the pile of "fatal" errors like GENERIC?
I would rather see something that gives a more clear indication this
current ref is rejected. Maybe have a range in the enum:
enum ref_transaction_error {
/* Default error code */
REF_TRANSACTION_ERROR_GENERIC = -1,
/* Ref rejected error range start */
REF_TRANSACTION_REF_REJECTED = -100,
/* Ref name conflict like A vs A/B */
REF_TRANSACTION_ERROR_NAME_CONFLICT = -101,
/* Ref to be created already exists */
REF_TRANSACTION_ERROR_CREATE_EXISTS = -102,
/* ref expected but doesn't exist */
REF_TRANSACTION_ERROR_NONEXISTENT_REF = -103,
/* Provided old_oid or old_target of reference doesn't match actual */
REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE = -104,
/* Provided new_oid or new_target is invalid */
REF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -105,
/* Expected ref to be symref, but is a regular ref */
REF_TRANSACTION_ERROR_EXPECTED_SYMREF = -106,
/* Cannot create ref due to case-insensitive filesystem */
REF_TRANSACTION_ERROR_CASE_CONFLICT = -107,
};
statis inline bool ref_rejected(enum ref_transaction_error err)
{
return err < REF_TRANSACTION_REF_REJECTED;
}
The thing is, now you have this checking on GENERIC in two places, I'm
worried one or the other might be forgotten in the future.
Now maybe this is a bit of an overkill, so feel free to reject that
suggestion. But if you want to keep looking at GENERIC, how do you feel
about this version:
if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
die("%s", err.buf);
if (tx_err && opts->allow_update_failures)
print_rejected_refs(refname, have_old ? &old_oid : NULL,
&new_oid, NULL, NULL, tx_err, err.buf,
NULL);
And a little line of comment saying why to die() on GENERIC wouldn't
hurt I think.
> @@ -341,13 +362,21 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
> if (*next != line_termination)
> die("symref-update %s: extra input: %s", refname, next);
>
> - if (ref_transaction_update(transaction, refname, NULL,
> - have_old_oid ? &old_oid : NULL,
> - new_target,
> - have_old_oid ? NULL : old_target,
> - update_flags | create_reflog_flag,
> - msg, &err))
> + tx_err = ref_transaction_update(transaction, refname, NULL,
> + have_old_oid ? &old_oid : NULL,
> + new_target,
> + have_old_oid ? NULL : old_target,
> + update_flags | create_reflog_flag,
> + msg, &err);
> +
> + if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
> + opts->allow_update_failures) {
> + print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
> + NULL, have_old_oid ? NULL : old_target,
> + new_target, tx_err, err.buf, NULL);
> + } else if (tx_err) {
> die("%s", err.buf);
> + }
Obviously the same suggestion could be applied here.
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH v2 6/9] update-ref: handle rejections while adding updates
2026-04-24 11:22 ` Toon Claes
@ 2026-04-27 8:47 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 8:47 UTC (permalink / raw)
To: Toon Claes, git; +Cc: gitster, ps
[-- Attachment #1: Type: text/plain, Size: 6488 bytes --]
Toon Claes <toon@iotcl.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> When using git-update-ref(1) with the '--batch-updates' flag, updates
>> rejected by the reference backend are displayed to the user while other
>> updates are applied. This only applies during the commit phase of the
>> transaction.
>>
>> In the following commits, we'll also extend `ref_transaction_update()`
>> to reject updates before a transaction is prepared/committed. In
>> preparation, modify the code in update-ref to also handle non-generic
>> rejections from `ref_transaction_update()`. This involves propagating
>> information to each of the commands on whether updates are allowed to be
>> rejected, and also checking for rejections and only dying for generic
>> failures.
>>
>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
>> ---
>> builtin/update-ref.c | 100 ++++++++++++++++++++++++++++++++++++---------------
>> 1 file changed, 71 insertions(+), 29 deletions(-)
>>
>> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
>> index 5259cc7226..99deaac6db 100644
>> --- a/builtin/update-ref.c
>> +++ b/builtin/update-ref.c
>> @@ -25,6 +25,15 @@ static unsigned int default_flags;
>> static unsigned create_reflog_flag;
>> static const char *msg;
>>
>> +struct command_options {
>> + /*
>> + * Individual updates are allowed to fail without causing
>> + * update-ref to exit. This is set when using the
>> + * '--batch-updates' flag.
>> + */
>> + bool allow_update_failures;
>> +};
>> +
>> /*
>> * Parse one whitespace- or NUL-terminated, possibly C-quoted argument
>> * and append the result to arg. Return a pointer to the terminator.
>> @@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,
>> */
>>
>> static void parse_cmd_update(struct ref_transaction *transaction,
>> - const char *next, const char *end)
>> + const char *next, const char *end,
>> + struct command_options *opts)
>> {
>> struct strbuf err = STRBUF_INIT;
>> char *refname;
>> struct object_id new_oid, old_oid;
>> + enum ref_transaction_error tx_err;
>> int have_old;
>>
>> refname = parse_refname(&next);
>> @@ -289,12 +300,20 @@ static void parse_cmd_update(struct ref_transaction *transaction,
>> if (*next != line_termination)
>> die("update %s: extra input: %s", refname, next);
>>
>> - if (ref_transaction_update(transaction, refname,
>> - &new_oid, have_old ? &old_oid : NULL,
>> - NULL, NULL,
>> - update_flags | create_reflog_flag,
>> - msg, &err))
>> + tx_err = ref_transaction_update(transaction, refname,
>> + &new_oid, have_old ? &old_oid : NULL,
>> + NULL, NULL,
>> + update_flags | create_reflog_flag,
>> + msg, &err);
>> +
>> + if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
>> + opts->allow_update_failures) {
>> + print_rejected_refs(refname, have_old ? &old_oid : NULL,
>> + &new_oid, NULL, NULL, tx_err, err.buf,
>> + NULL);
>> + } else if (tx_err) {
>> die("%s", err.buf);
>
> Why die() only on an ERROR_GENERIC? Is GENERIC the only error that stops
> processing of other refs? Why? Would there be more errors in the future
> that could be added to the pile of "fatal" errors like GENERIC?
>
> I would rather see something that gives a more clear indication this
> current ref is rejected. Maybe have a range in the enum:
>
> enum ref_transaction_error {
> /* Default error code */
> REF_TRANSACTION_ERROR_GENERIC = -1,
>
> /* Ref rejected error range start */
> REF_TRANSACTION_REF_REJECTED = -100,
>
> /* Ref name conflict like A vs A/B */
> REF_TRANSACTION_ERROR_NAME_CONFLICT = -101,
> /* Ref to be created already exists */
> REF_TRANSACTION_ERROR_CREATE_EXISTS = -102,
> /* ref expected but doesn't exist */
> REF_TRANSACTION_ERROR_NONEXISTENT_REF = -103,
> /* Provided old_oid or old_target of reference doesn't match actual */
> REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE = -104,
> /* Provided new_oid or new_target is invalid */
> REF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -105,
> /* Expected ref to be symref, but is a regular ref */
> REF_TRANSACTION_ERROR_EXPECTED_SYMREF = -106,
> /* Cannot create ref due to case-insensitive filesystem */
> REF_TRANSACTION_ERROR_CASE_CONFLICT = -107,
> };
>
> statis inline bool ref_rejected(enum ref_transaction_error err)
> {
> return err < REF_TRANSACTION_REF_REJECTED;
> }
>
> The thing is, now you have this checking on GENERIC in two places, I'm
> worried one or the other might be forgotten in the future.
But it's the same no? We moved from using the error to using a function
which checks the same error. This function will now be used in two
places and one of them could be forgotten.
Anyways, the GENERIC error is the default error code, this is to state
that this isn't a specific recoverable error type.
>
> Now maybe this is a bit of an overkill, so feel free to reject that
> suggestion. But if you want to keep looking at GENERIC, how do you feel
> about this version:
>
> if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
> die("%s", err.buf);
>
> if (tx_err && opts->allow_update_failures)
> print_rejected_refs(refname, have_old ? &old_oid : NULL,
> &new_oid, NULL, NULL, tx_err, err.buf,
> NULL);
>
> And a little line of comment saying why to die() on GENERIC wouldn't
> hurt I think.
>
This is nicer, I can modify to this :)
>> @@ -341,13 +362,21 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
>> if (*next != line_termination)
>> die("symref-update %s: extra input: %s", refname, next);
>>
>> - if (ref_transaction_update(transaction, refname, NULL,
>> - have_old_oid ? &old_oid : NULL,
>> - new_target,
>> - have_old_oid ? NULL : old_target,
>> - update_flags | create_reflog_flag,
>> - msg, &err))
>> + tx_err = ref_transaction_update(transaction, refname, NULL,
>> + have_old_oid ? &old_oid : NULL,
>> + new_target,
>> + have_old_oid ? NULL : old_target,
>> + update_flags | create_reflog_flag,
>> + msg, &err);
>> +
>> + if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
>> + opts->allow_update_failures) {
>> + print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
>> + NULL, have_old_oid ? NULL : old_target,
>> + new_target, tx_err, err.buf, NULL);
>> + } else if (tx_err) {
>> die("%s", err.buf);
>> + }
>
> Obviously the same suggestion could be applied here.
>
> --
> Cheers,
> Toon
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH v2 7/9] refs: move object parsing to the generic layer
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (5 preceding siblings ...)
2026-04-23 8:40 ` [PATCH v2 6/9] update-ref: handle rejections while adding updates Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-24 12:06 ` Toon Claes
2026-04-23 8:40 ` [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
2026-04-23 8:40 ` [PATCH v2 9/9] refs: use peeled tag values in reference backends Karthik Nayak
8 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
Regular reference updates made via reference transactions validate that
the provided object ID exists in the object database, which is done by
calling 'parse_object()'. This check is done independently by the
backends which leads to duplicated logic.
Let's move this to the generic layer, ensuring the backends only have to
care about reference storage and not about validation of the object IDs.
With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'
error type as its no longer used.
Since we don't iterate over individual references in
`ref_transaction_prepare()`, we add this check to
`ref_transaction_update()`. This means that the validation is done as
soon as an update is queued, without needing to prepare the
transaction. It can be argued that this is more ideal, since this
validation has no dependency on the reference transaction being
prepared.
It must be noted that the change in behavior means that this error
cannot be ignored even with usage of batched updates, since this happens
when the update is being added to the transaction. But since the caller
gets specific error codes, they can either abort the transaction or
continue adding other updates to the transaction.
Modify 'builtin/receive-pack.c' to now capture the error type so that
the error propagated to the client stays the same. Also remove two of
the tests which validates batch-updates with invalid new_oid.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/receive-pack.c | 22 +++++++++++++---------
refs.c | 18 ++++++++++++++++++
refs/files-backend.c | 28 ++--------------------------
refs/reftable-backend.c | 19 -------------------
4 files changed, 33 insertions(+), 54 deletions(-)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 878aa7f0ed..0fb3d57de8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
ret = NULL; /* good */
}
strbuf_release(&err);
- }
- else {
+ } else {
+ enum ref_transaction_error err_type;
struct strbuf err = STRBUF_INIT;
if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si)) {
@@ -1650,14 +1650,18 @@ static const char *update(struct command *cmd, struct shallow_info *si)
goto out;
}
- if (ref_transaction_update(transaction,
- namespaced_name,
- new_oid, old_oid,
- NULL, NULL,
- 0, "push",
- &err)) {
+ err_type = ref_transaction_update(transaction,
+ namespaced_name,
+ new_oid, old_oid,
+ NULL, NULL,
+ 0, "push",
+ &err);
+ if (err_type) {
rp_error("%s", err.buf);
- ret = "failed to update ref";
+ if (err_type == REF_TRANSACTION_ERROR_GENERIC)
+ ret = "failed to update ref";
+ else
+ ret = ref_transaction_error_msg(err_type);
} else {
ret = NULL; /* good */
}
diff --git a/refs.c b/refs.c
index efa16b739d..662a9e6f9e 100644
--- a/refs.c
+++ b/refs.c
@@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
+ if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&
+ !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {
+ struct object *o = parse_object(transaction->ref_store->repo, new_oid);
+
+ if (!o) {
+ strbuf_addf(err,
+ _("trying to write ref '%s' with nonexistent object %s"),
+ refname, oid_to_hex(new_oid));
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+
+ if (o->type != OBJ_COMMIT && is_branch(refname)) {
+ strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
+ oid_to_hex(new_oid), refname);
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+ }
+
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
old_target, NULL, msg);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 9b1fa955f8..6da0cb4f1b 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -19,7 +19,6 @@
#include "../iterator.h"
#include "../dir-iterator.h"
#include "../lockfile.h"
-#include "../object.h"
#include "../path.h"
#include "../dir.h"
#include "../chdir-notify.h"
@@ -1589,7 +1588,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid,
- int skip_oid_verification,
struct strbuf *err);
static int commit_ref_update(struct files_ref_store *refs,
struct ref_lock *lock,
@@ -1737,7 +1735,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
}
oidcpy(&lock->old_oid, &orig_oid);
- if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||
+ if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||
commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) {
error("unable to write current sha1 into %s: %s", newrefname, err.buf);
strbuf_release(&err);
@@ -1755,7 +1753,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
goto rollbacklog;
}
- if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||
+ if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||
commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) {
error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
strbuf_release(&err);
@@ -1999,32 +1997,11 @@ static int files_log_ref_write(struct files_ref_store *refs,
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid,
- int skip_oid_verification,
struct strbuf *err)
{
static char term = '\n';
- struct object *o;
int fd;
- if (!skip_oid_verification) {
- o = parse_object(refs->base.repo, oid);
- if (!o) {
- strbuf_addf(
- err,
- "trying to write ref '%s' with nonexistent object %s",
- lock->ref_name, oid_to_hex(oid));
- unlock_ref(lock);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
- strbuf_addf(
- err,
- "trying to write non-commit object %s to branch '%s'",
- oid_to_hex(oid), lock->ref_name);
- unlock_ref(lock);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- }
fd = get_lock_file_fd(&lock->lk);
if (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 ||
write_in_full(fd, &term, 1) < 0 ||
@@ -2828,7 +2805,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
} else {
ret = write_ref_to_lockfile(
refs, lock, &update->new_oid,
- update->flags & REF_SKIP_OID_VERIFICATION,
err);
if (ret) {
char *write_err = strbuf_detach(err, NULL);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 93374d25c2..444b0c24e5 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1081,25 +1081,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
return 0;
}
- /* Verify that the new object ID is valid. */
- if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&
- !(u->flags & REF_SKIP_OID_VERIFICATION) &&
- !(u->flags & REF_LOG_ONLY)) {
- struct object *o = parse_object(refs->base.repo, &u->new_oid);
- if (!o) {
- strbuf_addf(err,
- _("trying to write ref '%s' with nonexistent object %s"),
- u->refname, oid_to_hex(&u->new_oid));
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
-
- if (o->type != OBJ_COMMIT && is_branch(u->refname)) {
- strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
- oid_to_hex(&u->new_oid), u->refname);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- }
-
/*
* When we update the reference that HEAD points to we enqueue
* a second log-only update for HEAD so that its reflog is
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH v2 7/9] refs: move object parsing to the generic layer
2026-04-23 8:40 ` [PATCH v2 7/9] refs: move object parsing to the generic layer Karthik Nayak
@ 2026-04-24 12:06 ` Toon Claes
2026-04-27 9:32 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Toon Claes @ 2026-04-24 12:06 UTC (permalink / raw)
To: Karthik Nayak, git; +Cc: Karthik Nayak, gitster, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> Regular reference updates made via reference transactions validate that
> the provided object ID exists in the object database, which is done by
> calling 'parse_object()'. This check is done independently by the
> backends which leads to duplicated logic.
>
> Let's move this to the generic layer, ensuring the backends only have to
> care about reference storage and not about validation of the object IDs.
> With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'
> error type as its no longer used.
>
> Since we don't iterate over individual references in
> `ref_transaction_prepare()`, we add this check to
> `ref_transaction_update()`. This means that the validation is done as
> soon as an update is queued, without needing to prepare the
> transaction. It can be argued that this is more ideal, since this
> validation has no dependency on the reference transaction being
> prepared.
>
> It must be noted that the change in behavior means that this error
> cannot be ignored even with usage of batched updates, since this happens
> when the update is being added to the transaction. But since the caller
> gets specific error codes, they can either abort the transaction or
> continue adding other updates to the transaction.
>
> Modify 'builtin/receive-pack.c' to now capture the error type so that
> the error propagated to the client stays the same. Also remove two of
> the tests which validates batch-updates with invalid new_oid.
>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
> builtin/receive-pack.c | 22 +++++++++++++---------
> refs.c | 18 ++++++++++++++++++
> refs/files-backend.c | 28 ++--------------------------
> refs/reftable-backend.c | 19 -------------------
> 4 files changed, 33 insertions(+), 54 deletions(-)
>
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index 878aa7f0ed..0fb3d57de8 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
> ret = NULL; /* good */
> }
> strbuf_release(&err);
> - }
> - else {
> + } else {
> + enum ref_transaction_error err_type;
Shall we also use `tx_err` like in builtin/update-ref.c?
> diff --git a/refs.c b/refs.c
> index efa16b739d..662a9e6f9e 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
> flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
> flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
>
> + if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&
> + !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {
Compared to the version we used to have in refs/reftable-backend.c,
you've added `!new_target`. Why is that? If I understand correctly, that
only happens when `new_oid` is set. Wouldn't `!is_null_oid(new_oid)`
guard for that?
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH v2 7/9] refs: move object parsing to the generic layer
2026-04-24 12:06 ` Toon Claes
@ 2026-04-27 9:32 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 9:32 UTC (permalink / raw)
To: Toon Claes, git; +Cc: gitster, ps
[-- Attachment #1: Type: text/plain, Size: 1844 bytes --]
Toon Claes <toon@iotcl.com> writes:
[snip]
>> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
>> index 878aa7f0ed..0fb3d57de8 100644
>> --- a/builtin/receive-pack.c
>> +++ b/builtin/receive-pack.c
>> @@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
>> ret = NULL; /* good */
>> }
>> strbuf_release(&err);
>> - }
>> - else {
>> + } else {
>> + enum ref_transaction_error err_type;
>
> Shall we also use `tx_err` like in builtin/update-ref.c?
>
Makes sense, let's do that.
>> diff --git a/refs.c b/refs.c
>> index efa16b739d..662a9e6f9e 100644
>> --- a/refs.c
>> +++ b/refs.c
>> @@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
>> flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
>> flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
>>
>> + if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&
>> + !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {
>
> Compared to the version we used to have in refs/reftable-backend.c,
> you've added `!new_target`. Why is that? If I understand correctly, that
> only happens when `new_oid` is set. Wouldn't `!is_null_oid(new_oid)`
> guard for that?
>
Since `new_oid` is a pointer here, we don't know if it's NULL or not.
`is_null_oid()` doesn't validate NULL values, so the check must come
before it.
If `REF_HAVE_NEW` is set, it either means that either `new_target` is
set or `new_oid` is set. So we check that `new_target` is NULL.
The reftable check worked as there we operate on top of `struct
ref_update`, which contains `struct object_id new_oid;`, since it is not
a pointer, the value is 0 when `new_target` is set. This works with the
`is_null_oid()` check.
> --
> Cheers,
> Toon
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (6 preceding siblings ...)
2026-04-23 8:40 ` [PATCH v2 7/9] refs: move object parsing to the generic layer Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
2026-04-24 16:44 ` Toon Claes
2026-04-23 8:40 ` [PATCH v2 9/9] refs: use peeled tag values in reference backends Karthik Nayak
8 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
Certain reference backend {packed, reftable}, have the ability to also
store the peeled object ID for a reference pointing to a tag object.
This has the added benefit that during retrieval of such references, we
also obtain the peeled object ID without having to use the ODB.
To provide this functionality, each backend independently calls the ODB
to obtain the peeled OID. To move this functionality to the generic
layer, there must be support infrastructure to pass in a peeled OID for
reference updates.
Add a `peeled` field to the `ref_update` structure and modify
`ref_transaction_add_update()` to receive and copy this object ID to the
`ref_update` structure. Finally, modify `ref_transaction_update()` to
peel tag objects and pass the peeled OID to
`ref_transaction_add_update()`.
Update all callers of these functions with the new function parameters.
Callers which only add reflog updates, need to only pass in NULL, since
for reflogs, we don't store peeled OIDs. Reference deletions also only
need to pass in NULL. For others, pass along the peeled OID if
available.
In a following commit, we'll modify the backends to use this peeled OID
instead of parsing it themselves.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 15 +++++++++++++--
refs/files-backend.c | 20 ++++++++++++--------
refs/refs-internal.h | 14 ++++++++++++++
refs/reftable-backend.c | 6 +++---
4 files changed, 42 insertions(+), 13 deletions(-)
diff --git a/refs.c b/refs.c
index 662a9e6f9e..0648df2b6c 100644
--- a/refs.c
+++ b/refs.c
@@ -1307,6 +1307,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const struct object_id *peeled,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg)
@@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update(
update->committer_info = xstrdup_or_null(committer_info);
update->msg = normalize_reflog_message(msg);
}
+ if (flags & REF_HAVE_PEELED)
+ oidcpy(&update->peeled, peeled);
/*
* This list is generally used by the backends to avoid duplicates.
@@ -1392,6 +1395,8 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
unsigned int flags, const char *msg,
struct strbuf *err)
{
+ struct object_id peeled;
+
assert(err);
if ((flags & REF_FORCE_CREATE_REFLOG) &&
@@ -1432,10 +1437,16 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
oid_to_hex(new_oid), refname);
return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
}
+
+ if (o->type == OBJ_TAG) {
+ if (!peel_object(transaction->ref_store->repo, new_oid, &peeled,
+ PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))
+ flags |= REF_HAVE_PEELED;
+ }
}
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, new_target,
+ new_oid, old_oid, &peeled, new_target,
old_target, NULL, msg);
return 0;
@@ -1462,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction,
return -1;
update = ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, NULL, NULL,
+ new_oid, old_oid, NULL, NULL, NULL,
committer_info, msg);
update->index = index;
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 6da0cb4f1b..2ed1082d56 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1325,7 +1325,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);
+ null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,
+ NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -2468,7 +2469,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,
- &update->new_oid, &update->old_oid,
+ &update->new_oid, &update->old_oid, &update->peeled,
NULL, NULL, update->committer_info, update->msg);
new_update->parent_update = update;
@@ -2530,8 +2531,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
transaction, referent, new_flags,
update->new_target ? NULL : &update->new_oid,
update->old_target ? NULL : &update->old_oid,
- update->new_target, update->old_target, NULL,
- update->msg);
+ &update->peeled, update->new_target, update->old_target,
+ NULL, update->msg);
new_update->parent_update = update;
@@ -2994,7 +2995,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
ref_transaction_add_update(
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
- &update->new_oid, NULL,
+ &update->new_oid, NULL, NULL,
NULL, NULL, NULL, NULL);
}
}
@@ -3200,19 +3201,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
if (update->flags & REF_LOG_ONLY)
ref_transaction_add_update(loose_transaction, update->refname,
update->flags, &update->new_oid,
- &update->old_oid, NULL, NULL,
+ &update->old_oid, &update->peeled,
+ NULL, NULL,
update->committer_info, update->msg);
else
ref_transaction_add_update(loose_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
update->new_target ? NULL : &update->new_oid, NULL,
- update->new_target, NULL, update->committer_info,
+ &update->peeled, update->new_target,
+ NULL, update->committer_info,
NULL);
} else {
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL, NULL, update->committer_info, NULL);
+ &update->peeled, NULL, NULL,
+ update->committer_info, NULL);
}
}
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index d103387ebf..307dcb277b 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -39,6 +39,13 @@ struct ref_transaction;
*/
#define REF_LOG_ONLY (1 << 7)
+/*
+ * The reference contains a peeled object ID. This is used when the
+ * new_oid is pointing to a tag object and the reference backend
+ * wants to also store the peeled value for optimized retrieval.
+ */
+#define REF_HAVE_PEELED (1 << 15)
+
/*
* Return the length of time to retry acquiring a loose reference lock
* before giving up, in milliseconds:
@@ -92,6 +99,12 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If the new_oid points to a tag object, set this to the peeled
+ * object ID for optimized retrieval without needed to hit the odb.
+ */
+ struct object_id peeled;
+
/*
* If set, point the reference to this value. This can also be
* used to convert regular references to become symbolic refs.
@@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const struct object_id *peeled,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 444b0c24e5..b0c010387d 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1107,8 +1107,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, NULL, NULL, NULL,
- u->msg);
+ &u->new_oid, &u->old_oid, &u->peeled, NULL, NULL,
+ NULL, u->msg);
}
ret = reftable_backend_read_ref(be, rewritten_ref,
@@ -1194,7 +1194,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
transaction, referent->buf, new_flags,
u->new_target ? NULL : &u->new_oid,
u->old_target ? NULL : &u->old_oid,
- u->new_target, u->old_target,
+ &u->peeled, u->new_target, u->old_target,
u->committer_info, u->msg);
new_update->parent_update = u;
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct
2026-04-23 8:40 ` [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
@ 2026-04-24 16:44 ` Toon Claes
2026-04-27 9:33 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Toon Claes @ 2026-04-24 16:44 UTC (permalink / raw)
To: Karthik Nayak, git; +Cc: Karthik Nayak, gitster, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> Certain reference backend {packed, reftable}, have the ability to also
Shouldn't it be:
Certain reference backends {packed, reftable} have the ability to also
> store the peeled object ID for a reference pointing to a tag object.
> This has the added benefit that during retrieval of such references, we
> also obtain the peeled object ID without having to use the ODB.
>
> To provide this functionality, each backend independently calls the ODB
> to obtain the peeled OID. To move this functionality to the generic
> layer, there must be support infrastructure to pass in a peeled OID for
> reference updates.
>
> Add a `peeled` field to the `ref_update` structure and modify
> `ref_transaction_add_update()` to receive and copy this object ID to the
> `ref_update` structure. Finally, modify `ref_transaction_update()` to
> peel tag objects and pass the peeled OID to
> `ref_transaction_add_update()`.
>
> Update all callers of these functions with the new function parameters.
> Callers which only add reflog updates, need to only pass in NULL, since
> for reflogs, we don't store peeled OIDs. Reference deletions also only
> need to pass in NULL. For others, pass along the peeled OID if
> available.
>
> In a following commit, we'll modify the backends to use this peeled OID
> instead of parsing it themselves.
>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
> refs.c | 15 +++++++++++++--
> refs/files-backend.c | 20 ++++++++++++--------
> refs/refs-internal.h | 14 ++++++++++++++
> refs/reftable-backend.c | 6 +++---
> 4 files changed, 42 insertions(+), 13 deletions(-)
>
> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
> index d103387ebf..307dcb277b 100644
> --- a/refs/refs-internal.h
> +++ b/refs/refs-internal.h
> @@ -39,6 +39,13 @@ struct ref_transaction;
> */
> #define REF_LOG_ONLY (1 << 7)
>
> +/*
> + * The reference contains a peeled object ID. This is used when the
> + * new_oid is pointing to a tag object and the reference backend
> + * wants to also store the peeled value for optimized retrieval.
> + */
> +#define REF_HAVE_PEELED (1 << 15)
How did you end up picking this value?
I did some grepping to figure out if it would conflict with anything:
git grep -h '#define REF_' -- '*.h' '*.c' |
awk '/0x/{n=strtonum($3);b=0;while(n>1){n/=2;b++};$3="(1 << "b")"} 1' |
sort -t'<' -k3 -n |
column -t
(Yeah I got some help from AI to write the `awk` command)
Resulting in:
#define REF_EXCLUSIONS_INIT { \
#define REF_FILTER_H
#define REF_FILTER_INIT { \
#define REF_FORMAT_INIT { \
#define REF_FORMATTING_STATE_INIT { 0 }
#define REF_NO_DEREF (1 << 0)
#define REF_NORMAL (1u << 0)
#define REF_STATES_INIT { \
#define REF_STORE_ALL_CAPS (REF_STORE_READ | \
#define REF_STORE_CREATE_ON_DISK_IS_WORKTREE (1 << 0)
#define REF_STORE_READ (1 << 0)
#define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
#define REF_BRANCHES (1u << 1)
#define REF_FORCE_CREATE_REFLOG (1 << 1)
#define REF_STORE_WRITE (1 << 1) /* can perform update operations */
#define REF_HAVE_NEW (1 << 2)
#define REF_STORE_ODB (1 << 2) /* has access to object database */
#define REF_TAGS (1u << 2)
#define REF_HAVE_OLD (1 << 3)
#define REF_STORE_MAIN (1 << 3)
#define REF_DIR (1 << 4)
#define REF_IS_PRUNING (1 << 4)
#define REF_DELETING (1 << 5)
#define REF_INCOMPLETE (1 << 5)
#define REF_KNOWS_PEELED (1 << 6)
#define REF_NEEDS_COMMIT (1 << 6)
#define REF_LOG_ONLY (1 << 7)
#define REF_UPDATE_VIA_HEAD (1 << 8)
#define REF_UPDATE_VIA_HEAD (1 << 8)
#define REF_DELETED_RMDIR (1 << 9)
#define REF_SKIP_OID_VERIFICATION (1 << 10)
#define REF_SKIP_REFNAME_VERIFICATION (1 << 11)
#define REF_SKIP_CREATE_REFLOG (1 << 12)
#define REF_LOG_USE_PROVIDED_OIDS (1 << 13)
#define REF_LOG_VIA_SPLIT (1 << 14)
#define REF_HAVE_PEELED (1 << 15)
So I guess it makes sense to use `(1 << 15)`.
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct
2026-04-24 16:44 ` Toon Claes
@ 2026-04-27 9:33 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 9:33 UTC (permalink / raw)
To: Toon Claes, git; +Cc: gitster, ps
[-- Attachment #1: Type: text/plain, Size: 5714 bytes --]
Toon Claes <toon@iotcl.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> Certain reference backend {packed, reftable}, have the ability to also
>
> Shouldn't it be:
>
> Certain reference backends {packed, reftable} have the ability to also
>
Oh yeah, thanks!
>> store the peeled object ID for a reference pointing to a tag object.
>> This has the added benefit that during retrieval of such references, we
>> also obtain the peeled object ID without having to use the ODB.
>>
>> To provide this functionality, each backend independently calls the ODB
>> to obtain the peeled OID. To move this functionality to the generic
>> layer, there must be support infrastructure to pass in a peeled OID for
>> reference updates.
>>
>> Add a `peeled` field to the `ref_update` structure and modify
>> `ref_transaction_add_update()` to receive and copy this object ID to the
>> `ref_update` structure. Finally, modify `ref_transaction_update()` to
>> peel tag objects and pass the peeled OID to
>> `ref_transaction_add_update()`.
>>
>> Update all callers of these functions with the new function parameters.
>> Callers which only add reflog updates, need to only pass in NULL, since
>> for reflogs, we don't store peeled OIDs. Reference deletions also only
>> need to pass in NULL. For others, pass along the peeled OID if
>> available.
>>
>> In a following commit, we'll modify the backends to use this peeled OID
>> instead of parsing it themselves.
>>
>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
>> ---
>> refs.c | 15 +++++++++++++--
>> refs/files-backend.c | 20 ++++++++++++--------
>> refs/refs-internal.h | 14 ++++++++++++++
>> refs/reftable-backend.c | 6 +++---
>> 4 files changed, 42 insertions(+), 13 deletions(-)
>>
>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
>> index d103387ebf..307dcb277b 100644
>> --- a/refs/refs-internal.h
>> +++ b/refs/refs-internal.h
>> @@ -39,6 +39,13 @@ struct ref_transaction;
>> */
>> #define REF_LOG_ONLY (1 << 7)
>>
>> +/*
>> + * The reference contains a peeled object ID. This is used when the
>> + * new_oid is pointing to a tag object and the reference backend
>> + * wants to also store the peeled value for optimized retrieval.
>> + */
>> +#define REF_HAVE_PEELED (1 << 15)
>
> How did you end up picking this value?
>
> I did some grepping to figure out if it would conflict with anything:
>
> git grep -h '#define REF_' -- '*.h' '*.c' |
> awk '/0x/{n=strtonum($3);b=0;while(n>1){n/=2;b++};$3="(1 << "b")"} 1' |
> sort -t'<' -k3 -n |
> column -t
>
> (Yeah I got some help from AI to write the `awk` command)
>
> Resulting in:
>
> #define REF_EXCLUSIONS_INIT { \
> #define REF_FILTER_H
> #define REF_FILTER_INIT { \
> #define REF_FORMAT_INIT { \
> #define REF_FORMATTING_STATE_INIT { 0 }
> #define REF_NO_DEREF (1 << 0)
> #define REF_NORMAL (1u << 0)
> #define REF_STATES_INIT { \
> #define REF_STORE_ALL_CAPS (REF_STORE_READ | \
> #define REF_STORE_CREATE_ON_DISK_IS_WORKTREE (1 << 0)
> #define REF_STORE_READ (1 << 0)
> #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
> #define REF_BRANCHES (1u << 1)
> #define REF_FORCE_CREATE_REFLOG (1 << 1)
> #define REF_STORE_WRITE (1 << 1) /* can perform update operations */
> #define REF_HAVE_NEW (1 << 2)
> #define REF_STORE_ODB (1 << 2) /* has access to object database */
> #define REF_TAGS (1u << 2)
> #define REF_HAVE_OLD (1 << 3)
> #define REF_STORE_MAIN (1 << 3)
> #define REF_DIR (1 << 4)
> #define REF_IS_PRUNING (1 << 4)
> #define REF_DELETING (1 << 5)
> #define REF_INCOMPLETE (1 << 5)
> #define REF_KNOWS_PEELED (1 << 6)
> #define REF_NEEDS_COMMIT (1 << 6)
> #define REF_LOG_ONLY (1 << 7)
> #define REF_UPDATE_VIA_HEAD (1 << 8)
> #define REF_UPDATE_VIA_HEAD (1 << 8)
> #define REF_DELETED_RMDIR (1 << 9)
> #define REF_SKIP_OID_VERIFICATION (1 << 10)
> #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)
> #define REF_SKIP_CREATE_REFLOG (1 << 12)
> #define REF_LOG_USE_PROVIDED_OIDS (1 << 13)
> #define REF_LOG_VIA_SPLIT (1 << 14)
> #define REF_HAVE_PEELED (1 << 15)
>
> So I guess it makes sense to use `(1 << 15)`.
I wish I has this command, I manually went through the individual files.
I think it is messy though, hopefully something to cleanup next.
#leftoverbits
> --
> Cheers,
> Toon
Thanks for the review, appreciate it..
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH v2 9/9] refs: use peeled tag values in reference backends
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (7 preceding siblings ...)
2026-04-23 8:40 ` [PATCH v2 8/9] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
@ 2026-04-23 8:40 ` Karthik Nayak
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-23 8:40 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, gitster, ps
The reference backends peel tag objects when storing references to them.
This is to provide optimized reads which avoids hitting the odb. The
previous commits ensures that the peeled value is now propagated via the
generic layer. So modify the packed and reftable backend to directly use
this value instead of calling `peel_object()` independently.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/packed-backend.c | 6 ++----
refs/reftable-backend.c | 9 ++-------
2 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 35a0f32e1c..0acde48c45 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
*/
i++;
} else {
- struct object_id peeled;
- int peel_error = peel_object(refs->base.repo, &update->new_oid,
- &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
+ bool peeled = update->flags & REF_HAVE_PEELED;
if (write_packed_entry(out, update->refname,
&update->new_oid,
- peel_error ? NULL : &peeled))
+ peeled ? &update->peeled : NULL))
goto write_error;
i++;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index b0c010387d..8b4ac2e618 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -12,7 +12,6 @@
#include "../hex.h"
#include "../ident.h"
#include "../iterator.h"
-#include "../object.h"
#include "../parse.h"
#include "../path.h"
#include "../refs.h"
@@ -1584,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
goto done;
} else if (u->flags & REF_HAVE_NEW) {
struct reftable_ref_record ref = {0};
- struct object_id peeled;
- int peel_error;
ref.refname = (char *)u->refname;
ref.update_index = ts;
- peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,
- PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
- if (!peel_error) {
+ if (u->flags & REF_HAVE_PEELED) {
ref.value_type = REFTABLE_REF_VAL2;
- memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);
+ memcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ);
memcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ);
} else if (!is_null_oid(&u->new_oid)) {
ref.value_type = REFTABLE_REF_VAL1;
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread
* [PATCH v3 0/9] refs: move some of the generic logic out of the backends
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
` (8 preceding siblings ...)
2026-04-23 8:40 ` [PATCH v2 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
` (8 more replies)
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
10 siblings, 9 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
This series came together while I was working on other reference related
code and realized that some of the individual logic implemented with the
reference backends can be moved to the generic layer.
Moving code to the generic layer, simplifies the responsibility of
individual backends and avoids deviation in logic between the backends.
The biggest changes are related to moving out usage of `parse_object()`
and `peel_object()` from reference transactions. The former is used to
validate that the OID provided points to a commit object. The latter is
an optimization technique where the packed/reftable backend store the
peeled OID whenever available, so reading such references provides the
peeled OID without having to call the ODB.
Moving object parsing to the generic layout involves moving it out of
the prepare stage of the transaction and into `ref_transaction_update()`
where every added update is checked. As such, this also involves
modifying update-ref(1) and receive-pack(1) to follow this paradigm.
---
Changes in v3:
- Remove an unwanted change which creeped up during a rebase.
- Add information in the commit message around how the order of errors
in git-update-ref(1) will change while maintaining functionality.
- Change up the order of an `if..else` to make it clearer.
- Other small typos and fixes.
- Link to v2: https://patch.msgid.link/20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com
Changes in v2:
- Split the second commit into two: one introducing
`ref_store_init_options` and the second to use it for reflog config.
- Use opts as the variable name consistently.
- A bunch of grammar fixes.
- Link to v1: https://patch.msgid.link/20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com
---
builtin/receive-pack.c | 22 ++++---
builtin/update-ref.c | 151 +++++++++++++++++++++++++++++++-----------------
refs.c | 60 ++++++++++++++-----
refs.h | 16 ++---
refs/files-backend.c | 58 +++++++------------
refs/packed-backend.c | 10 ++--
refs/packed-backend.h | 3 +-
refs/refs-internal.h | 35 +++++++++--
refs/reftable-backend.c | 40 +++----------
9 files changed, 231 insertions(+), 164 deletions(-)
Karthik Nayak (9):
refs: remove unused typedef 'ref_transaction_commit_fn'
refs: introduce `ref_store_init_options`
refs: extract out reflog config to generic layer
refs: return `ref_transaction_error` from `ref_transaction_update()`
update-ref: move `print_rejected_refs()` up
update-ref: handle rejections while adding updates
refs: move object parsing to the generic layer
refs: add peeled object ID to the `ref_update` struct
refs: use peeled tag values in reference backends
Range-diff versus v2:
1: b8ab8a6c8b = 1: 704a218bce refs: remove unused typedef 'ref_transaction_commit_fn'
2: dd419614e0 ! 2: f3e0caa8e4 refs: introduce `ref_store_init_options`
@@ refs/files-backend.c: static struct ref_store *files_ref_store_init(struct repos
refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
refs->packed_ref_store =
- packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
-+ packed_ref_store_init(repo, payload, refs->gitcommondir, opts);
++ packed_ref_store_init(repo, NULL, refs->gitcommondir, opts);
+ refs->store_flags = opts->access_flags;
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+
3: 337e5c8c5b ! 3: 48ebcc2438 refs: extract out reflog config to generic layer
@@ refs.c: static struct ref_store *ref_store_init(struct repository *repo,
## refs/files-backend.c ##
@@ refs/files-backend.c: static struct ref_store *files_ref_store_init(struct repository *repo,
refs->packed_ref_store =
- packed_ref_store_init(repo, payload, refs->gitcommondir, opts);
+ packed_ref_store_init(repo, NULL, refs->gitcommondir, opts);
refs->store_flags = opts->access_flags;
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->log_all_ref_updates = opts->log_all_ref_updates;
4: cdb4aad11e = 4: d4c120ab28 refs: return `ref_transaction_error` from `ref_transaction_update()`
5: 454549240a = 5: 6e3b16258c update-ref: move `print_rejected_refs()` up
6: 1f224fb868 ! 6: 505c5b8edb update-ref: handle rejections while adding updates
@@ Commit message
rejected, and also checking for rejections and only dying for generic
failures.
+ Errors encountered during updates will be shown to the user immediately
+ unlike other errors encountered only when the transaction is
+ prepared/committed. As the verification of object IDs and peeled tag
+ objects will move into `ref_transaction_update()` in the following
+ commit, this means that those errors will be shown to the user before
+ other errors, this changes the order of errors, but the functionality
+ remains the same.
+
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
## builtin/update-ref.c ##
@@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *trans
+ update_flags | create_reflog_flag,
+ msg, &err);
+
-+ if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
-+ opts->allow_update_failures) {
++ /*
++ * Generic errors are non-recoverable, so we cannot skip the update
++ * or mark it as rejected.
++ */
++ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
+ die("%s", err.buf);
+
++ if (tx_err && opts->allow_update_failures)
+ print_rejected_refs(refname, have_old ? &old_oid : NULL,
+ &new_oid, NULL, NULL, tx_err, err.buf,
+ NULL);
-+ } else if (tx_err) {
- die("%s", err.buf);
-+ }
-
++
update_flags = default_flags;
free(refname);
-@@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *transaction,
+ strbuf_release(&err);
}
static void parse_cmd_symref_update(struct ref_transaction *transaction,
@@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction
+ update_flags | create_reflog_flag,
+ msg, &err);
+
-+ if (tx_err && tx_err != REF_TRANSACTION_ERROR_GENERIC &&
-+ opts->allow_update_failures) {
++ /*
++ * Generic errors are non-recoverable, so we cannot skip the update
++ * or mark it as rejected.
++ */
++ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
+ die("%s", err.buf);
+
++ if (tx_err && opts->allow_update_failures)
+ print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
+ NULL, have_old_oid ? NULL : old_target,
+ new_target, tx_err, err.buf, NULL);
-+ } else if (tx_err) {
- die("%s", err.buf);
-+ }
-
++
update_flags = default_flags;
free(refname);
+ free(old_arg);
@@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction *transaction,
}
7: a52b019afa ! 7: ad1aae6b35 refs: move object parsing to the generic layer
@@ builtin/receive-pack.c: static const char *update(struct command *cmd, struct sh
- }
- else {
+ } else {
-+ enum ref_transaction_error err_type;
++ enum ref_transaction_error tx_err;
struct strbuf err = STRBUF_INIT;
if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si)) {
@@ builtin/receive-pack.c: static const char *update(struct command *cmd, struct sh
- NULL, NULL,
- 0, "push",
- &err)) {
-+ err_type = ref_transaction_update(transaction,
++ tx_err = ref_transaction_update(transaction,
+ namespaced_name,
+ new_oid, old_oid,
+ NULL, NULL,
+ 0, "push",
+ &err);
-+ if (err_type) {
++ if (tx_err) {
rp_error("%s", err.buf);
- ret = "failed to update ref";
-+ if (err_type == REF_TRANSACTION_ERROR_GENERIC)
++ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
+ ret = "failed to update ref";
+ else
-+ ret = ref_transaction_error_msg(err_type);
++ ret = ref_transaction_error_msg(tx_err);
} else {
ret = NULL; /* good */
}
8: aa5fd09831 ! 8: 5cb7ee9853 refs: add peeled object ID to the `ref_update` struct
@@ Metadata
## Commit message ##
refs: add peeled object ID to the `ref_update` struct
- Certain reference backend {packed, reftable}, have the ability to also
+ Certain reference backends {packed, reftable}, have the ability to also
store the peeled object ID for a reference pointing to a tag object.
This has the added benefit that during retrieval of such references, we
also obtain the peeled object ID without having to use the ODB.
9: 96146b5083 = 9: ba34e10548 refs: use peeled tag values in reference backends
base-commit: f65aba1e87db64413b6d1ed5ae5a45b5a84a0997
change-id: 20260417-refs-move-to-generic-layer-f7525c5e8764
Thanks
- Karthik
^ permalink raw reply [flat|nested] 68+ messages in thread* [PATCH v3 1/9] refs: remove unused typedef 'ref_transaction_commit_fn'
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 2/9] refs: introduce `ref_store_init_options` Karthik Nayak
` (7 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
The typedef 'ref_transaction_commit_fn' is not used anywhere in our
code, let's remove it.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/refs-internal.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index d79e35fd26..2d963cc4f4 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,
struct ref_transaction *transaction,
struct strbuf *err);
-typedef int ref_transaction_commit_fn(struct ref_store *refs,
- struct ref_transaction *transaction,
- struct strbuf *err);
-
typedef int optimize_fn(struct ref_store *ref_store,
struct refs_optimize_opts *opts);
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v3 2/9] refs: introduce `ref_store_init_options`
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 3/9] refs: extract out reflog config to generic layer Karthik Nayak
` (6 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
Reference backends are initiated via the `init()` function. When
initiating the function, the backend is also provided flags which denote
the access levels of the initiator. Create a new structure
`ref_store_init_options` to house such options and move the access flags
to this structure.
This allows easier extension of providing further options to the
backends. In the following commit, we'll also provide config around
reflog creation to the backends via the same structure.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 6 +++++-
refs/files-backend.c | 8 +++++---
refs/packed-backend.c | 4 ++--
refs/packed-backend.h | 3 ++-
refs/refs-internal.h | 11 ++++++++++-
refs/reftable-backend.c | 4 ++--
6 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/refs.c b/refs.c
index bfcb9c7ac3..8992dd6ae8 100644
--- a/refs.c
+++ b/refs.c
@@ -2295,6 +2295,9 @@ static struct ref_store *ref_store_init(struct repository *repo,
{
const struct ref_storage_be *be;
struct ref_store *refs;
+ struct ref_store_init_options opts = {
+ .access_flags = flags,
+ };
be = find_ref_storage_backend(format);
if (!be)
@@ -2304,7 +2307,8 @@ static struct ref_store *ref_store_init(struct repository *repo,
* TODO Send in a 'struct worktree' instead of a 'gitdir', and
* allow the backend to handle how it wants to deal with worktrees.
*/
- refs = be->init(repo, repo->ref_storage_payload, gitdir, flags);
+ refs = be->init(repo, repo->ref_storage_payload, gitdir, &opts);
+
return refs;
}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index b3b0c25f84..72afe62cee 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -108,7 +108,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
static struct ref_store *files_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int flags)
+ const struct ref_store_init_options *opts)
{
struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
@@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
&ref_common_dir);
base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);
- refs->store_flags = flags;
+
refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
refs->packed_ref_store =
- packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
+ packed_ref_store_init(repo, NULL, refs->gitcommondir, opts);
+ refs->store_flags = opts->access_flags;
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 23ed62984b..35a0f32e1c 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload UNUSED,
const char *gitdir,
- unsigned int store_flags)
+ const struct ref_store_init_options *opts)
{
struct packed_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
struct strbuf sb = STRBUF_INIT;
base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);
- refs->store_flags = store_flags;
+ refs->store_flags = opts->access_flags;
strbuf_addf(&sb, "%s/packed-refs", gitdir);
refs->path = strbuf_detach(&sb, NULL);
diff --git a/refs/packed-backend.h b/refs/packed-backend.h
index 2c2377a356..1db48e801d 100644
--- a/refs/packed-backend.h
+++ b/refs/packed-backend.h
@@ -3,6 +3,7 @@
struct repository;
struct ref_transaction;
+struct ref_store_init_options;
/*
* Support for storing references in a `packed-refs` file.
@@ -16,7 +17,7 @@ struct ref_transaction;
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int store_flags);
+ const struct ref_store_init_options *options);
/*
* Lock the packed-refs file for writing. Flags is passed to
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2d963cc4f4..f49b3807bf 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -385,6 +385,15 @@ struct ref_store;
REF_STORE_ODB | \
REF_STORE_MAIN)
+/*
+ * Options for initializing the ref backend. All backend-agnostic information
+ * which backends required will be held here.
+ */
+struct ref_store_init_options {
+ /* The kind of operations that the ref_store is allowed to perform. */
+ unsigned int access_flags;
+};
+
/*
* Initialize the ref_store for the specified gitdir. These functions
* should call base_ref_store_init() to initialize the shared part of
@@ -393,7 +402,7 @@ struct ref_store;
typedef struct ref_store *ref_store_init_fn(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int flags);
+ const struct ref_store_init_options *opts);
/*
* Release all memory and resources associated with the ref store.
*/
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index daea30a5b4..ad4ee2627c 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -369,7 +369,7 @@ static int reftable_be_config(const char *var, const char *value,
static struct ref_store *reftable_be_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int store_flags)
+ const struct ref_store_init_options *opts)
{
struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
struct strbuf ref_common_dir = STRBUF_INIT;
@@ -386,8 +386,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
- refs->store_flags = store_flags;
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->store_flags = opts->access_flags;
switch (repo->hash_algo->format_id) {
case GIT_SHA1_FORMAT_ID:
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v3 3/9] refs: extract out reflog config to generic layer
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 2/9] refs: introduce `ref_store_init_options` Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
` (5 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
The reference backends need to know when to create reflog entries, this
is dictated by the 'core.logallrefupdates' config. Instead of relying on
the backends to call `repo_settings_get_log_all_ref_updates()` to obtain
this config value, let's do this in the generic layer and pass down the
value to the backends.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 1 +
refs/files-backend.c | 2 +-
refs/refs-internal.h | 6 ++++++
refs/reftable-backend.c | 2 +-
4 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 8992dd6ae8..6b506aeea3 100644
--- a/refs.c
+++ b/refs.c
@@ -2297,6 +2297,7 @@ static struct ref_store *ref_store_init(struct repository *repo,
struct ref_store *refs;
struct ref_store_init_options opts = {
.access_flags = flags,
+ .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),
};
be = find_ref_storage_backend(format);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 72afe62cee..4b2faf4777 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -125,7 +125,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
refs->packed_ref_store =
packed_ref_store_init(repo, NULL, refs->gitcommondir, opts);
refs->store_flags = opts->access_flags;
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->log_all_ref_updates = opts->log_all_ref_updates;
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index f49b3807bf..d103387ebf 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -392,6 +392,12 @@ struct ref_store;
struct ref_store_init_options {
/* The kind of operations that the ref_store is allowed to perform. */
unsigned int access_flags;
+
+ /*
+ * Denotes under what conditions reflogs should be created when updating
+ * references.
+ */
+ enum log_refs_config log_all_ref_updates;
};
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index ad4ee2627c..93374d25c2 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -386,7 +386,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->log_all_ref_updates = opts->log_all_ref_updates;
refs->store_flags = opts->access_flags;
switch (repo->hash_algo->format_id) {
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v3 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (2 preceding siblings ...)
2026-04-27 10:42 ` [PATCH v3 3/9] refs: extract out reflog config to generic layer Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 5/9] update-ref: move `print_rejected_refs()` up Karthik Nayak
` (4 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
The `ref_transaction_update()` function is used to add updates to a
given reference transactions. In the following commit, we'll add more
validation to this function. As such, it would be beneficial if the
function returns specific error types, so callers can differentiate
between different errors.
To facilitate this, return `enum ref_transaction_error` from the
function and covert the existing '-1' returns to
'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing
behavior, no changes are made to any of the callers but this sets the
necessary infrastructure for introduction of other errors.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 20 ++++++++++----------
refs.h | 16 ++++++++--------
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/refs.c b/refs.c
index 6b506aeea3..efa16b739d 100644
--- a/refs.c
+++ b/refs.c
@@ -1383,25 +1383,25 @@ static int transaction_refname_valid(const char *refname,
return 1;
}
-int ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- const char *new_target,
- const char *old_target,
- unsigned int flags, const char *msg,
- struct strbuf *err)
+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ unsigned int flags, const char *msg,
+ struct strbuf *err)
{
assert(err);
if ((flags & REF_FORCE_CREATE_REFLOG) &&
(flags & REF_SKIP_CREATE_REFLOG)) {
strbuf_addstr(err, _("refusing to force and skip creation of reflog"));
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
if (!transaction_refname_valid(refname, new_oid, flags, err))
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
diff --git a/refs.h b/refs.h
index d65de6ab5f..71d5c186d0 100644
--- a/refs.h
+++ b/refs.h
@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
* See the above comment "Reference transaction updates" for more
* information.
*/
-int ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- const char *new_target,
- const char *old_target,
- unsigned int flags, const char *msg,
- struct strbuf *err);
+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ unsigned int flags, const char *msg,
+ struct strbuf *err);
/*
* Similar to `ref_transaction_update`, but this function is only for adding
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v3 5/9] update-ref: move `print_rejected_refs()` up
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (3 preceding siblings ...)
2026-04-27 10:42 ` [PATCH v3 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 6/9] update-ref: handle rejections while adding updates Karthik Nayak
` (3 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
The `print_rejected_refs()` function is used to print any rejected refs
when using git-updated-ref(1) with the '--batch-updates' option. In the
following commit, we'll need to use this function in another place, so
move the function up to avoid a separate forward declaration.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 45 ++++++++++++++++++++++-----------------------
1 file changed, 22 insertions(+), 23 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 2d68c40ecb..5259cc7226 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -234,6 +234,28 @@ static int parse_next_oid(const char **next, const char *end,
command, refname);
}
+static void print_rejected_refs(const char *refname,
+ const struct object_id *old_oid,
+ const struct object_id *new_oid,
+ const char *old_target,
+ const char *new_target,
+ enum ref_transaction_error err,
+ const char *details,
+ void *cb_data UNUSED)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ if (details && *details)
+ error("%s", details);
+
+ strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
+ new_oid ? oid_to_hex(new_oid) : new_target,
+ old_oid ? oid_to_hex(old_oid) : old_target,
+ ref_transaction_error_msg(err));
+
+ fwrite(sb.buf, sb.len, 1, stdout);
+ strbuf_release(&sb);
+}
/*
* The following five parse_cmd_*() functions parse the corresponding
@@ -567,29 +589,6 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
report_ok("abort");
}
-static void print_rejected_refs(const char *refname,
- const struct object_id *old_oid,
- const struct object_id *new_oid,
- const char *old_target,
- const char *new_target,
- enum ref_transaction_error err,
- const char *details,
- void *cb_data UNUSED)
-{
- struct strbuf sb = STRBUF_INIT;
-
- if (details && *details)
- error("%s", details);
-
- strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
- new_oid ? oid_to_hex(new_oid) : new_target,
- old_oid ? oid_to_hex(old_oid) : old_target,
- ref_transaction_error_msg(err));
-
- fwrite(sb.buf, sb.len, 1, stdout);
- strbuf_release(&sb);
-}
-
static void parse_cmd_commit(struct ref_transaction *transaction,
const char *next, const char *end UNUSED)
{
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v3 6/9] update-ref: handle rejections while adding updates
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (4 preceding siblings ...)
2026-04-27 10:42 ` [PATCH v3 5/9] update-ref: move `print_rejected_refs()` up Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-29 12:24 ` Toon Claes
2026-04-27 10:42 ` [PATCH v3 7/9] refs: move object parsing to the generic layer Karthik Nayak
` (2 subsequent siblings)
8 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
When using git-update-ref(1) with the '--batch-updates' flag, updates
rejected by the reference backend are displayed to the user while other
updates are applied. This only applies during the commit phase of the
transaction.
In the following commits, we'll also extend `ref_transaction_update()`
to reject updates before a transaction is prepared/committed. In
preparation, modify the code in update-ref to also handle non-generic
rejections from `ref_transaction_update()`. This involves propagating
information to each of the commands on whether updates are allowed to be
rejected, and also checking for rejections and only dying for generic
failures.
Errors encountered during updates will be shown to the user immediately
unlike other errors encountered only when the transaction is
prepared/committed. As the verification of object IDs and peeled tag
objects will move into `ref_transaction_update()` in the following
commit, this means that those errors will be shown to the user before
other errors, this changes the order of errors, but the functionality
remains the same.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 106 +++++++++++++++++++++++++++++++++++++--------------
1 file changed, 77 insertions(+), 29 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 5259cc7226..348b7fec94 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -25,6 +25,15 @@ static unsigned int default_flags;
static unsigned create_reflog_flag;
static const char *msg;
+struct command_options {
+ /*
+ * Individual updates are allowed to fail without causing
+ * update-ref to exit. This is set when using the
+ * '--batch-updates' flag.
+ */
+ bool allow_update_failures;
+};
+
/*
* Parse one whitespace- or NUL-terminated, possibly C-quoted argument
* and append the result to arg. Return a pointer to the terminator.
@@ -268,11 +277,13 @@ static void print_rejected_refs(const char *refname,
*/
static void parse_cmd_update(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid, old_oid;
+ enum ref_transaction_error tx_err;
int have_old;
refname = parse_refname(&next);
@@ -289,22 +300,35 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname,
- &new_oid, have_old ? &old_oid : NULL,
- NULL, NULL,
- update_flags | create_reflog_flag,
- msg, &err))
+ tx_err = ref_transaction_update(transaction, refname,
+ &new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
+ update_flags | create_reflog_flag,
+ msg, &err);
+
+ /*
+ * Generic errors are non-recoverable, so we cannot skip the update
+ * or mark it as rejected.
+ */
+ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
die("%s", err.buf);
+ if (tx_err && opts->allow_update_failures)
+ print_rejected_refs(refname, have_old ? &old_oid : NULL,
+ &new_oid, NULL, NULL, tx_err, err.buf,
+ NULL);
+
update_flags = default_flags;
free(refname);
strbuf_release(&err);
}
static void parse_cmd_symref_update(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts)
{
char *refname, *new_target, *old_arg;
+ enum ref_transaction_error tx_err;
char *old_target = NULL;
struct strbuf err = STRBUF_INIT;
struct object_id old_oid;
@@ -341,14 +365,25 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("symref-update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname, NULL,
- have_old_oid ? &old_oid : NULL,
- new_target,
- have_old_oid ? NULL : old_target,
- update_flags | create_reflog_flag,
- msg, &err))
+ tx_err = ref_transaction_update(transaction, refname, NULL,
+ have_old_oid ? &old_oid : NULL,
+ new_target,
+ have_old_oid ? NULL : old_target,
+ update_flags | create_reflog_flag,
+ msg, &err);
+
+ /*
+ * Generic errors are non-recoverable, so we cannot skip the update
+ * or mark it as rejected.
+ */
+ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
die("%s", err.buf);
+ if (tx_err && opts->allow_update_failures)
+ print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
+ NULL, have_old_oid ? NULL : old_target,
+ new_target, tx_err, err.buf, NULL);
+
update_flags = default_flags;
free(refname);
free(old_arg);
@@ -358,7 +393,8 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
}
static void parse_cmd_create(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -387,9 +423,9 @@ static void parse_cmd_create(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_symref_create(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname, *new_target;
@@ -417,7 +453,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,
}
static void parse_cmd_delete(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -450,9 +487,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_symref_delete(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname, *old_target;
@@ -479,9 +516,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_verify(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -508,7 +545,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
}
static void parse_cmd_symref_verify(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
struct object_id old_oid;
@@ -550,7 +588,8 @@ static void report_ok(const char *command)
}
static void parse_cmd_option(struct ref_transaction *transaction UNUSED,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
const char *rest;
if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination)
@@ -560,7 +599,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED,
}
static void parse_cmd_start(struct ref_transaction *transaction UNUSED,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
if (*next != line_termination)
die("start: extra input: %s", next);
@@ -568,7 +608,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED,
}
static void parse_cmd_prepare(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -579,7 +620,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,
}
static void parse_cmd_abort(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -590,7 +632,8 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
}
static void parse_cmd_commit(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -618,7 +661,8 @@ enum update_refs_state {
static const struct parse_cmd {
const char *prefix;
- void (*fn)(struct ref_transaction *, const char *, const char *);
+ void (*fn)(struct ref_transaction *, const char *, const char *,
+ struct command_options *);
unsigned args;
enum update_refs_state state;
} command[] = {
@@ -644,6 +688,10 @@ static void update_refs_stdin(unsigned int flags)
struct ref_transaction *transaction;
int i, j;
+ struct command_options opts = {
+ .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,
+ };
+
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
flags, &err);
if (!transaction)
@@ -721,7 +769,7 @@ static void update_refs_stdin(unsigned int flags)
}
cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,
- input.buf + input.len);
+ input.buf + input.len, &opts);
}
switch (state) {
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH v3 6/9] update-ref: handle rejections while adding updates
2026-04-27 10:42 ` [PATCH v3 6/9] update-ref: handle rejections while adding updates Karthik Nayak
@ 2026-04-29 12:24 ` Toon Claes
2026-04-30 9:52 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Toon Claes @ 2026-04-29 12:24 UTC (permalink / raw)
To: Karthik Nayak, git; +Cc: Karthik Nayak, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> @@ -289,22 +300,35 @@ static void parse_cmd_update(struct ref_transaction *transaction,
> if (*next != line_termination)
> die("update %s: extra input: %s", refname, next);
>
> - if (ref_transaction_update(transaction, refname,
> - &new_oid, have_old ? &old_oid : NULL,
> - NULL, NULL,
> - update_flags | create_reflog_flag,
> - msg, &err))
> + tx_err = ref_transaction_update(transaction, refname,
> + &new_oid, have_old ? &old_oid : NULL,
> + NULL, NULL,
> + update_flags | create_reflog_flag,
> + msg, &err);
> +
> + /*
> + * Generic errors are non-recoverable, so we cannot skip the update
> + * or mark it as rejected.
> + */
> + if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
> die("%s", err.buf);
>
> + if (tx_err && opts->allow_update_failures)
> + print_rejected_refs(refname, have_old ? &old_oid : NULL,
> + &new_oid, NULL, NULL, tx_err, err.buf,
> + NULL);
I realize I've made this suggestion, but I think I've made a mistake.
When opts->allow_update_failures is falsey and tx_err is truthy we
should die also. Don't we?
I'm not sure what the nicest way is to write this, but maybe:
if (tx_err) {
if (tx_err == REF_TRANSACTION_ERROR_GENERIC || !opts->allow_update_failures)
die("%s", err.buf);
print_rejected_refs(refname, have_old ? &old_oid : NULL,
&new_oid, NULL, NULL, tx_err, err.buf,
NULL);
}
How did test coverage not find this?
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH v3 6/9] update-ref: handle rejections while adding updates
2026-04-29 12:24 ` Toon Claes
@ 2026-04-30 9:52 ` Karthik Nayak
0 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-30 9:52 UTC (permalink / raw)
To: Toon Claes, git; +Cc: ps
[-- Attachment #1: Type: text/plain, Size: 1936 bytes --]
Toon Claes <toon@iotcl.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> @@ -289,22 +300,35 @@ static void parse_cmd_update(struct ref_transaction *transaction,
>> if (*next != line_termination)
>> die("update %s: extra input: %s", refname, next);
>>
>> - if (ref_transaction_update(transaction, refname,
>> - &new_oid, have_old ? &old_oid : NULL,
>> - NULL, NULL,
>> - update_flags | create_reflog_flag,
>> - msg, &err))
>> + tx_err = ref_transaction_update(transaction, refname,
>> + &new_oid, have_old ? &old_oid : NULL,
>> + NULL, NULL,
>> + update_flags | create_reflog_flag,
>> + msg, &err);
>> +
>> + /*
>> + * Generic errors are non-recoverable, so we cannot skip the update
>> + * or mark it as rejected.
>> + */
>> + if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
>> die("%s", err.buf);
>>
>> + if (tx_err && opts->allow_update_failures)
>> + print_rejected_refs(refname, have_old ? &old_oid : NULL,
>> + &new_oid, NULL, NULL, tx_err, err.buf,
>> + NULL);
>
> I realize I've made this suggestion, but I think I've made a mistake.
> When opts->allow_update_failures is falsey and tx_err is truthy we
> should die also. Don't we?
>
Nice. I didn't think of that either.
> I'm not sure what the nicest way is to write this, but maybe:
>
> if (tx_err) {
> if (tx_err == REF_TRANSACTION_ERROR_GENERIC || !opts->allow_update_failures)
> die("%s", err.buf);
>
> print_rejected_refs(refname, have_old ? &old_oid : NULL,
> &new_oid, NULL, NULL, tx_err, err.buf,
> NULL);
> }
>
> How did test coverage not find this?
>
Because:
1. The function only returns `REF_TRANSACTION_ERROR_GENERIC` as of this
commit.
2. We only seem to be testing this scenario for batched updates.
3. I'll fix this and add some tests.
> --
> Cheers,
> Toon
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH v3 7/9] refs: move object parsing to the generic layer
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (5 preceding siblings ...)
2026-04-27 10:42 ` [PATCH v3 6/9] update-ref: handle rejections while adding updates Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 8/9] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 9/9] refs: use peeled tag values in reference backends Karthik Nayak
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
Regular reference updates made via reference transactions validate that
the provided object ID exists in the object database, which is done by
calling 'parse_object()'. This check is done independently by the
backends which leads to duplicated logic.
Let's move this to the generic layer, ensuring the backends only have to
care about reference storage and not about validation of the object IDs.
With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'
error type as its no longer used.
Since we don't iterate over individual references in
`ref_transaction_prepare()`, we add this check to
`ref_transaction_update()`. This means that the validation is done as
soon as an update is queued, without needing to prepare the
transaction. It can be argued that this is more ideal, since this
validation has no dependency on the reference transaction being
prepared.
It must be noted that the change in behavior means that this error
cannot be ignored even with usage of batched updates, since this happens
when the update is being added to the transaction. But since the caller
gets specific error codes, they can either abort the transaction or
continue adding other updates to the transaction.
Modify 'builtin/receive-pack.c' to now capture the error type so that
the error propagated to the client stays the same. Also remove two of
the tests which validates batch-updates with invalid new_oid.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/receive-pack.c | 22 +++++++++++++---------
refs.c | 18 ++++++++++++++++++
refs/files-backend.c | 28 ++--------------------------
refs/reftable-backend.c | 19 -------------------
4 files changed, 33 insertions(+), 54 deletions(-)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 878aa7f0ed..376e755e97 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
ret = NULL; /* good */
}
strbuf_release(&err);
- }
- else {
+ } else {
+ enum ref_transaction_error tx_err;
struct strbuf err = STRBUF_INIT;
if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si)) {
@@ -1650,14 +1650,18 @@ static const char *update(struct command *cmd, struct shallow_info *si)
goto out;
}
- if (ref_transaction_update(transaction,
- namespaced_name,
- new_oid, old_oid,
- NULL, NULL,
- 0, "push",
- &err)) {
+ tx_err = ref_transaction_update(transaction,
+ namespaced_name,
+ new_oid, old_oid,
+ NULL, NULL,
+ 0, "push",
+ &err);
+ if (tx_err) {
rp_error("%s", err.buf);
- ret = "failed to update ref";
+ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
+ ret = "failed to update ref";
+ else
+ ret = ref_transaction_error_msg(tx_err);
} else {
ret = NULL; /* good */
}
diff --git a/refs.c b/refs.c
index efa16b739d..662a9e6f9e 100644
--- a/refs.c
+++ b/refs.c
@@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
+ if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&
+ !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {
+ struct object *o = parse_object(transaction->ref_store->repo, new_oid);
+
+ if (!o) {
+ strbuf_addf(err,
+ _("trying to write ref '%s' with nonexistent object %s"),
+ refname, oid_to_hex(new_oid));
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+
+ if (o->type != OBJ_COMMIT && is_branch(refname)) {
+ strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
+ oid_to_hex(new_oid), refname);
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+ }
+
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
old_target, NULL, msg);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 4b2faf4777..f20f580fbc 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -19,7 +19,6 @@
#include "../iterator.h"
#include "../dir-iterator.h"
#include "../lockfile.h"
-#include "../object.h"
#include "../path.h"
#include "../dir.h"
#include "../chdir-notify.h"
@@ -1589,7 +1588,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid,
- int skip_oid_verification,
struct strbuf *err);
static int commit_ref_update(struct files_ref_store *refs,
struct ref_lock *lock,
@@ -1737,7 +1735,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
}
oidcpy(&lock->old_oid, &orig_oid);
- if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||
+ if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||
commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) {
error("unable to write current sha1 into %s: %s", newrefname, err.buf);
strbuf_release(&err);
@@ -1755,7 +1753,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
goto rollbacklog;
}
- if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||
+ if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||
commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) {
error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
strbuf_release(&err);
@@ -1999,32 +1997,11 @@ static int files_log_ref_write(struct files_ref_store *refs,
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid,
- int skip_oid_verification,
struct strbuf *err)
{
static char term = '\n';
- struct object *o;
int fd;
- if (!skip_oid_verification) {
- o = parse_object(refs->base.repo, oid);
- if (!o) {
- strbuf_addf(
- err,
- "trying to write ref '%s' with nonexistent object %s",
- lock->ref_name, oid_to_hex(oid));
- unlock_ref(lock);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
- strbuf_addf(
- err,
- "trying to write non-commit object %s to branch '%s'",
- oid_to_hex(oid), lock->ref_name);
- unlock_ref(lock);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- }
fd = get_lock_file_fd(&lock->lk);
if (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 ||
write_in_full(fd, &term, 1) < 0 ||
@@ -2828,7 +2805,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
} else {
ret = write_ref_to_lockfile(
refs, lock, &update->new_oid,
- update->flags & REF_SKIP_OID_VERIFICATION,
err);
if (ret) {
char *write_err = strbuf_detach(err, NULL);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 93374d25c2..444b0c24e5 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1081,25 +1081,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
return 0;
}
- /* Verify that the new object ID is valid. */
- if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&
- !(u->flags & REF_SKIP_OID_VERIFICATION) &&
- !(u->flags & REF_LOG_ONLY)) {
- struct object *o = parse_object(refs->base.repo, &u->new_oid);
- if (!o) {
- strbuf_addf(err,
- _("trying to write ref '%s' with nonexistent object %s"),
- u->refname, oid_to_hex(&u->new_oid));
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
-
- if (o->type != OBJ_COMMIT && is_branch(u->refname)) {
- strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
- oid_to_hex(&u->new_oid), u->refname);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- }
-
/*
* When we update the reference that HEAD points to we enqueue
* a second log-only update for HEAD so that its reflog is
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v3 8/9] refs: add peeled object ID to the `ref_update` struct
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (6 preceding siblings ...)
2026-04-27 10:42 ` [PATCH v3 7/9] refs: move object parsing to the generic layer Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
2026-04-27 10:42 ` [PATCH v3 9/9] refs: use peeled tag values in reference backends Karthik Nayak
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
Certain reference backends {packed, reftable}, have the ability to also
store the peeled object ID for a reference pointing to a tag object.
This has the added benefit that during retrieval of such references, we
also obtain the peeled object ID without having to use the ODB.
To provide this functionality, each backend independently calls the ODB
to obtain the peeled OID. To move this functionality to the generic
layer, there must be support infrastructure to pass in a peeled OID for
reference updates.
Add a `peeled` field to the `ref_update` structure and modify
`ref_transaction_add_update()` to receive and copy this object ID to the
`ref_update` structure. Finally, modify `ref_transaction_update()` to
peel tag objects and pass the peeled OID to
`ref_transaction_add_update()`.
Update all callers of these functions with the new function parameters.
Callers which only add reflog updates, need to only pass in NULL, since
for reflogs, we don't store peeled OIDs. Reference deletions also only
need to pass in NULL. For others, pass along the peeled OID if
available.
In a following commit, we'll modify the backends to use this peeled OID
instead of parsing it themselves.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 15 +++++++++++++--
refs/files-backend.c | 20 ++++++++++++--------
refs/refs-internal.h | 14 ++++++++++++++
refs/reftable-backend.c | 6 +++---
4 files changed, 42 insertions(+), 13 deletions(-)
diff --git a/refs.c b/refs.c
index 662a9e6f9e..0648df2b6c 100644
--- a/refs.c
+++ b/refs.c
@@ -1307,6 +1307,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const struct object_id *peeled,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg)
@@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update(
update->committer_info = xstrdup_or_null(committer_info);
update->msg = normalize_reflog_message(msg);
}
+ if (flags & REF_HAVE_PEELED)
+ oidcpy(&update->peeled, peeled);
/*
* This list is generally used by the backends to avoid duplicates.
@@ -1392,6 +1395,8 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
unsigned int flags, const char *msg,
struct strbuf *err)
{
+ struct object_id peeled;
+
assert(err);
if ((flags & REF_FORCE_CREATE_REFLOG) &&
@@ -1432,10 +1437,16 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
oid_to_hex(new_oid), refname);
return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
}
+
+ if (o->type == OBJ_TAG) {
+ if (!peel_object(transaction->ref_store->repo, new_oid, &peeled,
+ PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))
+ flags |= REF_HAVE_PEELED;
+ }
}
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, new_target,
+ new_oid, old_oid, &peeled, new_target,
old_target, NULL, msg);
return 0;
@@ -1462,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction,
return -1;
update = ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, NULL, NULL,
+ new_oid, old_oid, NULL, NULL, NULL,
committer_info, msg);
update->index = index;
diff --git a/refs/files-backend.c b/refs/files-backend.c
index f20f580fbc..d0896d0e37 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1325,7 +1325,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);
+ null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,
+ NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -2468,7 +2469,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,
- &update->new_oid, &update->old_oid,
+ &update->new_oid, &update->old_oid, &update->peeled,
NULL, NULL, update->committer_info, update->msg);
new_update->parent_update = update;
@@ -2530,8 +2531,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
transaction, referent, new_flags,
update->new_target ? NULL : &update->new_oid,
update->old_target ? NULL : &update->old_oid,
- update->new_target, update->old_target, NULL,
- update->msg);
+ &update->peeled, update->new_target, update->old_target,
+ NULL, update->msg);
new_update->parent_update = update;
@@ -2994,7 +2995,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
ref_transaction_add_update(
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
- &update->new_oid, NULL,
+ &update->new_oid, NULL, NULL,
NULL, NULL, NULL, NULL);
}
}
@@ -3200,19 +3201,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
if (update->flags & REF_LOG_ONLY)
ref_transaction_add_update(loose_transaction, update->refname,
update->flags, &update->new_oid,
- &update->old_oid, NULL, NULL,
+ &update->old_oid, &update->peeled,
+ NULL, NULL,
update->committer_info, update->msg);
else
ref_transaction_add_update(loose_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
update->new_target ? NULL : &update->new_oid, NULL,
- update->new_target, NULL, update->committer_info,
+ &update->peeled, update->new_target,
+ NULL, update->committer_info,
NULL);
} else {
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL, NULL, update->committer_info, NULL);
+ &update->peeled, NULL, NULL,
+ update->committer_info, NULL);
}
}
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index d103387ebf..307dcb277b 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -39,6 +39,13 @@ struct ref_transaction;
*/
#define REF_LOG_ONLY (1 << 7)
+/*
+ * The reference contains a peeled object ID. This is used when the
+ * new_oid is pointing to a tag object and the reference backend
+ * wants to also store the peeled value for optimized retrieval.
+ */
+#define REF_HAVE_PEELED (1 << 15)
+
/*
* Return the length of time to retry acquiring a loose reference lock
* before giving up, in milliseconds:
@@ -92,6 +99,12 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If the new_oid points to a tag object, set this to the peeled
+ * object ID for optimized retrieval without needed to hit the odb.
+ */
+ struct object_id peeled;
+
/*
* If set, point the reference to this value. This can also be
* used to convert regular references to become symbolic refs.
@@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const struct object_id *peeled,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 444b0c24e5..b0c010387d 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1107,8 +1107,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, NULL, NULL, NULL,
- u->msg);
+ &u->new_oid, &u->old_oid, &u->peeled, NULL, NULL,
+ NULL, u->msg);
}
ret = reftable_backend_read_ref(be, rewritten_ref,
@@ -1194,7 +1194,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
transaction, referent->buf, new_flags,
u->new_target ? NULL : &u->new_oid,
u->old_target ? NULL : &u->old_oid,
- u->new_target, u->old_target,
+ &u->peeled, u->new_target, u->old_target,
u->committer_info, u->msg);
new_update->parent_update = u;
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v3 9/9] refs: use peeled tag values in reference backends
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (7 preceding siblings ...)
2026-04-27 10:42 ` [PATCH v3 8/9] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
@ 2026-04-27 10:42 ` Karthik Nayak
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-04-27 10:42 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, ps, toon
The reference backends peel tag objects when storing references to them.
This is to provide optimized reads which avoids hitting the odb. The
previous commits ensures that the peeled value is now propagated via the
generic layer. So modify the packed and reftable backend to directly use
this value instead of calling `peel_object()` independently.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/packed-backend.c | 6 ++----
refs/reftable-backend.c | 9 ++-------
2 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 35a0f32e1c..0acde48c45 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
*/
i++;
} else {
- struct object_id peeled;
- int peel_error = peel_object(refs->base.repo, &update->new_oid,
- &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
+ bool peeled = update->flags & REF_HAVE_PEELED;
if (write_packed_entry(out, update->refname,
&update->new_oid,
- peel_error ? NULL : &peeled))
+ peeled ? &update->peeled : NULL))
goto write_error;
i++;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index b0c010387d..8b4ac2e618 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -12,7 +12,6 @@
#include "../hex.h"
#include "../ident.h"
#include "../iterator.h"
-#include "../object.h"
#include "../parse.h"
#include "../path.h"
#include "../refs.h"
@@ -1584,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
goto done;
} else if (u->flags & REF_HAVE_NEW) {
struct reftable_ref_record ref = {0};
- struct object_id peeled;
- int peel_error;
ref.refname = (char *)u->refname;
ref.update_index = ts;
- peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,
- PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
- if (!peel_error) {
+ if (u->flags & REF_HAVE_PEELED) {
ref.value_type = REFTABLE_REF_VAL2;
- memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);
+ memcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ);
memcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ);
} else if (!is_null_oid(&u->new_oid)) {
ref.value_type = REFTABLE_REF_VAL1;
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread
* [PATCH v4 0/9] refs: move some of the generic logic out of the backends
2026-04-20 10:11 [PATCH 0/8] refs: move some of the generic logic out of the backends Karthik Nayak
` (9 preceding siblings ...)
2026-04-27 10:42 ` [PATCH v3 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
` (8 more replies)
10 siblings, 9 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
This series came together while I was working on other reference related
code and realized that some of the individual logic implemented with the
reference backends can be moved to the generic layer.
Moving code to the generic layer, simplifies the responsibility of
individual backends and avoids deviation in logic between the backends.
The biggest changes are related to moving out usage of `parse_object()`
and `peel_object()` from reference transactions. The former is used to
validate that the OID provided points to a commit object. The latter is
an optimization technique where the packed/reftable backend store the
peeled OID whenever available, so reading such references provides the
peeled OID without having to call the ODB.
Moving object parsing to the generic layout involves moving it out of
the prepare stage of the transaction and into `ref_transaction_update()`
where every added update is checked. As such, this also involves
modifying update-ref(1) and receive-pack(1) to follow this paradigm.
---
Changes in v4:
- Fix a bug in the error handling code, move it to a function and also
add missing call sites. Add tests to ensure we catch this.
- Link to v3: https://patch.msgid.link/20260427-refs-move-to-generic-layer-v3-0-e4638dfb7897@gmail.com
Changes in v3:
- Remove an unwanted change which creeped up during a rebase.
- Add information in the commit message around how the order of errors
in git-update-ref(1) will change while maintaining functionality.
- Change up the order of an `if..else` to make it clearer.
- Other small typos and fixes.
- Link to v2: https://patch.msgid.link/20260423-refs-move-to-generic-layer-v2-0-ae5a4f146d7d@gmail.com
Changes in v2:
- Split the second commit into two: one introducing
`ref_store_init_options` and the second to use it for reflog config.
- Use opts as the variable name consistently.
- A bunch of grammar fixes.
- Link to v1: https://patch.msgid.link/20260420-refs-move-to-generic-layer-v1-0-513e354f376b@gmail.com
---
builtin/receive-pack.c | 22 +++---
builtin/update-ref.c | 182 +++++++++++++++++++++++++++++++-----------------
refs.c | 60 ++++++++++++----
refs.h | 16 ++---
refs/files-backend.c | 58 ++++++---------
refs/packed-backend.c | 10 ++-
refs/packed-backend.h | 3 +-
refs/refs-internal.h | 35 ++++++++--
refs/reftable-backend.c | 40 +++--------
t/t1400-update-ref.sh | 14 ++++
10 files changed, 266 insertions(+), 174 deletions(-)
Karthik Nayak (9):
refs: remove unused typedef 'ref_transaction_commit_fn'
refs: introduce `ref_store_init_options`
refs: extract out reflog config to generic layer
refs: return `ref_transaction_error` from `ref_transaction_update()`
update-ref: move `print_rejected_refs()` up
update-ref: handle rejections while adding updates
refs: move object parsing to the generic layer
refs: add peeled object ID to the `ref_update` struct
refs: use peeled tag values in reference backends
Range-diff versus v3:
1: b89e273583 = 1: cf5fa53f34 refs: remove unused typedef 'ref_transaction_commit_fn'
2: 8f7f8dadc8 = 2: afd10fd7a3 refs: introduce `ref_store_init_options`
3: 9cdc35f5b6 = 3: 4eb6950cdc refs: extract out reflog config to generic layer
4: 36c0c86a31 = 4: dd8177a243 refs: return `ref_transaction_error` from `ref_transaction_update()`
5: 1b7eca9353 = 5: eb86178ae5 update-ref: move `print_rejected_refs()` up
6: aef1529054 ! 6: 464c6371a4 update-ref: handle rejections while adding updates
@@ builtin/update-ref.c: static unsigned int default_flags;
/*
* Parse one whitespace- or NUL-terminated, possibly C-quoted argument
* and append the result to arg. Return a pointer to the terminator.
+@@ builtin/update-ref.c: static void print_rejected_refs(const char *refname,
+ strbuf_release(&sb);
+ }
+
++/*
++ * Handle transaction errors. If we're using batches updates, we want to only
++ * die for generic errors and print the remaining to the user.
++ */
++static void handle_ref_transaction_error(const char *refname,
++ struct object_id *new_oid,
++ struct object_id *old_oid,
++ const char *new_target,
++ const char *old_target,
++ enum ref_transaction_error tx_err,
++ struct strbuf *err,
++ struct command_options *opts)
++{
++ if (!tx_err)
++ return;
++
++ if (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) {
++ print_rejected_refs(refname, old_oid, new_oid, old_target,
++ new_target, tx_err, err->buf, NULL);
++ return;
++ }
++
++ die("%s", err->buf);
++}
++
+ /*
+ * The following five parse_cmd_*() functions parse the corresponding
+ * command. In each case, next points at the character following the
@@ builtin/update-ref.c: static void print_rejected_refs(const char *refname,
*/
@@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *trans
- NULL, NULL,
- update_flags | create_reflog_flag,
- msg, &err))
+- die("%s", err.buf);
+ tx_err = ref_transaction_update(transaction, refname,
+ &new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
+ update_flags | create_reflog_flag,
+ msg, &err);
++ handle_ref_transaction_error(refname, &new_oid, have_old ? &old_oid : NULL,
++ NULL, NULL, tx_err, &err, opts);
+
-+ /*
-+ * Generic errors are non-recoverable, so we cannot skip the update
-+ * or mark it as rejected.
-+ */
-+ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
- die("%s", err.buf);
-+ if (tx_err && opts->allow_update_failures)
-+ print_rejected_refs(refname, have_old ? &old_oid : NULL,
-+ &new_oid, NULL, NULL, tx_err, err.buf,
-+ NULL);
-+
update_flags = default_flags;
free(refname);
- strbuf_release(&err);
+@@ builtin/update-ref.c: static void parse_cmd_update(struct ref_transaction *transaction,
}
static void parse_cmd_symref_update(struct ref_transaction *transaction,
@@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction
- have_old_oid ? NULL : old_target,
- update_flags | create_reflog_flag,
- msg, &err))
+- die("%s", err.buf);
+ tx_err = ref_transaction_update(transaction, refname, NULL,
+ have_old_oid ? &old_oid : NULL,
+ new_target,
+ have_old_oid ? NULL : old_target,
+ update_flags | create_reflog_flag,
+ msg, &err);
-+
-+ /*
-+ * Generic errors are non-recoverable, so we cannot skip the update
-+ * or mark it as rejected.
-+ */
-+ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
- die("%s", err.buf);
++ handle_ref_transaction_error(refname, NULL, have_old_oid ? &old_oid : NULL,
++ new_target, have_old_oid ? NULL : old_target,
++ tx_err, &err, opts);
-+ if (tx_err && opts->allow_update_failures)
-+ print_rejected_refs(refname, have_old_oid ? &old_oid : NULL,
-+ NULL, have_old_oid ? NULL : old_target,
-+ new_target, tx_err, err.buf, NULL);
-+
update_flags = default_flags;
free(refname);
- free(old_arg);
@@ builtin/update-ref.c: static void parse_cmd_symref_update(struct ref_transaction *transaction,
}
static void parse_cmd_create(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
-+ struct command_options *opts UNUSED)
++ struct command_options *opts)
{
struct strbuf err = STRBUF_INIT;
char *refname;
+ struct object_id new_oid;
++ enum ref_transaction_error tx_err;
+
+ refname = parse_refname(&next);
+ if (!refname)
@@ builtin/update-ref.c: static void parse_cmd_create(struct ref_transaction *transaction,
+ if (*next != line_termination)
+ die("create %s: extra input: %s", refname, next);
+
+- if (ref_transaction_create(transaction, refname, &new_oid, NULL,
+- update_flags | create_reflog_flag,
+- msg, &err))
+- die("%s", err.buf);
++ tx_err = ref_transaction_create(transaction, refname, &new_oid, NULL,
++ update_flags | create_reflog_flag,
++ msg, &err);
++ handle_ref_transaction_error(refname, &new_oid, NULL, NULL, NULL, tx_err,
++ &err, opts);
+
+ update_flags = default_flags;
+ free(refname);
strbuf_release(&err);
}
@@ builtin/update-ref.c: static void parse_cmd_create(struct ref_transaction *trans
static void parse_cmd_symref_create(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
-+ struct command_options *opts UNUSED)
++ struct command_options *opts)
{
struct strbuf err = STRBUF_INIT;
char *refname, *new_target;
++ enum ref_transaction_error tx_err;
+
+ refname = parse_refname(&next);
+ if (!refname)
+@@ builtin/update-ref.c: static void parse_cmd_symref_create(struct ref_transaction *transaction,
+ if (*next != line_termination)
+ die("symref-create %s: extra input: %s", refname, next);
+
+- if (ref_transaction_create(transaction, refname, NULL, new_target,
+- update_flags | create_reflog_flag,
+- msg, &err))
+- die("%s", err.buf);
++ tx_err = ref_transaction_create(transaction, refname, NULL, new_target,
++ update_flags | create_reflog_flag,
++ msg, &err);
++ handle_ref_transaction_error(refname, NULL, NULL, new_target, NULL,
++ tx_err, &err, opts);
+
+ update_flags = default_flags;
+ free(refname);
@@ builtin/update-ref.c: static void parse_cmd_symref_create(struct ref_transaction *transaction,
}
7: 4b225f4f4d ! 7: f971226d89 refs: move object parsing to the generic layer
@@ refs/reftable-backend.c: static enum ref_transaction_error prepare_single_update
/*
* When we update the reference that HEAD points to we enqueue
* a second log-only update for HEAD so that its reflog is
+
+ ## t/t1400-update-ref.sh ##
+@@ t/t1400-update-ref.sh: test_expect_success 'stdin -z create ref fails with empty new value' '
+ test_must_fail git rev-parse --verify -q $c
+ '
+
++test_expect_success 'stdin -z create ref fails with non commit object' '
++ printf $F "create $c" "$(test_oid 001)" >stdin &&
++ test_must_fail git update-ref -z --stdin <stdin 2>err &&
++ grep "fatal: trying to write ref ${SQ}$c${SQ} with nonexistent object" err &&
++ test_must_fail git rev-parse --verify -q $c
++'
++
++test_expect_success 'stdin -z update ref fails with non commit object' '
++ printf $F "update $b" "$(test_oid 001)" "" >stdin &&
++ test_must_fail git update-ref -z --stdin <stdin 2>err &&
++ grep "fatal: trying to write ref ${SQ}$b${SQ} with nonexistent object" err &&
++ test_must_fail git rev-parse --verify -q $c
++'
++
+ test_expect_success 'stdin -z update ref works with right old value' '
+ printf $F "update $b" "$m~1" "$m" >stdin &&
+ git update-ref -z --stdin <stdin &&
8: 944af6a454 = 8: d271167077 refs: add peeled object ID to the `ref_update` struct
9: 47653fdde9 = 9: ed88b9e2ce refs: use peeled tag values in reference backends
base-commit: f65aba1e87db64413b6d1ed5ae5a45b5a84a0997
change-id: 20260417-refs-move-to-generic-layer-f7525c5e8764
Thanks
- Karthik
^ permalink raw reply [flat|nested] 68+ messages in thread* [PATCH v4 1/9] refs: remove unused typedef 'ref_transaction_commit_fn'
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 2/9] refs: introduce `ref_store_init_options` Karthik Nayak
` (7 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
The typedef 'ref_transaction_commit_fn' is not used anywhere in our
code, let's remove it.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/refs-internal.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index d79e35fd26..2d963cc4f4 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -421,10 +421,6 @@ typedef int ref_transaction_abort_fn(struct ref_store *refs,
struct ref_transaction *transaction,
struct strbuf *err);
-typedef int ref_transaction_commit_fn(struct ref_store *refs,
- struct ref_transaction *transaction,
- struct strbuf *err);
-
typedef int optimize_fn(struct ref_store *ref_store,
struct refs_optimize_opts *opts);
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v4 2/9] refs: introduce `ref_store_init_options`
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 3/9] refs: extract out reflog config to generic layer Karthik Nayak
` (6 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
Reference backends are initiated via the `init()` function. When
initiating the function, the backend is also provided flags which denote
the access levels of the initiator. Create a new structure
`ref_store_init_options` to house such options and move the access flags
to this structure.
This allows easier extension of providing further options to the
backends. In the following commit, we'll also provide config around
reflog creation to the backends via the same structure.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 6 +++++-
refs/files-backend.c | 8 +++++---
refs/packed-backend.c | 4 ++--
refs/packed-backend.h | 3 ++-
refs/refs-internal.h | 11 ++++++++++-
refs/reftable-backend.c | 4 ++--
6 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/refs.c b/refs.c
index bfcb9c7ac3..8992dd6ae8 100644
--- a/refs.c
+++ b/refs.c
@@ -2295,6 +2295,9 @@ static struct ref_store *ref_store_init(struct repository *repo,
{
const struct ref_storage_be *be;
struct ref_store *refs;
+ struct ref_store_init_options opts = {
+ .access_flags = flags,
+ };
be = find_ref_storage_backend(format);
if (!be)
@@ -2304,7 +2307,8 @@ static struct ref_store *ref_store_init(struct repository *repo,
* TODO Send in a 'struct worktree' instead of a 'gitdir', and
* allow the backend to handle how it wants to deal with worktrees.
*/
- refs = be->init(repo, repo->ref_storage_payload, gitdir, flags);
+ refs = be->init(repo, repo->ref_storage_payload, gitdir, &opts);
+
return refs;
}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index b3b0c25f84..72afe62cee 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -108,7 +108,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
static struct ref_store *files_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int flags)
+ const struct ref_store_init_options *opts)
{
struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
@@ -120,11 +120,13 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
&ref_common_dir);
base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);
- refs->store_flags = flags;
+
refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);
refs->packed_ref_store =
- packed_ref_store_init(repo, NULL, refs->gitcommondir, flags);
+ packed_ref_store_init(repo, NULL, refs->gitcommondir, opts);
+ refs->store_flags = opts->access_flags;
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 23ed62984b..35a0f32e1c 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -218,14 +218,14 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload UNUSED,
const char *gitdir,
- unsigned int store_flags)
+ const struct ref_store_init_options *opts)
{
struct packed_ref_store *refs = xcalloc(1, sizeof(*refs));
struct ref_store *ref_store = (struct ref_store *)refs;
struct strbuf sb = STRBUF_INIT;
base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed);
- refs->store_flags = store_flags;
+ refs->store_flags = opts->access_flags;
strbuf_addf(&sb, "%s/packed-refs", gitdir);
refs->path = strbuf_detach(&sb, NULL);
diff --git a/refs/packed-backend.h b/refs/packed-backend.h
index 2c2377a356..1db48e801d 100644
--- a/refs/packed-backend.h
+++ b/refs/packed-backend.h
@@ -3,6 +3,7 @@
struct repository;
struct ref_transaction;
+struct ref_store_init_options;
/*
* Support for storing references in a `packed-refs` file.
@@ -16,7 +17,7 @@ struct ref_transaction;
struct ref_store *packed_ref_store_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int store_flags);
+ const struct ref_store_init_options *options);
/*
* Lock the packed-refs file for writing. Flags is passed to
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 2d963cc4f4..f49b3807bf 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -385,6 +385,15 @@ struct ref_store;
REF_STORE_ODB | \
REF_STORE_MAIN)
+/*
+ * Options for initializing the ref backend. All backend-agnostic information
+ * which backends required will be held here.
+ */
+struct ref_store_init_options {
+ /* The kind of operations that the ref_store is allowed to perform. */
+ unsigned int access_flags;
+};
+
/*
* Initialize the ref_store for the specified gitdir. These functions
* should call base_ref_store_init() to initialize the shared part of
@@ -393,7 +402,7 @@ struct ref_store;
typedef struct ref_store *ref_store_init_fn(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int flags);
+ const struct ref_store_init_options *opts);
/*
* Release all memory and resources associated with the ref store.
*/
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index daea30a5b4..ad4ee2627c 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -369,7 +369,7 @@ static int reftable_be_config(const char *var, const char *value,
static struct ref_store *reftable_be_init(struct repository *repo,
const char *payload,
const char *gitdir,
- unsigned int store_flags)
+ const struct ref_store_init_options *opts)
{
struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
struct strbuf ref_common_dir = STRBUF_INIT;
@@ -386,8 +386,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
- refs->store_flags = store_flags;
refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->store_flags = opts->access_flags;
switch (repo->hash_algo->format_id) {
case GIT_SHA1_FORMAT_ID:
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v4 3/9] refs: extract out reflog config to generic layer
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 1/9] refs: remove unused typedef 'ref_transaction_commit_fn' Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 2/9] refs: introduce `ref_store_init_options` Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
` (5 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
The reference backends need to know when to create reflog entries, this
is dictated by the 'core.logallrefupdates' config. Instead of relying on
the backends to call `repo_settings_get_log_all_ref_updates()` to obtain
this config value, let's do this in the generic layer and pass down the
value to the backends.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 1 +
refs/files-backend.c | 2 +-
refs/refs-internal.h | 6 ++++++
refs/reftable-backend.c | 2 +-
4 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 8992dd6ae8..6b506aeea3 100644
--- a/refs.c
+++ b/refs.c
@@ -2297,6 +2297,7 @@ static struct ref_store *ref_store_init(struct repository *repo,
struct ref_store *refs;
struct ref_store_init_options opts = {
.access_flags = flags,
+ .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo),
};
be = find_ref_storage_backend(format);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 72afe62cee..4b2faf4777 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -125,7 +125,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
refs->packed_ref_store =
packed_ref_store_init(repo, NULL, refs->gitcommondir, opts);
refs->store_flags = opts->access_flags;
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->log_all_ref_updates = opts->log_all_ref_updates;
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index f49b3807bf..d103387ebf 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -392,6 +392,12 @@ struct ref_store;
struct ref_store_init_options {
/* The kind of operations that the ref_store is allowed to perform. */
unsigned int access_flags;
+
+ /*
+ * Denotes under what conditions reflogs should be created when updating
+ * references.
+ */
+ enum log_refs_config log_all_ref_updates;
};
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index ad4ee2627c..93374d25c2 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -386,7 +386,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,
base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);
strmap_init(&refs->worktree_backends);
- refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);
+ refs->log_all_ref_updates = opts->log_all_ref_updates;
refs->store_flags = opts->access_flags;
switch (repo->hash_algo->format_id) {
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v4 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()`
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (2 preceding siblings ...)
2026-05-04 17:44 ` [PATCH v4 3/9] refs: extract out reflog config to generic layer Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 5/9] update-ref: move `print_rejected_refs()` up Karthik Nayak
` (4 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
The `ref_transaction_update()` function is used to add updates to a
given reference transactions. In the following commit, we'll add more
validation to this function. As such, it would be beneficial if the
function returns specific error types, so callers can differentiate
between different errors.
To facilitate this, return `enum ref_transaction_error` from the
function and covert the existing '-1' returns to
'REF_TRANSACTION_ERROR_GENERIC'. Since this retains the existing
behavior, no changes are made to any of the callers but this sets the
necessary infrastructure for introduction of other errors.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 20 ++++++++++----------
refs.h | 16 ++++++++--------
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/refs.c b/refs.c
index 6b506aeea3..efa16b739d 100644
--- a/refs.c
+++ b/refs.c
@@ -1383,25 +1383,25 @@ static int transaction_refname_valid(const char *refname,
return 1;
}
-int ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- const char *new_target,
- const char *old_target,
- unsigned int flags, const char *msg,
- struct strbuf *err)
+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ unsigned int flags, const char *msg,
+ struct strbuf *err)
{
assert(err);
if ((flags & REF_FORCE_CREATE_REFLOG) &&
(flags & REF_SKIP_CREATE_REFLOG)) {
strbuf_addstr(err, _("refusing to force and skip creation of reflog"));
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
if (!transaction_refname_valid(refname, new_oid, flags, err))
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)
BUG("illegal flags 0x%x passed to ref_transaction_update()", flags);
diff --git a/refs.h b/refs.h
index d65de6ab5f..71d5c186d0 100644
--- a/refs.h
+++ b/refs.h
@@ -905,14 +905,14 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
* See the above comment "Reference transaction updates" for more
* information.
*/
-int ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- const char *new_target,
- const char *old_target,
- unsigned int flags, const char *msg,
- struct strbuf *err);
+enum ref_transaction_error ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ unsigned int flags, const char *msg,
+ struct strbuf *err);
/*
* Similar to `ref_transaction_update`, but this function is only for adding
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v4 5/9] update-ref: move `print_rejected_refs()` up
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (3 preceding siblings ...)
2026-05-04 17:44 ` [PATCH v4 4/9] refs: return `ref_transaction_error` from `ref_transaction_update()` Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 6/9] update-ref: handle rejections while adding updates Karthik Nayak
` (3 subsequent siblings)
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
The `print_rejected_refs()` function is used to print any rejected refs
when using git-updated-ref(1) with the '--batch-updates' option. In the
following commit, we'll need to use this function in another place, so
move the function up to avoid a separate forward declaration.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 45 ++++++++++++++++++++++-----------------------
1 file changed, 22 insertions(+), 23 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 2d68c40ecb..5259cc7226 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -234,6 +234,28 @@ static int parse_next_oid(const char **next, const char *end,
command, refname);
}
+static void print_rejected_refs(const char *refname,
+ const struct object_id *old_oid,
+ const struct object_id *new_oid,
+ const char *old_target,
+ const char *new_target,
+ enum ref_transaction_error err,
+ const char *details,
+ void *cb_data UNUSED)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ if (details && *details)
+ error("%s", details);
+
+ strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
+ new_oid ? oid_to_hex(new_oid) : new_target,
+ old_oid ? oid_to_hex(old_oid) : old_target,
+ ref_transaction_error_msg(err));
+
+ fwrite(sb.buf, sb.len, 1, stdout);
+ strbuf_release(&sb);
+}
/*
* The following five parse_cmd_*() functions parse the corresponding
@@ -567,29 +589,6 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
report_ok("abort");
}
-static void print_rejected_refs(const char *refname,
- const struct object_id *old_oid,
- const struct object_id *new_oid,
- const char *old_target,
- const char *new_target,
- enum ref_transaction_error err,
- const char *details,
- void *cb_data UNUSED)
-{
- struct strbuf sb = STRBUF_INIT;
-
- if (details && *details)
- error("%s", details);
-
- strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
- new_oid ? oid_to_hex(new_oid) : new_target,
- old_oid ? oid_to_hex(old_oid) : old_target,
- ref_transaction_error_msg(err));
-
- fwrite(sb.buf, sb.len, 1, stdout);
- strbuf_release(&sb);
-}
-
static void parse_cmd_commit(struct ref_transaction *transaction,
const char *next, const char *end UNUSED)
{
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v4 6/9] update-ref: handle rejections while adding updates
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (4 preceding siblings ...)
2026-05-04 17:44 ` [PATCH v4 5/9] update-ref: move `print_rejected_refs()` up Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-05 5:52 ` Patrick Steinhardt
2026-05-04 17:44 ` [PATCH v4 7/9] refs: move object parsing to the generic layer Karthik Nayak
` (2 subsequent siblings)
8 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
When using git-update-ref(1) with the '--batch-updates' flag, updates
rejected by the reference backend are displayed to the user while other
updates are applied. This only applies during the commit phase of the
transaction.
In the following commits, we'll also extend `ref_transaction_update()`
to reject updates before a transaction is prepared/committed. In
preparation, modify the code in update-ref to also handle non-generic
rejections from `ref_transaction_update()`. This involves propagating
information to each of the commands on whether updates are allowed to be
rejected, and also checking for rejections and only dying for generic
failures.
Errors encountered during updates will be shown to the user immediately
unlike other errors encountered only when the transaction is
prepared/committed. As the verification of object IDs and peeled tag
objects will move into `ref_transaction_update()` in the following
commit, this means that those errors will be shown to the user before
other errors, this changes the order of errors, but the functionality
remains the same.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 137 ++++++++++++++++++++++++++++++++++++---------------
1 file changed, 98 insertions(+), 39 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 5259cc7226..6355c3dd3e 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -25,6 +25,15 @@ static unsigned int default_flags;
static unsigned create_reflog_flag;
static const char *msg;
+struct command_options {
+ /*
+ * Individual updates are allowed to fail without causing
+ * update-ref to exit. This is set when using the
+ * '--batch-updates' flag.
+ */
+ bool allow_update_failures;
+};
+
/*
* Parse one whitespace- or NUL-terminated, possibly C-quoted argument
* and append the result to arg. Return a pointer to the terminator.
@@ -257,6 +266,31 @@ static void print_rejected_refs(const char *refname,
strbuf_release(&sb);
}
+/*
+ * Handle transaction errors. If we're using batches updates, we want to only
+ * die for generic errors and print the remaining to the user.
+ */
+static void handle_ref_transaction_error(const char *refname,
+ struct object_id *new_oid,
+ struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
+ enum ref_transaction_error tx_err,
+ struct strbuf *err,
+ struct command_options *opts)
+{
+ if (!tx_err)
+ return;
+
+ if (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) {
+ print_rejected_refs(refname, old_oid, new_oid, old_target,
+ new_target, tx_err, err->buf, NULL);
+ return;
+ }
+
+ die("%s", err->buf);
+}
+
/*
* The following five parse_cmd_*() functions parse the corresponding
* command. In each case, next points at the character following the
@@ -268,11 +302,13 @@ static void print_rejected_refs(const char *refname,
*/
static void parse_cmd_update(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid, old_oid;
+ enum ref_transaction_error tx_err;
int have_old;
refname = parse_refname(&next);
@@ -289,12 +325,14 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname,
- &new_oid, have_old ? &old_oid : NULL,
- NULL, NULL,
- update_flags | create_reflog_flag,
- msg, &err))
- die("%s", err.buf);
+ tx_err = ref_transaction_update(transaction, refname,
+ &new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
+ update_flags | create_reflog_flag,
+ msg, &err);
+ handle_ref_transaction_error(refname, &new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL, tx_err, &err, opts);
+
update_flags = default_flags;
free(refname);
@@ -302,9 +340,11 @@ static void parse_cmd_update(struct ref_transaction *transaction,
}
static void parse_cmd_symref_update(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts)
{
char *refname, *new_target, *old_arg;
+ enum ref_transaction_error tx_err;
char *old_target = NULL;
struct strbuf err = STRBUF_INIT;
struct object_id old_oid;
@@ -341,13 +381,15 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("symref-update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname, NULL,
- have_old_oid ? &old_oid : NULL,
- new_target,
- have_old_oid ? NULL : old_target,
- update_flags | create_reflog_flag,
- msg, &err))
- die("%s", err.buf);
+ tx_err = ref_transaction_update(transaction, refname, NULL,
+ have_old_oid ? &old_oid : NULL,
+ new_target,
+ have_old_oid ? NULL : old_target,
+ update_flags | create_reflog_flag,
+ msg, &err);
+ handle_ref_transaction_error(refname, NULL, have_old_oid ? &old_oid : NULL,
+ new_target, have_old_oid ? NULL : old_target,
+ tx_err, &err, opts);
update_flags = default_flags;
free(refname);
@@ -358,11 +400,13 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
}
static void parse_cmd_create(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid;
+ enum ref_transaction_error tx_err;
refname = parse_refname(&next);
if (!refname)
@@ -377,22 +421,24 @@ static void parse_cmd_create(struct ref_transaction *transaction,
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);
- if (ref_transaction_create(transaction, refname, &new_oid, NULL,
- update_flags | create_reflog_flag,
- msg, &err))
- die("%s", err.buf);
+ tx_err = ref_transaction_create(transaction, refname, &new_oid, NULL,
+ update_flags | create_reflog_flag,
+ msg, &err);
+ handle_ref_transaction_error(refname, &new_oid, NULL, NULL, NULL, tx_err,
+ &err, opts);
update_flags = default_flags;
free(refname);
strbuf_release(&err);
}
-
static void parse_cmd_symref_create(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts)
{
struct strbuf err = STRBUF_INIT;
char *refname, *new_target;
+ enum ref_transaction_error tx_err;
refname = parse_refname(&next);
if (!refname)
@@ -405,10 +451,11 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,
if (*next != line_termination)
die("symref-create %s: extra input: %s", refname, next);
- if (ref_transaction_create(transaction, refname, NULL, new_target,
- update_flags | create_reflog_flag,
- msg, &err))
- die("%s", err.buf);
+ tx_err = ref_transaction_create(transaction, refname, NULL, new_target,
+ update_flags | create_reflog_flag,
+ msg, &err);
+ handle_ref_transaction_error(refname, NULL, NULL, new_target, NULL,
+ tx_err, &err, opts);
update_flags = default_flags;
free(refname);
@@ -417,7 +464,8 @@ static void parse_cmd_symref_create(struct ref_transaction *transaction,
}
static void parse_cmd_delete(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -450,9 +498,9 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_symref_delete(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname, *old_target;
@@ -479,9 +527,9 @@ static void parse_cmd_symref_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
-
static void parse_cmd_verify(struct ref_transaction *transaction,
- const char *next, const char *end)
+ const char *next, const char *end,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
char *refname;
@@ -508,7 +556,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
}
static void parse_cmd_symref_verify(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf err = STRBUF_INIT;
struct object_id old_oid;
@@ -550,7 +599,8 @@ static void report_ok(const char *command)
}
static void parse_cmd_option(struct ref_transaction *transaction UNUSED,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
const char *rest;
if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination)
@@ -560,7 +610,8 @@ static void parse_cmd_option(struct ref_transaction *transaction UNUSED,
}
static void parse_cmd_start(struct ref_transaction *transaction UNUSED,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
if (*next != line_termination)
die("start: extra input: %s", next);
@@ -568,7 +619,8 @@ static void parse_cmd_start(struct ref_transaction *transaction UNUSED,
}
static void parse_cmd_prepare(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -579,7 +631,8 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,
}
static void parse_cmd_abort(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -590,7 +643,8 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
}
static void parse_cmd_commit(struct ref_transaction *transaction,
- const char *next, const char *end UNUSED)
+ const char *next, const char *end UNUSED,
+ struct command_options *opts UNUSED)
{
struct strbuf error = STRBUF_INIT;
if (*next != line_termination)
@@ -618,7 +672,8 @@ enum update_refs_state {
static const struct parse_cmd {
const char *prefix;
- void (*fn)(struct ref_transaction *, const char *, const char *);
+ void (*fn)(struct ref_transaction *, const char *, const char *,
+ struct command_options *);
unsigned args;
enum update_refs_state state;
} command[] = {
@@ -644,6 +699,10 @@ static void update_refs_stdin(unsigned int flags)
struct ref_transaction *transaction;
int i, j;
+ struct command_options opts = {
+ .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,
+ };
+
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
flags, &err);
if (!transaction)
@@ -721,7 +780,7 @@ static void update_refs_stdin(unsigned int flags)
}
cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,
- input.buf + input.len);
+ input.buf + input.len, &opts);
}
switch (state) {
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* Re: [PATCH v4 6/9] update-ref: handle rejections while adding updates
2026-05-04 17:44 ` [PATCH v4 6/9] update-ref: handle rejections while adding updates Karthik Nayak
@ 2026-05-05 5:52 ` Patrick Steinhardt
2026-05-05 8:23 ` Karthik Nayak
0 siblings, 1 reply; 68+ messages in thread
From: Patrick Steinhardt @ 2026-05-05 5:52 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, toon
On Mon, May 04, 2026 at 07:44:10PM +0200, Karthik Nayak wrote:
> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
> index 5259cc7226..6355c3dd3e 100644
> --- a/builtin/update-ref.c
> +++ b/builtin/update-ref.c
> @@ -257,6 +266,31 @@ static void print_rejected_refs(const char *refname,
> strbuf_release(&sb);
> }
>
> +/*
> + * Handle transaction errors. If we're using batches updates, we want to only
> + * die for generic errors and print the remaining to the user.
> + */
> +static void handle_ref_transaction_error(const char *refname,
> + struct object_id *new_oid,
> + struct object_id *old_oid,
> + const char *new_target,
> + const char *old_target,
> + enum ref_transaction_error tx_err,
> + struct strbuf *err,
> + struct command_options *opts)
> +{
> + if (!tx_err)
> + return;
> +
> + if (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) {
> + print_rejected_refs(refname, old_oid, new_oid, old_target,
> + new_target, tx_err, err->buf, NULL);
> + return;
> + }
> +
> + die("%s", err->buf);
> +}
It's a bit weird that we pass in the error message as a strbuf given
that we really only care about the actual message.
> @@ -644,6 +699,10 @@ static void update_refs_stdin(unsigned int flags)
> struct ref_transaction *transaction;
> int i, j;
>
> + struct command_options opts = {
> + .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,
> + };
> +
Nit: stray empty line between the variable declarations.
Other than that this patch looks good to me, thanks!
Patrick
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH v4 6/9] update-ref: handle rejections while adding updates
2026-05-05 5:52 ` Patrick Steinhardt
@ 2026-05-05 8:23 ` Karthik Nayak
2026-05-06 19:44 ` Toon Claes
0 siblings, 1 reply; 68+ messages in thread
From: Karthik Nayak @ 2026-05-05 8:23 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, toon
[-- Attachment #1: Type: text/plain, Size: 1844 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Mon, May 04, 2026 at 07:44:10PM +0200, Karthik Nayak wrote:
>> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
>> index 5259cc7226..6355c3dd3e 100644
>> --- a/builtin/update-ref.c
>> +++ b/builtin/update-ref.c
>> @@ -257,6 +266,31 @@ static void print_rejected_refs(const char *refname,
>> strbuf_release(&sb);
>> }
>>
>> +/*
>> + * Handle transaction errors. If we're using batches updates, we want to only
>> + * die for generic errors and print the remaining to the user.
>> + */
>> +static void handle_ref_transaction_error(const char *refname,
>> + struct object_id *new_oid,
>> + struct object_id *old_oid,
>> + const char *new_target,
>> + const char *old_target,
>> + enum ref_transaction_error tx_err,
>> + struct strbuf *err,
>> + struct command_options *opts)
>> +{
>> + if (!tx_err)
>> + return;
>> +
>> + if (tx_err != REF_TRANSACTION_ERROR_GENERIC && opts->allow_update_failures) {
>> + print_rejected_refs(refname, old_oid, new_oid, old_target,
>> + new_target, tx_err, err->buf, NULL);
>> + return;
>> + }
>> +
>> + die("%s", err->buf);
>> +}
>
> It's a bit weird that we pass in the error message as a strbuf given
> that we really only care about the actual message.
>
That's fair. I will add this locally but I think it is also not worth a
re-roll.
>> @@ -644,6 +699,10 @@ static void update_refs_stdin(unsigned int flags)
>> struct ref_transaction *transaction;
>> int i, j;
>>
>> + struct command_options opts = {
>> + .allow_update_failures = flags & REF_TRANSACTION_ALLOW_FAILURE,
>> + };
>> +
>
> Nit: stray empty line between the variable declarations.
>
Same here.
> Other than that this patch looks good to me, thanks!
>
> Patrick
>
I'll hold off on a re-roll unless needed.
Thanks for the review again!
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 68+ messages in thread* Re: [PATCH v4 6/9] update-ref: handle rejections while adding updates
2026-05-05 8:23 ` Karthik Nayak
@ 2026-05-06 19:44 ` Toon Claes
0 siblings, 0 replies; 68+ messages in thread
From: Toon Claes @ 2026-05-06 19:44 UTC (permalink / raw)
To: Karthik Nayak, Patrick Steinhardt; +Cc: git
Karthik Nayak <karthik.188@gmail.com> writes:
> I'll hold off on a re-roll unless needed.
Well, from my side there aren't any extra comments. I've reviewed the
range-diff and this patch, and glanced through the other patches (but
they didn't change compared to previous version) and all looks good to
me.
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 68+ messages in thread
* [PATCH v4 7/9] refs: move object parsing to the generic layer
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (5 preceding siblings ...)
2026-05-04 17:44 ` [PATCH v4 6/9] update-ref: handle rejections while adding updates Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 8/9] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 9/9] refs: use peeled tag values in reference backends Karthik Nayak
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
Regular reference updates made via reference transactions validate that
the provided object ID exists in the object database, which is done by
calling 'parse_object()'. This check is done independently by the
backends which leads to duplicated logic.
Let's move this to the generic layer, ensuring the backends only have to
care about reference storage and not about validation of the object IDs.
With this also remove the 'REF_TRANSACTION_ERROR_INVALID_NEW_VALUE'
error type as its no longer used.
Since we don't iterate over individual references in
`ref_transaction_prepare()`, we add this check to
`ref_transaction_update()`. This means that the validation is done as
soon as an update is queued, without needing to prepare the
transaction. It can be argued that this is more ideal, since this
validation has no dependency on the reference transaction being
prepared.
It must be noted that the change in behavior means that this error
cannot be ignored even with usage of batched updates, since this happens
when the update is being added to the transaction. But since the caller
gets specific error codes, they can either abort the transaction or
continue adding other updates to the transaction.
Modify 'builtin/receive-pack.c' to now capture the error type so that
the error propagated to the client stays the same. Also remove two of
the tests which validates batch-updates with invalid new_oid.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/receive-pack.c | 22 +++++++++++++---------
refs.c | 18 ++++++++++++++++++
refs/files-backend.c | 28 ++--------------------------
refs/reftable-backend.c | 19 -------------------
t/t1400-update-ref.sh | 14 ++++++++++++++
5 files changed, 47 insertions(+), 54 deletions(-)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 878aa7f0ed..376e755e97 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1641,8 +1641,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
ret = NULL; /* good */
}
strbuf_release(&err);
- }
- else {
+ } else {
+ enum ref_transaction_error tx_err;
struct strbuf err = STRBUF_INIT;
if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si)) {
@@ -1650,14 +1650,18 @@ static const char *update(struct command *cmd, struct shallow_info *si)
goto out;
}
- if (ref_transaction_update(transaction,
- namespaced_name,
- new_oid, old_oid,
- NULL, NULL,
- 0, "push",
- &err)) {
+ tx_err = ref_transaction_update(transaction,
+ namespaced_name,
+ new_oid, old_oid,
+ NULL, NULL,
+ 0, "push",
+ &err);
+ if (tx_err) {
rp_error("%s", err.buf);
- ret = "failed to update ref";
+ if (tx_err == REF_TRANSACTION_ERROR_GENERIC)
+ ret = "failed to update ref";
+ else
+ ret = ref_transaction_error_msg(tx_err);
} else {
ret = NULL; /* good */
}
diff --git a/refs.c b/refs.c
index efa16b739d..662a9e6f9e 100644
--- a/refs.c
+++ b/refs.c
@@ -1416,6 +1416,24 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
+ if ((flags & REF_HAVE_NEW) && !new_target && !is_null_oid(new_oid) &&
+ !(flags & REF_SKIP_OID_VERIFICATION) && !(flags & REF_LOG_ONLY)) {
+ struct object *o = parse_object(transaction->ref_store->repo, new_oid);
+
+ if (!o) {
+ strbuf_addf(err,
+ _("trying to write ref '%s' with nonexistent object %s"),
+ refname, oid_to_hex(new_oid));
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+
+ if (o->type != OBJ_COMMIT && is_branch(refname)) {
+ strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
+ oid_to_hex(new_oid), refname);
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+ }
+
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
old_target, NULL, msg);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 4b2faf4777..f20f580fbc 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -19,7 +19,6 @@
#include "../iterator.h"
#include "../dir-iterator.h"
#include "../lockfile.h"
-#include "../object.h"
#include "../path.h"
#include "../dir.h"
#include "../chdir-notify.h"
@@ -1589,7 +1588,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid,
- int skip_oid_verification,
struct strbuf *err);
static int commit_ref_update(struct files_ref_store *refs,
struct ref_lock *lock,
@@ -1737,7 +1735,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
}
oidcpy(&lock->old_oid, &orig_oid);
- if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||
+ if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||
commit_ref_update(refs, lock, &orig_oid, logmsg, 0, &err)) {
error("unable to write current sha1 into %s: %s", newrefname, err.buf);
strbuf_release(&err);
@@ -1755,7 +1753,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
goto rollbacklog;
}
- if (write_ref_to_lockfile(refs, lock, &orig_oid, 0, &err) ||
+ if (write_ref_to_lockfile(refs, lock, &orig_oid, &err) ||
commit_ref_update(refs, lock, &orig_oid, NULL, REF_SKIP_CREATE_REFLOG, &err)) {
error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
strbuf_release(&err);
@@ -1999,32 +1997,11 @@ static int files_log_ref_write(struct files_ref_store *refs,
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid,
- int skip_oid_verification,
struct strbuf *err)
{
static char term = '\n';
- struct object *o;
int fd;
- if (!skip_oid_verification) {
- o = parse_object(refs->base.repo, oid);
- if (!o) {
- strbuf_addf(
- err,
- "trying to write ref '%s' with nonexistent object %s",
- lock->ref_name, oid_to_hex(oid));
- unlock_ref(lock);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
- strbuf_addf(
- err,
- "trying to write non-commit object %s to branch '%s'",
- oid_to_hex(oid), lock->ref_name);
- unlock_ref(lock);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- }
fd = get_lock_file_fd(&lock->lk);
if (write_in_full(fd, oid_to_hex(oid), refs->base.repo->hash_algo->hexsz) < 0 ||
write_in_full(fd, &term, 1) < 0 ||
@@ -2828,7 +2805,6 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
} else {
ret = write_ref_to_lockfile(
refs, lock, &update->new_oid,
- update->flags & REF_SKIP_OID_VERIFICATION,
err);
if (ret) {
char *write_err = strbuf_detach(err, NULL);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 93374d25c2..444b0c24e5 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1081,25 +1081,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
return 0;
}
- /* Verify that the new object ID is valid. */
- if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&
- !(u->flags & REF_SKIP_OID_VERIFICATION) &&
- !(u->flags & REF_LOG_ONLY)) {
- struct object *o = parse_object(refs->base.repo, &u->new_oid);
- if (!o) {
- strbuf_addf(err,
- _("trying to write ref '%s' with nonexistent object %s"),
- u->refname, oid_to_hex(&u->new_oid));
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
-
- if (o->type != OBJ_COMMIT && is_branch(u->refname)) {
- strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
- oid_to_hex(&u->new_oid), u->refname);
- return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
- }
- }
-
/*
* When we update the reference that HEAD points to we enqueue
* a second log-only update for HEAD so that its reflog is
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index b2858a9061..1015f335e3 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1196,6 +1196,20 @@ test_expect_success 'stdin -z create ref fails with empty new value' '
test_must_fail git rev-parse --verify -q $c
'
+test_expect_success 'stdin -z create ref fails with non commit object' '
+ printf $F "create $c" "$(test_oid 001)" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: trying to write ref ${SQ}$c${SQ} with nonexistent object" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z update ref fails with non commit object' '
+ printf $F "update $b" "$(test_oid 001)" "" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: trying to write ref ${SQ}$b${SQ} with nonexistent object" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
test_expect_success 'stdin -z update ref works with right old value' '
printf $F "update $b" "$m~1" "$m" >stdin &&
git update-ref -z --stdin <stdin &&
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v4 8/9] refs: add peeled object ID to the `ref_update` struct
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (6 preceding siblings ...)
2026-05-04 17:44 ` [PATCH v4 7/9] refs: move object parsing to the generic layer Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
2026-05-04 17:44 ` [PATCH v4 9/9] refs: use peeled tag values in reference backends Karthik Nayak
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
Certain reference backends {packed, reftable}, have the ability to also
store the peeled object ID for a reference pointing to a tag object.
This has the added benefit that during retrieval of such references, we
also obtain the peeled object ID without having to use the ODB.
To provide this functionality, each backend independently calls the ODB
to obtain the peeled OID. To move this functionality to the generic
layer, there must be support infrastructure to pass in a peeled OID for
reference updates.
Add a `peeled` field to the `ref_update` structure and modify
`ref_transaction_add_update()` to receive and copy this object ID to the
`ref_update` structure. Finally, modify `ref_transaction_update()` to
peel tag objects and pass the peeled OID to
`ref_transaction_add_update()`.
Update all callers of these functions with the new function parameters.
Callers which only add reflog updates, need to only pass in NULL, since
for reflogs, we don't store peeled OIDs. Reference deletions also only
need to pass in NULL. For others, pass along the peeled OID if
available.
In a following commit, we'll modify the backends to use this peeled OID
instead of parsing it themselves.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 15 +++++++++++++--
refs/files-backend.c | 20 ++++++++++++--------
refs/refs-internal.h | 14 ++++++++++++++
refs/reftable-backend.c | 6 +++---
4 files changed, 42 insertions(+), 13 deletions(-)
diff --git a/refs.c b/refs.c
index 662a9e6f9e..0648df2b6c 100644
--- a/refs.c
+++ b/refs.c
@@ -1307,6 +1307,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const struct object_id *peeled,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg)
@@ -1339,6 +1340,8 @@ struct ref_update *ref_transaction_add_update(
update->committer_info = xstrdup_or_null(committer_info);
update->msg = normalize_reflog_message(msg);
}
+ if (flags & REF_HAVE_PEELED)
+ oidcpy(&update->peeled, peeled);
/*
* This list is generally used by the backends to avoid duplicates.
@@ -1392,6 +1395,8 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
unsigned int flags, const char *msg,
struct strbuf *err)
{
+ struct object_id peeled;
+
assert(err);
if ((flags & REF_FORCE_CREATE_REFLOG) &&
@@ -1432,10 +1437,16 @@ enum ref_transaction_error ref_transaction_update(struct ref_transaction *transa
oid_to_hex(new_oid), refname);
return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
}
+
+ if (o->type == OBJ_TAG) {
+ if (!peel_object(transaction->ref_store->repo, new_oid, &peeled,
+ PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))
+ flags |= REF_HAVE_PEELED;
+ }
}
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, new_target,
+ new_oid, old_oid, &peeled, new_target,
old_target, NULL, msg);
return 0;
@@ -1462,7 +1473,7 @@ int ref_transaction_update_reflog(struct ref_transaction *transaction,
return -1;
update = ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, NULL, NULL,
+ new_oid, old_oid, NULL, NULL, NULL,
committer_info, msg);
update->index = index;
diff --git a/refs/files-backend.c b/refs/files-backend.c
index f20f580fbc..d0896d0e37 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1325,7 +1325,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);
+ null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL,
+ NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -2468,7 +2469,7 @@ static enum ref_transaction_error split_head_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT,
- &update->new_oid, &update->old_oid,
+ &update->new_oid, &update->old_oid, &update->peeled,
NULL, NULL, update->committer_info, update->msg);
new_update->parent_update = update;
@@ -2530,8 +2531,8 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
transaction, referent, new_flags,
update->new_target ? NULL : &update->new_oid,
update->old_target ? NULL : &update->old_oid,
- update->new_target, update->old_target, NULL,
- update->msg);
+ &update->peeled, update->new_target, update->old_target,
+ NULL, update->msg);
new_update->parent_update = update;
@@ -2994,7 +2995,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
ref_transaction_add_update(
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
- &update->new_oid, NULL,
+ &update->new_oid, NULL, NULL,
NULL, NULL, NULL, NULL);
}
}
@@ -3200,19 +3201,22 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
if (update->flags & REF_LOG_ONLY)
ref_transaction_add_update(loose_transaction, update->refname,
update->flags, &update->new_oid,
- &update->old_oid, NULL, NULL,
+ &update->old_oid, &update->peeled,
+ NULL, NULL,
update->committer_info, update->msg);
else
ref_transaction_add_update(loose_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
update->new_target ? NULL : &update->new_oid, NULL,
- update->new_target, NULL, update->committer_info,
+ &update->peeled, update->new_target,
+ NULL, update->committer_info,
NULL);
} else {
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL, NULL, update->committer_info, NULL);
+ &update->peeled, NULL, NULL,
+ update->committer_info, NULL);
}
}
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index d103387ebf..307dcb277b 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -39,6 +39,13 @@ struct ref_transaction;
*/
#define REF_LOG_ONLY (1 << 7)
+/*
+ * The reference contains a peeled object ID. This is used when the
+ * new_oid is pointing to a tag object and the reference backend
+ * wants to also store the peeled value for optimized retrieval.
+ */
+#define REF_HAVE_PEELED (1 << 15)
+
/*
* Return the length of time to retry acquiring a loose reference lock
* before giving up, in milliseconds:
@@ -92,6 +99,12 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If the new_oid points to a tag object, set this to the peeled
+ * object ID for optimized retrieval without needed to hit the odb.
+ */
+ struct object_id peeled;
+
/*
* If set, point the reference to this value. This can also be
* used to convert regular references to become symbolic refs.
@@ -169,6 +182,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const struct object_id *peeled,
const char *new_target, const char *old_target,
const char *committer_info,
const char *msg);
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 444b0c24e5..b0c010387d 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1107,8 +1107,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, NULL, NULL, NULL,
- u->msg);
+ &u->new_oid, &u->old_oid, &u->peeled, NULL, NULL,
+ NULL, u->msg);
}
ret = reftable_backend_read_ref(be, rewritten_ref,
@@ -1194,7 +1194,7 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
transaction, referent->buf, new_flags,
u->new_target ? NULL : &u->new_oid,
u->old_target ? NULL : &u->old_oid,
- u->new_target, u->old_target,
+ &u->peeled, u->new_target, u->old_target,
u->committer_info, u->msg);
new_update->parent_update = u;
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread* [PATCH v4 9/9] refs: use peeled tag values in reference backends
2026-05-04 17:44 ` [PATCH v4 0/9] refs: move some of the generic logic out of the backends Karthik Nayak
` (7 preceding siblings ...)
2026-05-04 17:44 ` [PATCH v4 8/9] refs: add peeled object ID to the `ref_update` struct Karthik Nayak
@ 2026-05-04 17:44 ` Karthik Nayak
8 siblings, 0 replies; 68+ messages in thread
From: Karthik Nayak @ 2026-05-04 17:44 UTC (permalink / raw)
To: git; +Cc: ps, toon, Karthik Nayak
The reference backends peel tag objects when storing references to them.
This is to provide optimized reads which avoids hitting the odb. The
previous commits ensures that the peeled value is now propagated via the
generic layer. So modify the packed and reftable backend to directly use
this value instead of calling `peel_object()` independently.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/packed-backend.c | 6 ++----
refs/reftable-backend.c | 9 ++-------
2 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 35a0f32e1c..0acde48c45 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1531,13 +1531,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
*/
i++;
} else {
- struct object_id peeled;
- int peel_error = peel_object(refs->base.repo, &update->new_oid,
- &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
+ bool peeled = update->flags & REF_HAVE_PEELED;
if (write_packed_entry(out, update->refname,
&update->new_oid,
- peel_error ? NULL : &peeled))
+ peeled ? &update->peeled : NULL))
goto write_error;
i++;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index b0c010387d..8b4ac2e618 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -12,7 +12,6 @@
#include "../hex.h"
#include "../ident.h"
#include "../iterator.h"
-#include "../object.h"
#include "../parse.h"
#include "../path.h"
#include "../refs.h"
@@ -1584,17 +1583,13 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
goto done;
} else if (u->flags & REF_HAVE_NEW) {
struct reftable_ref_record ref = {0};
- struct object_id peeled;
- int peel_error;
ref.refname = (char *)u->refname;
ref.update_index = ts;
- peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,
- PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE);
- if (!peel_error) {
+ if (u->flags & REF_HAVE_PEELED) {
ref.value_type = REFTABLE_REF_VAL2;
- memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);
+ memcpy(ref.value.val2.target_value, u->peeled.hash, GIT_MAX_RAWSZ);
memcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ);
} else if (!is_null_oid(&u->new_oid)) {
ref.value_type = REFTABLE_REF_VAL1;
--
2.53.GIT
^ permalink raw reply related [flat|nested] 68+ messages in thread