diff --git a/ls-files.c b/ls-files.c index c6c32d94b0..fadfa01318 100644 --- a/ls-files.c +++ b/ls-files.c @@ -25,20 +25,31 @@ static const char *tag_removed = ""; static const char *tag_other = ""; static const char *tag_killed = ""; +static char *exclude_per_dir = NULL; static int nr_excludes; -static const char **excludes; static int excludes_alloc; +static struct exclude { + const char *pattern; + const char *base; + int baselen; +} **excludes; -static void add_exclude(const char *string) +static void add_exclude(const char *string, const char *base, int baselen) { + struct exclude *x = xmalloc(sizeof (*x)); + + x->pattern = string; + x->base = base; + x->baselen = baselen; if (nr_excludes == excludes_alloc) { excludes_alloc = alloc_nr(excludes_alloc); excludes = realloc(excludes, excludes_alloc*sizeof(char *)); } - excludes[nr_excludes++] = string; + excludes[nr_excludes++] = x; } -static void add_excludes_from_file(const char *fname) +static int add_excludes_from_file_1(const char *fname, + const char *base, int baselen) { int fd, i; long size; @@ -53,7 +64,7 @@ static void add_excludes_from_file(const char *fname) lseek(fd, 0, SEEK_SET); if (size == 0) { close(fd); - return; + return 0; } buf = xmalloc(size); if (read(fd, buf, size) != size) @@ -63,28 +74,89 @@ static void add_excludes_from_file(const char *fname) entry = buf; for (i = 0; i < size; i++) { if (buf[i] == '\n') { - if (entry != buf + i) { + if (entry != buf + i && entry[0] != '#') { buf[i] = 0; - add_exclude(entry); + add_exclude(entry, base, baselen); } entry = buf + i + 1; } } - return; + return 0; + + err: + if (0 <= fd) + close(fd); + return -1; +} + +static void add_excludes_from_file(const char *fname) +{ + if (add_excludes_from_file_1(fname, "", 0) < 0) + die("cannot use %s as an exclude file", fname); +} + +static int push_exclude_per_directory(const char *base, int baselen) +{ + char exclude_file[PATH_MAX]; + int current_nr = nr_excludes; + + if (exclude_per_dir) { + memcpy(exclude_file, base, baselen); + strcpy(exclude_file + baselen, exclude_per_dir); + add_excludes_from_file_1(exclude_file, base, baselen); + } + return current_nr; +} -err: perror(fname); - exit(1); +static void pop_exclude_per_directory(int stk) +{ + while (stk < nr_excludes) + free(excludes[--nr_excludes]); } static int excluded(const char *pathname) { int i; + if (nr_excludes) { - const char *basename = strrchr(pathname, '/'); - basename = (basename) ? basename+1 : pathname; - for (i = 0; i < nr_excludes; i++) - if (fnmatch(excludes[i], basename, 0) == 0) - return 1; + int pathlen = strlen(pathname); + + for (i = 0; i < nr_excludes; i++) { + struct exclude *x = excludes[i]; + const char *exclude = x->pattern; + int to_exclude = 1; + + if (*exclude == '!') { + to_exclude = 0; + exclude++; + } + + if (!strchr(exclude, '/')) { + /* match basename */ + const char *basename = strrchr(pathname, '/'); + basename = (basename) ? basename+1 : pathname; + if (fnmatch(exclude, basename, 0) == 0) + return to_exclude; + } + else { + /* match with FNM_PATHNAME: + * exclude has base (baselen long) inplicitly + * in front of it. + */ + int baselen = x->baselen; + if (*exclude == '/') + exclude++; + + if (pathlen < baselen || + (baselen && pathname[baselen-1] != '/') || + strncmp(pathname, x->base, baselen)) + continue; + + if (fnmatch(exclude, pathname+baselen, + FNM_PATHNAME) == 0) + return to_exclude; + } + } } return 0; } @@ -121,7 +193,7 @@ static void add_name(const char *pathname, int len) * doesn't handle them at all yet. Maybe that will change some * day. * - * Also, we currently ignore all names starting with a dot. + * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ static void read_directory(const char *path, const char *base, int baselen) @@ -129,10 +201,13 @@ static void read_directory(const char *path, const char *base, int baselen) DIR *dir = opendir(path); if (dir) { + int exclude_stk; struct dirent *de; char fullname[MAXPATHLEN + 1]; memcpy(fullname, base, baselen); + exclude_stk = push_exclude_per_directory(base, baselen); + while ((de = readdir(dir)) != NULL) { int len; @@ -141,10 +216,10 @@ static void read_directory(const char *path, const char *base, int baselen) !strcmp(de->d_name + 1, ".") || !strcmp(de->d_name + 1, "git"))) continue; - if (excluded(de->d_name) != show_ignored) - continue; len = strlen(de->d_name); memcpy(fullname + baselen, de->d_name, len+1); + if (excluded(fullname) != show_ignored) + continue; switch (DTYPE(de)) { struct stat st; @@ -170,6 +245,8 @@ static void read_directory(const char *path, const char *base, int baselen) add_name(fullname, baselen + len); } closedir(dir); + + pop_exclude_per_directory(exclude_stk); } } @@ -287,7 +364,9 @@ static void show_files(void) static const char *ls_files_usage = "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* " - "[ --ignored [--exclude=] [--exclude-from=) ]"; + "[ --ignored ] [--exclude=] [--exclude-from=] " + "[ --exclude-per-directory= ]"; +; int main(int argc, char **argv) { @@ -323,13 +402,15 @@ int main(int argc, char **argv) show_stage = 1; show_unmerged = 1; } else if (!strcmp(arg, "-x") && i+1 < argc) { - add_exclude(argv[++i]); + add_exclude(argv[++i], "", 0); } else if (!strncmp(arg, "--exclude=", 10)) { - add_exclude(arg+10); + add_exclude(arg+10, "", 0); } else if (!strcmp(arg, "-X") && i+1 < argc) { add_excludes_from_file(argv[++i]); } else if (!strncmp(arg, "--exclude-from=", 15)) { add_excludes_from_file(arg+15); + } else if (!strncmp(arg, "--exclude-per-directory=", 24)) { + exclude_per_dir = arg + 24; } else usage(ls_files_usage); } diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh new file mode 100755 index 0000000000..4b9380fa09 --- /dev/null +++ b/t/t3001-ls-files-others-exclude.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-ls-files --others --exclude + +This test runs git-ls-files --others and tests --exclude patterns. +' + +. ./test-lib.sh + +rm -fr one three +for dir in . one one/two three +do + mkdir -p $dir && + for i in 1 2 3 4 5 + do + >$dir/a.$i + done +done + +cat >expect <.git/ignore + +echo '*.1 +/*.3' >.gitignore +echo '*.2 +two/*.4' >one/.gitignore + +test_expect_success \ + 'git-ls-files --others --exclude.' \ + 'git-ls-files --others \ + --exclude-per-directory=.gitignore \ + --exclude-from=.git/ignore \ + >output && + diff -u expect output'