Browse Source

gitignore(5): Allow "foo/" in ignore list to match directory "foo"

A pattern "foo/" in the exclude list did not match directory
"foo", but a pattern "foo" did.  This attempts to extend the
exclude mechanism so that it would while not matching a regular
file or a symbolic link "foo".  In order to differentiate a
directory and non directory, this passes down the type of path
being checked to excluded() function.

A downside is that the recursive directory walk may need to run
lstat(2) more often on systems whose "struct dirent" do not give
the type of the entry; earlier it did not have to do so for an
excluded path, but we now need to figure out if a path is a
directory before deciding to exclude it.  This is especially bad
because an idea similar to the earlier CE_UPTODATE optimization
to reduce number of lstat(2) calls would by definition not apply
to the codepaths involved, as (1) directories will not be
registered in the index, and (2) excluded paths will not be in
the index anyway.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
Junio C Hamano 17 years ago
parent
commit
d6b8fc303b
  1. 7
      Documentation/gitignore.txt
  2. 6
      builtin-ls-files.c
  3. 12
      cache.h
  4. 42
      dir.c
  5. 3
      dir.h
  6. 41
      t/t3001-ls-files-others-exclude.sh
  7. 2
      unpack-trees.c

7
Documentation/gitignore.txt

@ -57,6 +57,13 @@ Patterns have the following format: @@ -57,6 +57,13 @@ Patterns have the following format:
included again. If a negated pattern matches, this will
override lower precedence patterns sources.

- If the pattern ends with a slash, it is removed for the
purpose of the following description, but it would only find
a match with a directory. In other words, `foo/` will match a
directory `foo` and paths underneath it, but will not match a
regular file or a symbolic link `foo` (this is consistent
with the way how pathspec works in general in git).

- If the pattern does not contain a slash '/', git treats it as
a shell glob pattern and checks for a match against the
pathname without leading directories.

6
builtin-ls-files.c

@ -238,7 +238,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) @@ -238,7 +238,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
if (show_cached | show_stage) {
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (excluded(dir, ce->name) != dir->show_ignored)
if (excluded(dir, ce->name, ce_to_dtype(ce)) !=
dir->show_ignored)
continue;
if (show_unmerged && !ce_stage(ce))
continue;
@ -252,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) @@ -252,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
struct cache_entry *ce = active_cache[i];
struct stat st;
int err;
if (excluded(dir, ce->name) != dir->show_ignored)
if (excluded(dir, ce->name, ce_to_dtype(ce)) !=
dir->show_ignored)
continue;
err = lstat(ce->name, &st);
if (show_deleted && err)

12
cache.h

@ -141,6 +141,18 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in @@ -141,6 +141,18 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in
}
return create_ce_mode(mode);
}
static inline int ce_to_dtype(const struct cache_entry *ce)
{
unsigned ce_mode = ntohl(ce->ce_mode);
if (S_ISREG(ce_mode))
return DT_REG;
else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))
return DT_DIR;
else if (S_ISLNK(ce_mode))
return DT_LNK;
else
return DT_UNKNOWN;
}
#define canon_mode(mode) \
(S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)

42
dir.c

@ -126,18 +126,34 @@ static int no_wildcard(const char *string) @@ -126,18 +126,34 @@ static int no_wildcard(const char *string)
void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which)
{
struct exclude *x = xmalloc(sizeof (*x));
struct exclude *x;
size_t len;
int to_exclude = 1;
int flags = 0;

x->to_exclude = 1;
if (*string == '!') {
x->to_exclude = 0;
to_exclude = 0;
string++;
}
x->pattern = string;
len = strlen(string);
if (len && string[len - 1] == '/') {
char *s;
x = xmalloc(sizeof(*x) + len);
s = (char*)(x+1);
memcpy(s, string, len - 1);
s[len - 1] = '\0';
string = s;
x->pattern = s;
flags = EXC_FLAG_MUSTBEDIR;
} else {
x = xmalloc(sizeof(*x));
x->pattern = string;
}
x->to_exclude = to_exclude;
x->patternlen = strlen(string);
x->base = base;
x->baselen = baselen;
x->flags = 0;
x->flags = flags;
if (!strchr(string, '/'))
x->flags |= EXC_FLAG_NODIR;
if (no_wildcard(string))
@ -261,7 +277,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) @@ -261,7 +277,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
* Return 1 for exclude, 0 for include and -1 for undecided.
*/
static int excluded_1(const char *pathname,
int pathlen, const char *basename,
int pathlen, const char *basename, int dtype,
struct exclude_list *el)
{
int i;
@ -272,6 +288,10 @@ static int excluded_1(const char *pathname, @@ -272,6 +288,10 @@ static int excluded_1(const char *pathname,
const char *exclude = x->pattern;
int to_exclude = x->to_exclude;

if ((x->flags & EXC_FLAG_MUSTBEDIR) &&
(dtype != DT_DIR))
continue;

if (x->flags & EXC_FLAG_NODIR) {
/* match basename */
if (x->flags & EXC_FLAG_NOWILDCARD) {
@ -314,7 +334,7 @@ static int excluded_1(const char *pathname, @@ -314,7 +334,7 @@ static int excluded_1(const char *pathname,
return -1; /* undecided */
}

int excluded(struct dir_struct *dir, const char *pathname)
int excluded(struct dir_struct *dir, const char *pathname, int dtype)
{
int pathlen = strlen(pathname);
int st;
@ -323,7 +343,8 @@ int excluded(struct dir_struct *dir, const char *pathname) @@ -323,7 +343,8 @@ int excluded(struct dir_struct *dir, const char *pathname)

prep_exclude(dir, pathname, basename-pathname);
for (st = EXC_CMDL; st <= EXC_FILE; st++) {
switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {
switch (excluded_1(pathname, pathlen, basename,
dtype, &dir->exclude_list[st])) {
case 0:
return 0;
case 1:
@ -560,7 +581,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co @@ -560,7 +581,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
if (simplify_away(fullname, baselen + len, simplify))
continue;

exclude = excluded(dir, fullname);
dtype = get_dtype(de, fullname);
exclude = excluded(dir, fullname, dtype);
if (exclude && dir->collect_ignored
&& in_pathspec(fullname, baselen + len, simplify))
dir_add_ignored(dir, fullname, baselen + len);
@ -572,8 +594,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co @@ -572,8 +594,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
if (exclude && !dir->show_ignored)
continue;

dtype = get_dtype(de, fullname);

/*
* Do we want to see just the ignored files?
* We still need to recurse into directories,

3
dir.h

@ -9,6 +9,7 @@ struct dir_entry { @@ -9,6 +9,7 @@ struct dir_entry {
#define EXC_FLAG_NODIR 1
#define EXC_FLAG_NOWILDCARD 2
#define EXC_FLAG_ENDSWITH 4
#define EXC_FLAG_MUSTBEDIR 8

struct exclude_list {
int nr;
@ -67,7 +68,7 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen, @@ -67,7 +68,7 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,

extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);

extern int excluded(struct dir_struct *, const char *);
extern int excluded(struct dir_struct *, const char *, int);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which);

41
t/t3001-ls-files-others-exclude.sh

@ -99,4 +99,45 @@ EOF @@ -99,4 +99,45 @@ EOF
test_expect_success 'git-status honours core.excludesfile' \
'diff -u expect output'

test_expect_success 'trailing slash in exclude allows directory match(1)' '

git ls-files --others --exclude=one/ >output &&
if grep "^one/" output
then
echo Ooops
false
else
: happy
fi

'

test_expect_success 'trailing slash in exclude allows directory match (2)' '

git ls-files --others --exclude=one/two/ >output &&
if grep "^one/two/" output
then
echo Ooops
false
else
: happy
fi

'

test_expect_success 'trailing slash in exclude forces directory match (1)' '

>two
git ls-files --others --exclude=two/ >output &&
grep "^two" output

'

test_expect_success 'trailing slash in exclude forces directory match (2)' '

git ls-files --others --exclude=one/a.1/ >output &&
grep "^one/a.1" output

'

test_done

2
unpack-trees.c

@ -523,7 +523,7 @@ static void verify_absent(struct cache_entry *ce, const char *action, @@ -523,7 +523,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
if (!lstat(ce->name, &st)) {
int cnt;

if (o->dir && excluded(o->dir, ce->name))
if (o->dir && excluded(o->dir, ce->name, ce_to_dtype(ce)))
/*
* ce->name is explicitly excluded, so it is Ok to
* overwrite it.

Loading…
Cancel
Save