Skip to content

Commit

Permalink
git-ls-files: --exclude mechanism updates.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Junio C Hamano committed Jul 26, 2005
1 parent b7e438f commit f87f949
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 21 deletions.
123 changes: 102 additions & 21 deletions ls-files.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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;
}
Expand Down Expand Up @@ -121,18 +193,21 @@ 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)
{
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;

Expand All @@ -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;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
}
Expand Down
55 changes: 55 additions & 0 deletions t/t3001-ls-files-others-exclude.sh
Original file line number Diff line number Diff line change
@@ -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'

0 comments on commit f87f949

Please sign in to comment.