16
# define fnmatch(x, y, z) (!PathMatchSpec(y,x))
16
#define fnmatch(x, y, z) (!PathMatchSpec(y, x))
19
const int fnmatch_flags = 0 & FNM_PATHNAME;
19
const int fnmatch_flags = FNM_PATHNAME;
22
22
/* TODO: build a huge-ass list of files we want to ignore by default (build cache stuff, pyc files, etc) */
70
void add_ignore_pattern(ignores *ig, const char* pattern) {
70
void add_ignore_pattern(ignores *ig, const char *pattern) {
74
/* Strip off the leading ./ so that matches are more likely. */
74
/* Strip off the leading dot so that matches are more likely. */
75
75
if (strncmp(pattern, "./", 2) == 0) {
79
79
/* Kill trailing whitespace */
80
80
for (pattern_len = strlen(pattern); pattern_len > 0; pattern_len--) {
81
if (!isspace(pattern[pattern_len-1])) {
81
if (!isspace(pattern[pattern_len - 1])) {
91
91
/* TODO: de-dupe these patterns */
92
92
if (is_fnmatch(pattern)) {
94
ig->regexes = ag_realloc(ig->regexes, ig->regexes_len * sizeof(char*));
95
ig->regexes[ig->regexes_len - 1] = ag_strndup(pattern, pattern_len);
94
ig->regexes = ag_realloc(ig->regexes, ig->regexes_len * sizeof(char *));
95
if (pattern[0] != '/') {
96
ag_asprintf(&(ig->regexes[ig->regexes_len - 1]), "*/%s", pattern);
98
ig->regexes[ig->regexes_len - 1] = ag_strndup(pattern, pattern_len);
96
100
log_debug("added regex ignore pattern %s", pattern);
98
102
/* a balanced binary tree is best for performance, but I'm lazy */
100
ig->names = ag_realloc(ig->names, ig->names_len * sizeof(char*));
104
ig->names = ag_realloc(ig->names, ig->names_len * sizeof(char *));
101
105
for (i = ig->names_len - 1; i > 0; i--) {
102
if (strcmp(pattern, ig->names[i-1]) > 0) {
106
if (strcmp(pattern, ig->names[i - 1]) > 0) {
105
ig->names[i] = ig->names[i-1];
109
ig->names[i] = ig->names[i - 1];
107
111
ig->names[i] = ag_strndup(pattern, pattern_len);
108
112
log_debug("added literal ignore pattern %s", pattern);
126
130
if (line_len == 0 || line[0] == '\n' || line[0] == '#') {
129
if (line[line_len-1] == '\n') {
130
line[line_len-1] = '\0'; /* kill the \n */
133
if (line[line_len - 1] == '\n') {
134
line[line_len - 1] = '\0'; /* kill the \n */
132
136
add_ignore_pattern(ig, line);
200
204
patterns_len -= line_len + 1;
204
208
free(dir_prop_base);
209
static int ackmate_dir_match(const char* dir_name) {
212
if (opts.ackmate_dir_filter != NULL) {
213
/* we just care about the match, not where the matches are */
214
rc = pcre_exec(opts.ackmate_dir_filter, NULL, dir_name, strlen(dir_name), 0, 0, NULL, 0);
216
log_debug("file %s ignored because name matches ackmate dir filter pattern", dir_name);
213
static int ackmate_dir_match(const char *dir_name) {
214
if (opts.ackmate_dir_filter == NULL) {
217
/* we just care about the match, not where the matches are */
218
return pcre_exec(opts.ackmate_dir_filter, NULL, dir_name, strlen(dir_name), 0, 0, NULL, 0);
224
221
static int filename_ignore_search(const ignores *ig, const char *filename) {
228
224
if (strncmp(filename, "./", 2) == 0) {
232
228
match_pos = binary_search(filename, ig->names, 0, ig->names_len);
238
if (ackmate_dir_match(filename)) {
239
log_debug("file %s ignored because name matches ackmate regex", filename);
243
for (i = 0; i < ig->regexes_len; i++) {
244
if (fnmatch(ig->regexes[i], filename, fnmatch_flags) == 0) {
245
log_debug("file %s ignored because name matches regex pattern %s", filename, ig->regexes[i]);
248
log_debug("pattern %s doesn't match file %s", ig->regexes[i], filename);
250
234
log_debug("file %s not ignored", filename);
257
241
if (filename_ignore_search(ig, filename)) {
260
ag_asprintf(&temp, "%s/%s", path, filename);
261
int rv = filename_ignore_search(ig, temp);
245
ag_asprintf(&temp, "%s/%s", path[0] == '.' ? path + 1 : path, filename);
247
if (filename_ignore_search(ig, temp)) {
255
for (i = 0; i < ig->regexes_len; i++) {
256
regex = ig->regexes[i];
257
/* TODO: behave specially if regex doesn't start with a slash
258
if (regex[0] == '/') {
260
if (fnmatch(regex, temp, fnmatch_flags) == 0) {
261
log_debug("file %s ignored because name matches regex pattern %s", temp, regex);
265
log_debug("pattern %s doesn't match file %s", regex, temp);
268
rv = ackmate_dir_match(temp);
266
274
/* This function is REALLY HOT. It gets called for every file */
267
275
int filename_filter(const char *path, const struct dirent *dir, void *baton) {
268
276
const char *filename = dir->d_name;
277
/* TODO: don't call strlen on filename every time we call filename_filter() */
269
278
size_t filename_len = strlen(filename);
271
scandir_baton_t *scandir_baton = (scandir_baton_t*) baton;
280
scandir_baton_t *scandir_baton = (scandir_baton_t *)baton;
272
281
const ignores *ig = scandir_baton->ig;
273
282
const char *base_path = scandir_baton->base_path;
274
size_t base_path_len = strlen(base_path);
283
const size_t base_path_len = scandir_baton->base_path_len;
275
284
const char *path_start = path;
302
311
/* base_path always ends with "/\0" while path doesn't, so this is safe */
303
312
path_start = path + i + 2;
305
log_debug("path_start is %s", path_start);
307
if (path_ignore_search(ig, path_start, filename)) {
311
if (is_directory(path, dir) && filename[filename_len - 1] != '/') {
312
ag_asprintf(&temp, "%s/", filename);
313
int rv = path_ignore_search(ig, path_start, temp);
320
/* TODO: copy-pasted from above */
321
if (scandir_baton->level == 0) {
322
char *temp2; /* horrible variable name, I know */
323
ag_asprintf(&temp, "/%s", filename);
324
if (path_ignore_search(ig, path_start, temp)) {
328
if (is_directory(path, dir) && temp[filename_len - 1] != '/') {
329
ag_asprintf(&temp2, "%s/", temp);
330
int rv = path_ignore_search(ig, path_start, temp2);
314
log_debug("path_start %s filename %s", path_start, filename);
317
if (path_ignore_search(ig, path_start, filename)) {
321
if (is_directory(path, dir) && filename[filename_len - 1] != '/') {
322
ag_asprintf(&temp, "%s/", filename);
323
int rv = path_ignore_search(ig, path_start, temp);
339
scandir_baton->level++;
340
if (ig->parent != NULL) {
341
scandir_baton->ig = ig->parent;
342
return filename_filter(path, dir, (void *)scandir_baton);