Skip to content

Commit 7be9f3f

Browse files
committed
Merge branch 'vd/sparse-reset-checkout-fixes' into maint
Fixes to sparse index compatibility work for "reset" and "checkout" commands. source: <[email protected]> * vd/sparse-reset-checkout-fixes: unpack-trees: unpack new trees as sparse directories cache.h: create 'index_name_pos_sparse()' oneway_diff: handle removed sparse directories checkout: fix nested sparse directory diff in sparse index
2 parents e5cb51d + b15207b commit 7be9f3f

File tree

6 files changed

+141
-10
lines changed

6 files changed

+141
-10
lines changed

builtin/checkout.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,7 @@ static void show_local_changes(struct object *head,
626626
repo_init_revisions(the_repository, &rev, NULL);
627627
rev.diffopt.flags = opts->flags;
628628
rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
629+
rev.diffopt.flags.recursive = 1;
629630
diff_setup_done(&rev.diffopt);
630631
add_pending_object(&rev, head, NULL);
631632
run_diff_index(&rev, 0);

cache.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,15 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
830830
*/
831831
int index_name_pos(struct index_state *, const char *name, int namelen);
832832

833+
/*
834+
* Like index_name_pos, returns the position of an entry of the given name in
835+
* the index if one exists, otherwise returns a negative value where the negated
836+
* value minus 1 is the position where the index entry would be inserted. Unlike
837+
* index_name_pos, however, a sparse index is not expanded to find an entry
838+
* inside a sparse directory.
839+
*/
840+
int index_name_pos_sparse(struct index_state *, const char *name, int namelen);
841+
833842
/*
834843
* Determines whether an entry with the given name exists within the
835844
* given index. The return value is 1 if an exact match is found, otherwise

diff-lib.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,11 @@ static void do_oneway_diff(struct unpack_trees_options *o,
466466
* Something removed from the tree?
467467
*/
468468
if (!idx) {
469+
if (S_ISSPARSEDIR(tree->ce_mode)) {
470+
diff_tree_oid(&tree->oid, NULL, tree->name, &revs->diffopt);
471+
return;
472+
}
473+
469474
diff_index_show_file(revs, "-", tree, &tree->oid, 1,
470475
tree->ce_mode, 0);
471476
return;

read-cache.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,11 @@ int index_name_pos(struct index_state *istate, const char *name, int namelen)
620620
return index_name_stage_pos(istate, name, namelen, 0, EXPAND_SPARSE);
621621
}
622622

623+
int index_name_pos_sparse(struct index_state *istate, const char *name, int namelen)
624+
{
625+
return index_name_stage_pos(istate, name, namelen, 0, NO_EXPAND_SPARSE);
626+
}
627+
623628
int index_entry_exists(struct index_state *istate, const char *name, int namelen)
624629
{
625630
return index_name_stage_pos(istate, name, namelen, 0, NO_EXPAND_SPARSE) >= 0;

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,14 @@ test_expect_success 'deep changes during checkout' '
372372
test_all_match git checkout base
373373
'
374374

375+
test_expect_success 'checkout with modified sparse directory' '
376+
init_repos &&
377+
378+
test_all_match git checkout rename-in-to-out -- . &&
379+
test_sparse_match git sparse-checkout reapply &&
380+
test_all_match git checkout base
381+
'
382+
375383
test_expect_success 'add outside sparse cone' '
376384
init_repos &&
377385
@@ -687,6 +695,23 @@ test_expect_success 'reset with wildcard pathspec' '
687695
test_all_match git ls-files -s -- folder1
688696
'
689697

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+
690715
test_expect_success 'update-index modify outside sparse definition' '
691716
init_repos &&
692717

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)