dir.c: use a single struct exclude_list per source of excludes

Previously each exclude_list could potentially contain patterns from multiple sources. For example dir->exclude_list[EXC_FILE] would typically contain patterns from .git/info/exclude and core.excludesfile, and dir->exclude_list[EXC_DIRS] could contain patterns from multiple per-directory .gitignore files during directory traversal (i.e. when dir->exclude_stack was more than one item deep). We split these composite exclude_lists up into three groups of exclude_lists (EXC_CMDL / EXC_DIRS / EXC_FILE as before), so that each exclude_list now contains patterns from a single source. This will allow us to cleanly track the origin of each pattern simply by adding a src field to struct exclude_list, rather than to struct exclude, which would make memory management of the source string tricky in the EXC_DIRS case where its contents are dynamically generated. Similarly, by moving the filebuf member from struct exclude_stack to struct exclude_list, it allows us to track and subsequently free memory buffers allocated during the parsing of all exclude files, rather than only tracking buffers allocated for files in the EXC_DIRS group. Signed-off-by: Adam Spiers <git@adamspiers.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Adam Spiers committed Jan 6, 2013 at 16:58 UTC c082df24539329c2e75395cf378f0a3fe187c028
6 files changed +86 -39
Documentation/technical/api-directory-listing.txt
+7 -5
@@ -67,11 +67,13 @@ marked. If you to exclude files, make sure you have loaded index first.
67 * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
68 sizeof(dir))`.
69
70 -* Call `add_exclude()` to add single exclude pattern,
71 - `add_excludes_from_file()` to add patterns from a file
72 - (e.g. `.git/info/exclude`), and/or set `dir.exclude_per_dir`. A
73 - short-hand function `setup_standard_excludes()` can be used to set up
74 - the standard set of exclude settings.
70 +* To add single exclude pattern, call `add_exclude_list()` and then
71 + `add_exclude()`.
72 +
73 +* To add patterns from a file (e.g. `.git/info/exclude`), call
74 + `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`. A
75 + short-hand function `setup_standard_excludes()` can be used to set
76 + up the standard set of exclude settings.
77
78 * Set options described in the Data Structure section above.
79
builtin/clean.c
+2 -1
@@ -97,9 +97,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
97 if (!ignored)
98 setup_standard_excludes(&dir);
99
100 + add_exclude_list(&dir, EXC_CMDL);
101 for (i = 0; i < exclude_list.nr; i++)
102 add_exclude(exclude_list.items[i].string, "", 0,
102 - &dir.exclude_list[EXC_CMDL]);
103 + &dir.exclude_list_group[EXC_CMDL].el[0]);
104
105 pathspec = get_pathspec(prefix, argv);
106
builtin/ls-files.c
+5 -3
@@ -420,10 +420,10 @@ static int option_parse_z(const struct option *opt,
420 static int option_parse_exclude(const struct option *opt,
421 const char *arg, int unset)
422 {
423 - struct exclude_list *list = opt->value;
423 + struct exclude_list_group *group = opt->value;
424
425 exc_given = 1;
426 - add_exclude(arg, "", 0, list);
426 + add_exclude(arg, "", 0, &group->el[0]);
427
428 return 0;
429 }
@@ -488,7 +488,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
488 "show unmerged files in the output"),
489 OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
490 "show resolve-undo information"),
491 - { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
491 + { OPTION_CALLBACK, 'x', "exclude",
492 + &dir.exclude_list_group[EXC_CMDL], "pattern",
493 "skip files matching pattern",
494 0, option_parse_exclude },
495 { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
@@ -523,6 +524,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
524 if (read_cache() < 0)
525 die("index file corrupt");
526
527 + add_exclude_list(&dir, EXC_CMDL);
528 argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
529 ls_files_usage, 0);
530 if (show_tag || show_valid_bit) {
dir.c
+45 -19
@@ -411,15 +411,16 @@ void clear_exclude_list(struct exclude_list *el)
411 for (i = 0; i < el->nr; i++)
412 free(el->excludes[i]);
413 free(el->excludes);
414 + free(el->filebuf);
415
416 el->nr = 0;
417 el->excludes = NULL;
418 + el->filebuf = NULL;
419 }
420
421 int add_excludes_from_file_to_list(const char *fname,
422 const char *base,
423 int baselen,
422 - char **buf_p,
424 struct exclude_list *el,
425 int check_index)
426 {
@@ -460,8 +461,7 @@ int add_excludes_from_file_to_list(const char *fname,
461 close(fd);
462 }
463
463 - if (buf_p)
464 - *buf_p = buf;
464 + el->filebuf = buf;
465 entry = buf;
466 for (i = 0; i < size; i++) {
467 if (buf[i] == '\n') {
@@ -475,10 +475,26 @@ int add_excludes_from_file_to_list(const char *fname,
475 return 0;
476 }
477
478 +struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type)
479 +{
480 + struct exclude_list *el;
481 + struct exclude_list_group *group;
482 +
483 + group = &dir->exclude_list_group[group_type];
484 + ALLOC_GROW(group->el, group->nr + 1, group->alloc);
485 + el = &group->el[group->nr++];
486 + memset(el, 0, sizeof(*el));
487 + return el;
488 +}
489 +
490 +/*
491 + * Used to set up core.excludesfile and .git/info/exclude lists.
492 + */
493 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
494 {
480 - if (add_excludes_from_file_to_list(fname, "", 0, NULL,
481 - &dir->exclude_list[EXC_FILE], 0) < 0)
495 + struct exclude_list *el;
496 + el = add_exclude_list(dir, EXC_FILE);
497 + if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
498 die("cannot use %s as an exclude file", fname);
499 }
500
@@ -488,6 +504,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
504 */
505 static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
506 {
507 + struct exclude_list_group *group;
508 struct exclude_list *el;
509 struct exclude_stack *stk = NULL;
510 int current;
@@ -496,17 +513,20 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
513 (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
514 return; /* too long a path -- ignore */
515
499 - /* Pop the directories that are not the prefix of the path being checked. */
500 - el = &dir->exclude_list[EXC_DIRS];
516 + group = &dir->exclude_list_group[EXC_DIRS];
517 +
518 + /* Pop the exclude lists from the EXCL_DIRS exclude_list_group
519 + * which originate from directories not in the prefix of the
520 + * path being checked. */
521 while ((stk = dir->exclude_stack) != NULL) {
522 if (stk->baselen <= baselen &&
523 !strncmp(dir->basebuf, base, stk->baselen))
524 break;
525 + el = &group->el[dir->exclude_stack->exclude_ix];
526 dir->exclude_stack = stk->prev;
506 - while (stk->exclude_ix < el->nr)
507 - free(el->excludes[--el->nr]);
508 - free(stk->filebuf);
527 + clear_exclude_list(el);
528 free(stk);
529 + group->nr--;
530 }
531
532 /* Read from the parent directories and push them down. */
@@ -527,13 +547,14 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
547 }
548 stk->prev = dir->exclude_stack;
549 stk->baselen = cp - base;
530 - stk->exclude_ix = el->nr;
550 memcpy(dir->basebuf + current, base + current,
551 stk->baselen - current);
552 strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
553 + el = add_exclude_list(dir, EXC_DIRS);
554 + stk->exclude_ix = group->nr - 1;
555 add_excludes_from_file_to_list(dir->basebuf,
556 dir->basebuf, stk->baselen,
536 - &stk->filebuf, el, 1);
557 + el, 1);
558 dir->exclude_stack = stk;
559 current = stk->baselen;
560 }
@@ -679,18 +700,23 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,
700 int *dtype_p)
701 {
702 int pathlen = strlen(pathname);
682 - int st;
703 + int i, j;
704 + struct exclude_list_group *group;
705 struct exclude *exclude;
706 const char *basename = strrchr(pathname, '/');
707 basename = (basename) ? basename+1 : pathname;
708
709 prep_exclude(dir, pathname, basename-pathname);
688 - for (st = EXC_CMDL; st <= EXC_FILE; st++) {
689 - exclude = last_exclude_matching_from_list(
690 - pathname, pathlen, basename, dtype_p,
691 - &dir->exclude_list[st]);
692 - if (exclude)
693 - return exclude;
710 +
711 + for (i = EXC_CMDL; i <= EXC_FILE; i++) {
712 + group = &dir->exclude_list_group[i];
713 + for (j = group->nr - 1; j >= 0; j--) {
714 + exclude = last_exclude_matching_from_list(
715 + pathname, pathlen, basename, dtype_p,
716 + &group->el[j]);
717 + if (exclude)
718 + return exclude;
719 + }
720 }
721 return NULL;
722 }
dir.h
+26 -10
@@ -16,14 +16,18 @@ struct dir_entry {
16 #define EXC_FLAG_NEGATIVE 16
17
18 /*
19 - * Each .gitignore file will be parsed into patterns which are then
20 - * appended to the relevant exclude_list (either EXC_DIRS or
21 - * EXC_FILE). exclude_lists are also used to represent the list of
22 - * --exclude values passed via CLI args (EXC_CMDL).
19 + * Each excludes file will be parsed into a fresh exclude_list which
20 + * is appended to the relevant exclude_list_group (either EXC_DIRS or
21 + * EXC_FILE). An exclude_list within the EXC_CMDL exclude_list_group
22 + * can also be used to represent the list of --exclude values passed
23 + * via CLI args.
24 */
25 struct exclude_list {
26 int nr;
27 int alloc;
28 + /* remember pointer to exclude file contents so we can free() */
29 + char *filebuf;
30 +
31 struct exclude {
32 const char *pattern;
33 int patternlen;
@@ -42,9 +46,13 @@ struct exclude_list {
46 */
47 struct exclude_stack {
48 struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
45 - char *filebuf; /* remember pointer to per-directory exclude file contents so we can free() */
49 int baselen;
47 - int exclude_ix;
50 + int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
51 +};
52 +
53 +struct exclude_list_group {
54 + int nr, alloc;
55 + struct exclude_list *el;
56 };
57
58 struct dir_struct {
@@ -62,16 +70,23 @@ struct dir_struct {
70
71 /* Exclude info */
72 const char *exclude_per_dir;
65 - struct exclude_list exclude_list[3];
73 +
74 /*
67 - * We maintain three exclude pattern lists:
75 + * We maintain three groups of exclude pattern lists:
76 + *
77 * EXC_CMDL lists patterns explicitly given on the command line.
78 * EXC_DIRS lists patterns obtained from per-directory ignore files.
70 - * EXC_FILE lists patterns from fallback ignore files.
79 + * EXC_FILE lists patterns from fallback ignore files, e.g.
80 + * - .git/info/exclude
81 + * - core.excludesfile
82 + *
83 + * Each group contains multiple exclude lists, a single list
84 + * per source.
85 */
86 #define EXC_CMDL 0
87 #define EXC_DIRS 1
88 #define EXC_FILE 2
89 + struct exclude_list_group exclude_list_group[3];
90
91 /*
92 * Temporary variables which are used during loading of the
@@ -129,8 +144,9 @@ extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, c
144 extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
145
146
147 +extern struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type);
148 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
133 - char **buf_p, struct exclude_list *el, int check_index);
149 + struct exclude_list *el, int check_index);
150 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
151 extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
152 extern void add_exclude(const char *string, const char *base,
unpack-trees.c
+1 -1
@@ -1019,7 +1019,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
1019 if (!core_apply_sparse_checkout || !o->update)
1020 o->skip_sparse_checkout = 1;
1021 if (!o->skip_sparse_checkout) {
1022 - if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0)
1022 + if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, &el, 0) < 0)
1023 o->skip_sparse_checkout = 1;
1024 else
1025 o->el = &el;