{"thread":{"id":"66441","subject":"[BUG] branch copy/rename update refs without running the reference-transaction hook","startedAt":"2026-10-02T04:13:09Z","lastAt":"2026-10-02T19:37:39Z","messageCount":2,"participants":["Никита Поникаров","brian m. carlson"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"553886","messageId":"CAPHjog3wuWOdZS3pHQd20hdZNwA5iXsQMkEfmSnp=NGhLBzuJg@mail.gmail.com","threadId":"66441","inReplyTo":null,"subject":"[BUG] branch copy/rename update refs without running the reference-transaction hook","fromName":"Никита Поникаров","fromEmail":"nick.ponikarov@gmail.com","sentAt":"2026-10-02T04:12:55Z","receivedAt":"2026-10-02T04:13:09Z","isPatch":false,"body":"Hi,\n\nThe report below was investigated and drafted by Claude in the process\nof building a branch-protection tool; I've reviewed it and verified\nthe reproduction before sending. Happy to answer questions or test\npatches.\n\n---\n\ngithooks(5) says the reference-transaction hook \"is invoked by any Git\ncommand that performs reference updates\". Some branch copy and rename\noperations update a ref without invoking it.\n\nObserved on git version 2.55.0.windows.5, with the hook registered both\nin .git/hooks and as a config-defined hook (hook.<name>.event).\n\n1. `git branch -C <src> <dst>` where <dst> exists and is not checked out\n   anywhere: <dst> is overwritten with <src>'s value and the hook is not\n   invoked in any phase. This happens on both the files and the reftable\n   backend. With every documented hook event registered to a logging\n   script, none of them fired, and a GIT_TRACE2_EVENT log showed no child\n   process.\n\n2. `git branch -c <src> <dst>` where <dst> does not exist: <dst> is\n   created and the hook sees no line for it.\n\n3. `git branch -m/-M <src> <dst>`:\n   - files backend: the hook sees the deletion of <src> and a\n     \"0000... 0000... refs/heads/<dst>\" line, but no line carrying\n     <dst>'s new value;\n   - reftable backend: no line names <dst> at all, and renaming a branch\n     away deletes it without a line for it.\n\n4. `git reflog delete --updateref --rewrite <branch>@{0}` rewinds the\n   branch without invoking the hook, on both backends.\n\nReproduction (any POSIX shell):\n\n  git init -q -b main t && cd t\n  git commit -q --allow-empty -m one\n  git branch other\n  git commit -q --allow-empty -m two\n  git switch -q other\n  printf '#!/bin/sh\\necho \"hook $1\" >>\"$PWD/hook.log\"\\ncat\n>/dev/null\\n' >../h.sh\n  git config hook.log.event reference-transaction\n  git config hook.log.command \"sh $(cd ..; pwd)/h.sh\"\n  : >hook.log\n  git branch -C other main     # main now points at \"one\"\n  cat hook.log                 # empty\n\nExpected: the hook runs with a line updating refs/heads/main, as it does\nfor `git branch -f main other`. Tools that enforce policy through this\nhook cannot otherwise see these updates.\n\nNote that git already refuses -C/-M onto a branch checked out in any\nworktree, so the gap is limited to branches that are not checked out.\n"},{"id":"554008","messageId":"asAIAHC_zuHPLljW@fruit.crustytoothpaste.net","threadId":"66441","inReplyTo":"CAPHjog3wuWOdZS3pHQd20hdZNwA5iXsQMkEfmSnp=NGhLBzuJg@mail.gmail.com","subject":"Re: [BUG] branch copy/rename update refs without running the reference-transaction hook","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2026-10-02T19:37:36Z","receivedAt":"2026-10-02T19:37:39Z","isPatch":false,"body":"On 2026-10-02 at 04:12:55, Никита Поникаров wrote:\n> Hi,\n\nHi,\n\n> githooks(5) says the reference-transaction hook \"is invoked by any Git\n> command that performs reference updates\". Some branch copy and rename\n> operations update a ref without invoking it.\n> \n> Observed on git version 2.55.0.windows.5, with the hook registered both\n> in .git/hooks and as a config-defined hook (hook.<name>.event).\n> \n> 1. `git branch -C <src> <dst>` where <dst> exists and is not checked out\n>    anywhere: <dst> is overwritten with <src>'s value and the hook is not\n>    invoked in any phase. This happens on both the files and the reftable\n>    backend. With every documented hook event registered to a logging\n>    script, none of them fired, and a GIT_TRACE2_EVENT log showed no child\n>    process.\n> \n> 2. `git branch -c <src> <dst>` where <dst> does not exist: <dst> is\n>    created and the hook sees no line for it.\n> \n> 3. `git branch -m/-M <src> <dst>`:\n>    - files backend: the hook sees the deletion of <src> and a\n>      \"0000... 0000... refs/heads/<dst>\" line, but no line carrying\n>      <dst>'s new value;\n>    - reftable backend: no line names <dst> at all, and renaming a branch\n>      away deletes it without a line for it.\n> \n> 4. `git reflog delete --updateref --rewrite <branch>@{0}` rewinds the\n>    branch without invoking the hook, on both backends.\n\nI think there was a recent thread about this at\nhttps://lore.kernel.org/git/CACQ=SRHCOCcmVCgHqd+sjMsZ9LCdSHuXdCo0gkwxXwYgF7iwig@mail.gmail.com/\n\nThere were some patches, but they appeared AI generated and were not\npicked up.  Perhaps you or someone else could send in some\nhigher-quality patches not using AI (see\nDocumentation/SubmittingPatches) that could fix the issue.\n\nI will say that using reference transactions to solve this problem would\nbe very desirable from a variety of perspectives, especially since it\nwould probably go a long way to unlocking way better performance for\n`git remote rename` with many remote-tracking branches as well.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"}]}