Skip to content

Commit b15207b

Browse files
vdyegitster
authored andcommitted
unpack-trees: unpack new trees as sparse directories
If 'unpack_single_entry()' is unpacking a new directory tree (that is, one not already present in the index) into a sparse index, unpack the tree as a sparse directory rather than traversing its contents and unpacking each file individually. This helps keep the sparse index as collapsed as possible in cases such as 'git reset --hard' restoring a outside-of-cone directory removed with 'git rm -r --sparse'. Without this patch, 'unpack_single_entry()' will only unpack a directory into the index as a sparse directory (rather than traversing into it and unpacking its files one-by-one) if an entry with the same name already exists in the index. This patch allows sparse directory unpacking without a matching index entry when the following conditions are met: 1. the directory's path is outside the sparse cone, and 2. there are no children of the directory in the index If a directory meets these requirements (as determined by 'is_new_sparse_dir()'), 'unpack_single_entry()' unpacks the sparse directory index entry and propagates the decision back up to 'unpack_callback()' to prevent unnecessary tree traversal into the unpacked directory. Reported-by: Shaoxuan Yuan <[email protected]> Signed-off-by: Victoria Dye <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9553aa0 commit b15207b

File tree

2 files changed

+113
-10
lines changed

2 files changed

+113
-10
lines changed

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,23 @@ test_expect_success 'reset with wildcard pathspec' '
695695
test_all_match git ls-files -s -- folder1
696696
'
697697

698+
test_expect_success 'reset hard with removed sparse dir' '
699+
init_repos &&
700+
701+
run_on_all git rm -r --sparse folder1 &&
702+
test_all_match git status --porcelain=v2 &&
703+
704+
test_all_match git reset --hard &&
705+
test_all_match git status --porcelain=v2 &&
706+
707+
cat >expect <<-\EOF &&
708+
folder1/
709+
EOF
710+
711+
git -C sparse-index ls-files --sparse folder1 >out &&
712+
test_cmp expect out
713+
'
714+
698715
test_expect_success 'update-index modify outside sparse definition' '
699716
init_repos &&
700717

unpack-trees.c

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,67 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
10691069
return ce;
10701070
}
10711071

1072+
/*
1073+
* Determine whether the path specified by 'p' should be unpacked as a new
1074+
* sparse directory in a sparse index. A new sparse directory 'A/':
1075+
* - must be outside the sparse cone.
1076+
* - must not already be in the index (i.e., no index entry with name 'A/'
1077+
* exists).
1078+
* - must not have any child entries in the index (i.e., no index entry
1079+
* 'A/<something>' exists).
1080+
* If 'p' meets the above requirements, return 1; otherwise, return 0.
1081+
*/
1082+
static int entry_is_new_sparse_dir(const struct traverse_info *info,
1083+
const struct name_entry *p)
1084+
{
1085+
int res, pos;
1086+
struct strbuf dirpath = STRBUF_INIT;
1087+
struct unpack_trees_options *o = info->data;
1088+
1089+
if (!S_ISDIR(p->mode))
1090+
return 0;
1091+
1092+
/*
1093+
* If the path is inside the sparse cone, it can't be a sparse directory.
1094+
*/
1095+
strbuf_add(&dirpath, info->traverse_path, info->pathlen);
1096+
strbuf_add(&dirpath, p->path, p->pathlen);
1097+
strbuf_addch(&dirpath, '/');
1098+
if (path_in_cone_mode_sparse_checkout(dirpath.buf, o->src_index)) {
1099+
res = 0;
1100+
goto cleanup;
1101+
}
1102+
1103+
pos = index_name_pos_sparse(o->src_index, dirpath.buf, dirpath.len);
1104+
if (pos >= 0) {
1105+
/* Path is already in the index, not a new sparse dir */
1106+
res = 0;
1107+
goto cleanup;
1108+
}
1109+
1110+
/* Where would this sparse dir be inserted into the index? */
1111+
pos = -pos - 1;
1112+
if (pos >= o->src_index->cache_nr) {
1113+
/*
1114+
* Sparse dir would be inserted at the end of the index, so we
1115+
* know it has no child entries.
1116+
*/
1117+
res = 1;
1118+
goto cleanup;
1119+
}
1120+
1121+
/*
1122+
* If the dir has child entries in the index, the first would be at the
1123+
* position the sparse directory would be inserted. If the entry at this
1124+
* position is inside the dir, not a new sparse dir.
1125+
*/
1126+
res = strncmp(o->src_index->cache[pos]->name, dirpath.buf, dirpath.len);
1127+
1128+
cleanup:
1129+
strbuf_release(&dirpath);
1130+
return res;
1131+
}
1132+
10721133
/*
10731134
* Note that traverse_by_cache_tree() duplicates some logic in this function
10741135
* without actually calling it. If you change the logic here you may need to
@@ -1078,21 +1139,44 @@ static int unpack_single_entry(int n, unsigned long mask,
10781139
unsigned long dirmask,
10791140
struct cache_entry **src,
10801141
const struct name_entry *names,
1081-
const struct traverse_info *info)
1142+
const struct traverse_info *info,
1143+
int *is_new_sparse_dir)
10821144
{
10831145
int i;
10841146
struct unpack_trees_options *o = info->data;
10851147
unsigned long conflicts = info->df_conflicts | dirmask;
1148+
const struct name_entry *p = names;
10861149

1087-
if (mask == dirmask && !src[0])
1088-
return 0;
1150+
*is_new_sparse_dir = 0;
1151+
if (mask == dirmask && !src[0]) {
1152+
/*
1153+
* If we're not in a sparse index, we can't unpack a directory
1154+
* without recursing into it, so we return.
1155+
*/
1156+
if (!o->src_index->sparse_index)
1157+
return 0;
1158+
1159+
/* Find first entry with a real name (we could use "mask" too) */
1160+
while (!p->mode)
1161+
p++;
1162+
1163+
/*
1164+
* If the directory is completely missing from the index but
1165+
* would otherwise be a sparse directory, we should unpack it.
1166+
* If not, we'll return and continue recursively traversing the
1167+
* tree.
1168+
*/
1169+
*is_new_sparse_dir = entry_is_new_sparse_dir(info, p);
1170+
if (!*is_new_sparse_dir)
1171+
return 0;
1172+
}
10891173

10901174
/*
1091-
* When we have a sparse directory entry for src[0],
1092-
* then this isn't necessarily a directory-file conflict.
1175+
* When we are unpacking a sparse directory, then this isn't necessarily
1176+
* a directory-file conflict.
10931177
*/
1094-
if (mask == dirmask && src[0] &&
1095-
S_ISSPARSEDIR(src[0]->ce_mode))
1178+
if (mask == dirmask &&
1179+
(*is_new_sparse_dir || (src[0] && S_ISSPARSEDIR(src[0]->ce_mode))))
10961180
conflicts = 0;
10971181

10981182
/*
@@ -1352,7 +1436,7 @@ static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirma
13521436
{
13531437
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
13541438
struct unpack_trees_options *o = info->data;
1355-
int ret;
1439+
int ret, is_new_sparse_dir;
13561440

13571441
assert(o->merge);
13581442

@@ -1376,7 +1460,7 @@ static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirma
13761460
* "index" tree (i.e., names[0]) and adjust 'names', 'n', 'mask', and
13771461
* 'dirmask' accordingly.
13781462
*/
1379-
ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info);
1463+
ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info, &is_new_sparse_dir);
13801464

13811465
if (src[0])
13821466
discard_cache_entry(src[0]);
@@ -1394,6 +1478,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
13941478
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
13951479
struct unpack_trees_options *o = info->data;
13961480
const struct name_entry *p = names;
1481+
int is_new_sparse_dir;
13971482

13981483
/* Find first entry with a real name (we could use "mask" too) */
13991484
while (!p->mode)
@@ -1440,7 +1525,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
14401525
}
14411526
}
14421527

1443-
if (unpack_single_entry(n, mask, dirmask, src, names, info) < 0)
1528+
if (unpack_single_entry(n, mask, dirmask, src, names, info, &is_new_sparse_dir))
14441529
return -1;
14451530

14461531
if (o->merge && src[0]) {
@@ -1478,6 +1563,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
14781563
}
14791564

14801565
if (!is_sparse_directory_entry(src[0], names, info) &&
1566+
!is_new_sparse_dir &&
14811567
traverse_trees_recursive(n, dirmask, mask & ~dirmask,
14821568
names, info) < 0) {
14831569
return -1;

0 commit comments

Comments
 (0)