* [PATCH 0/6] refs: excessive hook execution with packed refs @ 2021-12-07 10:55 Patrick Steinhardt 2021-12-07 10:55 ` [PATCH 1/6] refs: open-code deletion of " Patrick Steinhardt ` (6 more replies) 0 siblings, 7 replies; 51+ messages in thread From: Patrick Steinhardt @ 2021-12-07 10:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 1571 bytes --] Hi, as reported by Waleed in [1], the reference-transaction hook is being executed when packing refs. Given that the hook ideally ought to track logical updates to refs instead of leaking low-level implementation details of how the files backend works, this is understandably leading to some confusion. This patch series aims to fix that by improving how the tandom of loose and packed refs backends interact such that we skip executing the hook when the loose backend: - repacks references. - needs to delete packed refs when deleting a loose ref would uncover that packed ref. Patrick [1]: <CAKjfCeBcuYC3OXRVtxxDGWRGOxC38Fb7CNuSh_dMmxpGVip_9Q@mail.gmail.com> Patrick Steinhardt (6): refs: open-code deletion of packed refs refs: allow passing flags when beginning transactions refs: allow skipping the reference-transaction hook refs: demonstrate excessive execution of the reference-transaction hook refs: do not execute reference-transaction hook on packing refs refs: skip hooks when deleting uncovered packed refs refs.c | 11 +++++-- refs.h | 8 ++++- refs/files-backend.c | 25 +++++++++++----- refs/packed-backend.c | 30 ++++++++++++++----- refs/packed-backend.h | 6 ++++ refs/refs-internal.h | 1 + sequencer.c | 2 +- t/t1416-ref-transaction-hooks.sh | 50 ++++++++++++++++++++++++++++++++ 8 files changed, 113 insertions(+), 20 deletions(-) -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH 1/6] refs: open-code deletion of packed refs 2021-12-07 10:55 [PATCH 0/6] refs: excessive hook execution with packed refs Patrick Steinhardt @ 2021-12-07 10:55 ` Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt ` (5 subsequent siblings) 6 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2021-12-07 10:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 5049 bytes --] When deleting loose refs, then we also have to delete the refs in the packed backend. This is done by calling `refs_delete_refs()`, which then uses the packed-backend's logic to delete refs. This doesn't allow us to exercise any control over the reference transaction which is being created in the packed backend, which is required in a subsequent commit. Extract a new function `packed_refs_delete_refs()`, which hosts most of the logic to delete refs except for creating the transaction itself. Like this, we can easily create the transactionion in the files backend and thus exert more control over it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 12 +++++++++--- refs/packed-backend.c | 28 +++++++++++++++++++++------- refs/packed-backend.h | 6 ++++++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 4b14f30d48..453adc38ea 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1250,6 +1250,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct ref_transaction *transaction = NULL; struct strbuf err = STRBUF_INIT; int i, result = 0; @@ -1259,10 +1260,14 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { - packed_refs_unlock(refs->packed_ref_store); + transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + if (!transaction) + goto error; + + result = packed_refs_delete_refs(refs->packed_ref_store, + transaction, msg, refnames, flags); + if (result) goto error; - } packed_refs_unlock(refs->packed_ref_store); @@ -1289,6 +1294,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, else error(_("could not delete references: %s"), err.buf); + ref_transaction_free(transaction); strbuf_release(&err); return -1; } diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 9da932a540..cb19b5291e 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1522,15 +1522,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, static int packed_delete_refs(struct ref_store *ref_store, const char *msg, struct string_list *refnames, unsigned int flags) { - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - struct string_list_item *item; int ret; - (void)refs; /* We need the check above, but don't use the variable */ - if (!refnames->nr) return 0; @@ -1544,6 +1539,27 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, if (!transaction) return -1; + ret = packed_refs_delete_refs(ref_store, transaction, + msg, refnames, flags); + + ref_transaction_free(transaction); + return ret; +} + +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags) +{ + struct packed_ref_store *refs = + packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct strbuf err = STRBUF_INIT; + struct string_list_item *item; + int ret; + + (void)(refs); /* We need the check above, but don't use the variable */ + for_each_string_list_item(item, refnames) { if (ref_transaction_delete(transaction, item->string, NULL, flags, msg, &err)) { @@ -1554,7 +1570,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, } ret = ref_transaction_commit(transaction, &err); - if (ret) { if (refnames->nr == 1) error(_("could not delete reference %s: %s"), @@ -1563,7 +1578,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, error(_("could not delete references: %s"), err.buf); } - ref_transaction_free(transaction); strbuf_release(&err); return ret; } diff --git a/refs/packed-backend.h b/refs/packed-backend.h index f61a73ec25..5e0dd7d08e 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -27,6 +27,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) void packed_refs_unlock(struct ref_store *ref_store); int packed_refs_is_locked(struct ref_store *ref_store); +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags); + /* * Return true if `transaction` really needs to be carried out against * the specified packed_ref_store, or false if it can be skipped -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 2/6] refs: allow passing flags when beginning transactions 2021-12-07 10:55 [PATCH 0/6] refs: excessive hook execution with packed refs Patrick Steinhardt 2021-12-07 10:55 ` [PATCH 1/6] refs: open-code deletion of " Patrick Steinhardt @ 2021-12-07 10:56 ` Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt ` (4 subsequent siblings) 6 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2021-12-07 10:56 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 6362 bytes --] We do not currently have any flags when creating reference transactions, but we'll add one to disable execution of the reference transaction hook in some cases. Allow passing flags to `ref_store_transaction_begin()` to prepare for this change. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 8 +++++--- refs.h | 3 ++- refs/files-backend.c | 10 +++++----- refs/packed-backend.c | 2 +- refs/refs-internal.h | 1 + sequencer.c | 2 +- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/refs.c b/refs.c index 996ac27164..1c8a0c01f1 100644 --- a/refs.c +++ b/refs.c @@ -800,7 +800,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction || ref_transaction_delete(transaction, refname, old_oid, flags, msg, &err) || @@ -1005,6 +1005,7 @@ int read_ref_at(struct ref_store *refs, const char *refname, } struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err) { struct ref_transaction *tr; @@ -1012,12 +1013,13 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, CALLOC_ARRAY(tr, 1); tr->ref_store = refs; + tr->flags = flags; return tr; } struct ref_transaction *ref_transaction_begin(struct strbuf *err) { - return ref_store_transaction_begin(get_main_ref_store(the_repository), err); + return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err); } void ref_transaction_free(struct ref_transaction *transaction) @@ -1148,7 +1150,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, struct strbuf err = STRBUF_INIT; int ret = 0; - t = ref_store_transaction_begin(refs, &err); + t = ref_store_transaction_begin(refs, 0, &err); if (!t || ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, &err) || diff --git a/refs.h b/refs.h index 45c34e99e3..e3d3b7aa14 100644 --- a/refs.h +++ b/refs.h @@ -231,7 +231,7 @@ char *repo_default_branch_name(struct repository *r, int quiet); * struct strbuf err = STRBUF_INIT; * int ret = 0; * - * transaction = ref_store_transaction_begin(refs, &err); + * transaction = ref_store_transaction_begin(refs, 0, &err); * if (!transaction || * ref_transaction_update(...) || * ref_transaction_create(...) || @@ -551,6 +551,7 @@ enum action_on_err { * be freed by calling ref_transaction_free(). */ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err); struct ref_transaction *ref_transaction_begin(struct strbuf *err); diff --git a/refs/files-backend.c b/refs/files-backend.c index 453adc38ea..95583dcd00 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1122,7 +1122,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, &err); + transaction = ref_store_transaction_begin(&refs->base, 0, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1193,7 +1193,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); if (!transaction) return -1; @@ -1260,7 +1260,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); if (!transaction) goto error; @@ -2767,7 +2767,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, err); + refs->packed_ref_store, 0, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3038,7 +3038,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/refs/packed-backend.c b/refs/packed-backend.c index cb19b5291e..ac4d92460e 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1535,7 +1535,7 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, * updates into a single transaction. */ - transaction = ref_store_transaction_begin(ref_store, &err); + transaction = ref_store_transaction_begin(ref_store, 0, &err); if (!transaction) return -1; diff --git a/refs/refs-internal.h b/refs/refs-internal.h index fb2c58ce3b..5bec9014ac 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -213,6 +213,7 @@ struct ref_transaction { size_t nr; enum ref_transaction_state state; void *backend_data; + unsigned int flags; }; /* diff --git a/sequencer.c b/sequencer.c index b4135a78c9..78122e0f0c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3599,7 +3599,7 @@ static int do_label(struct repository *r, const char *name, int len) strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction) { error("%s", err.buf); ret = -1; -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 3/6] refs: allow skipping the reference-transaction hook 2021-12-07 10:55 [PATCH 0/6] refs: excessive hook execution with packed refs Patrick Steinhardt 2021-12-07 10:55 ` [PATCH 1/6] refs: open-code deletion of " Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt @ 2021-12-07 10:56 ` Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt ` (3 subsequent siblings) 6 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2021-12-07 10:56 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 1449 bytes --] The reference-transaction hook is executing whenever we prepare, commit or abort a reference transaction. While this is mostly intentional, in case of the files backend we're leaking the implementation detail that the store is in fact a composite store with one loose and one packed backend to the caller. So while we want to execute the hook for all logical updates, executing it for such implementation details is unexpected. Prepare for a fix by adding a new flag which allows to skip execution of the hook. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 3 +++ refs.h | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/refs.c b/refs.c index 1c8a0c01f1..462e629d37 100644 --- a/refs.c +++ b/refs.c @@ -2076,6 +2076,9 @@ static int run_transaction_hook(struct ref_transaction *transaction, const char *hook; int ret = 0, i; + if (transaction->flags & REF_TRANSACTION_SKIP_HOOK) + return 0; + hook = find_hook("reference-transaction"); if (!hook) return ret; diff --git a/refs.h b/refs.h index e3d3b7aa14..840fa02613 100644 --- a/refs.h +++ b/refs.h @@ -546,6 +546,11 @@ enum action_on_err { UPDATE_REFS_QUIET_ON_ERR }; +/* + * Skip executing the reference-transaction hook. + */ +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) + /* * Begin a reference transaction. The reference transaction must * be freed by calling ref_transaction_free(). -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 4/6] refs: demonstrate excessive execution of the reference-transaction hook 2021-12-07 10:55 [PATCH 0/6] refs: excessive hook execution with packed refs Patrick Steinhardt ` (2 preceding siblings ...) 2021-12-07 10:56 ` [PATCH 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt @ 2021-12-07 10:56 ` Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt ` (2 subsequent siblings) 6 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2021-12-07 10:56 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 2645 bytes --] Add tests which demonstate which demonstrates that we're executing the reference-transaction hook too often in some cases, which thus leaks implementation details about the reference store's implementation itself. Behaviour will be fixed in follow-up commits. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- t/t1416-ref-transaction-hooks.sh | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 6c941027a8..0567fbdf0b 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -136,4 +136,68 @@ test_expect_success 'interleaving hook calls succeed' ' test_cmp expect target-repo.git/actual ' +test_expect_success 'hook does not get called on packing refs' ' + # Pack references first such that we are in a known state. + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref refs/heads/unpacked-ref $POST_OID && + git pack-refs --all && + + # We only expect a single hook invocation, which is the call to + # git-update-ref(1). But currently, packing refs will also trigger the + # hook. + cat >expect <<-EOF && + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + prepared + $POST_OID $ZERO_OID refs/heads/unpacked-ref + committed + $POST_OID $ZERO_OID refs/heads/unpacked-ref + EOF + + test_cmp expect actual +' + +test_expect_success 'deleting packed ref calls hook once' ' + # Create a reference and pack it. + git update-ref refs/heads/to-be-deleted $POST_OID && + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref -d refs/heads/to-be-deleted $POST_OID && + + # We only expect a single hook invocation, which is the logical + # deletion. But currently, we see two interleaving transactions, once + # for deleting the loose refs and once for deleting the packed ref. + cat >expect <<-EOF && + prepared + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + prepared + $POST_OID $ZERO_OID refs/heads/to-be-deleted + committed + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + committed + $POST_OID $ZERO_OID refs/heads/to-be-deleted + EOF + + test_cmp expect actual +' + test_done -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 5/6] refs: do not execute reference-transaction hook on packing refs 2021-12-07 10:55 [PATCH 0/6] refs: excessive hook execution with packed refs Patrick Steinhardt ` (3 preceding siblings ...) 2021-12-07 10:56 ` [PATCH 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt @ 2021-12-07 10:56 ` Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt 6 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2021-12-07 10:56 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 2581 bytes --] The reference-transaction hook is supposed to track logical changes to references, but it currently also gets executed when packing refs in a repository. This is unexpected and ultimately not all that useful: packing refs is not supposed to result in any user-visible change to the refs' state, and it ultimately is an implementation detail of how refs stores work. Fix this excessive execution of the hook when packing refs. Reported-by: Waleed Khan <me@waleedkhan.name> Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 6 ++++-- t/t1416-ref-transaction-hooks.sh | 11 +---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 95583dcd00..f2bc72f81b 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1122,7 +1122,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, 0, &err); + transaction = ref_store_transaction_begin(&refs->base, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1193,7 +1194,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) return -1; diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 0567fbdf0b..f9d3d5213f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -150,21 +150,12 @@ test_expect_success 'hook does not get called on packing refs' ' git pack-refs --all && # We only expect a single hook invocation, which is the call to - # git-update-ref(1). But currently, packing refs will also trigger the - # hook. + # git-update-ref(1). cat >expect <<-EOF && prepared $ZERO_OID $POST_OID refs/heads/unpacked-ref committed $ZERO_OID $POST_OID refs/heads/unpacked-ref - prepared - $ZERO_OID $POST_OID refs/heads/unpacked-ref - committed - $ZERO_OID $POST_OID refs/heads/unpacked-ref - prepared - $POST_OID $ZERO_OID refs/heads/unpacked-ref - committed - $POST_OID $ZERO_OID refs/heads/unpacked-ref EOF test_cmp expect actual -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 6/6] refs: skip hooks when deleting uncovered packed refs 2021-12-07 10:55 [PATCH 0/6] refs: excessive hook execution with packed refs Patrick Steinhardt ` (4 preceding siblings ...) 2021-12-07 10:56 ` [PATCH 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt @ 2021-12-07 10:56 ` Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt 6 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2021-12-07 10:56 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 3560 bytes --] When deleting refs from the loose-files refs backend, then we need to be careful to also delete the same ref from the packed refs backend, if it exists. If we don't, then deleting the loose ref would "uncover" the packed ref. We thus always have to queue up deletions of refs for both the loose and the packed refs backend. This is done in two separate transactions, where the end result is that the reference-transaction hook is executed twice for the deleted refs. This behaviour is quite misleading: it's exposing implementation details of how the files backend works to the user, in contrast to the logical updates that we'd really want to expose via the hook. Worse yet, whether the hook gets executed once or twice depends on how well-packed the repository is: if the ref only exists as a loose ref, then we execute it once, otherwise if it is also packed then we execute it twice. Fix this behaviour and don't execute the reference-transaction hook at all when refs in the packed-refs backend if it's driven by the files backend. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 9 ++++++--- t/t1416-ref-transaction-hooks.sh | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index f2bc72f81b..0a2b5ea5da 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1262,7 +1262,8 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto error; @@ -2769,7 +2770,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, 0, err); + refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3040,7 +3042,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index f9d3d5213f..4e1e84a91f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -175,16 +175,11 @@ test_expect_success 'deleting packed ref calls hook once' ' git update-ref -d refs/heads/to-be-deleted $POST_OID && # We only expect a single hook invocation, which is the logical - # deletion. But currently, we see two interleaving transactions, once - # for deleting the loose refs and once for deleting the packed ref. + # deletion. cat >expect <<-EOF && - prepared - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted prepared $POST_OID $ZERO_OID refs/heads/to-be-deleted committed - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted - committed $POST_OID $ZERO_OID refs/heads/to-be-deleted EOF -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v2 0/6] refs: excessive hook execution with packed refs 2021-12-07 10:55 [PATCH 0/6] refs: excessive hook execution with packed refs Patrick Steinhardt ` (5 preceding siblings ...) 2021-12-07 10:56 ` [PATCH 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt @ 2022-01-07 11:55 ` Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 1/6] refs: open-code deletion of " Patrick Steinhardt ` (8 more replies) 6 siblings, 9 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-07 11:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 1833 bytes --] Hi, this is a resend of version 1 of this patch series to hopefully entice some reviews. The only change is that v2 is rebased onto the current main branch at commit e83ba647f7 (The seventh batch, 2022-01-05). The following was from the orignial cover letter: As reported by Waleed in [1], the reference-transaction hook is being executed when packing refs. Given that the hook ideally ought to track logical updates to refs instead of leaking low-level implementation details of how the files backend works, this is understandably leading to some confusion. This patch series aims to fix that by improving how the tandom of loose and packed refs backends interact such that we skip executing the hook when the loose backend: - repacks references. - needs to delete packed refs when deleting a loose ref would uncover that packed ref. Patrick [1]: <CAKjfCeBcuYC3OXRVtxxDGWRGOxC38Fb7CNuSh_dMmxpGVip_9Q@mail.gmail.com> Patrick Steinhardt (6): refs: open-code deletion of packed refs refs: allow passing flags when beginning transactions refs: allow skipping the reference-transaction hook refs: demonstrate excessive execution of the reference-transaction hook refs: do not execute reference-transaction hook on packing refs refs: skip hooks when deleting uncovered packed refs refs.c | 11 +++++-- refs.h | 8 ++++- refs/files-backend.c | 25 +++++++++++----- refs/packed-backend.c | 30 ++++++++++++++----- refs/packed-backend.h | 6 ++++ refs/refs-internal.h | 1 + sequencer.c | 2 +- t/t1416-ref-transaction-hooks.sh | 50 ++++++++++++++++++++++++++++++++ 8 files changed, 113 insertions(+), 20 deletions(-) -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v2 1/6] refs: open-code deletion of packed refs 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt @ 2022-01-07 11:55 ` Patrick Steinhardt 2022-01-08 0:57 ` Junio C Hamano ` (2 more replies) 2022-01-07 11:55 ` [PATCH v2 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt ` (7 subsequent siblings) 8 siblings, 3 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-07 11:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 5049 bytes --] When deleting loose refs, then we also have to delete the refs in the packed backend. This is done by calling `refs_delete_refs()`, which then uses the packed-backend's logic to delete refs. This doesn't allow us to exercise any control over the reference transaction which is being created in the packed backend, which is required in a subsequent commit. Extract a new function `packed_refs_delete_refs()`, which hosts most of the logic to delete refs except for creating the transaction itself. Like this, we can easily create the transactionion in the files backend and thus exert more control over it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 12 +++++++++--- refs/packed-backend.c | 28 +++++++++++++++++++++------- refs/packed-backend.h | 6 ++++++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 90b671025a..5795e54020 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1249,6 +1249,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct ref_transaction *transaction = NULL; struct strbuf err = STRBUF_INIT; int i, result = 0; @@ -1258,10 +1259,14 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { - packed_refs_unlock(refs->packed_ref_store); + transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + if (!transaction) + goto error; + + result = packed_refs_delete_refs(refs->packed_ref_store, + transaction, msg, refnames, flags); + if (result) goto error; - } packed_refs_unlock(refs->packed_ref_store); @@ -1288,6 +1293,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, else error(_("could not delete references: %s"), err.buf); + ref_transaction_free(transaction); strbuf_release(&err); return -1; } diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 67152c664e..e97d67f932 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1522,15 +1522,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, static int packed_delete_refs(struct ref_store *ref_store, const char *msg, struct string_list *refnames, unsigned int flags) { - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - struct string_list_item *item; int ret; - (void)refs; /* We need the check above, but don't use the variable */ - if (!refnames->nr) return 0; @@ -1544,6 +1539,27 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, if (!transaction) return -1; + ret = packed_refs_delete_refs(ref_store, transaction, + msg, refnames, flags); + + ref_transaction_free(transaction); + return ret; +} + +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags) +{ + struct packed_ref_store *refs = + packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct strbuf err = STRBUF_INIT; + struct string_list_item *item; + int ret; + + (void)(refs); /* We need the check above, but don't use the variable */ + for_each_string_list_item(item, refnames) { if (ref_transaction_delete(transaction, item->string, NULL, flags, msg, &err)) { @@ -1554,7 +1570,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, } ret = ref_transaction_commit(transaction, &err); - if (ret) { if (refnames->nr == 1) error(_("could not delete reference %s: %s"), @@ -1563,7 +1578,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, error(_("could not delete references: %s"), err.buf); } - ref_transaction_free(transaction); strbuf_release(&err); return ret; } diff --git a/refs/packed-backend.h b/refs/packed-backend.h index f61a73ec25..5e0dd7d08e 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -27,6 +27,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) void packed_refs_unlock(struct ref_store *ref_store); int packed_refs_is_locked(struct ref_store *ref_store); +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags); + /* * Return true if `transaction` really needs to be carried out against * the specified packed_ref_store, or false if it can be skipped -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v2 1/6] refs: open-code deletion of packed refs 2022-01-07 11:55 ` [PATCH v2 1/6] refs: open-code deletion of " Patrick Steinhardt @ 2022-01-08 0:57 ` Junio C Hamano 2022-01-08 1:51 ` Junio C Hamano 2022-01-13 0:24 ` Junio C Hamano 2 siblings, 0 replies; 51+ messages in thread From: Junio C Hamano @ 2022-01-08 0:57 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Jeff King, Bryan Turner, Waleed Khan Patrick Steinhardt <ps@pks.im> writes: This breaks the static analysis job at https://github.com/git/git/runs/4744820766 > refs/packed-backend.h | 6 ++++++ > diff --git a/refs/packed-backend.h b/refs/packed-backend.h > index f61a73ec25..5e0dd7d08e 100644 > --- a/refs/packed-backend.h > +++ b/refs/packed-backend.h > @@ -27,6 +27,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) > void packed_refs_unlock(struct ref_store *ref_store); > int packed_refs_is_locked(struct ref_store *ref_store); > > +int packed_refs_delete_refs(struct ref_store *ref_store, > + struct ref_transaction *transaction, > + const char *msg, > + struct string_list *refnames, > + unsigned int flags); > + > /* > * Return true if `transaction` really needs to be carried out against > * the specified packed_ref_store, or false if it can be skipped HDR reftable/block.h In file included from refs/packed-backend.hcc:2:0: ./refs/packed-backend.h:33:15: error: ‘struct string_list’ declared inside parameter list will not be visible outside of this definition or declaration [-Werror] struct string_list *refnames, ^~~~~~~~~~~ cc1: all warnings being treated as errors make: *** [refs/packed-backend.hco] Error 1 make: *** Waiting for unfinished jobs.... Makefile:3029: recipe for target 'refs/packed-backend.hco' failed Error: Process completed with exit code 1. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 1/6] refs: open-code deletion of packed refs 2022-01-07 11:55 ` [PATCH v2 1/6] refs: open-code deletion of " Patrick Steinhardt 2022-01-08 0:57 ` Junio C Hamano @ 2022-01-08 1:51 ` Junio C Hamano 2022-01-13 0:24 ` Junio C Hamano 2 siblings, 0 replies; 51+ messages in thread From: Junio C Hamano @ 2022-01-08 1:51 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Jeff King, Bryan Turner, Waleed Khan Patrick Steinhardt <ps@pks.im> writes: > When deleting loose refs, then we also have to delete the refs in the > packed backend. This is done by calling `refs_delete_refs()`, which > then uses the packed-backend's logic to delete refs. This doesn't allow > us to exercise any control over the reference transaction which is being > created in the packed backend, which is required in a subsequent commit. > > Extract a new function `packed_refs_delete_refs()`, which hosts most of > the logic to delete refs except for creating the transaction itself. > Like this, we can easily create the transactionion in the files backend ionion? > and thus exert more control over it. > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- The title might be technically correct, but I think the primary point, the benefit this step brings to the system, is now we have a helper function we can call in a transaction we create ourselves. refs: extract packed_refs_delete_refs() to be used in our own transaction or something along that line, perhaps? I dunno. > refs/files-backend.c | 12 +++++++++--- > refs/packed-backend.c | 28 +++++++++++++++++++++------- > refs/packed-backend.h | 6 ++++++ > 3 files changed, 36 insertions(+), 10 deletions(-) > > diff --git a/refs/files-backend.c b/refs/files-backend.c > index 90b671025a..5795e54020 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -1249,6 +1249,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > { > struct files_ref_store *refs = > files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); > + struct ref_transaction *transaction = NULL; > struct strbuf err = STRBUF_INIT; > int i, result = 0; > > @@ -1258,10 +1259,14 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > if (packed_refs_lock(refs->packed_ref_store, 0, &err)) > goto error; > > - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { > - packed_refs_unlock(refs->packed_ref_store); > + transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); > + if (!transaction) > + goto error; > + > + result = packed_refs_delete_refs(refs->packed_ref_store, > + transaction, msg, refnames, flags); > + if (result) > goto error; > - } Without looking at the later patches, my guess is that we'd create a single transaction for packed-refs backend and then issue multiple delete requests in that single transaction? Ah, no. We are already deleting multiple refs in one go, so that is not what is happening here. Makes readers a bit curious. Hopefully we will find it out in a later patch. > diff --git a/refs/packed-backend.h b/refs/packed-backend.h > index f61a73ec25..5e0dd7d08e 100644 > --- a/refs/packed-backend.h > +++ b/refs/packed-backend.h > @@ -27,6 +27,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) > void packed_refs_unlock(struct ref_store *ref_store); > int packed_refs_is_locked(struct ref_store *ref_store); > > +int packed_refs_delete_refs(struct ref_store *ref_store, > + struct ref_transaction *transaction, > + const char *msg, > + struct string_list *refnames, > + unsigned int flags); > + > /* > * Return true if `transaction` really needs to be carried out against > * the specified packed_ref_store, or false if it can be skipped ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 1/6] refs: open-code deletion of packed refs 2022-01-07 11:55 ` [PATCH v2 1/6] refs: open-code deletion of " Patrick Steinhardt 2022-01-08 0:57 ` Junio C Hamano 2022-01-08 1:51 ` Junio C Hamano @ 2022-01-13 0:24 ` Junio C Hamano 2 siblings, 0 replies; 51+ messages in thread From: Junio C Hamano @ 2022-01-13 0:24 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Jeff King, Bryan Turner, Waleed Khan Patrick Steinhardt <ps@pks.im> writes: > diff --git a/refs/packed-backend.h b/refs/packed-backend.h > index f61a73ec25..5e0dd7d08e 100644 > --- a/refs/packed-backend.h > +++ b/refs/packed-backend.h > @@ -27,6 +27,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) > void packed_refs_unlock(struct ref_store *ref_store); > int packed_refs_is_locked(struct ref_store *ref_store); > > +int packed_refs_delete_refs(struct ref_store *ref_store, > + struct ref_transaction *transaction, > + const char *msg, > + struct string_list *refnames, > + unsigned int flags); > + > /* > * Return true if `transaction` really needs to be carried out against > * the specified packed_ref_store, or false if it can be skipped This breaks "make hdr-check" (which is part of CI -- static-analysis). ----- >8 --------- >8 --------- >8 --------- >8 ----- From: Junio C Hamano <gitster@pobox.com> Date: Wed, 12 Jan 2022 16:24:13 -0800 Subject: [PATCH] fixup! refs: open-code deletion of packed refs --- refs/packed-backend.h | 1 + 1 file changed, 1 insertion(+) diff --git a/refs/packed-backend.h b/refs/packed-backend.h index 5e0dd7d08e..a2cca5d9a3 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -3,6 +3,7 @@ struct repository; struct ref_transaction; +struct string_list; /* * Support for storing references in a `packed-refs` file. -- 2.35.0-rc0-185-gedd802a149 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v2 2/6] refs: allow passing flags when beginning transactions 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 1/6] refs: open-code deletion of " Patrick Steinhardt @ 2022-01-07 11:55 ` Patrick Steinhardt 2022-01-08 1:51 ` Junio C Hamano 2022-01-07 11:55 ` [PATCH v2 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt ` (6 subsequent siblings) 8 siblings, 1 reply; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-07 11:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 6362 bytes --] We do not currently have any flags when creating reference transactions, but we'll add one to disable execution of the reference transaction hook in some cases. Allow passing flags to `ref_store_transaction_begin()` to prepare for this change. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 8 +++++--- refs.h | 3 ++- refs/files-backend.c | 10 +++++----- refs/packed-backend.c | 2 +- refs/refs-internal.h | 1 + sequencer.c | 2 +- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/refs.c b/refs.c index 4da4996c4d..7415864b62 100644 --- a/refs.c +++ b/refs.c @@ -800,7 +800,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction || ref_transaction_delete(transaction, refname, old_oid, flags, msg, &err) || @@ -1005,6 +1005,7 @@ int read_ref_at(struct ref_store *refs, const char *refname, } struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err) { struct ref_transaction *tr; @@ -1012,12 +1013,13 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, CALLOC_ARRAY(tr, 1); tr->ref_store = refs; + tr->flags = flags; return tr; } struct ref_transaction *ref_transaction_begin(struct strbuf *err) { - return ref_store_transaction_begin(get_main_ref_store(the_repository), err); + return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err); } void ref_transaction_free(struct ref_transaction *transaction) @@ -1156,7 +1158,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, struct strbuf err = STRBUF_INIT; int ret = 0; - t = ref_store_transaction_begin(refs, &err); + t = ref_store_transaction_begin(refs, 0, &err); if (!t || ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, &err) || diff --git a/refs.h b/refs.h index 92360e55a2..31f7bf9642 100644 --- a/refs.h +++ b/refs.h @@ -231,7 +231,7 @@ char *repo_default_branch_name(struct repository *r, int quiet); * struct strbuf err = STRBUF_INIT; * int ret = 0; * - * transaction = ref_store_transaction_begin(refs, &err); + * transaction = ref_store_transaction_begin(refs, 0, &err); * if (!transaction || * ref_transaction_update(...) || * ref_transaction_create(...) || @@ -573,6 +573,7 @@ enum action_on_err { * be freed by calling ref_transaction_free(). */ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err); struct ref_transaction *ref_transaction_begin(struct strbuf *err); diff --git a/refs/files-backend.c b/refs/files-backend.c index 5795e54020..ecf88cee04 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1121,7 +1121,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, &err); + transaction = ref_store_transaction_begin(&refs->base, 0, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1192,7 +1192,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); if (!transaction) return -1; @@ -1259,7 +1259,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); if (!transaction) goto error; @@ -2773,7 +2773,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, err); + refs->packed_ref_store, 0, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3044,7 +3044,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/refs/packed-backend.c b/refs/packed-backend.c index e97d67f932..cfebcd0b46 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1535,7 +1535,7 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, * updates into a single transaction. */ - transaction = ref_store_transaction_begin(ref_store, &err); + transaction = ref_store_transaction_begin(ref_store, 0, &err); if (!transaction) return -1; diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 46a839539e..a0af63f162 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -213,6 +213,7 @@ struct ref_transaction { size_t nr; enum ref_transaction_state state; void *backend_data; + unsigned int flags; }; /* diff --git a/sequencer.c b/sequencer.c index 6abd72160c..61edd39d7a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3588,7 +3588,7 @@ static int do_label(struct repository *r, const char *name, int len) strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction) { error("%s", err.buf); ret = -1; -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v2 2/6] refs: allow passing flags when beginning transactions 2022-01-07 11:55 ` [PATCH v2 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt @ 2022-01-08 1:51 ` Junio C Hamano 0 siblings, 0 replies; 51+ messages in thread From: Junio C Hamano @ 2022-01-08 1:51 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Jeff King, Bryan Turner, Waleed Khan Patrick Steinhardt <ps@pks.im> writes: > We do not currently have any flags when creating reference transactions, > but we'll add one to disable execution of the reference transaction hook > in some cases. > > Allow passing flags to `ref_store_transaction_begin()` to prepare for > this change. > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > refs.c | 8 +++++--- > refs.h | 3 ++- > refs/files-backend.c | 10 +++++----- > refs/packed-backend.c | 2 +- > refs/refs-internal.h | 1 + > sequencer.c | 2 +- > 6 files changed, 15 insertions(+), 11 deletions(-) > > diff --git a/refs.c b/refs.c > index 4da4996c4d..7415864b62 100644 > --- a/refs.c > +++ b/refs.c > @@ -800,7 +800,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, > struct ref_transaction *transaction; > struct strbuf err = STRBUF_INIT; > > - transaction = ref_store_transaction_begin(refs, &err); > + transaction = ref_store_transaction_begin(refs, 0, &err); Ah, OK, the "later" came soon enough. It wasn't that we wanted to cram multiple calls for "delete" in a single transaction. It was that we wanted to tweak how each transaction works by passing a flag word. > struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, > + unsigned int flags, > struct strbuf *err) OK. And in this step, everybody passes 0 (i.e. no bit set) to the function. ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v2 3/6] refs: allow skipping the reference-transaction hook 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 1/6] refs: open-code deletion of " Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt @ 2022-01-07 11:55 ` Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt ` (5 subsequent siblings) 8 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-07 11:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 1449 bytes --] The reference-transaction hook is executing whenever we prepare, commit or abort a reference transaction. While this is mostly intentional, in case of the files backend we're leaking the implementation detail that the store is in fact a composite store with one loose and one packed backend to the caller. So while we want to execute the hook for all logical updates, executing it for such implementation details is unexpected. Prepare for a fix by adding a new flag which allows to skip execution of the hook. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 3 +++ refs.h | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/refs.c b/refs.c index 7415864b62..526bf5ed97 100644 --- a/refs.c +++ b/refs.c @@ -2084,6 +2084,9 @@ static int run_transaction_hook(struct ref_transaction *transaction, const char *hook; int ret = 0, i; + if (transaction->flags & REF_TRANSACTION_SKIP_HOOK) + return 0; + hook = find_hook("reference-transaction"); if (!hook) return ret; diff --git a/refs.h b/refs.h index 31f7bf9642..d4056f9fe2 100644 --- a/refs.h +++ b/refs.h @@ -568,6 +568,11 @@ enum action_on_err { UPDATE_REFS_QUIET_ON_ERR }; +/* + * Skip executing the reference-transaction hook. + */ +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) + /* * Begin a reference transaction. The reference transaction must * be freed by calling ref_transaction_free(). -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v2 4/6] refs: demonstrate excessive execution of the reference-transaction hook 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (2 preceding siblings ...) 2022-01-07 11:55 ` [PATCH v2 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt @ 2022-01-07 11:55 ` Patrick Steinhardt 2022-01-08 1:31 ` Junio C Hamano 2022-01-08 5:43 ` Eric Sunshine 2022-01-07 11:55 ` [PATCH v2 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt ` (4 subsequent siblings) 8 siblings, 2 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-07 11:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 2645 bytes --] Add tests which demonstate which demonstrates that we're executing the reference-transaction hook too often in some cases, which thus leaks implementation details about the reference store's implementation itself. Behaviour will be fixed in follow-up commits. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- t/t1416-ref-transaction-hooks.sh | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 6c941027a8..0567fbdf0b 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -136,4 +136,68 @@ test_expect_success 'interleaving hook calls succeed' ' test_cmp expect target-repo.git/actual ' +test_expect_success 'hook does not get called on packing refs' ' + # Pack references first such that we are in a known state. + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref refs/heads/unpacked-ref $POST_OID && + git pack-refs --all && + + # We only expect a single hook invocation, which is the call to + # git-update-ref(1). But currently, packing refs will also trigger the + # hook. + cat >expect <<-EOF && + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + prepared + $POST_OID $ZERO_OID refs/heads/unpacked-ref + committed + $POST_OID $ZERO_OID refs/heads/unpacked-ref + EOF + + test_cmp expect actual +' + +test_expect_success 'deleting packed ref calls hook once' ' + # Create a reference and pack it. + git update-ref refs/heads/to-be-deleted $POST_OID && + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref -d refs/heads/to-be-deleted $POST_OID && + + # We only expect a single hook invocation, which is the logical + # deletion. But currently, we see two interleaving transactions, once + # for deleting the loose refs and once for deleting the packed ref. + cat >expect <<-EOF && + prepared + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + prepared + $POST_OID $ZERO_OID refs/heads/to-be-deleted + committed + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + committed + $POST_OID $ZERO_OID refs/heads/to-be-deleted + EOF + + test_cmp expect actual +' + test_done -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v2 4/6] refs: demonstrate excessive execution of the reference-transaction hook 2022-01-07 11:55 ` [PATCH v2 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt @ 2022-01-08 1:31 ` Junio C Hamano 2022-01-10 12:54 ` Patrick Steinhardt 2022-01-08 5:43 ` Eric Sunshine 1 sibling, 1 reply; 51+ messages in thread From: Junio C Hamano @ 2022-01-08 1:31 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Jeff King, Bryan Turner, Waleed Khan Patrick Steinhardt <ps@pks.im> writes: > Add tests which demonstate which demonstrates that we're executing the You demonstrate too often, which may be the point of the test, but looks wrong. I actually think this should be done as part of the fix to the code itself, which presumably is a single-liner to tell the "skip when running delete in packed-refs backend". IOW, just fix the code and test how the externally observable behaviour of the code should be in new tests, in the same commit. > diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh > index 6c941027a8..0567fbdf0b 100755 > --- a/t/t1416-ref-transaction-hooks.sh > +++ b/t/t1416-ref-transaction-hooks.sh > @@ -136,4 +136,68 @@ test_expect_success 'interleaving hook calls succeed' ' > test_cmp expect target-repo.git/actual > ' > > +test_expect_success 'hook does not get called on packing refs' ' > + # Pack references first such that we are in a known state. > + git pack-refs --all && > + > + write_script .git/hooks/reference-transaction <<-\EOF && > + echo "$@" >>actual > + cat >>actual > + EOF > + rm -f actual && > + > + git update-ref refs/heads/unpacked-ref $POST_OID && > + git pack-refs --all && > + > + # We only expect a single hook invocation, which is the call to > + # git-update-ref(1). But currently, packing refs will also trigger the > + # hook. > + cat >expect <<-EOF && > + prepared > + $ZERO_OID $POST_OID refs/heads/unpacked-ref > + committed > + $ZERO_OID $POST_OID refs/heads/unpacked-ref > + prepared > + $ZERO_OID $POST_OID refs/heads/unpacked-ref > + committed > + $ZERO_OID $POST_OID refs/heads/unpacked-ref > + prepared > + $POST_OID $ZERO_OID refs/heads/unpacked-ref > + committed > + $POST_OID $ZERO_OID refs/heads/unpacked-ref > + EOF > + > + test_cmp expect actual > +' > + > +test_expect_success 'deleting packed ref calls hook once' ' > + # Create a reference and pack it. > + git update-ref refs/heads/to-be-deleted $POST_OID && > + git pack-refs --all && > + > + write_script .git/hooks/reference-transaction <<-\EOF && > + echo "$@" >>actual > + cat >>actual > + EOF > + rm -f actual && > + > + git update-ref -d refs/heads/to-be-deleted $POST_OID && > + > + # We only expect a single hook invocation, which is the logical > + # deletion. But currently, we see two interleaving transactions, once > + # for deleting the loose refs and once for deleting the packed ref. > + cat >expect <<-EOF && > + prepared > + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted > + prepared > + $POST_OID $ZERO_OID refs/heads/to-be-deleted > + committed > + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted > + committed > + $POST_OID $ZERO_OID refs/heads/to-be-deleted > + EOF > + > + test_cmp expect actual > +' > + > test_done ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 4/6] refs: demonstrate excessive execution of the reference-transaction hook 2022-01-08 1:31 ` Junio C Hamano @ 2022-01-10 12:54 ` Patrick Steinhardt 0 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-10 12:54 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 3555 bytes --] On Fri, Jan 07, 2022 at 05:31:04PM -0800, Junio C Hamano wrote: > Patrick Steinhardt <ps@pks.im> writes: > > > Add tests which demonstate which demonstrates that we're executing the > > You demonstrate too often, which may be the point of the test, but > looks wrong. > > I actually think this should be done as part of the fix to the code > itself, which presumably is a single-liner to tell the "skip when > running delete in packed-refs backend". IOW, just fix the code and > test how the externally observable behaviour of the code should be > in new tests, in the same commit. The reason why I chose to split this out into a separate commit is that it makes it easier to see what behaviour exactly is changing. If it was a single step, then a reader would only see the post-image behaviour but cannot reason about the pre-image behaviour without puzzling everything together. So personally I'd prefer to keep it as a separate step, but I'm not opposed to merging them if you still disagree with my reasoning above. Patrick > > diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh > > index 6c941027a8..0567fbdf0b 100755 > > --- a/t/t1416-ref-transaction-hooks.sh > > +++ b/t/t1416-ref-transaction-hooks.sh > > @@ -136,4 +136,68 @@ test_expect_success 'interleaving hook calls succeed' ' > > test_cmp expect target-repo.git/actual > > ' > > > > +test_expect_success 'hook does not get called on packing refs' ' > > + # Pack references first such that we are in a known state. > > + git pack-refs --all && > > + > > + write_script .git/hooks/reference-transaction <<-\EOF && > > + echo "$@" >>actual > > + cat >>actual > > + EOF > > + rm -f actual && > > + > > + git update-ref refs/heads/unpacked-ref $POST_OID && > > + git pack-refs --all && > > + > > + # We only expect a single hook invocation, which is the call to > > + # git-update-ref(1). But currently, packing refs will also trigger the > > + # hook. > > + cat >expect <<-EOF && > > + prepared > > + $ZERO_OID $POST_OID refs/heads/unpacked-ref > > + committed > > + $ZERO_OID $POST_OID refs/heads/unpacked-ref > > + prepared > > + $ZERO_OID $POST_OID refs/heads/unpacked-ref > > + committed > > + $ZERO_OID $POST_OID refs/heads/unpacked-ref > > + prepared > > + $POST_OID $ZERO_OID refs/heads/unpacked-ref > > + committed > > + $POST_OID $ZERO_OID refs/heads/unpacked-ref > > + EOF > > + > > + test_cmp expect actual > > +' > > + > > +test_expect_success 'deleting packed ref calls hook once' ' > > + # Create a reference and pack it. > > + git update-ref refs/heads/to-be-deleted $POST_OID && > > + git pack-refs --all && > > + > > + write_script .git/hooks/reference-transaction <<-\EOF && > > + echo "$@" >>actual > > + cat >>actual > > + EOF > > + rm -f actual && > > + > > + git update-ref -d refs/heads/to-be-deleted $POST_OID && > > + > > + # We only expect a single hook invocation, which is the logical > > + # deletion. But currently, we see two interleaving transactions, once > > + # for deleting the loose refs and once for deleting the packed ref. > > + cat >expect <<-EOF && > > + prepared > > + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted > > + prepared > > + $POST_OID $ZERO_OID refs/heads/to-be-deleted > > + committed > > + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted > > + committed > > + $POST_OID $ZERO_OID refs/heads/to-be-deleted > > + EOF > > + > > + test_cmp expect actual > > +' > > + > > test_done [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 4/6] refs: demonstrate excessive execution of the reference-transaction hook 2022-01-07 11:55 ` [PATCH v2 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt 2022-01-08 1:31 ` Junio C Hamano @ 2022-01-08 5:43 ` Eric Sunshine 1 sibling, 0 replies; 51+ messages in thread From: Eric Sunshine @ 2022-01-08 5:43 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: Git List, Jeff King, Bryan Turner, Waleed Khan On Fri, Jan 7, 2022 at 3:09 PM Patrick Steinhardt <ps@pks.im> wrote: > Add tests which demonstate which demonstrates that we're executing the > reference-transaction hook too often in some cases, which thus leaks > implementation details about the reference store's implementation > itself. Behaviour will be fixed in follow-up commits. s/which demonstate which demonstrates/which demonstrate/ > Signed-off-by: Patrick Steinhardt <ps@pks.im> ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v2 5/6] refs: do not execute reference-transaction hook on packing refs 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (3 preceding siblings ...) 2022-01-07 11:55 ` [PATCH v2 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt @ 2022-01-07 11:55 ` Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt ` (3 subsequent siblings) 8 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-07 11:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 2581 bytes --] The reference-transaction hook is supposed to track logical changes to references, but it currently also gets executed when packing refs in a repository. This is unexpected and ultimately not all that useful: packing refs is not supposed to result in any user-visible change to the refs' state, and it ultimately is an implementation detail of how refs stores work. Fix this excessive execution of the hook when packing refs. Reported-by: Waleed Khan <me@waleedkhan.name> Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 6 ++++-- t/t1416-ref-transaction-hooks.sh | 11 +---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index ecf88cee04..3c0f3027fe 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1121,7 +1121,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, 0, &err); + transaction = ref_store_transaction_begin(&refs->base, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1192,7 +1193,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) return -1; diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 0567fbdf0b..f9d3d5213f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -150,21 +150,12 @@ test_expect_success 'hook does not get called on packing refs' ' git pack-refs --all && # We only expect a single hook invocation, which is the call to - # git-update-ref(1). But currently, packing refs will also trigger the - # hook. + # git-update-ref(1). cat >expect <<-EOF && prepared $ZERO_OID $POST_OID refs/heads/unpacked-ref committed $ZERO_OID $POST_OID refs/heads/unpacked-ref - prepared - $ZERO_OID $POST_OID refs/heads/unpacked-ref - committed - $ZERO_OID $POST_OID refs/heads/unpacked-ref - prepared - $POST_OID $ZERO_OID refs/heads/unpacked-ref - committed - $POST_OID $ZERO_OID refs/heads/unpacked-ref EOF test_cmp expect actual -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v2 6/6] refs: skip hooks when deleting uncovered packed refs 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (4 preceding siblings ...) 2022-01-07 11:55 ` [PATCH v2 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt @ 2022-01-07 11:55 ` Patrick Steinhardt 2022-01-08 2:01 ` Junio C Hamano 2022-01-07 22:17 ` [PATCH v2 0/6] refs: excessive hook execution with " Junio C Hamano ` (2 subsequent siblings) 8 siblings, 1 reply; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-07 11:55 UTC (permalink / raw) To: git; +Cc: Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 3560 bytes --] When deleting refs from the loose-files refs backend, then we need to be careful to also delete the same ref from the packed refs backend, if it exists. If we don't, then deleting the loose ref would "uncover" the packed ref. We thus always have to queue up deletions of refs for both the loose and the packed refs backend. This is done in two separate transactions, where the end result is that the reference-transaction hook is executed twice for the deleted refs. This behaviour is quite misleading: it's exposing implementation details of how the files backend works to the user, in contrast to the logical updates that we'd really want to expose via the hook. Worse yet, whether the hook gets executed once or twice depends on how well-packed the repository is: if the ref only exists as a loose ref, then we execute it once, otherwise if it is also packed then we execute it twice. Fix this behaviour and don't execute the reference-transaction hook at all when refs in the packed-refs backend if it's driven by the files backend. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 9 ++++++--- t/t1416-ref-transaction-hooks.sh | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 3c0f3027fe..9a20cb8fa8 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1261,7 +1261,8 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto error; @@ -2775,7 +2776,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, 0, err); + refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3046,7 +3048,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index f9d3d5213f..4e1e84a91f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -175,16 +175,11 @@ test_expect_success 'deleting packed ref calls hook once' ' git update-ref -d refs/heads/to-be-deleted $POST_OID && # We only expect a single hook invocation, which is the logical - # deletion. But currently, we see two interleaving transactions, once - # for deleting the loose refs and once for deleting the packed ref. + # deletion. cat >expect <<-EOF && - prepared - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted prepared $POST_OID $ZERO_OID refs/heads/to-be-deleted committed - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted - committed $POST_OID $ZERO_OID refs/heads/to-be-deleted EOF -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v2 6/6] refs: skip hooks when deleting uncovered packed refs 2022-01-07 11:55 ` [PATCH v2 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt @ 2022-01-08 2:01 ` Junio C Hamano 2022-01-10 13:18 ` Patrick Steinhardt 0 siblings, 1 reply; 51+ messages in thread From: Junio C Hamano @ 2022-01-08 2:01 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Jeff King, Bryan Turner, Waleed Khan Patrick Steinhardt <ps@pks.im> writes: > When deleting refs from the loose-files refs backend, then we need to be > careful to also delete the same ref from the packed refs backend, if it > exists. If we don't, then deleting the loose ref would "uncover" the > packed ref. We thus always have to queue up deletions of refs for both > the loose and the packed refs backend. This is done in two separate > transactions, where the end result is that the reference-transaction > hook is executed twice for the deleted refs. > > This behaviour is quite misleading: it's exposing implementation details > of how the files backend works to the user, in contrast to the logical > updates that we'd really want to expose via the hook. Worse yet, whether > the hook gets executed once or twice depends on how well-packed the > repository is: if the ref only exists as a loose ref, then we execute it > once, otherwise if it is also packed then we execute it twice. If the ref only exists as a packed ref, what happens? ... > Fix this behaviour and don't execute the reference-transaction hook at > all when refs in the packed-refs backend if it's driven by the files > backend. ... We try to remove from the loose backend, which would say "nah, it did not exist in my store". I am not sure if it should execute the delete hook in such a case for the ref. But if it does not, not running the hook in the ref transaction for packed backend driven by the loose backend would mean nobody notifies the deletion of the ref, no? To me, it seems that the only case this strategy would work correctly is when the files backend calls deletion hook for a request to delete nonexistent ref, which by itself sounds like a problem. > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > refs/files-backend.c | 9 ++++++--- > t/t1416-ref-transaction-hooks.sh | 7 +------ > 2 files changed, 7 insertions(+), 9 deletions(-) > > diff --git a/refs/files-backend.c b/refs/files-backend.c > index 3c0f3027fe..9a20cb8fa8 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -1261,7 +1261,8 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > if (packed_refs_lock(refs->packed_ref_store, 0, &err)) > goto error; > > - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); > + transaction = ref_store_transaction_begin(refs->packed_ref_store, > + REF_TRANSACTION_SKIP_HOOK, &err); > if (!transaction) > goto error; > > @@ -2775,7 +2776,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, > */ > if (!packed_transaction) { > packed_transaction = ref_store_transaction_begin( > - refs->packed_ref_store, 0, err); > + refs->packed_ref_store, > + REF_TRANSACTION_SKIP_HOOK, err); > if (!packed_transaction) { > ret = TRANSACTION_GENERIC_ERROR; > goto cleanup; > @@ -3046,7 +3048,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, > &affected_refnames)) > BUG("initial ref transaction called with existing refs"); > > - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); > + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, > + REF_TRANSACTION_SKIP_HOOK, err); > if (!packed_transaction) { > ret = TRANSACTION_GENERIC_ERROR; > goto cleanup; > diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh > index f9d3d5213f..4e1e84a91f 100755 > --- a/t/t1416-ref-transaction-hooks.sh > +++ b/t/t1416-ref-transaction-hooks.sh > @@ -175,16 +175,11 @@ test_expect_success 'deleting packed ref calls hook once' ' > git update-ref -d refs/heads/to-be-deleted $POST_OID && > > # We only expect a single hook invocation, which is the logical > - # deletion. But currently, we see two interleaving transactions, once > - # for deleting the loose refs and once for deleting the packed ref. > + # deletion. > cat >expect <<-EOF && > - prepared > - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted > prepared > $POST_OID $ZERO_OID refs/heads/to-be-deleted > committed > - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted > - committed > $POST_OID $ZERO_OID refs/heads/to-be-deleted > EOF ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 6/6] refs: skip hooks when deleting uncovered packed refs 2022-01-08 2:01 ` Junio C Hamano @ 2022-01-10 13:18 ` Patrick Steinhardt 2022-01-10 18:00 ` Junio C Hamano 0 siblings, 1 reply; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-10 13:18 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Jeff King, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 5588 bytes --] On Fri, Jan 07, 2022 at 06:01:04PM -0800, Junio C Hamano wrote: > Patrick Steinhardt <ps@pks.im> writes: > > > When deleting refs from the loose-files refs backend, then we need to be > > careful to also delete the same ref from the packed refs backend, if it > > exists. If we don't, then deleting the loose ref would "uncover" the > > packed ref. We thus always have to queue up deletions of refs for both > > the loose and the packed refs backend. This is done in two separate > > transactions, where the end result is that the reference-transaction > > hook is executed twice for the deleted refs. > > > > This behaviour is quite misleading: it's exposing implementation details > > of how the files backend works to the user, in contrast to the logical > > updates that we'd really want to expose via the hook. Worse yet, whether > > the hook gets executed once or twice depends on how well-packed the > > repository is: if the ref only exists as a loose ref, then we execute it > > once, otherwise if it is also packed then we execute it twice. > > If the ref only exists as a packed ref, what happens? ... > > > Fix this behaviour and don't execute the reference-transaction hook at > > all when refs in the packed-refs backend if it's driven by the files > > backend. > > ... We try to remove from the loose backend, which would say "nah, > it did not exist in my store". I am not sure if it should execute > the delete hook in such a case for the ref. But if it does not, not > running the hook in the ref transaction for packed backend driven by > the loose backend would mean nobody notifies the deletion of the > ref, no? This is shown in the test I've added, "deleting packed ref calls hook once". We create a new reference and pack it such that it doesn't exist as loose ref anymore, but only as a packed one. Updating that ref would've caused us to execute the hook twice before, once via the packed-backend and once via the loose-backend. With my fix we only execute it once via the loose-backend, even if it doesn't currently know it. This works because the loose-backend has to create a lock for the nonexistent reference such that no concurrent call touches the same ref. > To me, it seems that the only case this strategy would work > correctly is when the files backend calls deletion hook for a > request to delete nonexistent ref, which by itself sounds like a > problem. It does so only if the ref exists in either the loose or packed backend though. If trying to update a ref which exists in neither of those, then the reference transaction would fail with an "unable to resolve reference" error in `lock_raw_ref()`. So this should behave as expected: deleting a packed ref calls the hook once, deleting a nonexistent ref fails and doesn't call the hook at all. Patrick > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > > --- > > refs/files-backend.c | 9 ++++++--- > > t/t1416-ref-transaction-hooks.sh | 7 +------ > > 2 files changed, 7 insertions(+), 9 deletions(-) > > > > diff --git a/refs/files-backend.c b/refs/files-backend.c > > index 3c0f3027fe..9a20cb8fa8 100644 > > --- a/refs/files-backend.c > > +++ b/refs/files-backend.c > > @@ -1261,7 +1261,8 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > > if (packed_refs_lock(refs->packed_ref_store, 0, &err)) > > goto error; > > > > - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); > > + transaction = ref_store_transaction_begin(refs->packed_ref_store, > > + REF_TRANSACTION_SKIP_HOOK, &err); > > if (!transaction) > > goto error; > > > > @@ -2775,7 +2776,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, > > */ > > if (!packed_transaction) { > > packed_transaction = ref_store_transaction_begin( > > - refs->packed_ref_store, 0, err); > > + refs->packed_ref_store, > > + REF_TRANSACTION_SKIP_HOOK, err); > > if (!packed_transaction) { > > ret = TRANSACTION_GENERIC_ERROR; > > goto cleanup; > > @@ -3046,7 +3048,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, > > &affected_refnames)) > > BUG("initial ref transaction called with existing refs"); > > > > - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); > > + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, > > + REF_TRANSACTION_SKIP_HOOK, err); > > if (!packed_transaction) { > > ret = TRANSACTION_GENERIC_ERROR; > > goto cleanup; > > diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh > > index f9d3d5213f..4e1e84a91f 100755 > > --- a/t/t1416-ref-transaction-hooks.sh > > +++ b/t/t1416-ref-transaction-hooks.sh > > @@ -175,16 +175,11 @@ test_expect_success 'deleting packed ref calls hook once' ' > > git update-ref -d refs/heads/to-be-deleted $POST_OID && > > > > # We only expect a single hook invocation, which is the logical > > - # deletion. But currently, we see two interleaving transactions, once > > - # for deleting the loose refs and once for deleting the packed ref. > > + # deletion. > > cat >expect <<-EOF && > > - prepared > > - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted > > prepared > > $POST_OID $ZERO_OID refs/heads/to-be-deleted > > committed > > - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted > > - committed > > $POST_OID $ZERO_OID refs/heads/to-be-deleted > > EOF [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 6/6] refs: skip hooks when deleting uncovered packed refs 2022-01-10 13:18 ` Patrick Steinhardt @ 2022-01-10 18:00 ` Junio C Hamano 0 siblings, 0 replies; 51+ messages in thread From: Junio C Hamano @ 2022-01-10 18:00 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, Jeff King, Bryan Turner, Waleed Khan Patrick Steinhardt <ps@pks.im> writes: > It does so only if the ref exists in either the loose or packed backend > though. If trying to update a ref which exists in neither of those, then > the reference transaction would fail with an "unable to resolve > reference" error in `lock_raw_ref()`. > > So this should behave as expected: deleting a packed ref calls the hook > once, deleting a nonexistent ref fails and doesn't call the hook at all. OK. Your explanation deserves to be in the log message for those who ask questions next time, not lost in the archive in a message just answering my question and explaining it only for me. After all, that is why I ask questions in my reviews. Thanks. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 0/6] refs: excessive hook execution with packed refs 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (5 preceding siblings ...) 2022-01-07 11:55 ` [PATCH v2 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt @ 2022-01-07 22:17 ` Junio C Hamano 2022-01-13 18:24 ` Han-Wen Nienhuys 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt 8 siblings, 1 reply; 51+ messages in thread From: Junio C Hamano @ 2022-01-07 22:17 UTC (permalink / raw) To: Patrick Steinhardt Cc: Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys, git, Bryan Turner, Waleed Khan Patrick Steinhardt <ps@pks.im> writes: > Hi, > > this is a resend of version 1 of this patch series to hopefully entice > some reviews. The only change is that v2 is rebased onto the current > main branch at commit e83ba647f7 (The seventh batch, 2022-01-05). The > following was from the orignial cover letter: I'll add Ævar, who has been making a lot of changes to the refs subsystem, and Han-Wen, whose work to add a new ref backend may need to interact with this change, as possible stake-holders to the CC list. > As reported by Waleed in [1], the reference-transaction hook is being > executed when packing refs. Given that the hook ideally ought to track > logical updates to refs instead of leaking low-level implementation > details of how the files backend works, this is understandably leading > to some confusion. > > This patch series aims to fix that by improving how the tandom of loose > and packed refs backends interact such that we skip executing the hook > when the loose backend: > > - repacks references. > - needs to delete packed refs when deleting a loose ref would > uncover that packed ref. > > Patrick > > [1]: <CAKjfCeBcuYC3OXRVtxxDGWRGOxC38Fb7CNuSh_dMmxpGVip_9Q@mail.gmail.com> > > Patrick Steinhardt (6): > refs: open-code deletion of packed refs > refs: allow passing flags when beginning transactions > refs: allow skipping the reference-transaction hook > refs: demonstrate excessive execution of the reference-transaction > hook > refs: do not execute reference-transaction hook on packing refs > refs: skip hooks when deleting uncovered packed refs > > refs.c | 11 +++++-- > refs.h | 8 ++++- > refs/files-backend.c | 25 +++++++++++----- > refs/packed-backend.c | 30 ++++++++++++++----- > refs/packed-backend.h | 6 ++++ > refs/refs-internal.h | 1 + > sequencer.c | 2 +- > t/t1416-ref-transaction-hooks.sh | 50 ++++++++++++++++++++++++++++++++ > 8 files changed, 113 insertions(+), 20 deletions(-) ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 0/6] refs: excessive hook execution with packed refs 2022-01-07 22:17 ` [PATCH v2 0/6] refs: excessive hook execution with " Junio C Hamano @ 2022-01-13 18:24 ` Han-Wen Nienhuys 2022-01-17 7:18 ` Patrick Steinhardt 0 siblings, 1 reply; 51+ messages in thread From: Han-Wen Nienhuys @ 2022-01-13 18:24 UTC (permalink / raw) To: Junio C Hamano Cc: Patrick Steinhardt, Ævar Arnfjörð Bjarmason, git, Bryan Turner, Waleed Khan On Fri, Jan 7, 2022 at 11:17 PM Junio C Hamano <gitster@pobox.com> wrote: > > this is a resend of version 1 of this patch series to hopefully entice > > some reviews. The only change is that v2 is rebased onto the current > > main branch at commit e83ba647f7 (The seventh batch, 2022-01-05). The > > following was from the orignial cover letter: > > I'll add Ævar, who has been making a lot of changes to the refs > subsystem, and Han-Wen, whose work to add a new ref backend may need > to interact with this change, as possible stake-holders to the CC list. Thanks for the consideration, Jun. As the hook is called from refs.c, it should not make a difference for reftable. I looked over the patches. I didn't look at the bottom change to packed/loose refs as I'm not an expert. The individual transaction updates already support their own flags, so this change generates some confusion. Consider: int refs_delete_ref(struct ref_store *refs, const char *msg, const char *refname, const struct object_id *old_oid, unsigned int flags) how would one delete a ref skipping the transaction hook? It will be easy for someone to pass the SKIP_TRANSACTION_HOOK to refs_delete_ref(), with surprising results. It might make sense to not introduce a new flag namespace, but use update->flags instead. You'd have to add your new flag after REF_SKIP_REFNAME_VERIFICATION. Bonus is that you can unittest the new flag using the existing ref-store helper without extra work. (check that a transaction with & without the flag works as expected.) other than that, looks OK to me. -- Han-Wen Nienhuys - Google Munich I work 80%. Don't expect answers from me on Fridays. -- Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg Geschäftsführer: Paul Manicle, Halimah DeLaine Prado ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 0/6] refs: excessive hook execution with packed refs 2022-01-13 18:24 ` Han-Wen Nienhuys @ 2022-01-17 7:18 ` Patrick Steinhardt 2022-01-17 11:37 ` Han-Wen Nienhuys 0 siblings, 1 reply; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 7:18 UTC (permalink / raw) To: Han-Wen Nienhuys Cc: Junio C Hamano, Ævar Arnfjörð Bjarmason, git, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 2257 bytes --] On Thu, Jan 13, 2022 at 07:24:19PM +0100, Han-Wen Nienhuys wrote: > On Fri, Jan 7, 2022 at 11:17 PM Junio C Hamano <gitster@pobox.com> wrote: > > > this is a resend of version 1 of this patch series to hopefully entice > > > some reviews. The only change is that v2 is rebased onto the current > > > main branch at commit e83ba647f7 (The seventh batch, 2022-01-05). The > > > following was from the orignial cover letter: > > > > I'll add Ævar, who has been making a lot of changes to the refs > > subsystem, and Han-Wen, whose work to add a new ref backend may need > > to interact with this change, as possible stake-holders to the CC list. > > Thanks for the consideration, Jun. As the hook is called from refs.c, > it should not make a difference for reftable. > > I looked over the patches. I didn't look at the bottom change to > packed/loose refs as I'm not an expert. > > The individual transaction updates already support their own flags, so > this change generates some confusion. Consider: > > int refs_delete_ref(struct ref_store *refs, const char *msg, > const char *refname, > const struct object_id *old_oid, > unsigned int flags) > > how would one delete a ref skipping the transaction hook? It will be > easy for someone to pass the SKIP_TRANSACTION_HOOK to > refs_delete_ref(), with surprising results. > > It might make sense to not introduce a new flag namespace, but use > update->flags instead. You'd have to add your new flag after > REF_SKIP_REFNAME_VERIFICATION. > Bonus is that you can unittest the new flag using the existing > ref-store helper without extra work. (check that a transaction with & > without the flag works as expected.) > > other than that, looks OK to me. Thanks for your feedback! In fact the first version I had locally did exactly that. But I found the end version result harder to reason about, most importantly because it's not a 100% clear to the reader whether all callsites which delete refs in the packed-backend via the files-backend are adapted or whether any of the callsites was missing. By having the flag in a central place it's immediately clear that the hook won't be run at all, which is exactly what we want here. Patrick [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 0/6] refs: excessive hook execution with packed refs 2022-01-17 7:18 ` Patrick Steinhardt @ 2022-01-17 11:37 ` Han-Wen Nienhuys 2022-01-24 7:13 ` Patrick Steinhardt 0 siblings, 1 reply; 51+ messages in thread From: Han-Wen Nienhuys @ 2022-01-17 11:37 UTC (permalink / raw) To: Patrick Steinhardt Cc: Junio C Hamano, Ævar Arnfjörð Bjarmason, git, Bryan Turner, Waleed Khan On Mon, Jan 17, 2022 at 8:18 AM Patrick Steinhardt <ps@pks.im> wrote: > > It might make sense to not introduce a new flag namespace, but use > > update->flags instead. You'd have to add your new flag after > > REF_SKIP_REFNAME_VERIFICATION. > > Bonus is that you can unittest the new flag using the existing > > ref-store helper without extra work. (check that a transaction with & > > without the flag works as expected.) > > > > other than that, looks OK to me. > > Thanks for your feedback! > > In fact the first version I had locally did exactly that. But I found > the end version result harder to reason about, most importantly because > it's not a 100% clear to the reader whether all callsites which delete > refs in the packed-backend via the files-backend are adapted or whether > any of the callsites was missing. By having the flag in a central place > it's immediately clear that the hook won't be run at all, which is > exactly what we want here. If you do it like this, can you find the callsites that take update flags (but not transaction flags), and update the signature to say "update_flags" rather than "flags", and document appropriately? -- Han-Wen Nienhuys - Google Munich I work 80%. Don't expect answers from me on Fridays. -- Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg Geschäftsführer: Paul Manicle, Halimah DeLaine Prado ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 0/6] refs: excessive hook execution with packed refs 2022-01-17 11:37 ` Han-Wen Nienhuys @ 2022-01-24 7:13 ` Patrick Steinhardt 0 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-24 7:13 UTC (permalink / raw) To: Han-Wen Nienhuys Cc: Junio C Hamano, Ævar Arnfjörð Bjarmason, git, Bryan Turner, Waleed Khan [-- Attachment #1: Type: text/plain, Size: 12498 bytes --] On Mon, Jan 17, 2022 at 12:37:39PM +0100, Han-Wen Nienhuys wrote: > On Mon, Jan 17, 2022 at 8:18 AM Patrick Steinhardt <ps@pks.im> wrote: > > > It might make sense to not introduce a new flag namespace, but use > > > update->flags instead. You'd have to add your new flag after > > > REF_SKIP_REFNAME_VERIFICATION. > > > Bonus is that you can unittest the new flag using the existing > > > ref-store helper without extra work. (check that a transaction with & > > > without the flag works as expected.) > > > > > > other than that, looks OK to me. > > > > Thanks for your feedback! > > > > In fact the first version I had locally did exactly that. But I found > > the end version result harder to reason about, most importantly because > > it's not a 100% clear to the reader whether all callsites which delete > > refs in the packed-backend via the files-backend are adapted or whether > > any of the callsites was missing. By having the flag in a central place > > it's immediately clear that the hook won't be run at all, which is > > exactly what we want here. > > If you do it like this, can you find the callsites that take update > flags (but not transaction flags), and update the signature to say > "update_flags" rather than "flags", and document appropriately? All functions which accept "flags" document that they pass the parameter directly to `ref_transaction_{update,create,delete,verify}` already. And in the context of those latter functions it cannot possibly be related to the flags passed to `refs_store_transaction_begin()` because they already get a transaction as parameter and thus don't have to create a new one. So we could apply below patch, and I'm happy to polish and add it to the series. But it feels to me like it adds churn while only stating things that are documented already. It could only be me though given that I'm obviously biased, so please feel free to disagree. Patrick -- >8 -- From 91c77a69b6326b1b6cc743ebba6fb0970f0d01c2 Mon Sep 17 00:00:00 2001 Message-Id: <91c77a69b6326b1b6cc743ebba6fb0970f0d01c2.1643008212.git.ps@pks.im> From: Patrick Steinhardt <ps@pks.im> Date: Mon, 24 Jan 2022 08:09:45 +0100 Subject: [PATCH] refs: rename generic `flags` field to `update_flags` for clarity --- refs.c | 50 +++++++++++++++++++++++++------------------------- refs.h | 20 ++++++++++---------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/refs.c b/refs.c index 526bf5ed97..c15e8bd58d 100644 --- a/refs.c +++ b/refs.c @@ -795,7 +795,7 @@ long get_files_ref_lock_timeout_ms(void) int refs_delete_ref(struct ref_store *refs, const char *msg, const char *refname, const struct object_id *old_oid, - unsigned int flags) + unsigned int update_flags) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; @@ -803,7 +803,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction || ref_transaction_delete(transaction, refname, old_oid, - flags, msg, &err) || + update_flags, msg, &err) || ref_transaction_commit(transaction, &err)) { error("%s", err.buf); ref_transaction_free(transaction); @@ -816,10 +816,10 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, } int delete_ref(const char *msg, const char *refname, - const struct object_id *old_oid, unsigned int flags) + const struct object_id *old_oid, unsigned int update_flags) { return refs_delete_ref(get_main_ref_store(the_repository), msg, refname, - old_oid, flags); + old_oid, update_flags); } static void copy_reflog_msg(struct strbuf *sb, const char *msg) @@ -1080,12 +1080,12 @@ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const struct object_id *new_oid, const struct object_id *old_oid, - unsigned int flags, const char *msg, + unsigned int update_flags, const char *msg, struct strbuf *err) { assert(err); - if (!(flags & REF_SKIP_REFNAME_VERIFICATION) && + if (!(update_flags & REF_SKIP_REFNAME_VERIFICATION) && ((new_oid && !is_null_oid(new_oid)) ? check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) : !refname_is_safe(refname))) { @@ -1094,19 +1094,19 @@ int ref_transaction_update(struct ref_transaction *transaction, return -1; } - if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS) - BUG("illegal flags 0x%x passed to ref_transaction_update()", flags); + if (update_flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS) + BUG("illegal flags 0x%x passed to ref_transaction_update()", update_flags); /* * Clear flags outside the allowed set; this should be a noop because * of the BUG() check above, but it works around a -Wnonnull warning * with some versions of "gcc -O3". */ - flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS; + update_flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS; - flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0); + update_flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0); - ref_transaction_add_update(transaction, refname, flags, + ref_transaction_add_update(transaction, refname, update_flags, new_oid, old_oid, msg); return 0; } @@ -1114,44 +1114,44 @@ int ref_transaction_update(struct ref_transaction *transaction, int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const struct object_id *new_oid, - unsigned int flags, const char *msg, + unsigned int update_flags, const char *msg, struct strbuf *err) { if (!new_oid || is_null_oid(new_oid)) BUG("create called without valid new_oid"); return ref_transaction_update(transaction, refname, new_oid, - null_oid(), flags, msg, err); + null_oid(), update_flags, msg, err); } int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const struct object_id *old_oid, - unsigned int flags, const char *msg, + unsigned int update_flags, const char *msg, struct strbuf *err) { if (old_oid && is_null_oid(old_oid)) BUG("delete called with old_oid set to zeros"); return ref_transaction_update(transaction, refname, null_oid(), old_oid, - flags, msg, err); + update_flags, msg, err); } int ref_transaction_verify(struct ref_transaction *transaction, const char *refname, const struct object_id *old_oid, - unsigned int flags, + unsigned int update_flags, struct strbuf *err) { if (!old_oid) BUG("verify called with old_oid set to NULL"); return ref_transaction_update(transaction, refname, NULL, old_oid, - flags, NULL, err); + update_flags, NULL, err); } int refs_update_ref(struct ref_store *refs, const char *msg, const char *refname, const struct object_id *new_oid, - const struct object_id *old_oid, unsigned int flags, + const struct object_id *old_oid, unsigned int update_flags, enum action_on_err onerr) { struct ref_transaction *t = NULL; @@ -1160,7 +1160,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, t = ref_store_transaction_begin(refs, 0, &err); if (!t || - ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, + ref_transaction_update(t, refname, new_oid, old_oid, update_flags, msg, &err) || ref_transaction_commit(t, &err)) { ret = 1; @@ -1191,10 +1191,10 @@ int refs_update_ref(struct ref_store *refs, const char *msg, int update_ref(const char *msg, const char *refname, const struct object_id *new_oid, const struct object_id *old_oid, - unsigned int flags, enum action_on_err onerr) + unsigned int update_flags, enum action_on_err onerr) { return refs_update_ref(get_main_ref_store(the_repository), msg, refname, new_oid, - old_oid, flags, onerr); + old_oid, update_flags, onerr); } char *refs_shorten_unambiguous_ref(struct ref_store *refs, @@ -2435,21 +2435,21 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction, } int refs_delete_refs(struct ref_store *refs, const char *logmsg, - struct string_list *refnames, unsigned int flags) + struct string_list *refnames, unsigned int update_flags) { char *msg; int retval; msg = normalize_reflog_message(logmsg); - retval = refs->be->delete_refs(refs, msg, refnames, flags); + retval = refs->be->delete_refs(refs, msg, refnames, update_flags); free(msg); return retval; } int delete_refs(const char *msg, struct string_list *refnames, - unsigned int flags) + unsigned int update_flags) { - return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, flags); + return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, update_flags); } int refs_rename_ref(struct ref_store *refs, const char *oldref, diff --git a/refs.h b/refs.h index d4056f9fe2..5131db7e48 100644 --- a/refs.h +++ b/refs.h @@ -442,9 +442,9 @@ int reflog_exists(const char *refname); int refs_delete_ref(struct ref_store *refs, const char *msg, const char *refname, const struct object_id *old_oid, - unsigned int flags); + unsigned int update_flags); int delete_ref(const char *msg, const char *refname, - const struct object_id *old_oid, unsigned int flags); + const struct object_id *old_oid, unsigned int update_flags); /* * Delete the specified references. If there are any problems, emit @@ -453,9 +453,9 @@ int delete_ref(const char *msg, const char *refname, * ref_transaction_delete(). */ int refs_delete_refs(struct ref_store *refs, const char *msg, - struct string_list *refnames, unsigned int flags); + struct string_list *refnames, unsigned int update_flags); int delete_refs(const char *msg, struct string_list *refnames, - unsigned int flags); + unsigned int update_flags); /** Delete a reflog */ int refs_delete_reflog(struct ref_store *refs, const char *refname); @@ -680,7 +680,7 @@ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const struct object_id *new_oid, const struct object_id *old_oid, - unsigned int flags, const char *msg, + unsigned int update_flags, const char *msg, struct strbuf *err); /* @@ -695,7 +695,7 @@ int ref_transaction_update(struct ref_transaction *transaction, int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const struct object_id *new_oid, - unsigned int flags, const char *msg, + unsigned int update_flags, const char *msg, struct strbuf *err); /* @@ -709,7 +709,7 @@ int ref_transaction_create(struct ref_transaction *transaction, int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const struct object_id *old_oid, - unsigned int flags, const char *msg, + unsigned int update_flags, const char *msg, struct strbuf *err); /* @@ -723,7 +723,7 @@ int ref_transaction_delete(struct ref_transaction *transaction, int ref_transaction_verify(struct ref_transaction *transaction, const char *refname, const struct object_id *old_oid, - unsigned int flags, + unsigned int update_flags, struct strbuf *err); /* Naming conflict (for example, the ref names A and A/B conflict). */ @@ -796,10 +796,10 @@ void ref_transaction_free(struct ref_transaction *transaction); */ int refs_update_ref(struct ref_store *refs, const char *msg, const char *refname, const struct object_id *new_oid, const struct object_id *old_oid, - unsigned int flags, enum action_on_err onerr); + unsigned int update_flags, enum action_on_err onerr); int update_ref(const char *msg, const char *refname, const struct object_id *new_oid, const struct object_id *old_oid, - unsigned int flags, enum action_on_err onerr); + unsigned int update_flags, enum action_on_err onerr); int parse_hide_refs_config(const char *var, const char *value, const char *); -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v3 0/6] refs: excessive hook execution with packed refs 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (6 preceding siblings ...) 2022-01-07 22:17 ` [PATCH v2 0/6] refs: excessive hook execution with " Junio C Hamano @ 2022-01-13 6:11 ` Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt ` (5 more replies) 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt 8 siblings, 6 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-13 6:11 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 4845 bytes --] Hi, this is the third version of this patch series, which addresses an issue where the reference-transaction hook is being invoked twice when deleting refs both in the packed-refs and loose-refs file. The following things changed in comparison to v2: - Fixed some typos in commit messages. - Improved the subject of the first patch to more clearly highlight the purpose of it, not only say what the patch does. - Fixed a missing declaration for `struct string_list`. - Clarified why the last patch does the right thing even in case the ref only exists in the packed-refs backend, and in case it doesn't exist in either of the backends. Thanks for your feedback! Patrick Patrick Steinhardt (6): refs: extract packed_refs_delete_refs() to allow control of transaction refs: allow passing flags when beginning transactions refs: allow skipping the reference-transaction hook refs: demonstrate excessive execution of the reference-transaction hook refs: do not execute reference-transaction hook on packing refs refs: skip hooks when deleting uncovered packed refs refs.c | 11 +++++-- refs.h | 8 ++++- refs/files-backend.c | 25 +++++++++++----- refs/packed-backend.c | 30 ++++++++++++++----- refs/packed-backend.h | 7 +++++ refs/refs-internal.h | 1 + sequencer.c | 2 +- t/t1416-ref-transaction-hooks.sh | 50 ++++++++++++++++++++++++++++++++ 8 files changed, 114 insertions(+), 20 deletions(-) Range-diff against v2: 1: 0739f085b2 ! 1: abbc28822b refs: open-code deletion of packed refs @@ Metadata Author: Patrick Steinhardt <ps@pks.im> ## Commit message ## - refs: open-code deletion of packed refs + refs: extract packed_refs_delete_refs() to allow control of transaction When deleting loose refs, then we also have to delete the refs in the packed backend. This is done by calling `refs_delete_refs()`, which @@ Commit message Extract a new function `packed_refs_delete_refs()`, which hosts most of the logic to delete refs except for creating the transaction itself. - Like this, we can easily create the transactionion in the files backend + Like this, we can easily create the transaction in the files backend and thus exert more control over it. Signed-off-by: Patrick Steinhardt <ps@pks.im> @@ refs/packed-backend.c: static int packed_delete_refs(struct ref_store *ref_store } ## refs/packed-backend.h ## +@@ + + struct repository; + struct ref_transaction; ++struct string_list; + + /* + * Support for storing references in a `packed-refs` file. @@ refs/packed-backend.h: int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) void packed_refs_unlock(struct ref_store *ref_store); int packed_refs_is_locked(struct ref_store *ref_store); 2: 629be01d50 = 2: 9dd172a757 refs: allow passing flags when beginning transactions 3: 550d89a323 = 3: be826bae3b refs: allow skipping the reference-transaction hook 4: b52e59cdac ! 4: 662a6e6244 refs: demonstrate excessive execution of the reference-transaction hook @@ Metadata ## Commit message ## refs: demonstrate excessive execution of the reference-transaction hook - Add tests which demonstate which demonstrates that we're executing the + Add tests which demonstate that we're executing the reference-transaction hook too often in some cases, which thus leaks implementation details about the reference store's implementation itself. Behaviour will be fixed in follow-up commits. 5: 1539e9711f = 5: d83f309b9c refs: do not execute reference-transaction hook on packing refs 6: 0fbf68dbf4 ! 6: 279eadc41c refs: skip hooks when deleting uncovered packed refs @@ Commit message Fix this behaviour and don't execute the reference-transaction hook at all when refs in the packed-refs backend if it's driven by the files - backend. + backend. This works as expected even in case the refs to be deleted only + exist in the packed-refs backend because the loose-backend always queues + refs in its own transaction even if they don't exist such that they can + be locked for concurrent creation. And it also does the right thing in + case neither of the backends has the ref because that would cause the + transaction to fail completely. Signed-off-by: Patrick Steinhardt <ps@pks.im> -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt @ 2022-01-13 6:11 ` Patrick Steinhardt 2022-01-13 12:43 ` Ævar Arnfjörð Bjarmason 2022-01-13 6:11 ` [PATCH v3 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt ` (4 subsequent siblings) 5 siblings, 1 reply; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-13 6:11 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 5205 bytes --] When deleting loose refs, then we also have to delete the refs in the packed backend. This is done by calling `refs_delete_refs()`, which then uses the packed-backend's logic to delete refs. This doesn't allow us to exercise any control over the reference transaction which is being created in the packed backend, which is required in a subsequent commit. Extract a new function `packed_refs_delete_refs()`, which hosts most of the logic to delete refs except for creating the transaction itself. Like this, we can easily create the transaction in the files backend and thus exert more control over it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 12 +++++++++--- refs/packed-backend.c | 28 +++++++++++++++++++++------- refs/packed-backend.h | 7 +++++++ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 90b671025a..5795e54020 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1249,6 +1249,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct ref_transaction *transaction = NULL; struct strbuf err = STRBUF_INIT; int i, result = 0; @@ -1258,10 +1259,14 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { - packed_refs_unlock(refs->packed_ref_store); + transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + if (!transaction) + goto error; + + result = packed_refs_delete_refs(refs->packed_ref_store, + transaction, msg, refnames, flags); + if (result) goto error; - } packed_refs_unlock(refs->packed_ref_store); @@ -1288,6 +1293,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, else error(_("could not delete references: %s"), err.buf); + ref_transaction_free(transaction); strbuf_release(&err); return -1; } diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 67152c664e..e97d67f932 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1522,15 +1522,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, static int packed_delete_refs(struct ref_store *ref_store, const char *msg, struct string_list *refnames, unsigned int flags) { - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - struct string_list_item *item; int ret; - (void)refs; /* We need the check above, but don't use the variable */ - if (!refnames->nr) return 0; @@ -1544,6 +1539,27 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, if (!transaction) return -1; + ret = packed_refs_delete_refs(ref_store, transaction, + msg, refnames, flags); + + ref_transaction_free(transaction); + return ret; +} + +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags) +{ + struct packed_ref_store *refs = + packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct strbuf err = STRBUF_INIT; + struct string_list_item *item; + int ret; + + (void)(refs); /* We need the check above, but don't use the variable */ + for_each_string_list_item(item, refnames) { if (ref_transaction_delete(transaction, item->string, NULL, flags, msg, &err)) { @@ -1554,7 +1570,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, } ret = ref_transaction_commit(transaction, &err); - if (ret) { if (refnames->nr == 1) error(_("could not delete reference %s: %s"), @@ -1563,7 +1578,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, error(_("could not delete references: %s"), err.buf); } - ref_transaction_free(transaction); strbuf_release(&err); return ret; } diff --git a/refs/packed-backend.h b/refs/packed-backend.h index f61a73ec25..a2cca5d9a3 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -3,6 +3,7 @@ struct repository; struct ref_transaction; +struct string_list; /* * Support for storing references in a `packed-refs` file. @@ -27,6 +28,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) void packed_refs_unlock(struct ref_store *ref_store); int packed_refs_is_locked(struct ref_store *ref_store); +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags); + /* * Return true if `transaction` really needs to be carried out against * the specified packed_ref_store, or false if it can be skipped -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v3 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction 2022-01-13 6:11 ` [PATCH v3 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt @ 2022-01-13 12:43 ` Ævar Arnfjörð Bjarmason 2022-01-17 7:36 ` Patrick Steinhardt 0 siblings, 1 reply; 51+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-01-13 12:43 UTC (permalink / raw) To: Patrick Steinhardt Cc: git, Jeff King, Bryan Turner, Waleed Khan, Han-Wen Nienhuys On Thu, Jan 13 2022, Patrick Steinhardt wrote: > [[PGP Signed Part:Undecided]] > When deleting loose refs, then we also have to delete the refs in the > packed backend. This is done by calling `refs_delete_refs()`, which > then uses the packed-backend's logic to delete refs. This doesn't allow > us to exercise any control over the reference transaction which is being > created in the packed backend, which is required in a subsequent commit. > > Extract a new function `packed_refs_delete_refs()`, which hosts most of > the logic to delete refs except for creating the transaction itself. > Like this, we can easily create the transaction in the files backend > and thus exert more control over it. > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > refs/files-backend.c | 12 +++++++++--- > refs/packed-backend.c | 28 +++++++++++++++++++++------- > refs/packed-backend.h | 7 +++++++ > 3 files changed, 37 insertions(+), 10 deletions(-) > > diff --git a/refs/files-backend.c b/refs/files-backend.c > index 90b671025a..5795e54020 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -1249,6 +1249,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > { > struct files_ref_store *refs = > files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); > + struct ref_transaction *transaction = NULL; nit: "NULL initialization never needed? (re: https://lore.kernel.org/git/220113.86bl0gvtq3.gmgdl@evledraar.gmail.com/) > struct strbuf err = STRBUF_INIT; > int i, result = 0; > > @@ -1258,10 +1259,14 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > if (packed_refs_lock(refs->packed_ref_store, 0, &err)) > goto error; > > - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { > - packed_refs_unlock(refs->packed_ref_store); > + transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); > + if (!transaction) > + goto error; > + > + result = packed_refs_delete_refs(refs->packed_ref_store, > + transaction, msg, refnames, flags); > + if (result) > goto error; > - } > > packed_refs_unlock(refs->packed_ref_store); > > @@ -1288,6 +1293,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > else > error(_("could not delete references: %s"), err.buf); > > + ref_transaction_free(transaction); > strbuf_release(&err); > return -1; > } > diff --git a/refs/packed-backend.c b/refs/packed-backend.c > index 67152c664e..e97d67f932 100644 > --- a/refs/packed-backend.c > +++ b/refs/packed-backend.c > @@ -1522,15 +1522,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, > static int packed_delete_refs(struct ref_store *ref_store, const char *msg, > struct string_list *refnames, unsigned int flags) > { > - struct packed_ref_store *refs = > - packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); > struct strbuf err = STRBUF_INIT; > struct ref_transaction *transaction; > - struct string_list_item *item; > int ret; > > - (void)refs; /* We need the check above, but don't use the variable */ > - > if (!refnames->nr) > return 0; > > @@ -1544,6 +1539,27 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, > if (!transaction) > return -1; > > + ret = packed_refs_delete_refs(ref_store, transaction, > + msg, refnames, flags); > + > + ref_transaction_free(transaction); > + return ret; > +} > + > +int packed_refs_delete_refs(struct ref_store *ref_store, > + struct ref_transaction *transaction, > + const char *msg, > + struct string_list *refnames, > + unsigned int flags) > +{ > + struct packed_ref_store *refs = > + packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); > + struct strbuf err = STRBUF_INIT; > + struct string_list_item *item; > + int ret; > + > + (void)(refs); /* We need the check above, but don't use the variable */ > + > for_each_string_list_item(item, refnames) { > if (ref_transaction_delete(transaction, item->string, NULL, > flags, msg, &err)) { I see you're just moving this code around, but FWIW we can just do this (also in the pre-image): int packed_refs_delete_refs(...) { [declare variables] /* Assert ref store sanity */ packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs") [...] } Not sure it's good to change it around just for this mostly-move, just a note... > @@ -1554,7 +1570,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, > } > > ret = ref_transaction_commit(transaction, &err); > - Stray whitespace change. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction 2022-01-13 12:43 ` Ævar Arnfjörð Bjarmason @ 2022-01-17 7:36 ` Patrick Steinhardt 0 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 7:36 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, Jeff King, Bryan Turner, Waleed Khan, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 5406 bytes --] On Thu, Jan 13, 2022 at 01:43:18PM +0100, Ævar Arnfjörð Bjarmason wrote: > > On Thu, Jan 13 2022, Patrick Steinhardt wrote: > > > [[PGP Signed Part:Undecided]] > > When deleting loose refs, then we also have to delete the refs in the > > packed backend. This is done by calling `refs_delete_refs()`, which > > then uses the packed-backend's logic to delete refs. This doesn't allow > > us to exercise any control over the reference transaction which is being > > created in the packed backend, which is required in a subsequent commit. > > > > Extract a new function `packed_refs_delete_refs()`, which hosts most of > > the logic to delete refs except for creating the transaction itself. > > Like this, we can easily create the transaction in the files backend > > and thus exert more control over it. > > > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > > --- > > refs/files-backend.c | 12 +++++++++--- > > refs/packed-backend.c | 28 +++++++++++++++++++++------- > > refs/packed-backend.h | 7 +++++++ > > 3 files changed, 37 insertions(+), 10 deletions(-) > > > > diff --git a/refs/files-backend.c b/refs/files-backend.c > > index 90b671025a..5795e54020 100644 > > --- a/refs/files-backend.c > > +++ b/refs/files-backend.c > > @@ -1249,6 +1249,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > > { > > struct files_ref_store *refs = > > files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); > > + struct ref_transaction *transaction = NULL; > > nit: "NULL initialization never needed? > (re: https://lore.kernel.org/git/220113.86bl0gvtq3.gmgdl@evledraar.gmail.com/) It is needed: if locking the packed-refs file fails we `goto error;` and try to free the transaction without it having been initialized yet. > > struct strbuf err = STRBUF_INIT; > > int i, result = 0; > > > > @@ -1258,10 +1259,14 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > > if (packed_refs_lock(refs->packed_ref_store, 0, &err)) > > goto error; > > > > - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { > > - packed_refs_unlock(refs->packed_ref_store); > > + transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); > > + if (!transaction) > > + goto error; > > + > > + result = packed_refs_delete_refs(refs->packed_ref_store, > > + transaction, msg, refnames, flags); > > + if (result) > > goto error; > > - } > > > > packed_refs_unlock(refs->packed_ref_store); > > > > @@ -1288,6 +1293,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > > else > > error(_("could not delete references: %s"), err.buf); > > > > + ref_transaction_free(transaction); > > strbuf_release(&err); > > return -1; > > } > > diff --git a/refs/packed-backend.c b/refs/packed-backend.c > > index 67152c664e..e97d67f932 100644 > > --- a/refs/packed-backend.c > > +++ b/refs/packed-backend.c > > @@ -1522,15 +1522,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, > > static int packed_delete_refs(struct ref_store *ref_store, const char *msg, > > struct string_list *refnames, unsigned int flags) > > { > > - struct packed_ref_store *refs = > > - packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); > > struct strbuf err = STRBUF_INIT; > > struct ref_transaction *transaction; > > - struct string_list_item *item; > > int ret; > > > > - (void)refs; /* We need the check above, but don't use the variable */ > > - > > if (!refnames->nr) > > return 0; > > > > @@ -1544,6 +1539,27 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, > > if (!transaction) > > return -1; > > > > + ret = packed_refs_delete_refs(ref_store, transaction, > > + msg, refnames, flags); > > + > > + ref_transaction_free(transaction); > > + return ret; > > +} > > + > > +int packed_refs_delete_refs(struct ref_store *ref_store, > > + struct ref_transaction *transaction, > > + const char *msg, > > + struct string_list *refnames, > > + unsigned int flags) > > +{ > > + struct packed_ref_store *refs = > > + packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); > > + struct strbuf err = STRBUF_INIT; > > + struct string_list_item *item; > > + int ret; > > + > > + (void)(refs); /* We need the check above, but don't use the variable */ > > + > > for_each_string_list_item(item, refnames) { > > if (ref_transaction_delete(transaction, item->string, NULL, > > flags, msg, &err)) { > > I see you're just moving this code around, but FWIW we can just do this > (also in the pre-image): > > int packed_refs_delete_refs(...) > { > [declare variables] > > /* Assert ref store sanity */ > packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs") > > [...] > } > > Not sure it's good to change it around just for this mostly-move, just a > note... I think this change is trivial enough to make while at it. Patrick > > @@ -1554,7 +1570,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, > > } > > > > ret = ref_transaction_commit(transaction, &err); > > - > > Stray whitespace change. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 2/6] refs: allow passing flags when beginning transactions 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt @ 2022-01-13 6:11 ` Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt ` (3 subsequent siblings) 5 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-13 6:11 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 6362 bytes --] We do not currently have any flags when creating reference transactions, but we'll add one to disable execution of the reference transaction hook in some cases. Allow passing flags to `ref_store_transaction_begin()` to prepare for this change. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 8 +++++--- refs.h | 3 ++- refs/files-backend.c | 10 +++++----- refs/packed-backend.c | 2 +- refs/refs-internal.h | 1 + sequencer.c | 2 +- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/refs.c b/refs.c index 4da4996c4d..7415864b62 100644 --- a/refs.c +++ b/refs.c @@ -800,7 +800,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction || ref_transaction_delete(transaction, refname, old_oid, flags, msg, &err) || @@ -1005,6 +1005,7 @@ int read_ref_at(struct ref_store *refs, const char *refname, } struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err) { struct ref_transaction *tr; @@ -1012,12 +1013,13 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, CALLOC_ARRAY(tr, 1); tr->ref_store = refs; + tr->flags = flags; return tr; } struct ref_transaction *ref_transaction_begin(struct strbuf *err) { - return ref_store_transaction_begin(get_main_ref_store(the_repository), err); + return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err); } void ref_transaction_free(struct ref_transaction *transaction) @@ -1156,7 +1158,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, struct strbuf err = STRBUF_INIT; int ret = 0; - t = ref_store_transaction_begin(refs, &err); + t = ref_store_transaction_begin(refs, 0, &err); if (!t || ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, &err) || diff --git a/refs.h b/refs.h index 92360e55a2..31f7bf9642 100644 --- a/refs.h +++ b/refs.h @@ -231,7 +231,7 @@ char *repo_default_branch_name(struct repository *r, int quiet); * struct strbuf err = STRBUF_INIT; * int ret = 0; * - * transaction = ref_store_transaction_begin(refs, &err); + * transaction = ref_store_transaction_begin(refs, 0, &err); * if (!transaction || * ref_transaction_update(...) || * ref_transaction_create(...) || @@ -573,6 +573,7 @@ enum action_on_err { * be freed by calling ref_transaction_free(). */ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err); struct ref_transaction *ref_transaction_begin(struct strbuf *err); diff --git a/refs/files-backend.c b/refs/files-backend.c index 5795e54020..ecf88cee04 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1121,7 +1121,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, &err); + transaction = ref_store_transaction_begin(&refs->base, 0, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1192,7 +1192,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); if (!transaction) return -1; @@ -1259,7 +1259,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); if (!transaction) goto error; @@ -2773,7 +2773,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, err); + refs->packed_ref_store, 0, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3044,7 +3044,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/refs/packed-backend.c b/refs/packed-backend.c index e97d67f932..cfebcd0b46 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1535,7 +1535,7 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, * updates into a single transaction. */ - transaction = ref_store_transaction_begin(ref_store, &err); + transaction = ref_store_transaction_begin(ref_store, 0, &err); if (!transaction) return -1; diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 46a839539e..a0af63f162 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -213,6 +213,7 @@ struct ref_transaction { size_t nr; enum ref_transaction_state state; void *backend_data; + unsigned int flags; }; /* diff --git a/sequencer.c b/sequencer.c index 6abd72160c..61edd39d7a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3588,7 +3588,7 @@ static int do_label(struct repository *r, const char *name, int len) strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction) { error("%s", err.buf); ret = -1; -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v3 3/6] refs: allow skipping the reference-transaction hook 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt @ 2022-01-13 6:11 ` Patrick Steinhardt 2022-01-13 13:34 ` Ævar Arnfjörð Bjarmason 2022-01-13 6:11 ` [PATCH v3 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt ` (2 subsequent siblings) 5 siblings, 1 reply; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-13 6:11 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 1449 bytes --] The reference-transaction hook is executing whenever we prepare, commit or abort a reference transaction. While this is mostly intentional, in case of the files backend we're leaking the implementation detail that the store is in fact a composite store with one loose and one packed backend to the caller. So while we want to execute the hook for all logical updates, executing it for such implementation details is unexpected. Prepare for a fix by adding a new flag which allows to skip execution of the hook. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 3 +++ refs.h | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/refs.c b/refs.c index 7415864b62..526bf5ed97 100644 --- a/refs.c +++ b/refs.c @@ -2084,6 +2084,9 @@ static int run_transaction_hook(struct ref_transaction *transaction, const char *hook; int ret = 0, i; + if (transaction->flags & REF_TRANSACTION_SKIP_HOOK) + return 0; + hook = find_hook("reference-transaction"); if (!hook) return ret; diff --git a/refs.h b/refs.h index 31f7bf9642..d4056f9fe2 100644 --- a/refs.h +++ b/refs.h @@ -568,6 +568,11 @@ enum action_on_err { UPDATE_REFS_QUIET_ON_ERR }; +/* + * Skip executing the reference-transaction hook. + */ +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) + /* * Begin a reference transaction. The reference transaction must * be freed by calling ref_transaction_free(). -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v3 3/6] refs: allow skipping the reference-transaction hook 2022-01-13 6:11 ` [PATCH v3 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt @ 2022-01-13 13:34 ` Ævar Arnfjörð Bjarmason 2022-01-17 8:03 ` Patrick Steinhardt 0 siblings, 1 reply; 51+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-01-13 13:34 UTC (permalink / raw) To: Patrick Steinhardt Cc: git, Jeff King, Bryan Turner, Waleed Khan, Han-Wen Nienhuys On Thu, Jan 13 2022, Patrick Steinhardt wrote: > [[PGP Signed Part:Undecided]] > The reference-transaction hook is executing whenever we prepare, commit > or abort a reference transaction. While this is mostly intentional, in > case of the files backend we're leaking the implementation detail that > the store is in fact a composite store with one loose and one packed > backend to the caller. So while we want to execute the hook for all > logical updates, executing it for such implementation details is > unexpected. > > Prepare for a fix by adding a new flag which allows to skip execution of > the hook. > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > refs.c | 3 +++ > refs.h | 5 +++++ > 2 files changed, 8 insertions(+) > > diff --git a/refs.c b/refs.c > index 7415864b62..526bf5ed97 100644 > --- a/refs.c > +++ b/refs.c > @@ -2084,6 +2084,9 @@ static int run_transaction_hook(struct ref_transaction *transaction, > const char *hook; > int ret = 0, i; > > + if (transaction->flags & REF_TRANSACTION_SKIP_HOOK) > + return 0; > + > hook = find_hook("reference-transaction"); > if (!hook) > return ret; > diff --git a/refs.h b/refs.h > index 31f7bf9642..d4056f9fe2 100644 > --- a/refs.h > +++ b/refs.h > @@ -568,6 +568,11 @@ enum action_on_err { > UPDATE_REFS_QUIET_ON_ERR > }; > > +/* > + * Skip executing the reference-transaction hook. > + */ > +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) > + > /* > * Begin a reference transaction. The reference transaction must > * be freed by calling ref_transaction_free(). This isn't needed in refs.h, so let's put it in refs-internal.h where e.g. "enum ref_transaction_state" now lives: diff --git a/refs.h b/refs.h index d4056f9fe26..31f7bf96424 100644 --- a/refs.h +++ b/refs.h @@ -568,11 +568,6 @@ enum action_on_err { UPDATE_REFS_QUIET_ON_ERR }; -/* - * Skip executing the reference-transaction hook. - */ -#define REF_TRANSACTION_SKIP_HOOK (1 << 0) - /* * Begin a reference transaction. The reference transaction must * be freed by calling ref_transaction_free(). diff --git a/refs/refs-internal.h b/refs/refs-internal.h index a0af63f162f..87da39243f7 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -201,6 +201,11 @@ enum ref_transaction_state { REF_TRANSACTION_CLOSED = 2 }; +/* + * Skip executing the reference-transaction hook. + */ +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) + /* * Data structure for holding a reference transaction, which can * consist of checks and updates to multiple references, carried out A bit more odd is that this series ends up with a ref_transaction_begin() that doesn't correspond to its ref_store_*() parent, i.e. the others pass the ref store for you, but now we omit the flags too. I see why you did that, to avoid tweaking every existing ref_transaction_begin() caller. But isn't something like the below a better approach? We can introduce a refs-internal.h-only flag enum, and then just have a new ref_store_transaction_begin_no_hook() called from these new files-backend.c users. The diff on top is a bit verbose, but the exposed API is cleaner (presumably no "public" user should be allowed to skip the hook), and the overall diff if this is squashed in is smaller. diff --git a/refs.c b/refs.c index 526bf5ed97a..ffaf34e9710 100644 --- a/refs.c +++ b/refs.c @@ -800,7 +800,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - transaction = ref_store_transaction_begin(refs, 0, &err); + transaction = ref_store_transaction_begin(refs, &err); if (!transaction || ref_transaction_delete(transaction, refname, old_oid, flags, msg, &err) || @@ -1004,22 +1004,36 @@ int read_ref_at(struct ref_store *refs, const char *refname, return 1; } -struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, - unsigned int flags, - struct strbuf *err) +static struct ref_transaction *ref_store_transaction_begin_1(struct ref_store *refs, + unsigned int no_hook, + struct strbuf *err) { struct ref_transaction *tr; assert(err); CALLOC_ARRAY(tr, 1); tr->ref_store = refs; - tr->flags = flags; + if (no_hook) + tr->flags &= REF_TRANSACTION_SKIP_HOOK; return tr; } struct ref_transaction *ref_transaction_begin(struct strbuf *err) { - return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err); + return ref_store_transaction_begin_1(get_main_ref_store(the_repository), + 0, err); +} + +struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + struct strbuf *err) +{ + return ref_store_transaction_begin_1(refs, 0, err); +} + +struct ref_transaction *ref_store_transaction_begin_no_hook(struct ref_store *refs, + struct strbuf *err) +{ + return ref_store_transaction_begin_1(refs, 1, err); } void ref_transaction_free(struct ref_transaction *transaction) @@ -1158,7 +1172,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, struct strbuf err = STRBUF_INIT; int ret = 0; - t = ref_store_transaction_begin(refs, 0, &err); + t = ref_store_transaction_begin(refs, &err); if (!t || ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, &err) || diff --git a/refs.h b/refs.h index 31f7bf96424..3663e0347ff 100644 --- a/refs.h +++ b/refs.h @@ -573,8 +573,9 @@ enum action_on_err { * be freed by calling ref_transaction_free(). */ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, - unsigned int flags, struct strbuf *err); +struct ref_transaction *ref_store_transaction_begin_no_hook(struct ref_store *refs, + struct strbuf *err); struct ref_transaction *ref_transaction_begin(struct strbuf *err); /* diff --git a/refs/files-backend.c b/refs/files-backend.c index 9a20cb8fa89..f85c8f3a692 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1121,8 +1121,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, - REF_TRANSACTION_SKIP_HOOK, &err); + transaction = ref_store_transaction_begin_no_hook(&refs->base, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1193,8 +1192,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, - REF_TRANSACTION_SKIP_HOOK, &err); + transaction = ref_store_transaction_begin_no_hook(refs->packed_ref_store, &err); if (!transaction) return -1; @@ -1261,8 +1259,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, - REF_TRANSACTION_SKIP_HOOK, &err); + transaction = ref_store_transaction_begin_no_hook(refs->packed_ref_store, &err); if (!transaction) goto error; @@ -2775,9 +2772,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, * packed-refs if it exists there. */ if (!packed_transaction) { - packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, - REF_TRANSACTION_SKIP_HOOK, err); + packed_transaction = ref_store_transaction_begin_no_hook( + refs->packed_ref_store, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3048,8 +3044,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, - REF_TRANSACTION_SKIP_HOOK, err); + packed_transaction = ref_store_transaction_begin_no_hook(refs->packed_ref_store, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/refs/packed-backend.c b/refs/packed-backend.c index cfebcd0b46e..e97d67f9321 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1535,7 +1535,7 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, * updates into a single transaction. */ - transaction = ref_store_transaction_begin(ref_store, 0, &err); + transaction = ref_store_transaction_begin(ref_store, &err); if (!transaction) return -1; diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 87da39243f7..8ff9e0e6e3a 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -204,7 +204,9 @@ enum ref_transaction_state { /* * Skip executing the reference-transaction hook. */ -#define REF_TRANSACTION_SKIP_HOOK (1 << 0) +enum ref_transaction_flags { + REF_TRANSACTION_SKIP_HOOK = 1 << 0, +}; /* * Data structure for holding a reference transaction, which can @@ -218,7 +220,7 @@ struct ref_transaction { size_t nr; enum ref_transaction_state state; void *backend_data; - unsigned int flags; + enum ref_transaction_flags flags; }; /* diff --git a/sequencer.c b/sequencer.c index 61edd39d7a4..6abd72160cc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3588,7 +3588,7 @@ static int do_label(struct repository *r, const char *name, int len) strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); - transaction = ref_store_transaction_begin(refs, 0, &err); + transaction = ref_store_transaction_begin(refs, &err); if (!transaction) { error("%s", err.buf); ret = -1; ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v3 3/6] refs: allow skipping the reference-transaction hook 2022-01-13 13:34 ` Ævar Arnfjörð Bjarmason @ 2022-01-17 8:03 ` Patrick Steinhardt 0 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 8:03 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, Jeff King, Bryan Turner, Waleed Khan, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 11535 bytes --] On Thu, Jan 13, 2022 at 02:34:41PM +0100, Ævar Arnfjörð Bjarmason wrote: > > On Thu, Jan 13 2022, Patrick Steinhardt wrote: > > > [[PGP Signed Part:Undecided]] > > The reference-transaction hook is executing whenever we prepare, commit > > or abort a reference transaction. While this is mostly intentional, in > > case of the files backend we're leaking the implementation detail that > > the store is in fact a composite store with one loose and one packed > > backend to the caller. So while we want to execute the hook for all > > logical updates, executing it for such implementation details is > > unexpected. > > > > Prepare for a fix by adding a new flag which allows to skip execution of > > the hook. > > > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > > --- > > refs.c | 3 +++ > > refs.h | 5 +++++ > > 2 files changed, 8 insertions(+) > > > > diff --git a/refs.c b/refs.c > > index 7415864b62..526bf5ed97 100644 > > --- a/refs.c > > +++ b/refs.c > > @@ -2084,6 +2084,9 @@ static int run_transaction_hook(struct ref_transaction *transaction, > > const char *hook; > > int ret = 0, i; > > > > + if (transaction->flags & REF_TRANSACTION_SKIP_HOOK) > > + return 0; > > + > > hook = find_hook("reference-transaction"); > > if (!hook) > > return ret; > > diff --git a/refs.h b/refs.h > > index 31f7bf9642..d4056f9fe2 100644 > > --- a/refs.h > > +++ b/refs.h > > @@ -568,6 +568,11 @@ enum action_on_err { > > UPDATE_REFS_QUIET_ON_ERR > > }; > > > > +/* > > + * Skip executing the reference-transaction hook. > > + */ > > +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) > > + > > /* > > * Begin a reference transaction. The reference transaction must > > * be freed by calling ref_transaction_free(). > > This isn't needed in refs.h, so let's put it in refs-internal.h where > e.g. "enum ref_transaction_state" now lives: On the other hand we got the flags parameter in the function just two lines later. A reader would thus wonder "which flags?!" if we didn't carry the definition of flags nearby and would have to go search the codebase for the set of supported flags. > diff --git a/refs.h b/refs.h > index d4056f9fe26..31f7bf96424 100644 > --- a/refs.h > +++ b/refs.h > @@ -568,11 +568,6 @@ enum action_on_err { > UPDATE_REFS_QUIET_ON_ERR > }; > > -/* > - * Skip executing the reference-transaction hook. > - */ > -#define REF_TRANSACTION_SKIP_HOOK (1 << 0) > - > /* > * Begin a reference transaction. The reference transaction must > * be freed by calling ref_transaction_free(). > diff --git a/refs/refs-internal.h b/refs/refs-internal.h > index a0af63f162f..87da39243f7 100644 > --- a/refs/refs-internal.h > +++ b/refs/refs-internal.h > @@ -201,6 +201,11 @@ enum ref_transaction_state { > REF_TRANSACTION_CLOSED = 2 > }; > > +/* > + * Skip executing the reference-transaction hook. > + */ > +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) > + > /* > * Data structure for holding a reference transaction, which can > * consist of checks and updates to multiple references, carried out > > A bit more odd is that this series ends up with a > ref_transaction_begin() that doesn't correspond to its ref_store_*() > parent, i.e. the others pass the ref store for you, but now we omit the > flags too. > > I see why you did that, to avoid tweaking every existing > ref_transaction_begin() caller. > > But isn't something like the below a better approach? We can introduce a > refs-internal.h-only flag enum, and then just have a new > ref_store_transaction_begin_no_hook() called from these new > files-backend.c users. > > The diff on top is a bit verbose, but the exposed API is cleaner > (presumably no "public" user should be allowed to skip the hook), and > the overall diff if this is squashed in is smaller. While the diff does look sensible it makes me wonder how maintainable it is in the future. If we ever were to accept another flag then we might have to duplicate your `ref_store_transaction_begin_no_hook()` to also carry flags, or add `ref_store_transaciton_begin_another_option()` to stay consistent. So for now I tend to prefer the current version given that it feels more extensible going forward. But I'm happy to change it if others agree with you. Patrick > diff --git a/refs.c b/refs.c > index 526bf5ed97a..ffaf34e9710 100644 > --- a/refs.c > +++ b/refs.c > @@ -800,7 +800,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, > struct ref_transaction *transaction; > struct strbuf err = STRBUF_INIT; > > - transaction = ref_store_transaction_begin(refs, 0, &err); > + transaction = ref_store_transaction_begin(refs, &err); > if (!transaction || > ref_transaction_delete(transaction, refname, old_oid, > flags, msg, &err) || > @@ -1004,22 +1004,36 @@ int read_ref_at(struct ref_store *refs, const char *refname, > return 1; > } > > -struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, > - unsigned int flags, > - struct strbuf *err) > +static struct ref_transaction *ref_store_transaction_begin_1(struct ref_store *refs, > + unsigned int no_hook, > + struct strbuf *err) > { > struct ref_transaction *tr; > assert(err); > > CALLOC_ARRAY(tr, 1); > tr->ref_store = refs; > - tr->flags = flags; > + if (no_hook) > + tr->flags &= REF_TRANSACTION_SKIP_HOOK; > return tr; > } > > struct ref_transaction *ref_transaction_begin(struct strbuf *err) > { > - return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err); > + return ref_store_transaction_begin_1(get_main_ref_store(the_repository), > + 0, err); > +} > + > +struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, > + struct strbuf *err) > +{ > + return ref_store_transaction_begin_1(refs, 0, err); > +} > + > +struct ref_transaction *ref_store_transaction_begin_no_hook(struct ref_store *refs, > + struct strbuf *err) > +{ > + return ref_store_transaction_begin_1(refs, 1, err); > } > > void ref_transaction_free(struct ref_transaction *transaction) > @@ -1158,7 +1172,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, > struct strbuf err = STRBUF_INIT; > int ret = 0; > > - t = ref_store_transaction_begin(refs, 0, &err); > + t = ref_store_transaction_begin(refs, &err); > if (!t || > ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, > &err) || > diff --git a/refs.h b/refs.h > index 31f7bf96424..3663e0347ff 100644 > --- a/refs.h > +++ b/refs.h > @@ -573,8 +573,9 @@ enum action_on_err { > * be freed by calling ref_transaction_free(). > */ > struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, > - unsigned int flags, > struct strbuf *err); > +struct ref_transaction *ref_store_transaction_begin_no_hook(struct ref_store *refs, > + struct strbuf *err); > struct ref_transaction *ref_transaction_begin(struct strbuf *err); > > /* > diff --git a/refs/files-backend.c b/refs/files-backend.c > index 9a20cb8fa89..f85c8f3a692 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -1121,8 +1121,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) > if (check_refname_format(r->name, 0)) > return; > > - transaction = ref_store_transaction_begin(&refs->base, > - REF_TRANSACTION_SKIP_HOOK, &err); > + transaction = ref_store_transaction_begin_no_hook(&refs->base, &err); > if (!transaction) > goto cleanup; > ref_transaction_add_update( > @@ -1193,8 +1192,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) > struct strbuf err = STRBUF_INIT; > struct ref_transaction *transaction; > > - transaction = ref_store_transaction_begin(refs->packed_ref_store, > - REF_TRANSACTION_SKIP_HOOK, &err); > + transaction = ref_store_transaction_begin_no_hook(refs->packed_ref_store, &err); > if (!transaction) > return -1; > > @@ -1261,8 +1259,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, > if (packed_refs_lock(refs->packed_ref_store, 0, &err)) > goto error; > > - transaction = ref_store_transaction_begin(refs->packed_ref_store, > - REF_TRANSACTION_SKIP_HOOK, &err); > + transaction = ref_store_transaction_begin_no_hook(refs->packed_ref_store, &err); > if (!transaction) > goto error; > > @@ -2775,9 +2772,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, > * packed-refs if it exists there. > */ > if (!packed_transaction) { > - packed_transaction = ref_store_transaction_begin( > - refs->packed_ref_store, > - REF_TRANSACTION_SKIP_HOOK, err); > + packed_transaction = ref_store_transaction_begin_no_hook( > + refs->packed_ref_store, err); > if (!packed_transaction) { > ret = TRANSACTION_GENERIC_ERROR; > goto cleanup; > @@ -3048,8 +3044,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, > &affected_refnames)) > BUG("initial ref transaction called with existing refs"); > > - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, > - REF_TRANSACTION_SKIP_HOOK, err); > + packed_transaction = ref_store_transaction_begin_no_hook(refs->packed_ref_store, err); > if (!packed_transaction) { > ret = TRANSACTION_GENERIC_ERROR; > goto cleanup; > diff --git a/refs/packed-backend.c b/refs/packed-backend.c > index cfebcd0b46e..e97d67f9321 100644 > --- a/refs/packed-backend.c > +++ b/refs/packed-backend.c > @@ -1535,7 +1535,7 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, > * updates into a single transaction. > */ > > - transaction = ref_store_transaction_begin(ref_store, 0, &err); > + transaction = ref_store_transaction_begin(ref_store, &err); > if (!transaction) > return -1; > > diff --git a/refs/refs-internal.h b/refs/refs-internal.h > index 87da39243f7..8ff9e0e6e3a 100644 > --- a/refs/refs-internal.h > +++ b/refs/refs-internal.h > @@ -204,7 +204,9 @@ enum ref_transaction_state { > /* > * Skip executing the reference-transaction hook. > */ > -#define REF_TRANSACTION_SKIP_HOOK (1 << 0) > +enum ref_transaction_flags { > + REF_TRANSACTION_SKIP_HOOK = 1 << 0, > +}; > > /* > * Data structure for holding a reference transaction, which can > @@ -218,7 +220,7 @@ struct ref_transaction { > size_t nr; > enum ref_transaction_state state; > void *backend_data; > - unsigned int flags; > + enum ref_transaction_flags flags; > }; > > /* > diff --git a/sequencer.c b/sequencer.c > index 61edd39d7a4..6abd72160cc 100644 > --- a/sequencer.c > +++ b/sequencer.c > @@ -3588,7 +3588,7 @@ static int do_label(struct repository *r, const char *name, int len) > strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); > strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); > > - transaction = ref_store_transaction_begin(refs, 0, &err); > + transaction = ref_store_transaction_begin(refs, &err); > if (!transaction) { > error("%s", err.buf); > ret = -1; [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 4/6] refs: demonstrate excessive execution of the reference-transaction hook 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt ` (2 preceding siblings ...) 2022-01-13 6:11 ` [PATCH v3 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt @ 2022-01-13 6:11 ` Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt 5 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-13 6:11 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 2626 bytes --] Add tests which demonstate that we're executing the reference-transaction hook too often in some cases, which thus leaks implementation details about the reference store's implementation itself. Behaviour will be fixed in follow-up commits. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- t/t1416-ref-transaction-hooks.sh | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 6c941027a8..0567fbdf0b 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -136,4 +136,68 @@ test_expect_success 'interleaving hook calls succeed' ' test_cmp expect target-repo.git/actual ' +test_expect_success 'hook does not get called on packing refs' ' + # Pack references first such that we are in a known state. + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref refs/heads/unpacked-ref $POST_OID && + git pack-refs --all && + + # We only expect a single hook invocation, which is the call to + # git-update-ref(1). But currently, packing refs will also trigger the + # hook. + cat >expect <<-EOF && + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + prepared + $POST_OID $ZERO_OID refs/heads/unpacked-ref + committed + $POST_OID $ZERO_OID refs/heads/unpacked-ref + EOF + + test_cmp expect actual +' + +test_expect_success 'deleting packed ref calls hook once' ' + # Create a reference and pack it. + git update-ref refs/heads/to-be-deleted $POST_OID && + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref -d refs/heads/to-be-deleted $POST_OID && + + # We only expect a single hook invocation, which is the logical + # deletion. But currently, we see two interleaving transactions, once + # for deleting the loose refs and once for deleting the packed ref. + cat >expect <<-EOF && + prepared + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + prepared + $POST_OID $ZERO_OID refs/heads/to-be-deleted + committed + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + committed + $POST_OID $ZERO_OID refs/heads/to-be-deleted + EOF + + test_cmp expect actual +' + test_done -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v3 5/6] refs: do not execute reference-transaction hook on packing refs 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt ` (3 preceding siblings ...) 2022-01-13 6:11 ` [PATCH v3 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt @ 2022-01-13 6:11 ` Patrick Steinhardt 2022-01-13 13:00 ` Ævar Arnfjörð Bjarmason 2022-01-13 6:11 ` [PATCH v3 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt 5 siblings, 1 reply; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-13 6:11 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 2581 bytes --] The reference-transaction hook is supposed to track logical changes to references, but it currently also gets executed when packing refs in a repository. This is unexpected and ultimately not all that useful: packing refs is not supposed to result in any user-visible change to the refs' state, and it ultimately is an implementation detail of how refs stores work. Fix this excessive execution of the hook when packing refs. Reported-by: Waleed Khan <me@waleedkhan.name> Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 6 ++++-- t/t1416-ref-transaction-hooks.sh | 11 +---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index ecf88cee04..3c0f3027fe 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1121,7 +1121,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, 0, &err); + transaction = ref_store_transaction_begin(&refs->base, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1192,7 +1193,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) return -1; diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 0567fbdf0b..f9d3d5213f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -150,21 +150,12 @@ test_expect_success 'hook does not get called on packing refs' ' git pack-refs --all && # We only expect a single hook invocation, which is the call to - # git-update-ref(1). But currently, packing refs will also trigger the - # hook. + # git-update-ref(1). cat >expect <<-EOF && prepared $ZERO_OID $POST_OID refs/heads/unpacked-ref committed $ZERO_OID $POST_OID refs/heads/unpacked-ref - prepared - $ZERO_OID $POST_OID refs/heads/unpacked-ref - committed - $ZERO_OID $POST_OID refs/heads/unpacked-ref - prepared - $POST_OID $ZERO_OID refs/heads/unpacked-ref - committed - $POST_OID $ZERO_OID refs/heads/unpacked-ref EOF test_cmp expect actual -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v3 5/6] refs: do not execute reference-transaction hook on packing refs 2022-01-13 6:11 ` [PATCH v3 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt @ 2022-01-13 13:00 ` Ævar Arnfjörð Bjarmason 2022-01-17 7:44 ` Patrick Steinhardt 0 siblings, 1 reply; 51+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-01-13 13:00 UTC (permalink / raw) To: Patrick Steinhardt Cc: git, Jeff King, Bryan Turner, Waleed Khan, Han-Wen Nienhuys On Thu, Jan 13 2022, Patrick Steinhardt wrote: > [[PGP Signed Part:Undecided]] > The reference-transaction hook is supposed to track logical changes to > references, but it currently also gets executed when packing refs in a > repository. This is unexpected and ultimately not all that useful: > packing refs is not supposed to result in any user-visible change to the > refs' state, and it ultimately is an implementation detail of how refs > stores work. > > Fix this excessive execution of the hook when packing refs. > > Reported-by: Waleed Khan <me@waleedkhan.name> > Signed-off-by: Patrick Steinhardt <ps@pks.im> > --- > refs/files-backend.c | 6 ++++-- > t/t1416-ref-transaction-hooks.sh | 11 +---------- > 2 files changed, 5 insertions(+), 12 deletions(-) > > diff --git a/refs/files-backend.c b/refs/files-backend.c > index ecf88cee04..3c0f3027fe 100644 > --- a/refs/files-backend.c > +++ b/refs/files-backend.c > @@ -1121,7 +1121,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) > if (check_refname_format(r->name, 0)) > return; > > - transaction = ref_store_transaction_begin(&refs->base, 0, &err); > + transaction = ref_store_transaction_begin(&refs->base, > + REF_TRANSACTION_SKIP_HOOK, &err); > if (!transaction) > goto cleanup; > ref_transaction_add_update( > @@ -1192,7 +1193,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) > struct strbuf err = STRBUF_INIT; > struct ref_transaction *transaction; > > - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); > + transaction = ref_store_transaction_begin(refs->packed_ref_store, > + REF_TRANSACTION_SKIP_HOOK, &err); > if (!transaction) > return -1; > > diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh > index 0567fbdf0b..f9d3d5213f 100755 > --- a/t/t1416-ref-transaction-hooks.sh > +++ b/t/t1416-ref-transaction-hooks.sh > @@ -150,21 +150,12 @@ test_expect_success 'hook does not get called on packing refs' ' > git pack-refs --all && > > # We only expect a single hook invocation, which is the call to > - # git-update-ref(1). But currently, packing refs will also trigger the > - # hook. > + # git-update-ref(1). > cat >expect <<-EOF && > prepared > $ZERO_OID $POST_OID refs/heads/unpacked-ref > committed > $ZERO_OID $POST_OID refs/heads/unpacked-ref > - prepared > - $ZERO_OID $POST_OID refs/heads/unpacked-ref > - committed > - $ZERO_OID $POST_OID refs/heads/unpacked-ref > - prepared > - $POST_OID $ZERO_OID refs/heads/unpacked-ref > - committed > - $POST_OID $ZERO_OID refs/heads/unpacked-ref > EOF > > test_cmp expect actual I wondered how we'd deal with cases where the loose ref != the corresponding packed ref, but I can't think of ones where it won't be invisible externally, i.e. we'll correctly update the packed-refs and delete that loose ref as part of this transaction. I do wonder if the docs also need updating, currently they say: [The hook] executes whenever a reference transaction is prepared, committed or aborted[...] But now we'll explicitly exclude certain classes of transactions. Perhaps we should expand: "The hook does not cover symbolic references (but that may change in the future)." Into some list of types of changes we intentionally exclude, might include in the future etc. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 5/6] refs: do not execute reference-transaction hook on packing refs 2022-01-13 13:00 ` Ævar Arnfjörð Bjarmason @ 2022-01-17 7:44 ` Patrick Steinhardt 0 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 7:44 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, Jeff King, Bryan Turner, Waleed Khan, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 4480 bytes --] On Thu, Jan 13, 2022 at 02:00:10PM +0100, Ævar Arnfjörð Bjarmason wrote: > > On Thu, Jan 13 2022, Patrick Steinhardt wrote: > > > [[PGP Signed Part:Undecided]] > > The reference-transaction hook is supposed to track logical changes to > > references, but it currently also gets executed when packing refs in a > > repository. This is unexpected and ultimately not all that useful: > > packing refs is not supposed to result in any user-visible change to the > > refs' state, and it ultimately is an implementation detail of how refs > > stores work. > > > > Fix this excessive execution of the hook when packing refs. > > > > Reported-by: Waleed Khan <me@waleedkhan.name> > > Signed-off-by: Patrick Steinhardt <ps@pks.im> > > --- > > refs/files-backend.c | 6 ++++-- > > t/t1416-ref-transaction-hooks.sh | 11 +---------- > > 2 files changed, 5 insertions(+), 12 deletions(-) > > > > diff --git a/refs/files-backend.c b/refs/files-backend.c > > index ecf88cee04..3c0f3027fe 100644 > > --- a/refs/files-backend.c > > +++ b/refs/files-backend.c > > @@ -1121,7 +1121,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) > > if (check_refname_format(r->name, 0)) > > return; > > > > - transaction = ref_store_transaction_begin(&refs->base, 0, &err); > > + transaction = ref_store_transaction_begin(&refs->base, > > + REF_TRANSACTION_SKIP_HOOK, &err); > > if (!transaction) > > goto cleanup; > > ref_transaction_add_update( > > @@ -1192,7 +1193,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) > > struct strbuf err = STRBUF_INIT; > > struct ref_transaction *transaction; > > > > - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); > > + transaction = ref_store_transaction_begin(refs->packed_ref_store, > > + REF_TRANSACTION_SKIP_HOOK, &err); > > if (!transaction) > > return -1; > > > > diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh > > index 0567fbdf0b..f9d3d5213f 100755 > > --- a/t/t1416-ref-transaction-hooks.sh > > +++ b/t/t1416-ref-transaction-hooks.sh > > @@ -150,21 +150,12 @@ test_expect_success 'hook does not get called on packing refs' ' > > git pack-refs --all && > > > > # We only expect a single hook invocation, which is the call to > > - # git-update-ref(1). But currently, packing refs will also trigger the > > - # hook. > > + # git-update-ref(1). > > cat >expect <<-EOF && > > prepared > > $ZERO_OID $POST_OID refs/heads/unpacked-ref > > committed > > $ZERO_OID $POST_OID refs/heads/unpacked-ref > > - prepared > > - $ZERO_OID $POST_OID refs/heads/unpacked-ref > > - committed > > - $ZERO_OID $POST_OID refs/heads/unpacked-ref > > - prepared > > - $POST_OID $ZERO_OID refs/heads/unpacked-ref > > - committed > > - $POST_OID $ZERO_OID refs/heads/unpacked-ref > > EOF > > > > test_cmp expect actual > > I wondered how we'd deal with cases where the loose ref != the > corresponding packed ref, but I can't think of ones where it won't be > invisible externally, i.e. we'll correctly update the packed-refs and > delete that loose ref as part of this transaction. With the previous code we'd see two hook executions with different old OIDs. Given that we only care about logical updates though the user'd only want to see the one which deletes the user-visible OID, which is what's stored in the loose ref. And with the fixes in this series that's the hook invocation we retain. > I do wonder if the docs also need updating, currently they say: > > [The hook] executes whenever a reference transaction is prepared, > committed or aborted[...] > > But now we'll explicitly exclude certain classes of > transactions. Perhaps we should expand: > > "The hook does not cover symbolic references (but that may change in > the future)." > > Into some list of types of changes we intentionally exclude, might > include in the future etc. Well, from the user's perspective we do execute the hook whenever we drive a reference transaction: all modifications to the files backend are still visible to the hook after the changes in this series. The issue is that with the files backend being a combination of two backends, we essentially saw a subset of refs executing the hook twice, which really is an implementation detail. Patrick [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 6/6] refs: skip hooks when deleting uncovered packed refs 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt ` (4 preceding siblings ...) 2022-01-13 6:11 ` [PATCH v3 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt @ 2022-01-13 6:11 ` Patrick Steinhardt 2022-01-13 13:04 ` Ævar Arnfjörð Bjarmason 5 siblings, 1 reply; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-13 6:11 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 3947 bytes --] When deleting refs from the loose-files refs backend, then we need to be careful to also delete the same ref from the packed refs backend, if it exists. If we don't, then deleting the loose ref would "uncover" the packed ref. We thus always have to queue up deletions of refs for both the loose and the packed refs backend. This is done in two separate transactions, where the end result is that the reference-transaction hook is executed twice for the deleted refs. This behaviour is quite misleading: it's exposing implementation details of how the files backend works to the user, in contrast to the logical updates that we'd really want to expose via the hook. Worse yet, whether the hook gets executed once or twice depends on how well-packed the repository is: if the ref only exists as a loose ref, then we execute it once, otherwise if it is also packed then we execute it twice. Fix this behaviour and don't execute the reference-transaction hook at all when refs in the packed-refs backend if it's driven by the files backend. This works as expected even in case the refs to be deleted only exist in the packed-refs backend because the loose-backend always queues refs in its own transaction even if they don't exist such that they can be locked for concurrent creation. And it also does the right thing in case neither of the backends has the ref because that would cause the transaction to fail completely. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 9 ++++++--- t/t1416-ref-transaction-hooks.sh | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 3c0f3027fe..9a20cb8fa8 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1261,7 +1261,8 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto error; @@ -2775,7 +2776,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, 0, err); + refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3046,7 +3048,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index f9d3d5213f..4e1e84a91f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -175,16 +175,11 @@ test_expect_success 'deleting packed ref calls hook once' ' git update-ref -d refs/heads/to-be-deleted $POST_OID && # We only expect a single hook invocation, which is the logical - # deletion. But currently, we see two interleaving transactions, once - # for deleting the loose refs and once for deleting the packed ref. + # deletion. cat >expect <<-EOF && - prepared - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted prepared $POST_OID $ZERO_OID refs/heads/to-be-deleted committed - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted - committed $POST_OID $ZERO_OID refs/heads/to-be-deleted EOF -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v3 6/6] refs: skip hooks when deleting uncovered packed refs 2022-01-13 6:11 ` [PATCH v3 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt @ 2022-01-13 13:04 ` Ævar Arnfjörð Bjarmason 2022-01-17 7:56 ` Patrick Steinhardt 0 siblings, 1 reply; 51+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-01-13 13:04 UTC (permalink / raw) To: Patrick Steinhardt Cc: git, Jeff King, Bryan Turner, Waleed Khan, Han-Wen Nienhuys On Thu, Jan 13 2022, Patrick Steinhardt wrote: > [[PGP Signed Part:Undecided]] > When deleting refs from the loose-files refs backend, then we need to be > careful to also delete the same ref from the packed refs backend, if it > exists. If we don't, then deleting the loose ref would "uncover" the > packed ref. We thus always have to queue up deletions of refs for both > the loose and the packed refs backend. This is done in two separate > transactions, where the end result is that the reference-transaction > hook is executed twice for the deleted refs. But do we (which would be an issue before this series) delete the loose and and then the packed one, thus racily exposing the stale ref to any concurrent repository reader, or do we first update the packed ref to the valu of the now-locked loose ref to avoid such a race? > [...] > Fix this behaviour and don't execute the reference-transaction hook at > all when refs in the packed-refs backend if it's driven by the files > backend. This works as expected even in case the refs to be deleted only > exist in the packed-refs backend because the loose-backend always queues > refs in its own transaction even if they don't exist such that they can > be locked for concurrent creation. And it also does the right thing in > case neither of the backends has the ref because that would cause the > transaction to fail completely. I do wonder if the fundimental approach here is the right one. I.e. changing the hook to only expose "real" updates, as opposed to leaving it as a lower-level facility to listed in on any sort of ref updates. In such a scenario we could imagine adding a third parameter or otherwise flag the update as "real" to the hook, so a dumber hook consumer could ignore the more verbose inter-transactional chatter. I say that because this change does the right thing for the use-case you have in mind, but if you e.g. imagine a more gentle background-friendly "gc" such a thing could be implemented by backing off as soon as it sees an ongoing transaction being started. With my ae35e16cd43 (reflog expire: don't lock reflogs using previously seen OID, 2021-08-23) not getting that more chatty data should be be OK for such a hypothetical hook. But we might have more avoidable tripping over locks as the gc and ref transaction race one another to lock various things in the repository. Or maybe nobody cares in practice, just food for thought. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 6/6] refs: skip hooks when deleting uncovered packed refs 2022-01-13 13:04 ` Ævar Arnfjörð Bjarmason @ 2022-01-17 7:56 ` Patrick Steinhardt 0 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 7:56 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, Jeff King, Bryan Turner, Waleed Khan, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 3901 bytes --] On Thu, Jan 13, 2022 at 02:04:38PM +0100, Ævar Arnfjörð Bjarmason wrote: > > On Thu, Jan 13 2022, Patrick Steinhardt wrote: > > > [[PGP Signed Part:Undecided]] > > When deleting refs from the loose-files refs backend, then we need to be > > careful to also delete the same ref from the packed refs backend, if it > > exists. If we don't, then deleting the loose ref would "uncover" the > > packed ref. We thus always have to queue up deletions of refs for both > > the loose and the packed refs backend. This is done in two separate > > transactions, where the end result is that the reference-transaction > > hook is executed twice for the deleted refs. > > But do we (which would be an issue before this series) delete the loose > and and then the packed one, thus racily exposing the stale ref to any > concurrent repository reader, or do we first update the packed ref to > the valu of the now-locked loose ref to avoid such a race? We first commit the packed-refs file so that the stale ref is not exposed. > > [...] > > Fix this behaviour and don't execute the reference-transaction hook at > > all when refs in the packed-refs backend if it's driven by the files > > backend. This works as expected even in case the refs to be deleted only > > exist in the packed-refs backend because the loose-backend always queues > > refs in its own transaction even if they don't exist such that they can > > be locked for concurrent creation. And it also does the right thing in > > case neither of the backends has the ref because that would cause the > > transaction to fail completely. > > I do wonder if the fundimental approach here is the right > one. I.e. changing the hook to only expose "real" updates, as opposed to > leaving it as a lower-level facility to listed in on any sort of ref > updates. > > In such a scenario we could imagine adding a third parameter or > otherwise flag the update as "real" to the hook, so a dumber hook > consumer could ignore the more verbose inter-transactional chatter. > > I say that because this change does the right thing for the use-case you > have in mind, but if you e.g. imagine a more gentle background-friendly > "gc" such a thing could be implemented by backing off as soon as it sees > an ongoing transaction being started. I've mostly been acting on the original report by Waleed. And I tend to agree with his report given that we also got a workaround at GitLab which filters out reference transactions which only consist of force deletions because they're likely to be pruning refs in the packed backend which are about to be uncovered. The result is that execution of the reftx hook is dependent on how well-packed a repository's refs are: when refs are packed we execute the hook twice, whereas we execute it once when it's not well-packed. This is surprising behaviour, even though one can definitely argue that it's just working as intended. I think ultimately the question boils down to whether we want to treat the files backend as a single compound backend and whether the reftx hook should treat it like that. If we treat it as a single backend, then we shouldn't report a change in refs when pruning about-to-be-uncovered refs given that it wouldn't have been visible, but it's only internal cleanup. And neither should we report ref changes when repacking refs into a single file given that from the backend's perspective nothing is about to change. Patrick > With my ae35e16cd43 (reflog expire: don't lock reflogs using previously > seen OID, 2021-08-23) not getting that more chatty data should be be OK > for such a hypothetical hook. > > But we might have more avoidable tripping over locks as the gc and ref > transaction race one another to lock various things in the repository. > > Or maybe nobody cares in practice, just food for thought. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v4 0/6] refs: excessive hook execution with packed refs 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (7 preceding siblings ...) 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt @ 2022-01-17 8:12 ` Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt ` (5 more replies) 8 siblings, 6 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 8:12 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 3894 bytes --] Hi, this is the fourth version of this patch series, which addresses an issue where the reference-transaction hook is being invoked twice when deleting refs both in the packed-refs and loose-refs file. The following things changed in comparison to v3: - Fixed a memory leak in `files_delete_refs()`. - Refactored the `packed_downcast()` invocation such that we don't have to mark its unused returned variable as used. - Removed a spurious whitespace change. Patrick Patrick Steinhardt (6): refs: extract packed_refs_delete_refs() to allow control of transaction refs: allow passing flags when beginning transactions refs: allow skipping the reference-transaction hook refs: demonstrate excessive execution of the reference-transaction hook refs: do not execute reference-transaction hook on packing refs refs: skip hooks when deleting uncovered packed refs refs.c | 11 +++++-- refs.h | 8 ++++- refs/files-backend.c | 26 ++++++++++++----- refs/packed-backend.c | 28 +++++++++++++----- refs/packed-backend.h | 7 +++++ refs/refs-internal.h | 1 + sequencer.c | 2 +- t/t1416-ref-transaction-hooks.sh | 50 ++++++++++++++++++++++++++++++++ 8 files changed, 114 insertions(+), 19 deletions(-) Range-diff against v3: 1: abbc28822b ! 1: 14775046e1 refs: extract packed_refs_delete_refs() to allow control of transaction @@ refs/files-backend.c: static int files_delete_refs(struct ref_store *ref_store, packed_refs_unlock(refs->packed_ref_store); +@@ refs/files-backend.c: static int files_delete_refs(struct ref_store *ref_store, const char *msg, + result |= error(_("could not remove reference %s"), refname); + } + ++ ref_transaction_free(transaction); + strbuf_release(&err); + return result; + @@ refs/files-backend.c: static int files_delete_refs(struct ref_store *ref_store, const char *msg, else error(_("could not delete references: %s"), err.buf); @@ refs/packed-backend.c: static int packed_delete_refs(struct ref_store *ref_store + struct string_list *refnames, + unsigned int flags) +{ -+ struct packed_ref_store *refs = -+ packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct strbuf err = STRBUF_INIT; + struct string_list_item *item; + int ret; + -+ (void)(refs); /* We need the check above, but don't use the variable */ ++ /* Assert that the ref store refers to a packed backend. */ ++ packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + for_each_string_list_item(item, refnames) { if (ref_transaction_delete(transaction, item->string, NULL, flags, msg, &err)) { -@@ refs/packed-backend.c: static int packed_delete_refs(struct ref_store *ref_store, const char *msg, - } - - ret = ref_transaction_commit(transaction, &err); -- - if (ret) { - if (refnames->nr == 1) - error(_("could not delete reference %s: %s"), @@ refs/packed-backend.c: static int packed_delete_refs(struct ref_store *ref_store, const char *msg, error(_("could not delete references: %s"), err.buf); } 2: 9dd172a757 = 2: d4ac24c8b8 refs: allow passing flags when beginning transactions 3: be826bae3b = 3: f4a07fe9a8 refs: allow skipping the reference-transaction hook 4: 662a6e6244 = 4: a8981baef7 refs: demonstrate excessive execution of the reference-transaction hook 5: d83f309b9c = 5: 23c344854e refs: do not execute reference-transaction hook on packing refs 6: 279eadc41c = 6: d6c7d765af refs: skip hooks when deleting uncovered packed refs -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v4 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt @ 2022-01-17 8:12 ` Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt ` (4 subsequent siblings) 5 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 8:12 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 5158 bytes --] When deleting loose refs, then we also have to delete the refs in the packed backend. This is done by calling `refs_delete_refs()`, which then uses the packed-backend's logic to delete refs. This doesn't allow us to exercise any control over the reference transaction which is being created in the packed backend, which is required in a subsequent commit. Extract a new function `packed_refs_delete_refs()`, which hosts most of the logic to delete refs except for creating the transaction itself. Like this, we can easily create the transaction in the files backend and thus exert more control over it. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 13 ++++++++++--- refs/packed-backend.c | 26 ++++++++++++++++++++------ refs/packed-backend.h | 7 +++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 90b671025a..459f18dbc1 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1249,6 +1249,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct ref_transaction *transaction = NULL; struct strbuf err = STRBUF_INIT; int i, result = 0; @@ -1258,10 +1259,14 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { - packed_refs_unlock(refs->packed_ref_store); + transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + if (!transaction) + goto error; + + result = packed_refs_delete_refs(refs->packed_ref_store, + transaction, msg, refnames, flags); + if (result) goto error; - } packed_refs_unlock(refs->packed_ref_store); @@ -1272,6 +1277,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, result |= error(_("could not remove reference %s"), refname); } + ref_transaction_free(transaction); strbuf_release(&err); return result; @@ -1288,6 +1294,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, else error(_("could not delete references: %s"), err.buf); + ref_transaction_free(transaction); strbuf_release(&err); return -1; } diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 67152c664e..c964dd1617 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1522,15 +1522,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, static int packed_delete_refs(struct ref_store *ref_store, const char *msg, struct string_list *refnames, unsigned int flags) { - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - struct string_list_item *item; int ret; - (void)refs; /* We need the check above, but don't use the variable */ - if (!refnames->nr) return 0; @@ -1544,6 +1539,26 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, if (!transaction) return -1; + ret = packed_refs_delete_refs(ref_store, transaction, + msg, refnames, flags); + + ref_transaction_free(transaction); + return ret; +} + +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags) +{ + struct strbuf err = STRBUF_INIT; + struct string_list_item *item; + int ret; + + /* Assert that the ref store refers to a packed backend. */ + packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + for_each_string_list_item(item, refnames) { if (ref_transaction_delete(transaction, item->string, NULL, flags, msg, &err)) { @@ -1563,7 +1578,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, error(_("could not delete references: %s"), err.buf); } - ref_transaction_free(transaction); strbuf_release(&err); return ret; } diff --git a/refs/packed-backend.h b/refs/packed-backend.h index f61a73ec25..a2cca5d9a3 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -3,6 +3,7 @@ struct repository; struct ref_transaction; +struct string_list; /* * Support for storing references in a `packed-refs` file. @@ -27,6 +28,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) void packed_refs_unlock(struct ref_store *ref_store); int packed_refs_is_locked(struct ref_store *ref_store); +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags); + /* * Return true if `transaction` really needs to be carried out against * the specified packed_ref_store, or false if it can be skipped -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v4 2/6] refs: allow passing flags when beginning transactions 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt @ 2022-01-17 8:12 ` Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt ` (3 subsequent siblings) 5 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 8:12 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 6362 bytes --] We do not currently have any flags when creating reference transactions, but we'll add one to disable execution of the reference transaction hook in some cases. Allow passing flags to `ref_store_transaction_begin()` to prepare for this change. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 8 +++++--- refs.h | 3 ++- refs/files-backend.c | 10 +++++----- refs/packed-backend.c | 2 +- refs/refs-internal.h | 1 + sequencer.c | 2 +- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/refs.c b/refs.c index 4da4996c4d..7415864b62 100644 --- a/refs.c +++ b/refs.c @@ -800,7 +800,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction || ref_transaction_delete(transaction, refname, old_oid, flags, msg, &err) || @@ -1005,6 +1005,7 @@ int read_ref_at(struct ref_store *refs, const char *refname, } struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err) { struct ref_transaction *tr; @@ -1012,12 +1013,13 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, CALLOC_ARRAY(tr, 1); tr->ref_store = refs; + tr->flags = flags; return tr; } struct ref_transaction *ref_transaction_begin(struct strbuf *err) { - return ref_store_transaction_begin(get_main_ref_store(the_repository), err); + return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err); } void ref_transaction_free(struct ref_transaction *transaction) @@ -1156,7 +1158,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, struct strbuf err = STRBUF_INIT; int ret = 0; - t = ref_store_transaction_begin(refs, &err); + t = ref_store_transaction_begin(refs, 0, &err); if (!t || ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, &err) || diff --git a/refs.h b/refs.h index 92360e55a2..31f7bf9642 100644 --- a/refs.h +++ b/refs.h @@ -231,7 +231,7 @@ char *repo_default_branch_name(struct repository *r, int quiet); * struct strbuf err = STRBUF_INIT; * int ret = 0; * - * transaction = ref_store_transaction_begin(refs, &err); + * transaction = ref_store_transaction_begin(refs, 0, &err); * if (!transaction || * ref_transaction_update(...) || * ref_transaction_create(...) || @@ -573,6 +573,7 @@ enum action_on_err { * be freed by calling ref_transaction_free(). */ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err); struct ref_transaction *ref_transaction_begin(struct strbuf *err); diff --git a/refs/files-backend.c b/refs/files-backend.c index 459f18dbc1..4d4f0c2099 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1121,7 +1121,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, &err); + transaction = ref_store_transaction_begin(&refs->base, 0, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1192,7 +1192,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); if (!transaction) return -1; @@ -1259,7 +1259,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); if (!transaction) goto error; @@ -2774,7 +2774,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, err); + refs->packed_ref_store, 0, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3045,7 +3045,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/refs/packed-backend.c b/refs/packed-backend.c index c964dd1617..0b45598e18 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1535,7 +1535,7 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, * updates into a single transaction. */ - transaction = ref_store_transaction_begin(ref_store, &err); + transaction = ref_store_transaction_begin(ref_store, 0, &err); if (!transaction) return -1; diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 46a839539e..a0af63f162 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -213,6 +213,7 @@ struct ref_transaction { size_t nr; enum ref_transaction_state state; void *backend_data; + unsigned int flags; }; /* diff --git a/sequencer.c b/sequencer.c index 6abd72160c..61edd39d7a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3588,7 +3588,7 @@ static int do_label(struct repository *r, const char *name, int len) strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction) { error("%s", err.buf); ret = -1; -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v4 3/6] refs: allow skipping the reference-transaction hook 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt @ 2022-01-17 8:12 ` Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt ` (2 subsequent siblings) 5 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 8:12 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 1449 bytes --] The reference-transaction hook is executing whenever we prepare, commit or abort a reference transaction. While this is mostly intentional, in case of the files backend we're leaking the implementation detail that the store is in fact a composite store with one loose and one packed backend to the caller. So while we want to execute the hook for all logical updates, executing it for such implementation details is unexpected. Prepare for a fix by adding a new flag which allows to skip execution of the hook. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs.c | 3 +++ refs.h | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/refs.c b/refs.c index 7415864b62..526bf5ed97 100644 --- a/refs.c +++ b/refs.c @@ -2084,6 +2084,9 @@ static int run_transaction_hook(struct ref_transaction *transaction, const char *hook; int ret = 0, i; + if (transaction->flags & REF_TRANSACTION_SKIP_HOOK) + return 0; + hook = find_hook("reference-transaction"); if (!hook) return ret; diff --git a/refs.h b/refs.h index 31f7bf9642..d4056f9fe2 100644 --- a/refs.h +++ b/refs.h @@ -568,6 +568,11 @@ enum action_on_err { UPDATE_REFS_QUIET_ON_ERR }; +/* + * Skip executing the reference-transaction hook. + */ +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) + /* * Begin a reference transaction. The reference transaction must * be freed by calling ref_transaction_free(). -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v4 4/6] refs: demonstrate excessive execution of the reference-transaction hook 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (2 preceding siblings ...) 2022-01-17 8:12 ` [PATCH v4 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt @ 2022-01-17 8:12 ` Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt 5 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 8:12 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 2626 bytes --] Add tests which demonstate that we're executing the reference-transaction hook too often in some cases, which thus leaks implementation details about the reference store's implementation itself. Behaviour will be fixed in follow-up commits. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- t/t1416-ref-transaction-hooks.sh | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 6c941027a8..0567fbdf0b 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -136,4 +136,68 @@ test_expect_success 'interleaving hook calls succeed' ' test_cmp expect target-repo.git/actual ' +test_expect_success 'hook does not get called on packing refs' ' + # Pack references first such that we are in a known state. + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref refs/heads/unpacked-ref $POST_OID && + git pack-refs --all && + + # We only expect a single hook invocation, which is the call to + # git-update-ref(1). But currently, packing refs will also trigger the + # hook. + cat >expect <<-EOF && + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + prepared + $POST_OID $ZERO_OID refs/heads/unpacked-ref + committed + $POST_OID $ZERO_OID refs/heads/unpacked-ref + EOF + + test_cmp expect actual +' + +test_expect_success 'deleting packed ref calls hook once' ' + # Create a reference and pack it. + git update-ref refs/heads/to-be-deleted $POST_OID && + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref -d refs/heads/to-be-deleted $POST_OID && + + # We only expect a single hook invocation, which is the logical + # deletion. But currently, we see two interleaving transactions, once + # for deleting the loose refs and once for deleting the packed ref. + cat >expect <<-EOF && + prepared + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + prepared + $POST_OID $ZERO_OID refs/heads/to-be-deleted + committed + $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + committed + $POST_OID $ZERO_OID refs/heads/to-be-deleted + EOF + + test_cmp expect actual +' + test_done -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v4 5/6] refs: do not execute reference-transaction hook on packing refs 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (3 preceding siblings ...) 2022-01-17 8:12 ` [PATCH v4 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt @ 2022-01-17 8:12 ` Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt 5 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 8:12 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 2581 bytes --] The reference-transaction hook is supposed to track logical changes to references, but it currently also gets executed when packing refs in a repository. This is unexpected and ultimately not all that useful: packing refs is not supposed to result in any user-visible change to the refs' state, and it ultimately is an implementation detail of how refs stores work. Fix this excessive execution of the hook when packing refs. Reported-by: Waleed Khan <me@waleedkhan.name> Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 6 ++++-- t/t1416-ref-transaction-hooks.sh | 11 +---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 4d4f0c2099..565929210a 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1121,7 +1121,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, 0, &err); + transaction = ref_store_transaction_begin(&refs->base, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1192,7 +1193,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) return -1; diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 0567fbdf0b..f9d3d5213f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -150,21 +150,12 @@ test_expect_success 'hook does not get called on packing refs' ' git pack-refs --all && # We only expect a single hook invocation, which is the call to - # git-update-ref(1). But currently, packing refs will also trigger the - # hook. + # git-update-ref(1). cat >expect <<-EOF && prepared $ZERO_OID $POST_OID refs/heads/unpacked-ref committed $ZERO_OID $POST_OID refs/heads/unpacked-ref - prepared - $ZERO_OID $POST_OID refs/heads/unpacked-ref - committed - $ZERO_OID $POST_OID refs/heads/unpacked-ref - prepared - $POST_OID $ZERO_OID refs/heads/unpacked-ref - committed - $POST_OID $ZERO_OID refs/heads/unpacked-ref EOF test_cmp expect actual -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v4 6/6] refs: skip hooks when deleting uncovered packed refs 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt ` (4 preceding siblings ...) 2022-01-17 8:12 ` [PATCH v4 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt @ 2022-01-17 8:12 ` Patrick Steinhardt 5 siblings, 0 replies; 51+ messages in thread From: Patrick Steinhardt @ 2022-01-17 8:12 UTC (permalink / raw) To: git Cc: Jeff King, Bryan Turner, Waleed Khan, Ævar Arnfjörð Bjarmason, Han-Wen Nienhuys [-- Attachment #1: Type: text/plain, Size: 3947 bytes --] When deleting refs from the loose-files refs backend, then we need to be careful to also delete the same ref from the packed refs backend, if it exists. If we don't, then deleting the loose ref would "uncover" the packed ref. We thus always have to queue up deletions of refs for both the loose and the packed refs backend. This is done in two separate transactions, where the end result is that the reference-transaction hook is executed twice for the deleted refs. This behaviour is quite misleading: it's exposing implementation details of how the files backend works to the user, in contrast to the logical updates that we'd really want to expose via the hook. Worse yet, whether the hook gets executed once or twice depends on how well-packed the repository is: if the ref only exists as a loose ref, then we execute it once, otherwise if it is also packed then we execute it twice. Fix this behaviour and don't execute the reference-transaction hook at all when refs in the packed-refs backend if it's driven by the files backend. This works as expected even in case the refs to be deleted only exist in the packed-refs backend because the loose-backend always queues refs in its own transaction even if they don't exist such that they can be locked for concurrent creation. And it also does the right thing in case neither of the backends has the ref because that would cause the transaction to fail completely. Signed-off-by: Patrick Steinhardt <ps@pks.im> --- refs/files-backend.c | 9 ++++++--- t/t1416-ref-transaction-hooks.sh | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index 565929210a..844918cbd8 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1261,7 +1261,8 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto error; @@ -2776,7 +2777,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, 0, err); + refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3047,7 +3049,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index f9d3d5213f..4e1e84a91f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -175,16 +175,11 @@ test_expect_success 'deleting packed ref calls hook once' ' git update-ref -d refs/heads/to-be-deleted $POST_OID && # We only expect a single hook invocation, which is the logical - # deletion. But currently, we see two interleaving transactions, once - # for deleting the loose refs and once for deleting the packed ref. + # deletion. cat >expect <<-EOF && - prepared - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted prepared $POST_OID $ZERO_OID refs/heads/to-be-deleted committed - $ZERO_OID $ZERO_OID refs/heads/to-be-deleted - committed $POST_OID $ZERO_OID refs/heads/to-be-deleted EOF -- 2.34.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply related [flat|nested] 51+ messages in thread
end of thread, other threads:[~2022-01-24 7:13 UTC | newest] Thread overview: 51+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2021-12-07 10:55 [PATCH 0/6] refs: excessive hook execution with packed refs Patrick Steinhardt 2021-12-07 10:55 ` [PATCH 1/6] refs: open-code deletion of " Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt 2021-12-07 10:56 ` [PATCH 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 0/6] refs: excessive hook execution with " Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 1/6] refs: open-code deletion of " Patrick Steinhardt 2022-01-08 0:57 ` Junio C Hamano 2022-01-08 1:51 ` Junio C Hamano 2022-01-13 0:24 ` Junio C Hamano 2022-01-07 11:55 ` [PATCH v2 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt 2022-01-08 1:51 ` Junio C Hamano 2022-01-07 11:55 ` [PATCH v2 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt 2022-01-08 1:31 ` Junio C Hamano 2022-01-10 12:54 ` Patrick Steinhardt 2022-01-08 5:43 ` Eric Sunshine 2022-01-07 11:55 ` [PATCH v2 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt 2022-01-07 11:55 ` [PATCH v2 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt 2022-01-08 2:01 ` Junio C Hamano 2022-01-10 13:18 ` Patrick Steinhardt 2022-01-10 18:00 ` Junio C Hamano 2022-01-07 22:17 ` [PATCH v2 0/6] refs: excessive hook execution with " Junio C Hamano 2022-01-13 18:24 ` Han-Wen Nienhuys 2022-01-17 7:18 ` Patrick Steinhardt 2022-01-17 11:37 ` Han-Wen Nienhuys 2022-01-24 7:13 ` Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 " Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt 2022-01-13 12:43 ` Ævar Arnfjörð Bjarmason 2022-01-17 7:36 ` Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt 2022-01-13 13:34 ` Ævar Arnfjörð Bjarmason 2022-01-17 8:03 ` Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt 2022-01-13 13:00 ` Ævar Arnfjörð Bjarmason 2022-01-17 7:44 ` Patrick Steinhardt 2022-01-13 6:11 ` [PATCH v3 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt 2022-01-13 13:04 ` Ævar Arnfjörð Bjarmason 2022-01-17 7:56 ` Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 0/6] refs: excessive hook execution with " Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 1/6] refs: extract packed_refs_delete_refs() to allow control of transaction Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 2/6] refs: allow passing flags when beginning transactions Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 3/6] refs: allow skipping the reference-transaction hook Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 4/6] refs: demonstrate excessive execution of " Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 5/6] refs: do not execute reference-transaction hook on packing refs Patrick Steinhardt 2022-01-17 8:12 ` [PATCH v4 6/6] refs: skip hooks when deleting uncovered packed refs Patrick Steinhardt
Code repositories for project(s) associated with this public inbox https://80x24.org/mirrors/git.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).