Code

Mention -m as an abbreviation for --merge
[git.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"
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';
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;
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 = "";
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;
47         while ((m = *spec++) != NULL) {
48                 int matchlen = strlen(m + len);
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 }
73 static void show_dir_entry(const char *tag, struct dir_entry *ent)
74 {
75         int len = prefix_len;
76         int offset = prefix_offset;
78         if (len >= ent->len)
79                 die("git-ls-files: internal error - directory entry not superset of prefix");
81         if (pathspec && !match(pathspec, ps_matched, ent->name, len))
82                 return;
84         fputs(tag, stdout);
85         write_name_quoted("", 0, ent->name + offset, line_terminator, stdout);
86         putchar(line_terminator);
87 }
89 static void show_other_files(struct dir_struct *dir)
90 {
91         int i;
94         /*
95          * Skip matching and unmerged entries for the paths,
96          * since we want just "others".
97          *
98          * (Matching entries are normally pruned during
99          * the directory tree walk, but will show up for
100          * gitlinks because we don't necessarily have
101          * dir->show_other_directories set to suppress
102          * them).
103          */
104         for (i = 0; i < dir->nr; i++) {
105                 struct dir_entry *ent = dir->entries[i];
106                 int len, pos;
107                 struct cache_entry *ce;
109                 /*
110                  * Remove the '/' at the end that directory
111                  * walking adds for directory entries.
112                  */
113                 len = ent->len;
114                 if (len && ent->name[len-1] == '/')
115                         len--;
116                 pos = cache_name_pos(ent->name, len);
117                 if (0 <= pos)
118                         continue;       /* exact match */
119                 pos = -pos - 1;
120                 if (pos < active_nr) {
121                         ce = active_cache[pos];
122                         if (ce_namelen(ce) == len &&
123                             !memcmp(ce->name, ent->name, len))
124                                 continue; /* Yup, this one exists unmerged */
125                 }
126                 show_dir_entry(tag_other, ent);
127         }
130 static void show_killed_files(struct dir_struct *dir)
132         int i;
133         for (i = 0; i < dir->nr; i++) {
134                 struct dir_entry *ent = dir->entries[i];
135                 char *cp, *sp;
136                 int pos, len, killed = 0;
138                 for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
139                         sp = strchr(cp, '/');
140                         if (!sp) {
141                                 /* If ent->name is prefix of an entry in the
142                                  * cache, it will be killed.
143                                  */
144                                 pos = cache_name_pos(ent->name, ent->len);
145                                 if (0 <= pos)
146                                         die("bug in show-killed-files");
147                                 pos = -pos - 1;
148                                 while (pos < active_nr &&
149                                        ce_stage(active_cache[pos]))
150                                         pos++; /* skip unmerged */
151                                 if (active_nr <= pos)
152                                         break;
153                                 /* pos points at a name immediately after
154                                  * ent->name in the cache.  Does it expect
155                                  * ent->name to be a directory?
156                                  */
157                                 len = ce_namelen(active_cache[pos]);
158                                 if ((ent->len < len) &&
159                                     !strncmp(active_cache[pos]->name,
160                                              ent->name, ent->len) &&
161                                     active_cache[pos]->name[ent->len] == '/')
162                                         killed = 1;
163                                 break;
164                         }
165                         if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
166                                 /* If any of the leading directories in
167                                  * ent->name is registered in the cache,
168                                  * ent->name will be killed.
169                                  */
170                                 killed = 1;
171                                 break;
172                         }
173                 }
174                 if (killed)
175                         show_dir_entry(tag_killed, dir->entries[i]);
176         }
179 static void show_ce_entry(const char *tag, struct cache_entry *ce)
181         int len = prefix_len;
182         int offset = prefix_offset;
184         if (len >= ce_namelen(ce))
185                 die("git-ls-files: internal error - cache entry not superset of prefix");
187         if (pathspec && !match(pathspec, ps_matched, ce->name, len))
188                 return;
190         if (tag && *tag && show_valid_bit &&
191             (ce->ce_flags & htons(CE_VALID))) {
192                 static char alttag[4];
193                 memcpy(alttag, tag, 3);
194                 if (isalpha(tag[0]))
195                         alttag[0] = tolower(tag[0]);
196                 else if (tag[0] == '?')
197                         alttag[0] = '!';
198                 else {
199                         alttag[0] = 'v';
200                         alttag[1] = tag[0];
201                         alttag[2] = ' ';
202                         alttag[3] = 0;
203                 }
204                 tag = alttag;
205         }
207         if (!show_stage) {
208                 fputs(tag, stdout);
209                 write_name_quoted("", 0, ce->name + offset,
210                                   line_terminator, stdout);
211                 putchar(line_terminator);
212         }
213         else {
214                 printf("%s%06o %s %d\t",
215                        tag,
216                        ntohl(ce->ce_mode),
217                        abbrev ? find_unique_abbrev(ce->sha1,abbrev)
218                                 : sha1_to_hex(ce->sha1),
219                        ce_stage(ce));
220                 write_name_quoted("", 0, ce->name + offset,
221                                   line_terminator, stdout);
222                 putchar(line_terminator);
223         }
226 static void show_files(struct dir_struct *dir, const char *prefix)
228         int i;
230         /* For cached/deleted files we don't need to even do the readdir */
231         if (show_others || show_killed) {
232                 const char *path = ".", *base = "";
233                 int baselen = prefix_len;
235                 if (baselen)
236                         path = base = prefix;
237                 read_directory(dir, path, base, baselen, pathspec);
238                 if (show_others)
239                         show_other_files(dir);
240                 if (show_killed)
241                         show_killed_files(dir);
242         }
243         if (show_cached | show_stage) {
244                 for (i = 0; i < active_nr; i++) {
245                         struct cache_entry *ce = active_cache[i];
246                         if (excluded(dir, ce->name) != dir->show_ignored)
247                                 continue;
248                         if (show_unmerged && !ce_stage(ce))
249                                 continue;
250                         show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
251                 }
252         }
253         if (show_deleted | show_modified) {
254                 for (i = 0; i < active_nr; i++) {
255                         struct cache_entry *ce = active_cache[i];
256                         struct stat st;
257                         int err;
258                         if (excluded(dir, ce->name) != dir->show_ignored)
259                                 continue;
260                         err = lstat(ce->name, &st);
261                         if (show_deleted && err)
262                                 show_ce_entry(tag_removed, ce);
263                         if (show_modified && ce_modified(ce, &st, 0))
264                                 show_ce_entry(tag_modified, ce);
265                 }
266         }
269 /*
270  * Prune the index to only contain stuff starting with "prefix"
271  */
272 static void prune_cache(const char *prefix)
274         int pos = cache_name_pos(prefix, prefix_len);
275         unsigned int first, last;
277         if (pos < 0)
278                 pos = -pos-1;
279         active_cache += pos;
280         active_nr -= pos;
281         first = 0;
282         last = active_nr;
283         while (last > first) {
284                 int next = (last + first) >> 1;
285                 struct cache_entry *ce = active_cache[next];
286                 if (!strncmp(ce->name, prefix, prefix_len)) {
287                         first = next+1;
288                         continue;
289                 }
290                 last = next;
291         }
292         active_nr = last;
295 static const char *verify_pathspec(const char *prefix)
297         const char **p, *n, *prev;
298         char *real_prefix;
299         unsigned long max;
301         prev = NULL;
302         max = PATH_MAX;
303         for (p = pathspec; (n = *p) != NULL; p++) {
304                 int i, len = 0;
305                 for (i = 0; i < max; i++) {
306                         char c = n[i];
307                         if (prev && prev[i] != c)
308                                 break;
309                         if (!c || c == '*' || c == '?')
310                                 break;
311                         if (c == '/')
312                                 len = i+1;
313                 }
314                 prev = n;
315                 if (len < max) {
316                         max = len;
317                         if (!max)
318                                 break;
319                 }
320         }
322         if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
323                 die("git-ls-files: cannot generate relative filenames containing '..'");
325         real_prefix = NULL;
326         prefix_len = max;
327         if (max) {
328                 real_prefix = xmalloc(max + 1);
329                 memcpy(real_prefix, prev, max);
330                 real_prefix[max] = 0;
331         }
332         return real_prefix;
335 static const char ls_files_usage[] =
336         "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
337         "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
338         "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
339         "[--] [<file>]*";
341 int cmd_ls_files(int argc, const char **argv, const char *prefix)
343         int i;
344         int exc_given = 0, require_work_tree = 0;
345         struct dir_struct dir;
347         memset(&dir, 0, sizeof(dir));
348         if (prefix)
349                 prefix_offset = strlen(prefix);
350         git_config(git_default_config);
352         for (i = 1; i < argc; i++) {
353                 const char *arg = argv[i];
355                 if (!strcmp(arg, "--")) {
356                         i++;
357                         break;
358                 }
359                 if (!strcmp(arg, "-z")) {
360                         line_terminator = 0;
361                         continue;
362                 }
363                 if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
364                         tag_cached = "H ";
365                         tag_unmerged = "M ";
366                         tag_removed = "R ";
367                         tag_modified = "C ";
368                         tag_other = "? ";
369                         tag_killed = "K ";
370                         if (arg[1] == 'v')
371                                 show_valid_bit = 1;
372                         continue;
373                 }
374                 if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
375                         show_cached = 1;
376                         continue;
377                 }
378                 if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
379                         show_deleted = 1;
380                         continue;
381                 }
382                 if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
383                         show_modified = 1;
384                         require_work_tree = 1;
385                         continue;
386                 }
387                 if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
388                         show_others = 1;
389                         require_work_tree = 1;
390                         continue;
391                 }
392                 if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
393                         dir.show_ignored = 1;
394                         require_work_tree = 1;
395                         continue;
396                 }
397                 if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
398                         show_stage = 1;
399                         continue;
400                 }
401                 if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
402                         show_killed = 1;
403                         require_work_tree = 1;
404                         continue;
405                 }
406                 if (!strcmp(arg, "--directory")) {
407                         dir.show_other_directories = 1;
408                         continue;
409                 }
410                 if (!strcmp(arg, "--no-empty-directory")) {
411                         dir.hide_empty_directories = 1;
412                         continue;
413                 }
414                 if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
415                         /* There's no point in showing unmerged unless
416                          * you also show the stage information.
417                          */
418                         show_stage = 1;
419                         show_unmerged = 1;
420                         continue;
421                 }
422                 if (!strcmp(arg, "-x") && i+1 < argc) {
423                         exc_given = 1;
424                         add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
425                         continue;
426                 }
427                 if (!prefixcmp(arg, "--exclude=")) {
428                         exc_given = 1;
429                         add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
430                         continue;
431                 }
432                 if (!strcmp(arg, "-X") && i+1 < argc) {
433                         exc_given = 1;
434                         add_excludes_from_file(&dir, argv[++i]);
435                         continue;
436                 }
437                 if (!prefixcmp(arg, "--exclude-from=")) {
438                         exc_given = 1;
439                         add_excludes_from_file(&dir, arg+15);
440                         continue;
441                 }
442                 if (!prefixcmp(arg, "--exclude-per-directory=")) {
443                         exc_given = 1;
444                         dir.exclude_per_dir = arg + 24;
445                         continue;
446                 }
447                 if (!strcmp(arg, "--full-name")) {
448                         prefix_offset = 0;
449                         continue;
450                 }
451                 if (!strcmp(arg, "--error-unmatch")) {
452                         error_unmatch = 1;
453                         continue;
454                 }
455                 if (!prefixcmp(arg, "--abbrev=")) {
456                         abbrev = strtoul(arg+9, NULL, 10);
457                         if (abbrev && abbrev < MINIMUM_ABBREV)
458                                 abbrev = MINIMUM_ABBREV;
459                         else if (abbrev > 40)
460                                 abbrev = 40;
461                         continue;
462                 }
463                 if (!strcmp(arg, "--abbrev")) {
464                         abbrev = DEFAULT_ABBREV;
465                         continue;
466                 }
467                 if (*arg == '-')
468                         usage(ls_files_usage);
469                 break;
470         }
472         if (require_work_tree && !is_inside_work_tree()) {
473                 const char *work_tree = get_git_work_tree();
474                 if (!work_tree || chdir(work_tree))
475                         die("This operation must be run in a work tree");
476         }
478         pathspec = get_pathspec(prefix, argv + i);
480         /* Verify that the pathspec matches the prefix */
481         if (pathspec)
482                 prefix = verify_pathspec(prefix);
484         /* Treat unmatching pathspec elements as errors */
485         if (pathspec && error_unmatch) {
486                 int num;
487                 for (num = 0; pathspec[num]; num++)
488                         ;
489                 ps_matched = xcalloc(1, num);
490         }
492         if (dir.show_ignored && !exc_given) {
493                 fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
494                         argv[0]);
495                 exit(1);
496         }
498         /* With no flags, we default to showing the cached files */
499         if (!(show_stage | show_deleted | show_others | show_unmerged |
500               show_killed | show_modified))
501                 show_cached = 1;
503         read_cache();
504         if (prefix)
505                 prune_cache(prefix);
506         show_files(&dir, prefix);
508         if (ps_matched) {
509                 /* We need to make sure all pathspec matched otherwise
510                  * it is an error.
511                  */
512                 int num, errors = 0;
513                 for (num = 0; pathspec[num]; num++) {
514                         int other, found_dup;
516                         if (ps_matched[num])
517                                 continue;
518                         /*
519                          * The caller might have fed identical pathspec
520                          * twice.  Do not barf on such a mistake.
521                          */
522                         for (found_dup = other = 0;
523                              !found_dup && pathspec[other];
524                              other++) {
525                                 if (other == num || !ps_matched[other])
526                                         continue;
527                                 if (!strcmp(pathspec[other], pathspec[num]))
528                                         /*
529                                          * Ok, we have a match already.
530                                          */
531                                         found_dup = 1;
532                         }
533                         if (found_dup)
534                                 continue;
536                         error("pathspec '%s' did not match any file(s) known to git.",
537                               pathspec[num] + prefix_offset);
538                         errors++;
539                 }
541                 if (errors)
542                         fprintf(stderr, "Did you forget to 'git add'?\n");
544                 return errors ? 1 : 0;
545         }
547         return 0;