mirror of
https://github.com/git/git
synced 2024-08-28 12:09:19 +00:00
![Johannes Schindelin](/assets/img/avatar_default.png)
In03267e8656
(commit: discard partial cache before (re-)reading it, 2022-11-08), a memory leak was plugged by discarding any partial index before re-reading it. The problem with this memory leak fix is that it was based on an incomplete understanding of the logic introduced in7168624c35
(Do not generate full commit log message if it is not going to be used, 2007-11-28). That logic was introduced to add a shortcut when committing without editing the commit message interactively. A part of that logic was to ensure that the index was read into memory: if (!active_nr && read_cache() < 0) die(...) Translation to English: If the index has not yet been read, read it, and if that fails, error out. That logic was incorrect, though: It used `!active_nr` as an indicator that the index was not yet read. Usually this is not a problem because in the vast majority of instances, the index contains at least one entry. And it was natural to do it this way because at the time that condition was introduced, the `index_state` structure had no explicit flag to indicate that it was initialized: This flag was only introduced in913e0e99b6
(unpack_trees(): protect the handcrafted in-core index from read_cache(), 2008-08-23), but that commit did not adjust the code path where no index file was found and a new, pristine index was initialized. Now, when the index does not contain any entry (which is quite common in Git's test suite because it starts quite a many repositories from scratch), subsequent calls to `do_read_index()` will mistake the index not to be initialized, and read it again unnecessarily. This is a problem because after initializing the empty index e.g. the `cache_tree` in that index could have been initialized before a subsequent call to `do_read_index()` wants to ensure an initialized index. And if that subsequent call mistakes the index not to have been initialized, it would lead to leaked memory. The correct fix for that memory leak is to adjust the condition so that it does not mistake `active_nr == 0` to mean that the index has not yet been read. Using the `initialized` flag instead, we avoid that mistake, and as a bonus we can fix a bug at the same time that was introduced by the memory leak fix: When deleting all tracked files and then asking `git commit -a -m ...` to commit the result, Git would internally update the index, then discard and re-read the index undoing the update, and fail to commit anything. This fixes https://github.com/git-for-windows/git/issues/4462 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
212 lines
4.5 KiB
Bash
Executable file
212 lines
4.5 KiB
Bash
Executable file
#!/bin/sh
|
|
|
|
test_description='git add -u
|
|
|
|
This test creates a working tree state with three files:
|
|
|
|
top (previously committed, modified)
|
|
dir/sub (previously committed, modified)
|
|
dir/other (untracked)
|
|
|
|
and issues a git add -u with path limiting on "dir" to add
|
|
only the updates to dir/sub.
|
|
|
|
Also tested are "git add -u" without limiting, and "git add -u"
|
|
without contents changes, and other conditions'
|
|
|
|
TEST_PASSES_SANITIZE_LEAK=true
|
|
. ./test-lib.sh
|
|
|
|
test_expect_success setup '
|
|
echo initial >check &&
|
|
echo initial >top &&
|
|
echo initial >foo &&
|
|
mkdir dir1 dir2 &&
|
|
echo initial >dir1/sub1 &&
|
|
echo initial >dir1/sub2 &&
|
|
echo initial >dir2/sub3 &&
|
|
git add check dir1 dir2 top foo &&
|
|
test_tick &&
|
|
git commit -m initial &&
|
|
|
|
echo changed >check &&
|
|
echo changed >top &&
|
|
echo changed >dir2/sub3 &&
|
|
rm -f dir1/sub1 &&
|
|
echo other >dir2/other
|
|
'
|
|
|
|
test_expect_success update '
|
|
git add -u dir1 dir2
|
|
'
|
|
|
|
test_expect_success 'update noticed a removal' '
|
|
git ls-files dir1/sub1 >out &&
|
|
test_must_be_empty out
|
|
'
|
|
|
|
test_expect_success 'update touched correct path' '
|
|
git diff-files --name-status dir2/sub3 >out &&
|
|
test_must_be_empty out
|
|
'
|
|
|
|
test_expect_success 'update did not touch other tracked files' '
|
|
echo "M check" >expect &&
|
|
git diff-files --name-status check >actual &&
|
|
test_cmp expect actual &&
|
|
|
|
echo "M top" >expect &&
|
|
git diff-files --name-status top >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'update did not touch untracked files' '
|
|
git ls-files dir2/other >out &&
|
|
test_must_be_empty out
|
|
'
|
|
|
|
test_expect_success 'cache tree has not been corrupted' '
|
|
|
|
git ls-files -s |
|
|
sed -e "s/ 0 / /" >expect &&
|
|
git ls-tree -r $(git write-tree) |
|
|
sed -e "s/ blob / /" >current &&
|
|
test_cmp expect current
|
|
|
|
'
|
|
|
|
test_expect_success 'update from a subdirectory' '
|
|
(
|
|
cd dir1 &&
|
|
echo more >sub2 &&
|
|
git add -u sub2
|
|
)
|
|
'
|
|
|
|
test_expect_success 'change gets noticed' '
|
|
git diff-files --name-status dir1 >out &&
|
|
test_must_be_empty out
|
|
'
|
|
|
|
test_expect_success 'non-qualified update in subdir updates from the root' '
|
|
(
|
|
cd dir1 &&
|
|
echo even more >>sub2 &&
|
|
git --literal-pathspecs add -u &&
|
|
echo even more >>sub2 &&
|
|
git add -u
|
|
) &&
|
|
git diff-files --name-only >actual &&
|
|
test_must_be_empty actual
|
|
'
|
|
|
|
test_expect_success 'replace a file with a symlink' '
|
|
|
|
rm foo &&
|
|
test_ln_s_add top foo
|
|
|
|
'
|
|
|
|
test_expect_success 'add everything changed' '
|
|
|
|
git add -u &&
|
|
git diff-files >out &&
|
|
test_must_be_empty out
|
|
|
|
'
|
|
|
|
test_expect_success 'touch and then add -u' '
|
|
|
|
touch check &&
|
|
git add -u &&
|
|
git diff-files >out &&
|
|
test_must_be_empty out
|
|
|
|
'
|
|
|
|
test_expect_success 'touch and then add explicitly' '
|
|
|
|
touch check &&
|
|
git add check &&
|
|
git diff-files >out &&
|
|
test_must_be_empty out
|
|
|
|
'
|
|
|
|
test_expect_success 'add -n -u should not add but just report' '
|
|
|
|
(
|
|
echo "add '\''check'\''" &&
|
|
echo "remove '\''top'\''"
|
|
) >expect &&
|
|
before=$(git ls-files -s check top) &&
|
|
git count-objects -v >objects_before &&
|
|
echo changed >>check &&
|
|
rm -f top &&
|
|
git add -n -u >actual &&
|
|
after=$(git ls-files -s check top) &&
|
|
git count-objects -v >objects_after &&
|
|
|
|
test "$before" = "$after" &&
|
|
test_cmp objects_before objects_after &&
|
|
test_cmp expect actual
|
|
|
|
'
|
|
|
|
test_expect_success 'add -u resolves unmerged paths' '
|
|
git reset --hard &&
|
|
one=$(echo 1 | git hash-object -w --stdin) &&
|
|
two=$(echo 2 | git hash-object -w --stdin) &&
|
|
three=$(echo 3 | git hash-object -w --stdin) &&
|
|
{
|
|
for path in path1 path2
|
|
do
|
|
echo "100644 $one 1 $path" &&
|
|
echo "100644 $two 2 $path" &&
|
|
echo "100644 $three 3 $path" || return 1
|
|
done &&
|
|
echo "100644 $one 1 path3" &&
|
|
echo "100644 $one 1 path4" &&
|
|
echo "100644 $one 3 path5" &&
|
|
echo "100644 $one 3 path6"
|
|
} |
|
|
git update-index --index-info &&
|
|
echo 3 >path1 &&
|
|
echo 2 >path3 &&
|
|
echo 2 >path5 &&
|
|
|
|
# Fail to explicitly resolve removed paths with "git add"
|
|
test_must_fail git add --no-all path4 &&
|
|
test_must_fail git add --no-all path6 &&
|
|
|
|
# "add -u" should notice removals no matter what stages
|
|
# the index entries are in.
|
|
git add -u &&
|
|
git ls-files -s path1 path2 path3 path4 path5 path6 >actual &&
|
|
{
|
|
echo "100644 $three 0 path1" &&
|
|
echo "100644 $two 0 path3" &&
|
|
echo "100644 $two 0 path5"
|
|
} >expect &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success '"add -u non-existent" should fail' '
|
|
test_must_fail git add -u non-existent &&
|
|
git ls-files >actual &&
|
|
! grep "non-existent" actual
|
|
'
|
|
|
|
test_expect_success '"commit -a" implies "add -u" if index becomes empty' '
|
|
git rm -rf \* &&
|
|
git commit -m clean-slate &&
|
|
test_commit file1 &&
|
|
rm file1.t &&
|
|
test_tick &&
|
|
git commit -a -m remove &&
|
|
git ls-tree HEAD: >out &&
|
|
test_must_be_empty out
|
|
'
|
|
|
|
test_done
|