[PATCH] Improve the compilation-time settings interface
[git.git] / ls-files.c
1 /*
2  * This merges the file listing in the directory cache index
3  * with the actual working directory list, and shows different
4  * combinations of the two.
5  *
6  * Copyright (C) Linus Torvalds, 2005
7  */
8 #include <dirent.h>
9 #include <fnmatch.h>
10
11 #include "cache.h"
12
13 static int show_deleted = 0;
14 static int show_cached = 0;
15 static int show_others = 0;
16 static int show_ignored = 0;
17 static int show_stage = 0;
18 static int show_unmerged = 0;
19 static int show_killed = 0;
20 static int line_terminator = '\n';
21
22 static const char *tag_cached = "";
23 static const char *tag_unmerged = "";
24 static const char *tag_removed = "";
25 static const char *tag_other = "";
26 static const char *tag_killed = "";
27
28 static char *exclude_per_dir = NULL;
29
30 /* We maintain three exclude pattern lists:
31  * EXC_CMDL lists patterns explicitly given on the command line.
32  * EXC_DIRS lists patterns obtained from per-directory ignore files.
33  * EXC_FILE lists patterns from fallback ignore files.
34  */
35 #define EXC_CMDL 0
36 #define EXC_DIRS 1
37 #define EXC_FILE 2
38 static struct exclude_list {
39         int nr;
40         int alloc;
41         struct exclude {
42                 const char *pattern;
43                 const char *base;
44                 int baselen;
45         } **excludes;
46 } exclude_list[3];
47
48 static void add_exclude(const char *string, const char *base,
49                         int baselen, struct exclude_list *which)
50 {
51         struct exclude *x = xmalloc(sizeof (*x));
52
53         x->pattern = string;
54         x->base = base;
55         x->baselen = baselen;
56         if (which->nr == which->alloc) {
57                 which->alloc = alloc_nr(which->alloc);
58                 which->excludes = realloc(which->excludes,
59                                           which->alloc * sizeof(x));
60         }
61         which->excludes[which->nr++] = x;
62 }
63
64 static int add_excludes_from_file_1(const char *fname,
65                                     const char *base,
66                                     int baselen,
67                                     struct exclude_list *which)
68 {
69         int fd, i;
70         long size;
71         char *buf, *entry;
72
73         fd = open(fname, O_RDONLY);
74         if (fd < 0)
75                 goto err;
76         size = lseek(fd, 0, SEEK_END);
77         if (size < 0)
78                 goto err;
79         lseek(fd, 0, SEEK_SET);
80         if (size == 0) {
81                 close(fd);
82                 return 0;
83         }
84         buf = xmalloc(size);
85         if (read(fd, buf, size) != size)
86                 goto err;
87         close(fd);
88
89         entry = buf;
90         for (i = 0; i < size; i++) {
91                 if (buf[i] == '\n') {
92                         if (entry != buf + i && entry[0] != '#') {
93                                 buf[i] = 0;
94                                 add_exclude(entry, base, baselen, which);
95                         }
96                         entry = buf + i + 1;
97                 }
98         }
99         return 0;
100
101  err:
102         if (0 <= fd)
103                 close(fd);
104         return -1;
105 }
106
107 static void add_excludes_from_file(const char *fname)
108 {
109         if (add_excludes_from_file_1(fname, "", 0,
110                                      &exclude_list[EXC_FILE]) < 0)
111                 die("cannot use %s as an exclude file", fname);
112 }
113
114 static int push_exclude_per_directory(const char *base, int baselen)
115 {
116         char exclude_file[PATH_MAX];
117         struct exclude_list *el = &exclude_list[EXC_DIRS];
118         int current_nr = el->nr;
119
120         if (exclude_per_dir) {
121                 memcpy(exclude_file, base, baselen);
122                 strcpy(exclude_file + baselen, exclude_per_dir);
123                 add_excludes_from_file_1(exclude_file, base, baselen, el);
124         }
125         return current_nr;
126 }
127
128 static void pop_exclude_per_directory(int stk)
129 {
130         struct exclude_list *el = &exclude_list[EXC_DIRS];
131
132         while (stk < el->nr)
133                 free(el->excludes[--el->nr]);
134 }
135
136 /* Scan the list and let the last match determines the fate.
137  * Return 1 for exclude, 0 for include and -1 for undecided.
138  */
139 static int excluded_1(const char *pathname,
140                       int pathlen,
141                       struct exclude_list *el)
142 {
143         int i;
144
145         if (el->nr) {
146                 for (i = el->nr - 1; 0 <= i; i--) {
147                         struct exclude *x = el->excludes[i];
148                         const char *exclude = x->pattern;
149                         int to_exclude = 1;
150
151                         if (*exclude == '!') {
152                                 to_exclude = 0;
153                                 exclude++;
154                         }
155
156                         if (!strchr(exclude, '/')) {
157                                 /* match basename */
158                                 const char *basename = strrchr(pathname, '/');
159                                 basename = (basename) ? basename+1 : pathname;
160                                 if (fnmatch(exclude, basename, 0) == 0)
161                                         return to_exclude;
162                         }
163                         else {
164                                 /* match with FNM_PATHNAME:
165                                  * exclude has base (baselen long) inplicitly
166                                  * in front of it.
167                                  */
168                                 int baselen = x->baselen;
169                                 if (*exclude == '/')
170                                         exclude++;
171
172                                 if (pathlen < baselen ||
173                                     (baselen && pathname[baselen-1] != '/') ||
174                                     strncmp(pathname, x->base, baselen))
175                                     continue;
176
177                                 if (fnmatch(exclude, pathname+baselen,
178                                             FNM_PATHNAME) == 0)
179                                         return to_exclude;
180                         }
181                 }
182         }
183         return -1; /* undecided */
184 }
185
186 static int excluded(const char *pathname)
187 {
188         int pathlen = strlen(pathname);
189         int st;
190
191         for (st = EXC_CMDL; st <= EXC_FILE; st++) {
192                 switch (excluded_1(pathname, pathlen, &exclude_list[st])) {
193                 case 0:
194                         return 0;
195                 case 1:
196                         return 1;
197                 }
198         }
199         return 0;
200 }
201
202 struct nond_on_fs {
203         int len;
204         char name[0];
205 };
206
207 static struct nond_on_fs **dir;
208 static int nr_dir;
209 static int dir_alloc;
210
211 static void add_name(const char *pathname, int len)
212 {
213         struct nond_on_fs *ent;
214
215         if (cache_name_pos(pathname, len) >= 0)
216                 return;
217
218         if (nr_dir == dir_alloc) {
219                 dir_alloc = alloc_nr(dir_alloc);
220                 dir = xrealloc(dir, dir_alloc*sizeof(ent));
221         }
222         ent = xmalloc(sizeof(*ent) + len + 1);
223         ent->len = len;
224         memcpy(ent->name, pathname, len);
225         dir[nr_dir++] = ent;
226 }
227
228 /*
229  * Read a directory tree. We currently ignore anything but
230  * directories, regular files and symlinks. That's because git
231  * doesn't handle them at all yet. Maybe that will change some
232  * day.
233  *
234  * Also, we ignore the name ".git" (even if it is not a directory).
235  * That likely will not change.
236  */
237 static void read_directory(const char *path, const char *base, int baselen)
238 {
239         DIR *dir = opendir(path);
240
241         if (dir) {
242                 int exclude_stk;
243                 struct dirent *de;
244                 char fullname[MAXPATHLEN + 1];
245                 memcpy(fullname, base, baselen);
246
247                 exclude_stk = push_exclude_per_directory(base, baselen);
248
249                 while ((de = readdir(dir)) != NULL) {
250                         int len;
251
252                         if ((de->d_name[0] == '.') &&
253                             (de->d_name[1] == 0 ||
254                              !strcmp(de->d_name + 1, ".") ||
255                              !strcmp(de->d_name + 1, "git")))
256                                 continue;
257                         len = strlen(de->d_name);
258                         memcpy(fullname + baselen, de->d_name, len+1);
259                         if (excluded(fullname) != show_ignored)
260                                 continue;
261
262                         switch (DTYPE(de)) {
263                         struct stat st;
264                         default:
265                                 continue;
266                         case DT_UNKNOWN:
267                                 if (lstat(fullname, &st))
268                                         continue;
269                                 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
270                                         break;
271                                 if (!S_ISDIR(st.st_mode))
272                                         continue;
273                                 /* fallthrough */
274                         case DT_DIR:
275                                 memcpy(fullname + baselen + len, "/", 2);
276                                 read_directory(fullname, fullname,
277                                                baselen + len + 1);
278                                 continue;
279                         case DT_REG:
280                         case DT_LNK:
281                                 break;
282                         }
283                         add_name(fullname, baselen + len);
284                 }
285                 closedir(dir);
286
287                 pop_exclude_per_directory(exclude_stk);
288         }
289 }
290
291 static int cmp_name(const void *p1, const void *p2)
292 {
293         const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1;
294         const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2;
295
296         return cache_name_compare(e1->name, e1->len,
297                                   e2->name, e2->len);
298 }
299
300 static void show_killed_files(void)
301 {
302         int i;
303         for (i = 0; i < nr_dir; i++) {
304                 struct nond_on_fs *ent = dir[i];
305                 char *cp, *sp;
306                 int pos, len, killed = 0;
307
308                 for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
309                         sp = strchr(cp, '/');
310                         if (!sp) {
311                                 /* If ent->name is prefix of an entry in the
312                                  * cache, it will be killed.
313                                  */
314                                 pos = cache_name_pos(ent->name, ent->len);
315                                 if (0 <= pos)
316                                         die("bug in show-killed-files");
317                                 pos = -pos - 1;
318                                 while (pos < active_nr &&
319                                        ce_stage(active_cache[pos]))
320                                         pos++; /* skip unmerged */
321                                 if (active_nr <= pos)
322                                         break;
323                                 /* pos points at a name immediately after
324                                  * ent->name in the cache.  Does it expect
325                                  * ent->name to be a directory?
326                                  */
327                                 len = ce_namelen(active_cache[pos]);
328                                 if ((ent->len < len) &&
329                                     !strncmp(active_cache[pos]->name,
330                                              ent->name, ent->len) &&
331                                     active_cache[pos]->name[ent->len] == '/')
332                                         killed = 1;
333                                 break;
334                         }
335                         if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
336                                 /* If any of the leading directories in
337                                  * ent->name is registered in the cache,
338                                  * ent->name will be killed.
339                                  */
340                                 killed = 1;
341                                 break;
342                         }
343                 }
344                 if (killed)
345                         printf("%s%.*s%c", tag_killed,
346                                dir[i]->len, dir[i]->name,
347                                line_terminator);
348         }
349 }
350
351 static void show_files(void)
352 {
353         int i;
354
355         /* For cached/deleted files we don't need to even do the readdir */
356         if (show_others || show_killed) {
357                 read_directory(".", "", 0);
358                 qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
359                 if (show_others)
360                         for (i = 0; i < nr_dir; i++)
361                                 printf("%s%.*s%c", tag_other,
362                                        dir[i]->len, dir[i]->name,
363                                        line_terminator);
364                 if (show_killed)
365                         show_killed_files();
366         }
367         if (show_cached | show_stage) {
368                 for (i = 0; i < active_nr; i++) {
369                         struct cache_entry *ce = active_cache[i];
370                         if (excluded(ce->name) != show_ignored)
371                                 continue;
372                         if (show_unmerged && !ce_stage(ce))
373                                 continue;
374                         if (!show_stage)
375                                 printf("%s%s%c",
376                                        ce_stage(ce) ? tag_unmerged :
377                                        tag_cached,
378                                        ce->name, line_terminator);
379                         else
380                                 printf("%s%06o %s %d\t%s%c",
381                                        ce_stage(ce) ? tag_unmerged :
382                                        tag_cached,
383                                        ntohl(ce->ce_mode),
384                                        sha1_to_hex(ce->sha1),
385                                        ce_stage(ce),
386                                        ce->name, line_terminator); 
387                 }
388         }
389         if (show_deleted) {
390                 for (i = 0; i < active_nr; i++) {
391                         struct cache_entry *ce = active_cache[i];
392                         struct stat st;
393                         if (excluded(ce->name) != show_ignored)
394                                 continue;
395                         if (!lstat(ce->name, &st))
396                                 continue;
397                         printf("%s%s%c", tag_removed, ce->name,
398                                line_terminator);
399                 }
400         }
401 }
402
403 static const char ls_files_usage[] =
404         "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* "
405         "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
406         "[ --exclude-per-directory=<filename> ]";
407
408 int main(int argc, char **argv)
409 {
410         int i;
411         int exc_given = 0;
412
413         for (i = 1; i < argc; i++) {
414                 char *arg = argv[i];
415
416                 if (!strcmp(arg, "-z")) {
417                         line_terminator = 0;
418                 } else if (!strcmp(arg, "-t")) {
419                         tag_cached = "H ";
420                         tag_unmerged = "M ";
421                         tag_removed = "R ";
422                         tag_other = "? ";
423                         tag_killed = "K ";
424                 } else if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
425                         show_cached = 1;
426                 } else if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
427                         show_deleted = 1;
428                 } else if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
429                         show_others = 1;
430                 } else if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
431                         show_ignored = 1;
432                 } else if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
433                         show_stage = 1;
434                 } else if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
435                         show_killed = 1;
436                 } else if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
437                         /* There's no point in showing unmerged unless
438                          * you also show the stage information.
439                          */
440                         show_stage = 1;
441                         show_unmerged = 1;
442                 } else if (!strcmp(arg, "-x") && i+1 < argc) {
443                         exc_given = 1;
444                         add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
445                 } else if (!strncmp(arg, "--exclude=", 10)) {
446                         exc_given = 1;
447                         add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]);
448                 } else if (!strcmp(arg, "-X") && i+1 < argc) {
449                         exc_given = 1;
450                         add_excludes_from_file(argv[++i]);
451                 } else if (!strncmp(arg, "--exclude-from=", 15)) {
452                         exc_given = 1;
453                         add_excludes_from_file(arg+15);
454                 } else if (!strncmp(arg, "--exclude-per-directory=", 24)) {
455                         exc_given = 1;
456                         exclude_per_dir = arg + 24;
457                 } else
458                         usage(ls_files_usage);
459         }
460
461         if (show_ignored && !exc_given) {
462                 fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
463                         argv[0]);
464                 exit(1);
465         }
466
467         /* With no flags, we default to showing the cached files */
468         if (!(show_stage | show_deleted | show_others | show_unmerged | show_killed))
469                 show_cached = 1;
470
471         read_cache();
472         show_files();
473         return 0;
474 }