Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create empty initial commit though non-empty index

Tags:

git

With Git < 2.40.0 it was possible to create an initial empty commit (even though files were staged) using following steps:

$ git init test
$ cd test
$ touch readme.txt
$ git add .
$ git commit -m "initial commit" --allow-empty --only
[master (root-commit) 93f098e] initial commit

but with Git 2.40.0 this already commits the staged file:

$ git commit -m "initial commit" --allow-empty --only
[master (root-commit) c0b3214] initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 readme.txt

Is this an intentional change or a regression? If the first, how to create an empty commit though files have been staged?

like image 246
Thomas S. Avatar asked Sep 12 '25 03:09

Thomas S.


1 Answers

[This is only a partial answer at the moment -- I believe it accurately describes the cause, but it does not offer a solution.]


I wrote a script to test this behavior:

#!/bin/bash

set -e

make CFLAGS="-g -O0 -Wall" -j 10 > /dev/null || exit 125
PATH="$PWD:$PATH"

tmpdir=$(mktemp -d repoXXXXXX)
trap "rm -rf $tmpdir" EXIT

git -C "$tmpdir" init
echo this is a test > "$tmpdir/file1"
git -C "$tmpdir" add file1
git -C "$tmpdir" commit -m test --allow-empty --only
tree=$(git -C "$tmpdir" cat-file -p HEAD^{tree})

if [[ -z "$tree" ]]; then
    echo OKAY
    retval=0
else
    echo FAIL
    retval=1
fi

exit "$retval"

We can use this script with git bisect to find when this behavior changed:

git bisect start HEAD v2.39.2
git bisect run ./test-empty-commit.sh

The git bisect process shows the the behavior changed in this commit:

commit 03267e8656c23cf1e2d1df8204d4cee236fb0077
Author: Ævar Arnfjörð Bjarmason <[email protected]>
Date:   Tue Nov 8 19:17:39 2022 +0100

    commit: discard partial cache before (re-)reading it
    
    The read_cache() in prepare_to_commit() would end up clobbering the
    pointer we had for a previously populated "the_index.cache_tree" in
    the very common case of "git commit" stressed by e.g. the tests being
    changed here.
    
    We'd populate "the_index.cache_tree" by calling
    "update_main_cache_tree" in prepare_index(), but would not end up with
    a "fully prepared" index. What constitutes an existing index is
    clearly overly fuzzy, here we'll check "active_nr" (aka
    "the_index.cache_nr"), but our "the_index.cache_tree" might have been
    malloc()'d already.
    
    Thus the code added in 11c8a74a64a (commit: write cache-tree data when
    writing index anyway, 2011-12-06) would end up allocating the
    "cache_tree", and would interact here with code added in
    7168624c353 (Do not generate full commit log message if it is not
    going to be used, 2007-11-28). The result was a very common memory
    leak.
    
    Signed-off-by: Ævar Arnfjörð Bjarmason <[email protected]>
    Signed-off-by: Taylor Blau <[email protected]>

Which introduced the following change in builtin/commit.c:

diff --git a/builtin/commit.c b/builtin/commit.c
index e22bdf23f5..c291199b70 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -987,8 +987,11 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
        struct object_id oid;
        const char *parent = "HEAD";
 
-       if (!active_nr && read_cache() < 0)
-           die(_("Cannot read index"));
+       if (!active_nr) {
+           discard_cache();
+           if (read_cache() < 0)
+               die(_("Cannot read index"));
+       }
 
        if (amend)
            parent = "HEAD^1";

Update: I've reported this to the git mailing list along with a proposed solution: https://lore.kernel.org/git/bt4342bdip3nzlikjsv6wozszmcbsc2av6cyo3z2lra4jhx3ii@ut2sl5h4f5xn/T/#u

like image 120
larsks Avatar answered Sep 13 '25 16:09

larsks