Merge branch 'dt/cache-tree-repair'
Add a few more places in "commit" and "checkout" that make sure that the cache-tree is fully populated in the index. * dt/cache-tree-repair: cache-tree: do not try to use an invalidated subtree info to build a tree cache-tree: Write updated cache-tree after commit cache-tree: subdirectory tests test-dump-cache-tree: invalid trees are not errors cache-tree: create/update cache-tree on checkout
This commit is contained in:
@ -552,6 +552,12 @@ static int merge_working_tree(const struct checkout_opts *opts,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!active_cache_tree)
|
||||||
|
active_cache_tree = cache_tree();
|
||||||
|
|
||||||
|
if (!cache_tree_fully_valid(active_cache_tree))
|
||||||
|
cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
|
||||||
|
|
||||||
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
|
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
|
||||||
die(_("unable to write new index file"));
|
die(_("unable to write new index file"));
|
||||||
|
|
||||||
|
|||||||
@ -353,6 +353,13 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
|
|||||||
|
|
||||||
discard_cache();
|
discard_cache();
|
||||||
read_cache_from(index_lock.filename);
|
read_cache_from(index_lock.filename);
|
||||||
|
if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
|
||||||
|
if (reopen_lock_file(&index_lock) < 0)
|
||||||
|
die(_("unable to write index file"));
|
||||||
|
if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
|
||||||
|
die(_("unable to update temporary index"));
|
||||||
|
} else
|
||||||
|
warning(_("Failed to update main cache tree"));
|
||||||
|
|
||||||
commit_style = COMMIT_NORMAL;
|
commit_style = COMMIT_NORMAL;
|
||||||
return index_lock.filename;
|
return index_lock.filename;
|
||||||
@ -393,8 +400,12 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
|
|||||||
if (!only && !pathspec.nr) {
|
if (!only && !pathspec.nr) {
|
||||||
hold_locked_index(&index_lock, 1);
|
hold_locked_index(&index_lock, 1);
|
||||||
refresh_cache_or_die(refresh_flags);
|
refresh_cache_or_die(refresh_flags);
|
||||||
if (active_cache_changed) {
|
if (active_cache_changed
|
||||||
|
|| !cache_tree_fully_valid(active_cache_tree)) {
|
||||||
update_main_cache_tree(WRITE_TREE_SILENT);
|
update_main_cache_tree(WRITE_TREE_SILENT);
|
||||||
|
active_cache_changed = 1;
|
||||||
|
}
|
||||||
|
if (active_cache_changed) {
|
||||||
if (write_locked_index(&the_index, &index_lock,
|
if (write_locked_index(&the_index, &index_lock,
|
||||||
COMMIT_LOCK))
|
COMMIT_LOCK))
|
||||||
die(_("unable to write new_index file"));
|
die(_("unable to write new_index file"));
|
||||||
@ -444,6 +455,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
|
|||||||
hold_locked_index(&index_lock, 1);
|
hold_locked_index(&index_lock, 1);
|
||||||
add_remove_files(&partial);
|
add_remove_files(&partial);
|
||||||
refresh_cache(REFRESH_QUIET);
|
refresh_cache(REFRESH_QUIET);
|
||||||
|
update_main_cache_tree(WRITE_TREE_SILENT);
|
||||||
if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
|
if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
|
||||||
die(_("unable to write new_index file"));
|
die(_("unable to write new_index file"));
|
||||||
|
|
||||||
|
|||||||
19
cache-tree.c
19
cache-tree.c
@ -246,9 +246,12 @@ static int update_one(struct cache_tree *it,
|
|||||||
struct strbuf buffer;
|
struct strbuf buffer;
|
||||||
int missing_ok = flags & WRITE_TREE_MISSING_OK;
|
int missing_ok = flags & WRITE_TREE_MISSING_OK;
|
||||||
int dryrun = flags & WRITE_TREE_DRY_RUN;
|
int dryrun = flags & WRITE_TREE_DRY_RUN;
|
||||||
|
int repair = flags & WRITE_TREE_REPAIR;
|
||||||
int to_invalidate = 0;
|
int to_invalidate = 0;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
assert(!(dryrun && repair));
|
||||||
|
|
||||||
*skip_count = 0;
|
*skip_count = 0;
|
||||||
|
|
||||||
if (0 <= it->entry_count && has_sha1_file(it->sha1))
|
if (0 <= it->entry_count && has_sha1_file(it->sha1))
|
||||||
@ -320,6 +323,7 @@ static int update_one(struct cache_tree *it,
|
|||||||
int pathlen, entlen;
|
int pathlen, entlen;
|
||||||
const unsigned char *sha1;
|
const unsigned char *sha1;
|
||||||
unsigned mode;
|
unsigned mode;
|
||||||
|
int expected_missing = 0;
|
||||||
|
|
||||||
path = ce->name;
|
path = ce->name;
|
||||||
pathlen = ce_namelen(ce);
|
pathlen = ce_namelen(ce);
|
||||||
@ -336,8 +340,10 @@ static int update_one(struct cache_tree *it,
|
|||||||
i += sub->count;
|
i += sub->count;
|
||||||
sha1 = sub->cache_tree->sha1;
|
sha1 = sub->cache_tree->sha1;
|
||||||
mode = S_IFDIR;
|
mode = S_IFDIR;
|
||||||
if (sub->cache_tree->entry_count < 0)
|
if (sub->cache_tree->entry_count < 0) {
|
||||||
to_invalidate = 1;
|
to_invalidate = 1;
|
||||||
|
expected_missing = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sha1 = ce->sha1;
|
sha1 = ce->sha1;
|
||||||
@ -347,6 +353,8 @@ static int update_one(struct cache_tree *it,
|
|||||||
}
|
}
|
||||||
if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) {
|
if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) {
|
||||||
strbuf_release(&buffer);
|
strbuf_release(&buffer);
|
||||||
|
if (expected_missing)
|
||||||
|
return -1;
|
||||||
return error("invalid object %06o %s for '%.*s'",
|
return error("invalid object %06o %s for '%.*s'",
|
||||||
mode, sha1_to_hex(sha1), entlen+baselen, path);
|
mode, sha1_to_hex(sha1), entlen+baselen, path);
|
||||||
}
|
}
|
||||||
@ -381,7 +389,14 @@ static int update_one(struct cache_tree *it,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dryrun)
|
if (repair) {
|
||||||
|
unsigned char sha1[20];
|
||||||
|
hash_sha1_file(buffer.buf, buffer.len, tree_type, sha1);
|
||||||
|
if (has_sha1_file(sha1))
|
||||||
|
hashcpy(it->sha1, sha1);
|
||||||
|
else
|
||||||
|
to_invalidate = 1;
|
||||||
|
} else if (dryrun)
|
||||||
hash_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1);
|
hash_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1);
|
||||||
else if (write_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1)) {
|
else if (write_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1)) {
|
||||||
strbuf_release(&buffer);
|
strbuf_release(&buffer);
|
||||||
|
|||||||
@ -39,6 +39,7 @@ int update_main_cache_tree(int);
|
|||||||
#define WRITE_TREE_IGNORE_CACHE_TREE 2
|
#define WRITE_TREE_IGNORE_CACHE_TREE 2
|
||||||
#define WRITE_TREE_DRY_RUN 4
|
#define WRITE_TREE_DRY_RUN 4
|
||||||
#define WRITE_TREE_SILENT 8
|
#define WRITE_TREE_SILENT 8
|
||||||
|
#define WRITE_TREE_REPAIR 16
|
||||||
|
|
||||||
/* error return codes */
|
/* error return codes */
|
||||||
#define WRITE_TREE_UNREADABLE_INDEX (-1)
|
#define WRITE_TREE_UNREADABLE_INDEX (-1)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ cache-tree extension.
|
|||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
cmp_cache_tree () {
|
cmp_cache_tree () {
|
||||||
test-dump-cache-tree >actual &&
|
test-dump-cache-tree | sed -e '/#(ref)/d' >actual &&
|
||||||
sed "s/$_x40/SHA/" <actual >filtered &&
|
sed "s/$_x40/SHA/" <actual >filtered &&
|
||||||
test_cmp "$1" filtered
|
test_cmp "$1" filtered
|
||||||
}
|
}
|
||||||
@ -16,15 +16,40 @@ cmp_cache_tree () {
|
|||||||
# We don't bother with actually checking the SHA1:
|
# We don't bother with actually checking the SHA1:
|
||||||
# test-dump-cache-tree already verifies that all existing data is
|
# test-dump-cache-tree already verifies that all existing data is
|
||||||
# correct.
|
# correct.
|
||||||
test_shallow_cache_tree () {
|
generate_expected_cache_tree_rec () {
|
||||||
printf "SHA (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >expect &&
|
dir="$1${1:+/}" &&
|
||||||
|
parent="$2" &&
|
||||||
|
# ls-files might have foo/bar, foo/bar/baz, and foo/bar/quux
|
||||||
|
# We want to count only foo because it's the only direct child
|
||||||
|
subtrees=$(git ls-files|grep /|cut -d / -f 1|uniq) &&
|
||||||
|
subtree_count=$(echo "$subtrees"|awk '$1 {++c} END {print c}') &&
|
||||||
|
entries=$(git ls-files|wc -l) &&
|
||||||
|
printf "SHA $dir (%d entries, %d subtrees)\n" "$entries" "$subtree_count" &&
|
||||||
|
for subtree in $subtrees
|
||||||
|
do
|
||||||
|
cd "$subtree"
|
||||||
|
generate_expected_cache_tree_rec "$dir$subtree" "$dir" || return 1
|
||||||
|
cd ..
|
||||||
|
done &&
|
||||||
|
dir=$parent
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_expected_cache_tree () {
|
||||||
|
(
|
||||||
|
generate_expected_cache_tree_rec
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_cache_tree () {
|
||||||
|
generate_expected_cache_tree >expect &&
|
||||||
cmp_cache_tree expect
|
cmp_cache_tree expect
|
||||||
}
|
}
|
||||||
|
|
||||||
test_invalid_cache_tree () {
|
test_invalid_cache_tree () {
|
||||||
echo "invalid (0 subtrees)" >expect &&
|
printf "invalid %s ()\n" "" "$@" >expect &&
|
||||||
printf "SHA #(ref) (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >>expect &&
|
test-dump-cache-tree |
|
||||||
cmp_cache_tree expect
|
sed -n -e "s/[0-9]* subtrees//" -e '/#(ref)/d' -e '/^invalid /p' >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
}
|
}
|
||||||
|
|
||||||
test_no_cache_tree () {
|
test_no_cache_tree () {
|
||||||
@ -32,26 +57,59 @@ test_no_cache_tree () {
|
|||||||
cmp_cache_tree expect
|
cmp_cache_tree expect
|
||||||
}
|
}
|
||||||
|
|
||||||
test_expect_failure 'initial commit has cache-tree' '
|
test_expect_success 'initial commit has cache-tree' '
|
||||||
test_commit foo &&
|
test_commit foo &&
|
||||||
test_shallow_cache_tree
|
test_cache_tree
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'read-tree HEAD establishes cache-tree' '
|
test_expect_success 'read-tree HEAD establishes cache-tree' '
|
||||||
git read-tree HEAD &&
|
git read-tree HEAD &&
|
||||||
test_shallow_cache_tree
|
test_cache_tree
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'git-add invalidates cache-tree' '
|
test_expect_success 'git-add invalidates cache-tree' '
|
||||||
test_when_finished "git reset --hard; git read-tree HEAD" &&
|
test_when_finished "git reset --hard; git read-tree HEAD" &&
|
||||||
echo "I changed this file" > foo &&
|
echo "I changed this file" >foo &&
|
||||||
git add foo &&
|
git add foo &&
|
||||||
test_invalid_cache_tree
|
test_invalid_cache_tree
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git-add in subdir invalidates cache-tree' '
|
||||||
|
test_when_finished "git reset --hard; git read-tree HEAD" &&
|
||||||
|
mkdir dirx &&
|
||||||
|
echo "I changed this file" >dirx/foo &&
|
||||||
|
git add dirx/foo &&
|
||||||
|
test_invalid_cache_tree
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >before <<\EOF
|
||||||
|
SHA (3 entries, 2 subtrees)
|
||||||
|
SHA dir1/ (1 entries, 0 subtrees)
|
||||||
|
SHA dir2/ (1 entries, 0 subtrees)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >expect <<\EOF
|
||||||
|
invalid (2 subtrees)
|
||||||
|
invalid dir1/ (0 subtrees)
|
||||||
|
SHA dir2/ (1 entries, 0 subtrees)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'git-add in subdir does not invalidate sibling cache-tree' '
|
||||||
|
git tag no-children &&
|
||||||
|
test_when_finished "git reset --hard no-children; git read-tree HEAD" &&
|
||||||
|
mkdir dir1 dir2 &&
|
||||||
|
test_commit dir1/a &&
|
||||||
|
test_commit dir2/b &&
|
||||||
|
echo "I changed this file" >dir1/a &&
|
||||||
|
cmp_cache_tree before &&
|
||||||
|
echo "I changed this file" >dir1/a &&
|
||||||
|
git add dir1/a &&
|
||||||
|
cmp_cache_tree expect
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'update-index invalidates cache-tree' '
|
test_expect_success 'update-index invalidates cache-tree' '
|
||||||
test_when_finished "git reset --hard; git read-tree HEAD" &&
|
test_when_finished "git reset --hard; git read-tree HEAD" &&
|
||||||
echo "I changed this file" > foo &&
|
echo "I changed this file" >foo &&
|
||||||
git update-index --add foo &&
|
git update-index --add foo &&
|
||||||
test_invalid_cache_tree
|
test_invalid_cache_tree
|
||||||
'
|
'
|
||||||
@ -59,7 +117,7 @@ test_expect_success 'update-index invalidates cache-tree' '
|
|||||||
test_expect_success 'write-tree establishes cache-tree' '
|
test_expect_success 'write-tree establishes cache-tree' '
|
||||||
test-scrap-cache-tree &&
|
test-scrap-cache-tree &&
|
||||||
git write-tree &&
|
git write-tree &&
|
||||||
test_shallow_cache_tree
|
test_cache_tree
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'test-scrap-cache-tree works' '
|
test_expect_success 'test-scrap-cache-tree works' '
|
||||||
@ -70,24 +128,94 @@ test_expect_success 'test-scrap-cache-tree works' '
|
|||||||
|
|
||||||
test_expect_success 'second commit has cache-tree' '
|
test_expect_success 'second commit has cache-tree' '
|
||||||
test_commit bar &&
|
test_commit bar &&
|
||||||
test_shallow_cache_tree
|
test_cache_tree
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'commit --interactive gives cache-tree on partial commit' '
|
||||||
|
cat <<-\EOT >foo.c &&
|
||||||
|
int foo()
|
||||||
|
{
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
int bar()
|
||||||
|
{
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
EOT
|
||||||
|
git add foo.c &&
|
||||||
|
test_invalid_cache_tree &&
|
||||||
|
git commit -m "add a file" &&
|
||||||
|
test_cache_tree &&
|
||||||
|
cat <<-\EOT >foo.c &&
|
||||||
|
int foo()
|
||||||
|
{
|
||||||
|
return 43;
|
||||||
|
}
|
||||||
|
int bar()
|
||||||
|
{
|
||||||
|
return 44;
|
||||||
|
}
|
||||||
|
EOT
|
||||||
|
(echo p; echo 1; echo; echo s; echo n; echo y; echo q) |
|
||||||
|
git commit --interactive -m foo &&
|
||||||
|
test_cache_tree
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'commit in child dir has cache-tree' '
|
||||||
|
mkdir dir &&
|
||||||
|
>dir/child.t &&
|
||||||
|
git add dir/child.t &&
|
||||||
|
git commit -m dir/child.t &&
|
||||||
|
test_cache_tree
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'reset --hard gives cache-tree' '
|
test_expect_success 'reset --hard gives cache-tree' '
|
||||||
test-scrap-cache-tree &&
|
test-scrap-cache-tree &&
|
||||||
git reset --hard &&
|
git reset --hard &&
|
||||||
test_shallow_cache_tree
|
test_cache_tree
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'reset --hard without index gives cache-tree' '
|
test_expect_success 'reset --hard without index gives cache-tree' '
|
||||||
rm -f .git/index &&
|
rm -f .git/index &&
|
||||||
git reset --hard &&
|
git reset --hard &&
|
||||||
test_shallow_cache_tree
|
test_cache_tree
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_failure 'checkout gives cache-tree' '
|
test_expect_success 'checkout gives cache-tree' '
|
||||||
|
git tag current &&
|
||||||
git checkout HEAD^ &&
|
git checkout HEAD^ &&
|
||||||
test_shallow_cache_tree
|
test_cache_tree
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'checkout -b gives cache-tree' '
|
||||||
|
git checkout current &&
|
||||||
|
git checkout -b prev HEAD^ &&
|
||||||
|
test_cache_tree
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'checkout -B gives cache-tree' '
|
||||||
|
git checkout current &&
|
||||||
|
git checkout -B prev HEAD^ &&
|
||||||
|
test_cache_tree
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'partial commit gives cache-tree' '
|
||||||
|
git checkout -b partial no-children &&
|
||||||
|
test_commit one &&
|
||||||
|
test_commit two &&
|
||||||
|
echo "some change" >one.t &&
|
||||||
|
git add one.t &&
|
||||||
|
echo "some other change" >two.t &&
|
||||||
|
git commit two.t -m partial &&
|
||||||
|
test_cache_tree
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'no phantom error when switching trees' '
|
||||||
|
mkdir newdir &&
|
||||||
|
>newdir/one &&
|
||||||
|
git add newdir/one &&
|
||||||
|
git checkout 2>errors &&
|
||||||
|
! test -s errors
|
||||||
'
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|||||||
@ -26,16 +26,16 @@ static int dump_cache_tree(struct cache_tree *it,
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (it->entry_count < 0) {
|
if (it->entry_count < 0) {
|
||||||
|
/* invalid */
|
||||||
dump_one(it, pfx, "");
|
dump_one(it, pfx, "");
|
||||||
dump_one(ref, pfx, "#(ref) ");
|
dump_one(ref, pfx, "#(ref) ");
|
||||||
if (it->subtree_nr != ref->subtree_nr)
|
|
||||||
errs = 1;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dump_one(it, pfx, "");
|
dump_one(it, pfx, "");
|
||||||
if (hashcmp(it->sha1, ref->sha1) ||
|
if (hashcmp(it->sha1, ref->sha1) ||
|
||||||
ref->entry_count != it->entry_count ||
|
ref->entry_count != it->entry_count ||
|
||||||
ref->subtree_nr != it->subtree_nr) {
|
ref->subtree_nr != it->subtree_nr) {
|
||||||
|
/* claims to be valid but is lying */
|
||||||
dump_one(ref, pfx, "#(ref) ");
|
dump_one(ref, pfx, "#(ref) ");
|
||||||
errs = 1;
|
errs = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user