builtin-prune: memory diet.
[git] / builtin-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 "cache.h"
9 #include "quote.h"
10 #include "dir.h"
11 #include "builtin.h"
12
13 static int abbrev;
14 static int show_deleted;
15 static int show_cached;
16 static int show_others;
17 static int show_stage;
18 static int show_unmerged;
19 static int show_modified;
20 static int show_killed;
21 static int show_valid_bit;
22 static int line_terminator = '\n';
23
24 static int prefix_len;
25 static int prefix_offset;
26 static const char **pathspec;
27 static int error_unmatch;
28 static char *ps_matched;
29
30 static const char *tag_cached = "";
31 static const char *tag_unmerged = "";
32 static const char *tag_removed = "";
33 static const char *tag_other = "";
34 static const char *tag_killed = "";
35 static const char *tag_modified = "";
36
37
38 /*
39  * Match a pathspec against a filename. The first "len" characters
40  * are the common prefix
41  */
42 static int match(const char **spec, char *ps_matched,
43                  const char *filename, int len)
44 {
45         const char *m;
46
47         while ((m = *spec++) != NULL) {
48                 int matchlen = strlen(m + len);
49
50                 if (!matchlen)
51                         goto matched;
52                 if (!strncmp(m + len, filename + len, matchlen)) {
53                         if (m[len + matchlen - 1] == '/')
54                                 goto matched;
55                         switch (filename[len + matchlen]) {
56                         case '/': case '\0':
57                                 goto matched;
58                         }
59                 }
60                 if (!fnmatch(m + len, filename + len, 0))
61                         goto matched;
62                 if (ps_matched)
63                         ps_matched++;
64                 continue;
65         matched:
66                 if (ps_matched)
67                         *ps_matched = 1;
68                 return 1;
69         }
70         return 0;
71 }
72
73 static void show_dir_entry(const char *tag, struct dir_entry *ent)
74 {
75         int len = prefix_len;
76         int offset = prefix_offset;
77
78         if (len >= ent->len)
79                 die("git-ls-files: internal error - directory entry not superset of prefix");
80
81         if (pathspec && !match(pathspec, ps_matched, ent->name, len))
82                 return;
83
84         fputs(tag, stdout);
85         write_name_quoted("", 0, ent->name + offset, line_terminator, stdout);
86         putchar(line_terminator);
87 }
88
89 static void show_other_files(struct dir_struct *dir)
90 {
91         int i;
92         for (i = 0; i < dir->nr; i++) {
93                 /* We should not have a matching entry, but we
94                  * may have an unmerged entry for this path.
95                  */
96                 struct dir_entry *ent = dir->entries[i];
97                 int pos = cache_name_pos(ent->name, ent->len);
98                 struct cache_entry *ce;
99                 if (0 <= pos)
100                         die("bug in show-other-files");
101                 pos = -pos - 1;
102                 if (pos < active_nr) { 
103                         ce = active_cache[pos];
104                         if (ce_namelen(ce) == ent->len &&
105                             !memcmp(ce->name, ent->name, ent->len))
106                                 continue; /* Yup, this one exists unmerged */
107                 }
108                 show_dir_entry(tag_other, ent);
109         }
110 }
111
112 static void show_killed_files(struct dir_struct *dir)
113 {
114         int i;
115         for (i = 0; i < dir->nr; i++) {
116                 struct dir_entry *ent = dir->entries[i];
117                 char *cp, *sp;
118                 int pos, len, killed = 0;
119
120                 for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
121                         sp = strchr(cp, '/');
122                         if (!sp) {
123                                 /* If ent->name is prefix of an entry in the
124                                  * cache, it will be killed.
125                                  */
126                                 pos = cache_name_pos(ent->name, ent->len);
127                                 if (0 <= pos)
128                                         die("bug in show-killed-files");
129                                 pos = -pos - 1;
130                                 while (pos < active_nr &&
131                                        ce_stage(active_cache[pos]))
132                                         pos++; /* skip unmerged */
133                                 if (active_nr <= pos)
134                                         break;
135                                 /* pos points at a name immediately after
136                                  * ent->name in the cache.  Does it expect
137                                  * ent->name to be a directory?
138                                  */
139                                 len = ce_namelen(active_cache[pos]);
140                                 if ((ent->len < len) &&
141                                     !strncmp(active_cache[pos]->name,
142                                              ent->name, ent->len) &&
143                                     active_cache[pos]->name[ent->len] == '/')
144                                         killed = 1;
145                                 break;
146                         }
147                         if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
148                                 /* If any of the leading directories in
149                                  * ent->name is registered in the cache,
150                                  * ent->name will be killed.
151                                  */
152                                 killed = 1;
153                                 break;
154                         }
155                 }
156                 if (killed)
157                         show_dir_entry(tag_killed, dir->entries[i]);
158         }
159 }
160
161 static void show_ce_entry(const char *tag, struct cache_entry *ce)
162 {
163         int len = prefix_len;
164         int offset = prefix_offset;
165
166         if (len >= ce_namelen(ce))
167                 die("git-ls-files: internal error - cache entry not superset of prefix");
168
169         if (pathspec && !match(pathspec, ps_matched, ce->name, len))
170                 return;
171
172         if (tag && *tag && show_valid_bit &&
173             (ce->ce_flags & htons(CE_VALID))) {
174                 static char alttag[4];
175                 memcpy(alttag, tag, 3);
176                 if (isalpha(tag[0]))
177                         alttag[0] = tolower(tag[0]);
178                 else if (tag[0] == '?')
179                         alttag[0] = '!';
180                 else {
181                         alttag[0] = 'v';
182                         alttag[1] = tag[0];
183                         alttag[2] = ' ';
184                         alttag[3] = 0;
185                 }
186                 tag = alttag;
187         }
188
189         if (!show_stage) {
190                 fputs(tag, stdout);
191                 write_name_quoted("", 0, ce->name + offset,
192                                   line_terminator, stdout);
193                 putchar(line_terminator);
194         }
195         else {
196                 printf("%s%06o %s %d\t",
197                        tag,
198                        ntohl(ce->ce_mode),
199                        abbrev ? find_unique_abbrev(ce->sha1,abbrev)
200                                 : sha1_to_hex(ce->sha1),
201                        ce_stage(ce));
202                 write_name_quoted("", 0, ce->name + offset,
203                                   line_terminator, stdout);
204                 putchar(line_terminator);
205         }
206 }
207
208 static void show_files(struct dir_struct *dir, const char *prefix)
209 {
210         int i;
211
212         /* For cached/deleted files we don't need to even do the readdir */
213         if (show_others || show_killed) {
214                 const char *path = ".", *base = "";
215                 int baselen = prefix_len;
216
217                 if (baselen)
218                         path = base = prefix;
219                 read_directory(dir, path, base, baselen);
220                 if (show_others)
221                         show_other_files(dir);
222                 if (show_killed)
223                         show_killed_files(dir);
224         }
225         if (show_cached | show_stage) {
226                 for (i = 0; i < active_nr; i++) {
227                         struct cache_entry *ce = active_cache[i];
228                         if (excluded(dir, ce->name) != dir->show_ignored)
229                                 continue;
230                         if (show_unmerged && !ce_stage(ce))
231                                 continue;
232                         show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
233                 }
234         }
235         if (show_deleted | show_modified) {
236                 for (i = 0; i < active_nr; i++) {
237                         struct cache_entry *ce = active_cache[i];
238                         struct stat st;
239                         int err;
240                         if (excluded(dir, ce->name) != dir->show_ignored)
241                                 continue;
242                         err = lstat(ce->name, &st);
243                         if (show_deleted && err)
244                                 show_ce_entry(tag_removed, ce);
245                         if (show_modified && ce_modified(ce, &st, 0))
246                                 show_ce_entry(tag_modified, ce);
247                 }
248         }
249 }
250
251 /*
252  * Prune the index to only contain stuff starting with "prefix"
253  */
254 static void prune_cache(const char *prefix)
255 {
256         int pos = cache_name_pos(prefix, prefix_len);
257         unsigned int first, last;
258
259         if (pos < 0)
260                 pos = -pos-1;
261         active_cache += pos;
262         active_nr -= pos;
263         first = 0;
264         last = active_nr;
265         while (last > first) {
266                 int next = (last + first) >> 1;
267                 struct cache_entry *ce = active_cache[next];
268                 if (!strncmp(ce->name, prefix, prefix_len)) {
269                         first = next+1;
270                         continue;
271                 }
272                 last = next;
273         }
274         active_nr = last;
275 }
276
277 static const char *verify_pathspec(const char *prefix)
278 {
279         const char **p, *n, *prev;
280         char *real_prefix;
281         unsigned long max;
282
283         prev = NULL;
284         max = PATH_MAX;
285         for (p = pathspec; (n = *p) != NULL; p++) {
286                 int i, len = 0;
287                 for (i = 0; i < max; i++) {
288                         char c = n[i];
289                         if (prev && prev[i] != c)
290                                 break;
291                         if (!c || c == '*' || c == '?')
292                                 break;
293                         if (c == '/')
294                                 len = i+1;
295                 }
296                 prev = n;
297                 if (len < max) {
298                         max = len;
299                         if (!max)
300                                 break;
301                 }
302         }
303
304         if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
305                 die("git-ls-files: cannot generate relative filenames containing '..'");
306
307         real_prefix = NULL;
308         prefix_len = max;
309         if (max) {
310                 real_prefix = xmalloc(max + 1);
311                 memcpy(real_prefix, prev, max);
312                 real_prefix[max] = 0;
313         }
314         return real_prefix;
315 }
316
317 static const char ls_files_usage[] =
318         "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
319         "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
320         "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
321         "[--] [<file>]*";
322
323 int cmd_ls_files(int argc, const char **argv, const char *prefix)
324 {
325         int i;
326         int exc_given = 0;
327         struct dir_struct dir;
328
329         memset(&dir, 0, sizeof(dir));
330         if (prefix)
331                 prefix_offset = strlen(prefix);
332         git_config(git_default_config);
333
334         for (i = 1; i < argc; i++) {
335                 const char *arg = argv[i];
336
337                 if (!strcmp(arg, "--")) {
338                         i++;
339                         break;
340                 }
341                 if (!strcmp(arg, "-z")) {
342                         line_terminator = 0;
343                         continue;
344                 }
345                 if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
346                         tag_cached = "H ";
347                         tag_unmerged = "M ";
348                         tag_removed = "R ";
349                         tag_modified = "C ";
350                         tag_other = "? ";
351                         tag_killed = "K ";
352                         if (arg[1] == 'v')
353                                 show_valid_bit = 1;
354                         continue;
355                 }
356                 if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
357                         show_cached = 1;
358                         continue;
359                 }
360                 if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
361                         show_deleted = 1;
362                         continue;
363                 }
364                 if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
365                         show_modified = 1;
366                         continue;
367                 }
368                 if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
369                         show_others = 1;
370                         continue;
371                 }
372                 if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
373                         dir.show_ignored = 1;
374                         continue;
375                 }
376                 if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
377                         show_stage = 1;
378                         continue;
379                 }
380                 if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
381                         show_killed = 1;
382                         continue;
383                 }
384                 if (!strcmp(arg, "--directory")) {
385                         dir.show_other_directories = 1;
386                         continue;
387                 }
388                 if (!strcmp(arg, "--no-empty-directory")) {
389                         dir.hide_empty_directories = 1;
390                         continue;
391                 }
392                 if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
393                         /* There's no point in showing unmerged unless
394                          * you also show the stage information.
395                          */
396                         show_stage = 1;
397                         show_unmerged = 1;
398                         continue;
399                 }
400                 if (!strcmp(arg, "-x") && i+1 < argc) {
401                         exc_given = 1;
402                         add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
403                         continue;
404                 }
405                 if (!strncmp(arg, "--exclude=", 10)) {
406                         exc_given = 1;
407                         add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
408                         continue;
409                 }
410                 if (!strcmp(arg, "-X") && i+1 < argc) {
411                         exc_given = 1;
412                         add_excludes_from_file(&dir, argv[++i]);
413                         continue;
414                 }
415                 if (!strncmp(arg, "--exclude-from=", 15)) {
416                         exc_given = 1;
417                         add_excludes_from_file(&dir, arg+15);
418                         continue;
419                 }
420                 if (!strncmp(arg, "--exclude-per-directory=", 24)) {
421                         exc_given = 1;
422                         dir.exclude_per_dir = arg + 24;
423                         continue;
424                 }
425                 if (!strcmp(arg, "--full-name")) {
426                         prefix_offset = 0;
427                         continue;
428                 }
429                 if (!strcmp(arg, "--error-unmatch")) {
430                         error_unmatch = 1;
431                         continue;
432                 }
433                 if (!strncmp(arg, "--abbrev=", 9)) {
434                         abbrev = strtoul(arg+9, NULL, 10);
435                         if (abbrev && abbrev < MINIMUM_ABBREV)
436                                 abbrev = MINIMUM_ABBREV;
437                         else if (abbrev > 40)
438                                 abbrev = 40;
439                         continue;
440                 }
441                 if (!strcmp(arg, "--abbrev")) {
442                         abbrev = DEFAULT_ABBREV;
443                         continue;
444                 }
445                 if (*arg == '-')
446                         usage(ls_files_usage);
447                 break;
448         }
449
450         pathspec = get_pathspec(prefix, argv + i);
451
452         /* Verify that the pathspec matches the prefix */
453         if (pathspec)
454                 prefix = verify_pathspec(prefix);
455
456         /* Treat unmatching pathspec elements as errors */
457         if (pathspec && error_unmatch) {
458                 int num;
459                 for (num = 0; pathspec[num]; num++)
460                         ;
461                 ps_matched = xcalloc(1, num);
462         }
463
464         if (dir.show_ignored && !exc_given) {
465                 fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
466                         argv[0]);
467                 exit(1);
468         }
469
470         /* With no flags, we default to showing the cached files */
471         if (!(show_stage | show_deleted | show_others | show_unmerged |
472               show_killed | show_modified))
473                 show_cached = 1;
474
475         read_cache();
476         if (prefix)
477                 prune_cache(prefix);
478         show_files(&dir, prefix);
479
480         if (ps_matched) {
481                 /* We need to make sure all pathspec matched otherwise
482                  * it is an error.
483                  */
484                 int num, errors = 0;
485                 for (num = 0; pathspec[num]; num++) {
486                         if (ps_matched[num])
487                                 continue;
488                         error("pathspec '%s' did not match any file(s) known to git.",
489                               pathspec[num] + prefix_offset);
490                         errors++;
491                 }
492
493                 if (errors)
494                         fprintf(stderr, "Did you forget to 'git add'?\n");
495
496                 return errors ? 1 : 0;
497         }
498
499         return 0;
500 }