Skip to content

Commit 63d285c

Browse files
committed
per-directory-exclude: lazily read .gitignore files
Operations that walk directories or trees, which potentially need to consult the .gitignore files, used to always try to open the .gitignore file every time they entered a new directory, even when they ended up not needing to call excluded() function to see if a path in the directory is ignored. This was done by push/pop exclude_per_directory() functions that managed the data in a stack. This changes the directory walking API to remove the need to call these two functions. Instead, the directory walk data structure caches the data used by excluded() function the last time, and lazily reuses it as much as possible. Among the data the last check used, the ones from deeper directories that the path we are checking is outside are discarded, data from the common leading directories are reused, and then the directories between the common directory and the directory the path being checked is in are checked for .gitignore file. This is very similar to the way gitattributes are handled. This API change also fixes "ls-files -c -i", which called excluded() without setting up the gitignore data via the old push/pop functions. Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 686a4a0 commit 63d285c

File tree

3 files changed

+72
-69
lines changed

3 files changed

+72
-69
lines changed

dir.c

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ void add_exclude(const char *string, const char *base,
151151
static int add_excludes_from_file_1(const char *fname,
152152
const char *base,
153153
int baselen,
154+
char **buf_p,
154155
struct exclude_list *which)
155156
{
156157
struct stat st;
@@ -171,6 +172,8 @@ static int add_excludes_from_file_1(const char *fname,
171172
goto err;
172173
close(fd);
173174

175+
if (buf_p)
176+
*buf_p = buf;
174177
buf[size++] = '\n';
175178
entry = buf;
176179
for (i = 0; i < size; i++) {
@@ -192,31 +195,63 @@ static int add_excludes_from_file_1(const char *fname,
192195

193196
void add_excludes_from_file(struct dir_struct *dir, const char *fname)
194197
{
195-
if (add_excludes_from_file_1(fname, "", 0,
198+
if (add_excludes_from_file_1(fname, "", 0, NULL,
196199
&dir->exclude_list[EXC_FILE]) < 0)
197200
die("cannot use %s as an exclude file", fname);
198201
}
199202

200-
int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
203+
static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
201204
{
202-
char exclude_file[PATH_MAX];
203-
struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
204-
int current_nr = el->nr;
205-
206-
if (dir->exclude_per_dir) {
207-
memcpy(exclude_file, base, baselen);
208-
strcpy(exclude_file + baselen, dir->exclude_per_dir);
209-
add_excludes_from_file_1(exclude_file, base, baselen, el);
205+
struct exclude_list *el;
206+
struct exclude_stack *stk = NULL;
207+
int current;
208+
209+
if ((!dir->exclude_per_dir) ||
210+
(baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
211+
return; /* too long a path -- ignore */
212+
213+
/* Pop the ones that are not the prefix of the path being checked. */
214+
el = &dir->exclude_list[EXC_DIRS];
215+
while ((stk = dir->exclude_stack) != NULL) {
216+
if (stk->baselen <= baselen &&
217+
!strncmp(dir->basebuf, base, stk->baselen))
218+
break;
219+
dir->exclude_stack = stk->prev;
220+
while (stk->exclude_ix < el->nr)
221+
free(el->excludes[--el->nr]);
222+
free(stk->filebuf);
223+
free(stk);
210224
}
211-
return current_nr;
212-
}
213225

214-
void pop_exclude_per_directory(struct dir_struct *dir, int stk)
215-
{
216-
struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
226+
/* Read from the parent directories and push them down. */
227+
current = stk ? stk->baselen : -1;
228+
while (current < baselen) {
229+
struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
230+
const char *cp;
217231

218-
while (stk < el->nr)
219-
free(el->excludes[--el->nr]);
232+
if (current < 0) {
233+
cp = base;
234+
current = 0;
235+
}
236+
else {
237+
cp = strchr(base + current + 1, '/');
238+
if (!cp)
239+
die("oops in prep_exclude");
240+
cp++;
241+
}
242+
stk->prev = dir->exclude_stack;
243+
stk->baselen = cp - base;
244+
stk->exclude_ix = el->nr;
245+
memcpy(dir->basebuf + current, base + current,
246+
stk->baselen - current);
247+
strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
248+
add_excludes_from_file_1(dir->basebuf,
249+
dir->basebuf, stk->baselen,
250+
&stk->filebuf, el);
251+
dir->exclude_stack = stk;
252+
current = stk->baselen;
253+
}
254+
dir->basebuf[baselen] = '\0';
220255
}
221256

222257
/* Scan the list and let the last match determines the fate.
@@ -283,6 +318,7 @@ int excluded(struct dir_struct *dir, const char *pathname)
283318
const char *basename = strrchr(pathname, '/');
284319
basename = (basename) ? basename+1 : pathname;
285320

321+
prep_exclude(dir, pathname, basename-pathname);
286322
for (st = EXC_CMDL; st <= EXC_FILE; st++) {
287323
switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {
288324
case 0:
@@ -500,13 +536,10 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
500536
int contents = 0;
501537

502538
if (fdir) {
503-
int exclude_stk;
504539
struct dirent *de;
505540
char fullname[PATH_MAX + 1];
506541
memcpy(fullname, base, baselen);
507542

508-
exclude_stk = push_exclude_per_directory(dir, base, baselen);
509-
510543
while ((de = readdir(fdir)) != NULL) {
511544
int len, dtype;
512545
int exclude;
@@ -580,8 +613,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
580613
}
581614
exit_early:
582615
closedir(fdir);
583-
584-
pop_exclude_per_directory(dir, exclude_stk);
585616
}
586617

587618
return contents;
@@ -650,37 +681,9 @@ static void free_simplify(struct path_simplify *simplify)
650681
int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
651682
{
652683
struct path_simplify *simplify = create_simplify(pathspec);
653-
char *pp = NULL;
654-
655-
/*
656-
* Make sure to do the per-directory exclude for all the
657-
* directories leading up to our base.
658-
*/
659-
if (baselen) {
660-
if (dir->exclude_per_dir) {
661-
char *p;
662-
pp = xmalloc(baselen+1);
663-
memcpy(pp, base, baselen+1);
664-
p = pp;
665-
while (1) {
666-
char save = *p;
667-
*p = 0;
668-
push_exclude_per_directory(dir, pp, p-pp);
669-
*p++ = save;
670-
if (!save)
671-
break;
672-
p = strchr(p, '/');
673-
if (p)
674-
p++;
675-
else
676-
p = pp + baselen;
677-
}
678-
}
679-
}
680684

681685
read_directory_recursive(dir, path, base, baselen, 0, simplify);
682686
free_simplify(simplify);
683-
free(pp);
684687
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
685688
qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
686689
return dir->nr;

dir.h

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
11
#ifndef DIR_H
22
#define DIR_H
33

4-
/*
5-
* We maintain three exclude pattern lists:
6-
* EXC_CMDL lists patterns explicitly given on the command line.
7-
* EXC_DIRS lists patterns obtained from per-directory ignore files.
8-
* EXC_FILE lists patterns from fallback ignore files.
9-
*/
10-
#define EXC_CMDL 0
11-
#define EXC_DIRS 1
12-
#define EXC_FILE 2
13-
14-
154
struct dir_entry {
165
unsigned int len;
176
char name[FLEX_ARRAY]; /* more */
@@ -34,6 +23,13 @@ struct exclude_list {
3423
} **excludes;
3524
};
3625

26+
struct exclude_stack {
27+
struct exclude_stack *prev;
28+
char *filebuf;
29+
int baselen;
30+
int exclude_ix;
31+
};
32+
3733
struct dir_struct {
3834
int nr, alloc;
3935
int ignored_nr, ignored_alloc;
@@ -48,6 +44,18 @@ struct dir_struct {
4844
/* Exclude info */
4945
const char *exclude_per_dir;
5046
struct exclude_list exclude_list[3];
47+
/*
48+
* We maintain three exclude pattern lists:
49+
* EXC_CMDL lists patterns explicitly given on the command line.
50+
* EXC_DIRS lists patterns obtained from per-directory ignore files.
51+
* EXC_FILE lists patterns from fallback ignore files.
52+
*/
53+
#define EXC_CMDL 0
54+
#define EXC_DIRS 1
55+
#define EXC_FILE 2
56+
57+
struct exclude_stack *exclude_stack;
58+
char basebuf[PATH_MAX];
5159
};
5260

5361
extern int common_prefix(const char **pathspec);
@@ -58,8 +66,6 @@ extern int common_prefix(const char **pathspec);
5866
extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
5967

6068
extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);
61-
extern int push_exclude_per_directory(struct dir_struct *, const char *, int);
62-
extern void pop_exclude_per_directory(struct dir_struct *, int);
6369

6470
extern int excluded(struct dir_struct *, const char *);
6571
extern void add_excludes_from_file(struct dir_struct *, const char *fname);

unpack-trees.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
7171
int remove;
7272
int baselen = strlen(base);
7373
int src_size = len + 1;
74-
int i_stk = i_stk;
7574
int retval = 0;
7675

77-
if (o->dir)
78-
i_stk = push_exclude_per_directory(o->dir, base, strlen(base));
79-
8076
do {
8177
int i;
8278
const char *first;
@@ -255,8 +251,6 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
255251
} while (1);
256252

257253
leave_directory:
258-
if (o->dir)
259-
pop_exclude_per_directory(o->dir, i_stk);
260254
return retval;
261255
}
262256

0 commit comments

Comments
 (0)