{"thread":{"id":"54572","subject":"[PATCH] refs: Always pass old object name to reftx hook","startedAt":"2020-11-04T14:58:47Z","lastAt":"2021-01-22T19:17:39Z","messageCount":13,"participants":["Patrick Steinhardt","Taylor Blau","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"409086","messageId":"d255c7a5f95635c2e7ae36b9689c3efd07b4df5d.1604501894.git.ps@pks.im","threadId":"54572","inReplyTo":null,"subject":"[PATCH] refs: Always pass old object name to reftx hook","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-11-04T14:58:40Z","receivedAt":"2020-11-04T14:58:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Inputs of the reference-transaction hook currently depends on the\ncommand which is being run. For example if the command `git update-ref\n$REF $A $B` is executed, it will receive \"$B $A $REF\" as input, but if\nthe command `git update-ref $REF $A` is executed without providing the\nold value, then it will receive \"0*40 $A $REF\" as input. This is due to\nthe fact that we directly write queued transaction updates into the\nhook's standard input, which will not contain the old object value in\ncase it wasn't provided.\n\nWhile this behaviour reflects what is happening as part of the\nrepository, it doesn't feel like it is useful. The main intent of the\nreference-transaction hook is to be able to completely audit all\nreference updates, no matter where they come from. As such, it makes a\nlot more sense to always provide actual values instead of what the user\nwanted. Furthermore, it's impossible for the hook to distinguish whether\nthis is intended to be a branch creation or a branch update without\ndoing additional digging with the current format.\n\nFix the issue by storing the old object value into the queued\ntransaction update operation if it wasn't provided by the caller.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/githooks.txt       |  6 ++++++\n refs/files-backend.c             |  8 ++++++++\n refs/packed-backend.c            |  2 ++\n t/t1416-ref-transaction-hooks.sh | 12 ++++++------\n 4 files changed, 22 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 4e097dc4e9..8f3540e2f6 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -492,6 +492,12 @@ receives on standard input a line of the format:\n \n   <old-value> SP <new-value> SP <ref-name> LF\n \n+where `<old-value>` is the old object name stored in the ref,\n+`<new-value>` is the new object name to be stored in the ref and\n+`<ref-name>` is the full name of the ref.\n+When creating a new ref, `<old-value>` is 40 `0`.\n+When deleting an old ref, `<new-value>` is 40 `0`.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 04e85e7002..5b10d3822b 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2452,6 +2452,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\n \t\t\t}\n+\n+\t\t\tif (!(update->flags & REF_HAVE_OLD))\n+\t\t\t\toidcpy(&update->old_oid, &lock->old_oid);\n \t\t} else {\n \t\t\t/*\n \t\t\t * Create a new update for the reference this\n@@ -2474,6 +2477,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\tgoto out;\n \t\t}\n \n+\t\tif (!(update->flags & REF_HAVE_OLD))\n+\t\t\toidcpy(&update->old_oid, &lock->old_oid);\n+\n \t\t/*\n \t\t * If this update is happening indirectly because of a\n \t\t * symref update, record the old OID in the parent\n@@ -2484,6 +2490,8 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t     parent_update = parent_update->parent_update) {\n \t\t\tstruct ref_lock *parent_lock = parent_update->backend_data;\n \t\t\toidcpy(&parent_lock->old_oid, &lock->old_oid);\n+\t\t\tif (!(parent_update->flags & REF_HAVE_OLD))\n+\t\t\t\toidcpy(&parent_update->old_oid, &lock->old_oid);\n \t\t}\n \t}\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex b912f2505f..08f0feee3d 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1178,6 +1178,8 @@ static int write_with_updates(struct packed_ref_store *refs,\n \t\t\t\t\t\t    oid_to_hex(&update->old_oid));\n \t\t\t\t\tgoto error;\n \t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\toidcpy(&update->old_oid, iter->oid);\n \t\t\t}\n \n \t\t\t/* Now figure out what to use for the new value: */\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex f6e741c6c0..111533682a 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -52,12 +52,12 @@ test_expect_success 'hook gets all queued updates in prepared state' '\n \t\tfi\n \tEOF\n \tcat >expect <<-EOF &&\n-\t\t$ZERO_OID $POST_OID HEAD\n-\t\t$ZERO_OID $POST_OID refs/heads/master\n+\t\t$PRE_OID $POST_OID HEAD\n+\t\t$PRE_OID $POST_OID refs/heads/master\n \tEOF\n \tgit update-ref HEAD POST <<-EOF &&\n-\t\tupdate HEAD $ZERO_OID $POST_OID\n-\t\tupdate refs/heads/master $ZERO_OID $POST_OID\n+\t\tupdate HEAD $PRE_OID $POST_OID\n+\t\tupdate refs/heads/master $PRE_OID $POST_OID\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -75,8 +75,8 @@ test_expect_success 'hook gets all queued updates in committed state' '\n \t\tfi\n \tEOF\n \tcat >expect <<-EOF &&\n-\t\t$ZERO_OID $POST_OID HEAD\n-\t\t$ZERO_OID $POST_OID refs/heads/master\n+\t\t$PRE_OID $POST_OID HEAD\n+\t\t$PRE_OID $POST_OID refs/heads/master\n \tEOF\n \tgit update-ref HEAD POST &&\n \ttest_cmp expect actual\n-- \n2.29.2\n\n"},{"id":"411318","messageId":"X8n1Qf7TJyqIOE/l@ncase","threadId":"54572","inReplyTo":"d255c7a5f95635c2e7ae36b9689c3efd07b4df5d.1604501894.git.ps@pks.im","subject":"Re: [PATCH] refs: Always pass old object name to reftx hook","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-04T08:37:21Z","receivedAt":"2020-12-04T08:38:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Nov 04, 2020 at 03:58:40PM +0100, Patrick Steinhardt wrote:\n> Inputs of the reference-transaction hook currently depends on the\n> command which is being run. For example if the command `git update-ref\n> $REF $A $B` is executed, it will receive \"$B $A $REF\" as input, but if\n> the command `git update-ref $REF $A` is executed without providing the\n> old value, then it will receive \"0*40 $A $REF\" as input. This is due to\n> the fact that we directly write queued transaction updates into the\n> hook's standard input, which will not contain the old object value in\n> case it wasn't provided.\n> \n> While this behaviour reflects what is happening as part of the\n> repository, it doesn't feel like it is useful. The main intent of the\n> reference-transaction hook is to be able to completely audit all\n> reference updates, no matter where they come from. As such, it makes a\n> lot more sense to always provide actual values instead of what the user\n> wanted. Furthermore, it's impossible for the hook to distinguish whether\n> this is intended to be a branch creation or a branch update without\n> doing additional digging with the current format.\n> \n> Fix the issue by storing the old object value into the queued\n> transaction update operation if it wasn't provided by the caller.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n\nQuick ping on this patch. Is there any interest or shall I just drop it?\n\nPatrick\n\n>  Documentation/githooks.txt       |  6 ++++++\n>  refs/files-backend.c             |  8 ++++++++\n>  refs/packed-backend.c            |  2 ++\n>  t/t1416-ref-transaction-hooks.sh | 12 ++++++------\n>  4 files changed, 22 insertions(+), 6 deletions(-)\n> \n> diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\n> index 4e097dc4e9..8f3540e2f6 100644\n> --- a/Documentation/githooks.txt\n> +++ b/Documentation/githooks.txt\n> @@ -492,6 +492,12 @@ receives on standard input a line of the format:\n>  \n>    <old-value> SP <new-value> SP <ref-name> LF\n>  \n> +where `<old-value>` is the old object name stored in the ref,\n> +`<new-value>` is the new object name to be stored in the ref and\n> +`<ref-name>` is the full name of the ref.\n> +When creating a new ref, `<old-value>` is 40 `0`.\n> +When deleting an old ref, `<new-value>` is 40 `0`.\n> +\n>  The exit status of the hook is ignored for any state except for the\n>  \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n>  cause the transaction to be aborted. The hook will not be called with\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 04e85e7002..5b10d3822b 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2452,6 +2452,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>  \t\t\t\tgoto out;\n>  \t\t\t}\n> +\n> +\t\t\tif (!(update->flags & REF_HAVE_OLD))\n> +\t\t\t\toidcpy(&update->old_oid, &lock->old_oid);\n>  \t\t} else {\n>  \t\t\t/*\n>  \t\t\t * Create a new update for the reference this\n> @@ -2474,6 +2477,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t\tgoto out;\n>  \t\t}\n>  \n> +\t\tif (!(update->flags & REF_HAVE_OLD))\n> +\t\t\toidcpy(&update->old_oid, &lock->old_oid);\n> +\n>  \t\t/*\n>  \t\t * If this update is happening indirectly because of a\n>  \t\t * symref update, record the old OID in the parent\n> @@ -2484,6 +2490,8 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t     parent_update = parent_update->parent_update) {\n>  \t\t\tstruct ref_lock *parent_lock = parent_update->backend_data;\n>  \t\t\toidcpy(&parent_lock->old_oid, &lock->old_oid);\n> +\t\t\tif (!(parent_update->flags & REF_HAVE_OLD))\n> +\t\t\t\toidcpy(&parent_update->old_oid, &lock->old_oid);\n>  \t\t}\n>  \t}\n>  \n> diff --git a/refs/packed-backend.c b/refs/packed-backend.c\n> index b912f2505f..08f0feee3d 100644\n> --- a/refs/packed-backend.c\n> +++ b/refs/packed-backend.c\n> @@ -1178,6 +1178,8 @@ static int write_with_updates(struct packed_ref_store *refs,\n>  \t\t\t\t\t\t    oid_to_hex(&update->old_oid));\n>  \t\t\t\t\tgoto error;\n>  \t\t\t\t}\n> +\t\t\t} else {\n> +\t\t\t\toidcpy(&update->old_oid, iter->oid);\n>  \t\t\t}\n>  \n>  \t\t\t/* Now figure out what to use for the new value: */\n> diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\n> index f6e741c6c0..111533682a 100755\n> --- a/t/t1416-ref-transaction-hooks.sh\n> +++ b/t/t1416-ref-transaction-hooks.sh\n> @@ -52,12 +52,12 @@ test_expect_success 'hook gets all queued updates in prepared state' '\n>  \t\tfi\n>  \tEOF\n>  \tcat >expect <<-EOF &&\n> -\t\t$ZERO_OID $POST_OID HEAD\n> -\t\t$ZERO_OID $POST_OID refs/heads/master\n> +\t\t$PRE_OID $POST_OID HEAD\n> +\t\t$PRE_OID $POST_OID refs/heads/master\n>  \tEOF\n>  \tgit update-ref HEAD POST <<-EOF &&\n> -\t\tupdate HEAD $ZERO_OID $POST_OID\n> -\t\tupdate refs/heads/master $ZERO_OID $POST_OID\n> +\t\tupdate HEAD $PRE_OID $POST_OID\n> +\t\tupdate refs/heads/master $PRE_OID $POST_OID\n>  \tEOF\n>  \ttest_cmp expect actual\n>  '\n> @@ -75,8 +75,8 @@ test_expect_success 'hook gets all queued updates in committed state' '\n>  \t\tfi\n>  \tEOF\n>  \tcat >expect <<-EOF &&\n> -\t\t$ZERO_OID $POST_OID HEAD\n> -\t\t$ZERO_OID $POST_OID refs/heads/master\n> +\t\t$PRE_OID $POST_OID HEAD\n> +\t\t$PRE_OID $POST_OID refs/heads/master\n>  \tEOF\n>  \tgit update-ref HEAD POST &&\n>  \ttest_cmp expect actual\n> -- \n> 2.29.2\n> \n\n\n"},{"id":"414201","messageId":"X/4Pin4Zdf58n1jc@nand.local","threadId":"54572","inReplyTo":"X8n1Qf7TJyqIOE/l@ncase","subject":"Re: [PATCH] refs: Always pass old object name to reftx hook","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2021-01-12T21:07:22Z","receivedAt":"2021-01-12T21:41:05Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Dec 04, 2020 at 09:37:21AM +0100, Patrick Steinhardt wrote:\n> Quick ping on this patch. Is there any interest or shall I just drop it?\n\nApologies for completely dropping this from inbox. I am interested in\nit, and the patch looks good to me. I suspect the reason that this never\ngot queued was that nobody reviewed it.\n\nWould you consider resubmitting this patch if you are still interested\nin pushing it forwards?\n\n\nThanks,\nTaylor\n"},{"id":"414251","messageId":"X/7X5ku3wvMr2hw6@ncase","threadId":"54572","inReplyTo":"X/4Pin4Zdf58n1jc@nand.local","subject":"Re: [PATCH] refs: Always pass old object name to reftx hook","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-13T11:22:14Z","receivedAt":"2021-01-13T11:23:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jan 12, 2021 at 04:07:22PM -0500, Taylor Blau wrote:\n> On Fri, Dec 04, 2020 at 09:37:21AM +0100, Patrick Steinhardt wrote:\n> > Quick ping on this patch. Is there any interest or shall I just drop it?\n> \n> Apologies for completely dropping this from inbox. I am interested in\n> it, and the patch looks good to me. I suspect the reason that this never\n> got queued was that nobody reviewed it.\n\nNo worries.\n\n> Would you consider resubmitting this patch if you are still interested\n> in pushing it forwards?\n\nI can, but does it help to resubmit the same patch? Your response bumped\nthe thread up to the top anyway.\n\nPatrick\n"},{"id":"414267","messageId":"X/8NMPiE8SrrcRGU@nand.local","threadId":"54572","inReplyTo":"X/7X5ku3wvMr2hw6@ncase","subject":"Re: [PATCH] refs: Always pass old object name to reftx hook","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2021-01-13T15:09:36Z","receivedAt":"2021-01-13T15:10:22Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 13, 2021 at 12:22:14PM +0100, Patrick Steinhardt wrote:\n> > Would you consider resubmitting this patch if you are still interested\n> > in pushing it forwards?\n>\n> I can, but does it help to resubmit the same patch? Your response bumped\n> the thread up to the top anyway.\n\nLet's see if Junio picks it up in the next cycle. If he doesn't, it may\nhelp to re-submit the thread.\n\n> Patrick\n\nThanks,\nTaylor\n"},{"id":"414284","messageId":"xmqqbldsy4un.fsf@gitster.c.googlers.com","threadId":"54572","inReplyTo":"X/7X5ku3wvMr2hw6@ncase","subject":"Re: [PATCH] refs: Always pass old object name to reftx hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-13T20:11:28Z","receivedAt":"2021-01-13T20:12:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Tue, Jan 12, 2021 at 04:07:22PM -0500, Taylor Blau wrote:\n>> On Fri, Dec 04, 2020 at 09:37:21AM +0100, Patrick Steinhardt wrote:\n>> > Quick ping on this patch. Is there any interest or shall I just drop it?\n>> \n>> Apologies for completely dropping this from inbox. I am interested in\n>> it, and the patch looks good to me. I suspect the reason that this never\n>> got queued was that nobody reviewed it.\n>\n> No worries.\n>\n>> Would you consider resubmitting this patch if you are still interested\n>> in pushing it forwards?\n>\n> I can, but does it help to resubmit the same patch? Your response bumped\n> the thread up to the top anyway.\n\nBumping without resending would often not help people to see the\npatch at all.\n\nFor example, already-read-and-old messages are not even shown to me\nunless I ask my newsreader \"I told you to show only the latest 200\nmessages, and I see this recent 'bump' message, but it responds to a\nway old message so you need to show me the latest 3000 messages to\ncover that era in order to see the patch message(s) it bumps.\"\n\nThanks.\n"},{"id":"414588","messageId":"YAWCxk+T0U98ee6y@ncase","threadId":"54572","inReplyTo":"xmqqbldsy4un.fsf@gitster.c.googlers.com","subject":"Re: [PATCH] refs: Always pass old object name to reftx hook","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-18T12:44:54Z","receivedAt":"2021-01-18T12:46:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Jan 13, 2021 at 12:11:28PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > On Tue, Jan 12, 2021 at 04:07:22PM -0500, Taylor Blau wrote:\n> >> On Fri, Dec 04, 2020 at 09:37:21AM +0100, Patrick Steinhardt wrote:\n> >> > Quick ping on this patch. Is there any interest or shall I just drop it?\n> >> \n> >> Apologies for completely dropping this from inbox. I am interested in\n> >> it, and the patch looks good to me. I suspect the reason that this never\n> >> got queued was that nobody reviewed it.\n> >\n> > No worries.\n> >\n> >> Would you consider resubmitting this patch if you are still interested\n> >> in pushing it forwards?\n> >\n> > I can, but does it help to resubmit the same patch? Your response bumped\n> > the thread up to the top anyway.\n> \n> Bumping without resending would often not help people to see the\n> patch at all.\n> \n> For example, already-read-and-old messages are not even shown to me\n> unless I ask my newsreader \"I told you to show only the latest 200\n> messages, and I see this recent 'bump' message, but it responds to a\n> way old message so you need to show me the latest 3000 messages to\n> cover that era in order to see the patch message(s) it bumps.\"\n\nFair point, I'll resend the patch. Thanks!\n\nPatrick\n"},{"id":"414589","messageId":"ae5c3b2b783f912a02b26142ecd753bf92530d2f.1610974040.git.ps@pks.im","threadId":"54572","inReplyTo":"d255c7a5f95635c2e7ae36b9689c3efd07b4df5d.1604501894.git.ps@pks.im","subject":"[PATCH RESEND] refs: Always pass old object name to reftx hook","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-18T12:49:05Z","receivedAt":"2021-01-18T12:52:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Inputs of the reference-transaction hook currently depends on the\ncommand which is being run. For example if the command `git update-ref\n$REF $A $B` is executed, it will receive \"$B $A $REF\" as input, but if\nthe command `git update-ref $REF $A` is executed without providing the\nold value, then it will receive \"0*40 $A $REF\" as input. This is due to\nthe fact that we directly write queued transaction updates into the\nhook's standard input, which will not contain the old object value in\ncase it wasn't provided.\n\nWhile this behaviour reflects what is happening as part of the\nrepository, it doesn't feel like it is useful. The main intent of the\nreference-transaction hook is to be able to completely audit all\nreference updates, no matter where they come from. As such, it makes a\nlot more sense to always provide actual values instead of what the user\nwanted. Furthermore, it's impossible for the hook to distinguish whether\nthis is intended to be a branch creation or a branch update without\ndoing additional digging with the current format.\n\nFix the issue by storing the old object value into the queued\ntransaction update operation if it wasn't provided by the caller.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/githooks.txt       |  6 ++++++\n refs/files-backend.c             |  8 ++++++++\n refs/packed-backend.c            |  2 ++\n t/t1416-ref-transaction-hooks.sh | 12 ++++++------\n 4 files changed, 22 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 1f3b57d04d..e7baf0e2a0 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -492,6 +492,12 @@ receives on standard input a line of the format:\n \n   <old-value> SP <new-value> SP <ref-name> LF\n \n+where `<old-value>` is the old object name stored in the ref,\n+`<new-value>` is the new object name to be stored in the ref and\n+`<ref-name>` is the full name of the ref.\n+When creating a new ref, `<old-value>` is 40 `0`.\n+When deleting an old ref, `<new-value>` is 40 `0`.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 04e85e7002..5b10d3822b 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2452,6 +2452,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\n \t\t\t}\n+\n+\t\t\tif (!(update->flags & REF_HAVE_OLD))\n+\t\t\t\toidcpy(&update->old_oid, &lock->old_oid);\n \t\t} else {\n \t\t\t/*\n \t\t\t * Create a new update for the reference this\n@@ -2474,6 +2477,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\tgoto out;\n \t\t}\n \n+\t\tif (!(update->flags & REF_HAVE_OLD))\n+\t\t\toidcpy(&update->old_oid, &lock->old_oid);\n+\n \t\t/*\n \t\t * If this update is happening indirectly because of a\n \t\t * symref update, record the old OID in the parent\n@@ -2484,6 +2490,8 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t     parent_update = parent_update->parent_update) {\n \t\t\tstruct ref_lock *parent_lock = parent_update->backend_data;\n \t\t\toidcpy(&parent_lock->old_oid, &lock->old_oid);\n+\t\t\tif (!(parent_update->flags & REF_HAVE_OLD))\n+\t\t\t\toidcpy(&parent_update->old_oid, &lock->old_oid);\n \t\t}\n \t}\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex b912f2505f..08f0feee3d 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1178,6 +1178,8 @@ static int write_with_updates(struct packed_ref_store *refs,\n \t\t\t\t\t\t    oid_to_hex(&update->old_oid));\n \t\t\t\t\tgoto error;\n \t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\toidcpy(&update->old_oid, iter->oid);\n \t\t\t}\n \n \t\t\t/* Now figure out what to use for the new value: */\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex f6e741c6c0..111533682a 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -52,12 +52,12 @@ test_expect_success 'hook gets all queued updates in prepared state' '\n \t\tfi\n \tEOF\n \tcat >expect <<-EOF &&\n-\t\t$ZERO_OID $POST_OID HEAD\n-\t\t$ZERO_OID $POST_OID refs/heads/master\n+\t\t$PRE_OID $POST_OID HEAD\n+\t\t$PRE_OID $POST_OID refs/heads/master\n \tEOF\n \tgit update-ref HEAD POST <<-EOF &&\n-\t\tupdate HEAD $ZERO_OID $POST_OID\n-\t\tupdate refs/heads/master $ZERO_OID $POST_OID\n+\t\tupdate HEAD $PRE_OID $POST_OID\n+\t\tupdate refs/heads/master $PRE_OID $POST_OID\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -75,8 +75,8 @@ test_expect_success 'hook gets all queued updates in committed state' '\n \t\tfi\n \tEOF\n \tcat >expect <<-EOF &&\n-\t\t$ZERO_OID $POST_OID HEAD\n-\t\t$ZERO_OID $POST_OID refs/heads/master\n+\t\t$PRE_OID $POST_OID HEAD\n+\t\t$PRE_OID $POST_OID refs/heads/master\n \tEOF\n \tgit update-ref HEAD POST &&\n \ttest_cmp expect actual\n-- \n2.30.0\n\n"},{"id":"414634","messageId":"xmqq4kjdkgol.fsf@gitster.c.googlers.com","threadId":"54572","inReplyTo":"ae5c3b2b783f912a02b26142ecd753bf92530d2f.1610974040.git.ps@pks.im","subject":"Re: [PATCH RESEND] refs: Always pass old object name to reftx hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-18T22:45:30Z","receivedAt":"2021-01-18T22:47:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Inputs of the reference-transaction hook currently depends on the\n> command which is being run. For example if the command `git update-ref\n> $REF $A $B` is executed, it will receive \"$B $A $REF\" as input, but if\n> the command `git update-ref $REF $A` is executed without providing the\n> old value, then it will receive \"0*40 $A $REF\" as input. This is due to\n> the fact that we directly write queued transaction updates into the\n> hook's standard input, which will not contain the old object value in\n> case it wasn't provided.\n\nIn effect, the user says \"I do not care if this update races with\nsomebody else and it is perfectly OK if it overwrites their update\"\nby not giving $B.\n\n> While this behaviour reflects what is happening as part of the\n> repository, it doesn't feel like it is useful. The main intent of the\n> reference-transaction hook is to be able to completely audit all\n> reference updates, no matter where they come from. As such, it makes a\n> lot more sense to always provide actual values instead of what the user\n> wanted. Furthermore, it's impossible for the hook to distinguish whether\n> this is intended to be a branch creation or a branch update without\n> doing additional digging with the current format.\n\nBut shouldn't the transaction hook script be allowed to learn the\nend-user intention and behave differently?  If we replace the\nmissing old object before calling the script, wouldn't it lose\ninformation?\n\nThe above is not an objection posed as two rhetoric questions.  I am\npurely curious why losing information is OK in this case, or why it\nmay not be so OK but should still be acceptable because it is lessor\nevil than giving 0{40} to the hooks.\n\nEven without this change, the current value the hook can learn by\nlooking the ref up itself if it really wanted to, no?\n"},{"id":"414774","messageId":"YAfNcyZ7mj4J69XT@ncase","threadId":"54572","inReplyTo":"xmqq4kjdkgol.fsf@gitster.c.googlers.com","subject":"Re: [PATCH RESEND] refs: Always pass old object name to reftx hook","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-20T06:28:03Z","receivedAt":"2021-01-20T06:42:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Jan 18, 2021 at 02:45:30PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Inputs of the reference-transaction hook currently depends on the\n> > command which is being run. For example if the command `git update-ref\n> > $REF $A $B` is executed, it will receive \"$B $A $REF\" as input, but if\n> > the command `git update-ref $REF $A` is executed without providing the\n> > old value, then it will receive \"0*40 $A $REF\" as input. This is due to\n> > the fact that we directly write queued transaction updates into the\n> > hook's standard input, which will not contain the old object value in\n> > case it wasn't provided.\n> \n> In effect, the user says \"I do not care if this update races with\n> somebody else and it is perfectly OK if it overwrites their update\"\n> by not giving $B.\n> \n> > While this behaviour reflects what is happening as part of the\n> > repository, it doesn't feel like it is useful. The main intent of the\n> > reference-transaction hook is to be able to completely audit all\n> > reference updates, no matter where they come from. As such, it makes a\n> > lot more sense to always provide actual values instead of what the user\n> > wanted. Furthermore, it's impossible for the hook to distinguish whether\n> > this is intended to be a branch creation or a branch update without\n> > doing additional digging with the current format.\n> \n> But shouldn't the transaction hook script be allowed to learn the\n> end-user intention and behave differently?  If we replace the\n> missing old object before calling the script, wouldn't it lose\n> information?\n> \n> The above is not an objection posed as two rhetoric questions.  I am\n> purely curious why losing information is OK in this case, or why it\n> may not be so OK but should still be acceptable because it is lessor\n> evil than giving 0{40} to the hooks.\n> \n> Even without this change, the current value the hook can learn by\n> looking the ref up itself if it really wanted to, no?\n\nI think the biggest problem is that right now, you cannot discern the\nactual intention of the user because the information provided to the\nhook is ambiguous in the branch creation case: \"$ZERO_OID $NEW_OID $REF\"\ncould mean the user intends to create a new branch where it shouldn't\nhave existed previously. BUT it could also mean that the user just\ndoesn't care what the reference previously pointed to.\n\nThe user could now try to derive the intention by manually looking up\nthe current state of the reference. But that does feel kind of awkward\nto me.\n\nTo me, having clearly defined semantics (\"The script always gets old and\nnew value of the branch regardless of what the user did\") is preferable\nto having ambiguous semantics.\n\nPatrick\n"},{"id":"414778","messageId":"xmqqft2wgk9k.fsf@gitster.c.googlers.com","threadId":"54572","inReplyTo":"YAfNcyZ7mj4J69XT@ncase","subject":"Re: [PATCH RESEND] refs: Always pass old object name to reftx hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-20T07:06:15Z","receivedAt":"2021-01-20T07:07:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> Even without this change, the current value the hook can learn by\n>> looking the ref up itself if it really wanted to, no?\n>\n> I think the biggest problem is that right now, you cannot discern the\n> actual intention of the user because the information provided to the\n> hook is ambiguous in the branch creation case: \"$ZERO_OID $NEW_OID $REF\"\n> could mean the user intends to create a new branch where it shouldn't\n> have existed previously. BUT it could also mean that the user just\n> doesn't care what the reference previously pointed to.\n\nYes, it can mean both, but when you pretend to be that hook,\nwouldn't you check if the ref exists?  If not, the user is trying to\ncreate it, and otherwise, the user does not know or care what the\noriginal value is, no?\n\n> The user could now try to derive the intention by manually looking up\n> the current state of the reference. But that does feel kind of awkward\n> to me.\n\nSo in short, with respect to the OLD slot, there are three kind of\nend-user intention that could be conveyed to the hook:\n\n (1) the user does not care, so 0{40} appears in the OLD slot here,\n (2) the user is creating, so 0{40} apears in the OLD slot here, and\n (3) the user does care, and this is the OID in the OLD slot,\n\nAnd (1) and (2) cannot be separated without looking at the ref (in\nother words, if the hook really cares, it can find it out).\n\nBut if you replace 0{40} with the current OID, then you are making\nit impossible to tell (1) and (3) apart.  The hook cannot tell the\ndistinction even if it is willing to go the extra mile.\n\nSo that sounds like a strict disimprovement to me.\n\nIf you can invent a way to help the hook to tell all three apart, I\nam very much interested.  It would earn you a bonus point if you can\ndo so without breaking backward compatibility (but I doubt that it\nis possible).\n\n> To me, having clearly defined semantics (\"The script always gets old and\n> new value of the branch regardless of what the user did\") is preferable\n> to having ambiguous semantics.\n\nBut \"The script always gets old that was given by the user and the\nnew value to be stored\" is very clearly defined semantics already.\n\nOn the other hand, \"The script gets a non-NULL object name in both\ncases, either when the user says s/he does not care, or when the\nuser insists that the old value must be that, and it is not just\nambiguous but is impossible to tell apart\" is worse than just being\nambiguous.\n"},{"id":"414952","messageId":"YAp0Y3rHty7itayo@ncase","threadId":"54572","inReplyTo":"xmqqft2wgk9k.fsf@gitster.c.googlers.com","subject":"Re: [PATCH RESEND] refs: Always pass old object name to reftx hook","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-22T06:44:51Z","receivedAt":"2021-01-22T06:46:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jan 19, 2021 at 11:06:15PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> >> Even without this change, the current value the hook can learn by\n> >> looking the ref up itself if it really wanted to, no?\n> >\n> > I think the biggest problem is that right now, you cannot discern the\n> > actual intention of the user because the information provided to the\n> > hook is ambiguous in the branch creation case: \"$ZERO_OID $NEW_OID $REF\"\n> > could mean the user intends to create a new branch where it shouldn't\n> > have existed previously. BUT it could also mean that the user just\n> > doesn't care what the reference previously pointed to.\n> \n> Yes, it can mean both, but when you pretend to be that hook,\n> wouldn't you check if the ref exists?  If not, the user is trying to\n> create it, and otherwise, the user does not know or care what the\n> original value is, no?\n\nAs long as you're aware as the script author, yes.\n\nThere is one gotcha though: you can verify the state when the\nreference-transaction hook gets invoked in the \"prepared\" state, as it\nmeans that all references have been locked and thus cannot be changed by\nany other well-behaved process accessing the git repository. In\n\"committed\" or \"aborted\" that's not true anymore, given that the state\nhas changed already, so any locks have been released and it's impossible\nto find out what happened now.\n\n> > The user could now try to derive the intention by manually looking up\n> > the current state of the reference. But that does feel kind of awkward\n> > to me.\n> \n> So in short, with respect to the OLD slot, there are three kind of\n> end-user intention that could be conveyed to the hook:\n> \n>  (1) the user does not care, so 0{40} appears in the OLD slot here,\n>  (2) the user is creating, so 0{40} apears in the OLD slot here, and\n>  (3) the user does care, and this is the OID in the OLD slot,\n> \n> And (1) and (2) cannot be separated without looking at the ref (in\n> other words, if the hook really cares, it can find it out).\n> \n> But if you replace 0{40} with the current OID, then you are making\n> it impossible to tell (1) and (3) apart.  The hook cannot tell the\n> distinction even if it is willing to go the extra mile.\n> \n> So that sounds like a strict disimprovement to me.\n\nTrue.\n\n> If you can invent a way to help the hook to tell all three apart, I\n> am very much interested.  It would earn you a bonus point if you can\n> do so without breaking backward compatibility (but I doubt that it\n> is possible).\n\nI did think about any way to do this, but wasn't yet able to find one.\nAnd doing it in a backwards-compatible way is probably going to be\nimpossible. One idea I had is to use something similar to the\npeeled-format we use in packed refs in case the actual change is\ndifferent from the user-provided change. E.g.\n\n    0{40} <new> <ref>\n    ^<old>\n\nor\n\n    0{40}^<old> <new> <ref>\n\nThat can be considered as backwards-incompatible though.\n\n> > To me, having clearly defined semantics (\"The script always gets old and\n> > new value of the branch regardless of what the user did\") is preferable\n> > to having ambiguous semantics.\n> \n> But \"The script always gets old that was given by the user and the\n> new value to be stored\" is very clearly defined semantics already.\n> \n> On the other hand, \"The script gets a non-NULL object name in both\n> cases, either when the user says s/he does not care, or when the\n> user insists that the old value must be that, and it is not just\n> ambiguous but is impossible to tell apart\" is worse than just being\n> ambiguous.\n\nYup. Whatever we agree on, what is clear is that the documentation needs\nto be more specific here.\n\nPatrick\n"},{"id":"414985","messageId":"xmqqwnw495yq.fsf@gitster.c.googlers.com","threadId":"54572","inReplyTo":"YAp0Y3rHty7itayo@ncase","subject":"Re: [PATCH RESEND] refs: Always pass old object name to reftx hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-22T18:33:49Z","receivedAt":"2021-01-22T19:17:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> Yes, it can mean both, but when you pretend to be that hook,\n>> wouldn't you check if the ref exists?  If not, the user is trying to\n>> create it, and otherwise, the user does not know or care what the\n>> original value is, no?\n>\n> As long as you're aware as the script author, yes.\n\nAs you said downbelow, I agree that clear documentation may be\nnecessary.\n\n> There is one gotcha though: you can verify the state when the\n> reference-transaction hook gets invoked in the \"prepared\" state, as it\n> means that all references have been locked and thus cannot be changed by\n> any other well-behaved process accessing the git repository. In\n> \"committed\" or \"aborted\" that's not true anymore, given that the state\n> has changed already, so any locks have been released and it's impossible\n> to find out what happened now.\n\nTrue, but isn't the situation the same if we replaced the 0{40} old\nside with (one version of) original value of the ref?\n\n> different from the user-provided change. E.g.\n>\n>     0{40} <new> <ref>\n>     ^<old>\n>\n> or\n>\n>     0{40}^<old> <new> <ref>\n>\n> That can be considered as backwards-incompatible though.\n\nYes, it is an incompatible change.  I thought of somehow annotating\nthe old side, e.g. \"<old> <new> <ref>\" vs \"<OLD> <new> <ref>\", to\nshow the distinction between \"this is the original value of ref the\nuser wanted to see when updating <ref>\" and \"the user does not care\nwhat value the <ref> gets updated from, but by the way, here is the\noriginal value of the ref as Git sees it\" [*], but I cannot think of\na way to do so without breaking existing readers.\n\n    Side note: here, I am exploring the approach to replace 0{40}\n    that is given when \"do not care\" into an actual original object\n    name taken from the current state, like your patch did, but\n    trying to find a way to make non-NULL object name distinguishable\n    between the two cases (i.e. user-supplied vs system-filled).\n\nThat raises another question.  How much trust should the hook place\non the value of the <old> given to it?  When a non-NULL <old> value\nis given by the end-user, does the hook get the value as-is, or do\nwe read the current value of the ref and send that as <old>?  Does\nthe transaction get rejected if the two are different and such a\nrecord is not even given to the hook?\n\n> Yup. Whatever we agree on, what is clear is that the documentation needs\n> to be more specific here.\n\nYes, agreed.\n\nThanks.\n"}]}