aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <junkio@cox.net>2005-07-24 15:26:09 -0700
committerJunio C Hamano <junkio@cox.net>2005-07-25 17:03:52 -0700
commitf87f94974869b1ddd579d4c6aaff12a5f86ca26e (patch)
tree2423ec84272bfc8d589b42e41f521e7d6877c779
parentb7e438f951f68be704b001343811e9ff89da07f7 (diff)
downloadgit-f87f94974869b1ddd579d4c6aaff12a5f86ca26e.tar.gz
git-f87f94974869b1ddd579d4c6aaff12a5f86ca26e.tar.xz
git-ls-files: --exclude mechanism updates.
Add --exclude-per-directory=<name> option that specifies a file to contain exclude patterns local to that directory and its subdirectories. Update the exclusion logic to be able to say "include files that match this more specific pattern, even though later exclude patterns may match them". Also enhances that a pattern can contain '/' in which case fnmatch is called with FNM_PATHNAME flag to match the entire path. Signed-off-by: Junio C Hamano <junkio@cox.net>
-rw-r--r--ls-files.c123
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh55
2 files changed, 157 insertions, 21 deletions
diff --git a/ls-files.c b/ls-files.c
index c6c32d94b..fadfa0131 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=<pattern>] [--exclude-from=<file>) ]";
+ "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
+ "[ --exclude-per-directory=<filename> ]";
+;
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 000000000..4b9380fa0
--- /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 <<EOF
+a.2
+a.4
+a.5
+one/a.3
+one/a.4
+one/a.5
+one/two/a.3
+one/two/a.5
+three/a.2
+three/a.3
+three/a.4
+three/a.5
+EOF
+
+echo '.gitignore
+output
+expect
+.gitignore
+' >.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'