@matheusbsbs / matheus-bagliano / commits / 190a65f9db
Fork 0

sparse-checkout: respect core.ignoreCase in cone mode

When a user uses the sparse-checkout feature in cone mode, they add patterns using "git sparse-checkout set <dir1> <dir2> ..." or by using "--stdin" to provide the directories line-by-line over stdin. This behaviour naturally looks a lot like the way a user would type "git add <dir1> <dir2> ..." If core.ignoreCase is enabled, then "git add" will match the input using a case-insensitive match. Do the same for the sparse-checkout feature. Perform case-insensitive checks while updating the skip-worktree bits during unpack_trees(). This is done by changing the hash algorithm and hashmap comparison methods to optionally use case- insensitive methods. When this is enabled, there is a small performance cost in the hashing algorithm. To tease out the worst possible case, the following was run on a repo with a deep directory structure: git ls-tree -d -r --name-only HEAD | git sparse-checkout set --stdin The 'set' command was timed with core.ignoreCase disabled or enabled. For the repo with a deep history, the numbers were core.ignoreCase=false: 62s core.ignoreCase=true: 74s (+19.3%) For reproducibility, the equivalent test on the Linux kernel repository had these numbers: core.ignoreCase=false: 3.1s core.ignoreCase=true: 3.6s (+16%) Now, this is not an entirely fair comparison, as most users will define their sparse cone using more shallow directories, and the performance improvement from eb42feca97 ("unpack-trees: hash less in cone mode" 2019-11-21) can remove most of the hash cost. For a more realistic test, drop the "-r" from the ls-tree command to store only the first-level directories. In that case, the Linux kernel repository takes 0.2-0.25s in each case, and the deep repository takes one second, plus or minus 0.05s, in each case. Thus, we _can_ demonstrate a cost to this change, but it is unlikely to matter to any reasonable sparse-checkout cone. Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Dec 13, 2019 at 18:09 UTC 190a65f9db8db9d87d54351429f7879fcb4ad608
4 files changed +42 -5
Documentation/git-sparse-checkout.txt
+5
@@ -150,6 +150,11 @@ expecting patterns of these types. Git will warn if the patterns do not match.
150 If the patterns do match the expected format, then Git will use faster hash-
151 based algorithms to compute inclusion in the sparse-checkout.
152
153 +If `core.ignoreCase=true`, then the pattern-matching algorithm will use a
154 +case-insensitive check. This corrects for case mismatched filenames in the
155 +'git sparse-checkout set' command to reflect the expected cone in the working
156 +directory.
157 +
158 SEE ALSO
159 --------
160
builtin/sparse-checkout.c
+8 -2
@@ -313,7 +313,10 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat
313 struct pattern_entry *e = xmalloc(sizeof(*e));
314 e->patternlen = path->len;
315 e->pattern = strbuf_detach(path, NULL);
316 - hashmap_entry_init(&e->ent, memhash(e->pattern, e->patternlen));
316 + hashmap_entry_init(&e->ent,
317 + ignore_case ?
318 + strihash(e->pattern) :
319 + strhash(e->pattern));
320
321 hashmap_add(&pl->recursive_hashmap, &e->ent);
322
@@ -329,7 +332,10 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat
332 e = xmalloc(sizeof(struct pattern_entry));
333 e->patternlen = newlen;
334 e->pattern = xstrndup(oldpattern, newlen);
332 - hashmap_entry_init(&e->ent, memhash(e->pattern, e->patternlen));
335 + hashmap_entry_init(&e->ent,
336 + ignore_case ?
337 + strihash(e->pattern) :
338 + strhash(e->pattern));
339
340 if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL))
341 hashmap_add(&pl->parent_hashmap, &e->ent);
dir.c
+12 -3
@@ -625,6 +625,8 @@ int pl_hashmap_cmp(const void *unused_cmp_data,
625 ? ee1->patternlen
626 : ee2->patternlen;
627
628 + if (ignore_case)
629 + return strncasecmp(ee1->pattern, ee2->pattern, min_len);
630 return strncmp(ee1->pattern, ee2->pattern, min_len);
631 }
632
@@ -665,7 +667,9 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
667 translated->pattern = truncated;
668 translated->patternlen = given->patternlen - 2;
669 hashmap_entry_init(&translated->ent,
668 - memhash(translated->pattern, translated->patternlen));
670 + ignore_case ?
671 + strihash(translated->pattern) :
672 + strhash(translated->pattern));
673
674 if (!hashmap_get_entry(&pl->recursive_hashmap,
675 translated, ent, NULL)) {
@@ -694,7 +698,9 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
698 translated->pattern = xstrdup(given->pattern);
699 translated->patternlen = given->patternlen;
700 hashmap_entry_init(&translated->ent,
697 - memhash(translated->pattern, translated->patternlen));
701 + ignore_case ?
702 + strihash(translated->pattern) :
703 + strhash(translated->pattern));
704
705 hashmap_add(&pl->recursive_hashmap, &translated->ent);
706
@@ -724,7 +730,10 @@ static int hashmap_contains_path(struct hashmap *map,
730 /* Check straight mapping */
731 p.pattern = pattern->buf;
732 p.patternlen = pattern->len;
727 - hashmap_entry_init(&p.ent, memhash(p.pattern, p.patternlen));
733 + hashmap_entry_init(&p.ent,
734 + ignore_case ?
735 + strihash(p.pattern) :
736 + strhash(p.pattern));
737 return !!hashmap_get_entry(map, &p, ent, NULL);
738 }
739
t/t1091-sparse-checkout-builtin.sh
+17
@@ -304,4 +304,21 @@ test_expect_success 'sparse-checkout (init|set|disable) fails with dirty status'
304 git -C dirty sparse-checkout disable
305 '
306
307 +test_expect_success 'cone mode: set with core.ignoreCase=true' '
308 + git -C repo sparse-checkout init --cone &&
309 + git -C repo -c core.ignoreCase=true sparse-checkout set folder1 &&
310 + cat >expect <<-EOF &&
311 + /*
312 + !/*/
313 + /folder1/
314 + EOF
315 + test_cmp expect repo/.git/info/sparse-checkout &&
316 + ls repo >dir &&
317 + cat >expect <<-EOF &&
318 + a
319 + folder1
320 + EOF
321 + test_cmp expect dir
322 +'
323 +
324 test_done