Skip to content

Commit 523506d

Browse files
derrickstoleegitster
authored andcommitted
unpack-trees: unpack sparse directory entries
During unpack_callback(), index entries are compared against tree entries. These are matched according to names and types. One goal is to decide if we should recurse into subtrees or simply operate on one index entry. In the case of a sparse-directory entry, we do not want to recurse into that subtree and instead simply compare the trees. In some cases, we might want to perform a merge operation on the entry, such as during 'git checkout <commit>' which wants to replace a sparse tree entry with the tree for that path at the target commit. We extend the logic within unpack_single_entry() to create a sparse-directory entry in this case, and then that is sent to call_unpack_fn(). There are some subtleties in this process. For instance, we need to update find_cache_entry() to allow finding a sparse-directory entry that exactly matches a given path. Use the new helper method sparse_dir_matches_path() for this. We also need to ignore conflict markers in the case that the entries correspond to directories and we already have a sparse directory entry. Reviewed-by: Elijah Newren <[email protected]> Signed-off-by: Derrick Stolee <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent bd6a3fd commit 523506d

File tree

1 file changed

+99
-8
lines changed

1 file changed

+99
-8
lines changed

unpack-trees.c

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,13 +1052,15 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
10521052
const struct name_entry *n,
10531053
int stage,
10541054
struct index_state *istate,
1055-
int is_transient)
1055+
int is_transient,
1056+
int is_sparse_directory)
10561057
{
10571058
size_t len = traverse_path_len(info, tree_entry_len(n));
1059+
size_t alloc_len = is_sparse_directory ? len + 1 : len;
10581060
struct cache_entry *ce =
10591061
is_transient ?
1060-
make_empty_transient_cache_entry(len, NULL) :
1061-
make_empty_cache_entry(istate, len);
1062+
make_empty_transient_cache_entry(alloc_len, NULL) :
1063+
make_empty_cache_entry(istate, alloc_len);
10621064

10631065
ce->ce_mode = create_ce_mode(n->mode);
10641066
ce->ce_flags = create_ce_flags(stage);
@@ -1067,6 +1069,13 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
10671069
/* len+1 because the cache_entry allocates space for NUL */
10681070
make_traverse_path(ce->name, len + 1, info, n->path, n->pathlen);
10691071

1072+
if (is_sparse_directory) {
1073+
ce->name[len] = '/';
1074+
ce->name[len + 1] = '\0';
1075+
ce->ce_namelen++;
1076+
ce->ce_flags |= CE_SKIP_WORKTREE;
1077+
}
1078+
10701079
return ce;
10711080
}
10721081

@@ -1085,10 +1094,17 @@ static int unpack_single_entry(int n, unsigned long mask,
10851094
struct unpack_trees_options *o = info->data;
10861095
unsigned long conflicts = info->df_conflicts | dirmask;
10871096

1088-
/* Do we have *only* directories? Nothing to do */
10891097
if (mask == dirmask && !src[0])
10901098
return 0;
10911099

1100+
/*
1101+
* When we have a sparse directory entry for src[0],
1102+
* then this isn't necessarily a directory-file conflict.
1103+
*/
1104+
if (mask == dirmask && src[0] &&
1105+
S_ISSPARSEDIR(src[0]->ce_mode))
1106+
conflicts = 0;
1107+
10921108
/*
10931109
* Ok, we've filled in up to any potential index entry in src[0],
10941110
* now do the rest.
@@ -1118,7 +1134,9 @@ static int unpack_single_entry(int n, unsigned long mask,
11181134
* not stored in the index. otherwise construct the
11191135
* cache entry from the index aware logic.
11201136
*/
1121-
src[i + o->merge] = create_ce_entry(info, names + i, stage, &o->result, o->merge);
1137+
src[i + o->merge] = create_ce_entry(info, names + i, stage,
1138+
&o->result, o->merge,
1139+
bit & dirmask);
11221140
}
11231141

11241142
if (o->merge) {
@@ -1222,16 +1240,71 @@ static int find_cache_pos(struct traverse_info *info,
12221240
return -1;
12231241
}
12241242

1243+
/*
1244+
* Given a sparse directory entry 'ce', compare ce->name to
1245+
* info->name + '/' + p->path + '/' if info->name is non-empty.
1246+
* Compare ce->name to p->path + '/' otherwise. Note that
1247+
* ce->name must end in a trailing '/' because it is a sparse
1248+
* directory entry.
1249+
*/
1250+
static int sparse_dir_matches_path(const struct cache_entry *ce,
1251+
struct traverse_info *info,
1252+
const struct name_entry *p)
1253+
{
1254+
assert(S_ISSPARSEDIR(ce->ce_mode));
1255+
assert(ce->name[ce->ce_namelen - 1] == '/');
1256+
1257+
if (info->namelen)
1258+
return ce->ce_namelen == info->namelen + p->pathlen + 2 &&
1259+
ce->name[info->namelen] == '/' &&
1260+
!strncmp(ce->name, info->name, info->namelen) &&
1261+
!strncmp(ce->name + info->namelen + 1, p->path, p->pathlen);
1262+
return ce->ce_namelen == p->pathlen + 1 &&
1263+
!strncmp(ce->name, p->path, p->pathlen);
1264+
}
1265+
12251266
static struct cache_entry *find_cache_entry(struct traverse_info *info,
12261267
const struct name_entry *p)
12271268
{
1269+
struct cache_entry *ce;
12281270
int pos = find_cache_pos(info, p->path, p->pathlen);
12291271
struct unpack_trees_options *o = info->data;
12301272

12311273
if (0 <= pos)
12321274
return o->src_index->cache[pos];
1233-
else
1275+
1276+
/*
1277+
* Check for a sparse-directory entry named "path/".
1278+
* Due to the input p->path not having a trailing
1279+
* slash, the negative 'pos' value overshoots the
1280+
* expected position, hence "-2" instead of "-1".
1281+
*/
1282+
pos = -pos - 2;
1283+
1284+
if (pos < 0 || pos >= o->src_index->cache_nr)
12341285
return NULL;
1286+
1287+
/*
1288+
* Due to lexicographic sorting and sparse directory
1289+
* entries ending with a trailing slash, our path as a
1290+
* sparse directory (e.g "subdir/") and our path as a
1291+
* file (e.g. "subdir") might be separated by other
1292+
* paths (e.g. "subdir-").
1293+
*/
1294+
while (pos >= 0) {
1295+
ce = o->src_index->cache[pos];
1296+
1297+
if (strncmp(ce->name, p->path, p->pathlen))
1298+
return NULL;
1299+
1300+
if (S_ISSPARSEDIR(ce->ce_mode) &&
1301+
sparse_dir_matches_path(ce, info, p))
1302+
return ce;
1303+
1304+
pos--;
1305+
}
1306+
1307+
return NULL;
12351308
}
12361309

12371310
static void debug_path(struct traverse_info *info)
@@ -1266,6 +1339,21 @@ static void debug_unpack_callback(int n,
12661339
debug_name_entry(i, names + i);
12671340
}
12681341

1342+
/*
1343+
* Returns true if and only if the given cache_entry is a
1344+
* sparse-directory entry that matches the given name_entry
1345+
* from the tree walk at the given traverse_info.
1346+
*/
1347+
static int is_sparse_directory_entry(struct cache_entry *ce,
1348+
struct name_entry *name,
1349+
struct traverse_info *info)
1350+
{
1351+
if (!ce || !name || !S_ISSPARSEDIR(ce->ce_mode))
1352+
return 0;
1353+
1354+
return sparse_dir_matches_path(ce, info, name);
1355+
}
1356+
12691357
/*
12701358
* Note that traverse_by_cache_tree() duplicates some logic in this function
12711359
* without actually calling it. If you change the logic here you may need to
@@ -1352,9 +1440,12 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
13521440
}
13531441
}
13541442

1355-
if (traverse_trees_recursive(n, dirmask, mask & ~dirmask,
1356-
names, info) < 0)
1443+
if (!is_sparse_directory_entry(src[0], names, info) &&
1444+
traverse_trees_recursive(n, dirmask, mask & ~dirmask,
1445+
names, info) < 0) {
13571446
return -1;
1447+
}
1448+
13581449
return mask;
13591450
}
13601451

0 commit comments

Comments
 (0)