@@ -25,20 +25,31 @@ static const char *tag_removed = "";
2525static const char * tag_other = "" ;
2626static const char * tag_killed = "" ;
2727
28+ static char * exclude_per_dir = NULL ;
2829static int nr_excludes ;
29- static const char * * excludes ;
3030static int excludes_alloc ;
31+ static struct exclude {
32+ const char * pattern ;
33+ const char * base ;
34+ int baselen ;
35+ } * * excludes ;
3136
32- static void add_exclude (const char * string )
37+ static void add_exclude (const char * string , const char * base , int baselen )
3338{
39+ struct exclude * x = xmalloc (sizeof (* x ));
40+
41+ x -> pattern = string ;
42+ x -> base = base ;
43+ x -> baselen = baselen ;
3444 if (nr_excludes == excludes_alloc ) {
3545 excludes_alloc = alloc_nr (excludes_alloc );
3646 excludes = realloc (excludes , excludes_alloc * sizeof (char * ));
3747 }
38- excludes [nr_excludes ++ ] = string ;
48+ excludes [nr_excludes ++ ] = x ;
3949}
4050
41- static void add_excludes_from_file (const char * fname )
51+ static int add_excludes_from_file_1 (const char * fname ,
52+ const char * base , int baselen )
4253{
4354 int fd , i ;
4455 long size ;
@@ -53,7 +64,7 @@ static void add_excludes_from_file(const char *fname)
5364 lseek (fd , 0 , SEEK_SET );
5465 if (size == 0 ) {
5566 close (fd );
56- return ;
67+ return 0 ;
5768 }
5869 buf = xmalloc (size );
5970 if (read (fd , buf , size ) != size )
@@ -63,28 +74,89 @@ static void add_excludes_from_file(const char *fname)
6374 entry = buf ;
6475 for (i = 0 ; i < size ; i ++ ) {
6576 if (buf [i ] == '\n' ) {
66- if (entry != buf + i ) {
77+ if (entry != buf + i && entry [ 0 ] != '#' ) {
6778 buf [i ] = 0 ;
68- add_exclude (entry );
79+ add_exclude (entry , base , baselen );
6980 }
7081 entry = buf + i + 1 ;
7182 }
7283 }
73- return ;
84+ return 0 ;
85+
86+ err :
87+ if (0 <= fd )
88+ close (fd );
89+ return -1 ;
90+ }
91+
92+ static void add_excludes_from_file (const char * fname )
93+ {
94+ if (add_excludes_from_file_1 (fname , "" , 0 ) < 0 )
95+ die ("cannot use %s as an exclude file" , fname );
96+ }
97+
98+ static int push_exclude_per_directory (const char * base , int baselen )
99+ {
100+ char exclude_file [PATH_MAX ];
101+ int current_nr = nr_excludes ;
102+
103+ if (exclude_per_dir ) {
104+ memcpy (exclude_file , base , baselen );
105+ strcpy (exclude_file + baselen , exclude_per_dir );
106+ add_excludes_from_file_1 (exclude_file , base , baselen );
107+ }
108+ return current_nr ;
109+ }
74110
75- err : perror (fname );
76- exit (1 );
111+ static void pop_exclude_per_directory (int stk )
112+ {
113+ while (stk < nr_excludes )
114+ free (excludes [-- nr_excludes ]);
77115}
78116
79117static int excluded (const char * pathname )
80118{
81119 int i ;
120+
82121 if (nr_excludes ) {
83- const char * basename = strrchr (pathname , '/' );
84- basename = (basename ) ? basename + 1 : pathname ;
85- for (i = 0 ; i < nr_excludes ; i ++ )
86- if (fnmatch (excludes [i ], basename , 0 ) == 0 )
87- return 1 ;
122+ int pathlen = strlen (pathname );
123+
124+ for (i = 0 ; i < nr_excludes ; i ++ ) {
125+ struct exclude * x = excludes [i ];
126+ const char * exclude = x -> pattern ;
127+ int to_exclude = 1 ;
128+
129+ if (* exclude == '!' ) {
130+ to_exclude = 0 ;
131+ exclude ++ ;
132+ }
133+
134+ if (!strchr (exclude , '/' )) {
135+ /* match basename */
136+ const char * basename = strrchr (pathname , '/' );
137+ basename = (basename ) ? basename + 1 : pathname ;
138+ if (fnmatch (exclude , basename , 0 ) == 0 )
139+ return to_exclude ;
140+ }
141+ else {
142+ /* match with FNM_PATHNAME:
143+ * exclude has base (baselen long) inplicitly
144+ * in front of it.
145+ */
146+ int baselen = x -> baselen ;
147+ if (* exclude == '/' )
148+ exclude ++ ;
149+
150+ if (pathlen < baselen ||
151+ (baselen && pathname [baselen - 1 ] != '/' ) ||
152+ strncmp (pathname , x -> base , baselen ))
153+ continue ;
154+
155+ if (fnmatch (exclude , pathname + baselen ,
156+ FNM_PATHNAME ) == 0 )
157+ return to_exclude ;
158+ }
159+ }
88160 }
89161 return 0 ;
90162}
@@ -121,18 +193,21 @@ static void add_name(const char *pathname, int len)
121193 * doesn't handle them at all yet. Maybe that will change some
122194 * day.
123195 *
124- * Also, we currently ignore all names starting with a dot .
196+ * Also, we ignore the name ".git" (even if it is not a directory) .
125197 * That likely will not change.
126198 */
127199static void read_directory (const char * path , const char * base , int baselen )
128200{
129201 DIR * dir = opendir (path );
130202
131203 if (dir ) {
204+ int exclude_stk ;
132205 struct dirent * de ;
133206 char fullname [MAXPATHLEN + 1 ];
134207 memcpy (fullname , base , baselen );
135208
209+ exclude_stk = push_exclude_per_directory (base , baselen );
210+
136211 while ((de = readdir (dir )) != NULL ) {
137212 int len ;
138213
@@ -141,10 +216,10 @@ static void read_directory(const char *path, const char *base, int baselen)
141216 !strcmp (de -> d_name + 1 , "." ) ||
142217 !strcmp (de -> d_name + 1 , "git" )))
143218 continue ;
144- if (excluded (de -> d_name ) != show_ignored )
145- continue ;
146219 len = strlen (de -> d_name );
147220 memcpy (fullname + baselen , de -> d_name , len + 1 );
221+ if (excluded (fullname ) != show_ignored )
222+ continue ;
148223
149224 switch (DTYPE (de )) {
150225 struct stat st ;
@@ -170,6 +245,8 @@ static void read_directory(const char *path, const char *base, int baselen)
170245 add_name (fullname , baselen + len );
171246 }
172247 closedir (dir );
248+
249+ pop_exclude_per_directory (exclude_stk );
173250 }
174251}
175252
@@ -287,7 +364,9 @@ static void show_files(void)
287364
288365static const char * ls_files_usage =
289366 "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* "
290- "[ --ignored [--exclude=<pattern>] [--exclude-from=<file>) ]" ;
367+ "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
368+ "[ --exclude-per-directory=<filename> ]" ;
369+ ;
291370
292371int main (int argc , char * * argv )
293372{
@@ -323,13 +402,15 @@ int main(int argc, char **argv)
323402 show_stage = 1 ;
324403 show_unmerged = 1 ;
325404 } else if (!strcmp (arg , "-x" ) && i + 1 < argc ) {
326- add_exclude (argv [++ i ]);
405+ add_exclude (argv [++ i ], "" , 0 );
327406 } else if (!strncmp (arg , "--exclude=" , 10 )) {
328- add_exclude (arg + 10 );
407+ add_exclude (arg + 10 , "" , 0 );
329408 } else if (!strcmp (arg , "-X" ) && i + 1 < argc ) {
330409 add_excludes_from_file (argv [++ i ]);
331410 } else if (!strncmp (arg , "--exclude-from=" , 15 )) {
332411 add_excludes_from_file (arg + 15 );
412+ } else if (!strncmp (arg , "--exclude-per-directory=" , 24 )) {
413+ exclude_per_dir = arg + 24 ;
333414 } else
334415 usage (ls_files_usage );
335416 }
0 commit comments