Thread (4 messages) flat view 4 messages, 4 authors, 2021-12-07

Re: Bug report: Strange behavior with `git gc` and `reference-transaction` hook

From: Bryan Turner <hidden>
Date: 2021-11-18 00:52:59

On Wed, Nov 17, 2021 at 4:42 PM Waleed Khan [off-list ref] wrote:
Hi all,

I'm seeing unusual behavior on Git built from source at
cd3e606211bb1cf8bc57f7d76bab98cc17a150bc, but which appears to extend
back to Git v2.29.

This is a repro script:
#!/bin/sh

temp_dir=$(mktemp -d)
echo "git dir is: $temp_dir"
mkdir -p "$temp_dir"
trap "rm -rf $temp_dir" EXIT

cd "$temp_dir" || exit 1
git init -q
date='Thu, 07 Apr 2005 22:13:13 +0200'
GIT_AUTHOR_DATE="$date" GIT_COMMITTER_DATE="$date" git commit -q
--allow-empty -m 'Initial commit'

mkdir -p '.git/hooks'
cat >.git/hooks/reference-transaction <<'EOT'
#!/bin/sh

[[ "$1" != 'committed' ]] && exit 0
echo 'New reference-transaction invocation'

while read old new ref; do
  echo "  old: $old, new: $new, ref: $ref"
done
EOT
chmod +x .git/hooks/reference-transaction

git gc --prune=now -q
git show-ref refs/heads/master
And this is the output it produces:
git dir is: /var/folders/gn/gdp9z_g968b9nx7c9lvgy8y00000gp/T/tmp.b3Jc6qnb
New reference-transaction invocation
  old: 0000000000000000000000000000000000000000, new:
e197d18c017d4038418be8b1cd38f4503e289165, ref: refs/heads/master
New reference-transaction invocation
  old: e197d18c017d4038418be8b1cd38f4503e289165, new:
0000000000000000000000000000000000000000, ref: refs/heads/master
e197d18c017d4038418be8b1cd38f4503e289165 refs/heads/master
These hooks are invoked a few milliseconds one after another.
There are two built-in "ref backends" that Git uses out of the box: A
packed backend, which manages the "packed-refs" file, and a loose
backend, which manages other files under "$GIT_DIR/refs". What you're
seeing is a reference transaction for the packed backend which is
adding the new value for "refs/heads/master" to "packed-refs" (packed
backend reference-transactions rarely, if ever, include an actual old
hash, as far as I can tell), and then a second reference transaction
for the loose backend to delete the loose "refs/heads/master" file,
now that it's packed.
The expected behavior would be that the latest reference transaction
hook refers to the state of the references on disk. That is, either
`master` should point to 0 (be deleted), or it should have said that
`master` pointed to `e197d1`.

But if we actually examine `master`, it's set to `e197d1`, just as you
would expect. The GC should have been a no-op overall.
One of the subtasks of "git gc" is "git pack-refs". If you inspect in
more detail, I suspect you'll find that "refs/heads/master" was loose
before "git gc" ran (as in, there was a file
"$GIT_DIR/refs/heads/master") and "packed-refs" either didn't have a
"refs/heads/master" entry or had a different hash. (Loose refs always
"win" over packed, since ref updates only write loose refs.)

Hope this helps,
Bryan Turner
Best,
Waleed
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help