]>
Commit | Line | Data |
---|---|---|
8695c8bf LT |
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> | |
9ff768e9 | 9 | #include <fnmatch.h> |
8695c8bf LT |
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; | |
aee46198 | 17 | static int show_stage = 0; |
eec8c633 | 18 | static int show_unmerged = 0; |
6ca45943 | 19 | static int show_killed = 0; |
b83c8345 | 20 | static int line_terminator = '\n'; |
8695c8bf | 21 | |
20d37ef6 PB |
22 | static const char *tag_cached = ""; |
23 | static const char *tag_unmerged = ""; | |
24 | static const char *tag_removed = ""; | |
25 | static const char *tag_other = ""; | |
6ca45943 | 26 | static const char *tag_killed = ""; |
20d37ef6 | 27 | |
f87f9497 | 28 | static char *exclude_per_dir = NULL; |
fee88256 JH |
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) | |
9ff768e9 | 50 | { |
f87f9497 JH |
51 | struct exclude *x = xmalloc(sizeof (*x)); |
52 | ||
53 | x->pattern = string; | |
54 | x->base = base; | |
55 | x->baselen = baselen; | |
fee88256 JH |
56 | if (which->nr == which->alloc) { |
57 | which->alloc = alloc_nr(which->alloc); | |
58 | which->excludes = realloc(which->excludes, | |
59 | which->alloc * sizeof(x)); | |
9ff768e9 | 60 | } |
fee88256 | 61 | which->excludes[which->nr++] = x; |
9ff768e9 NP |
62 | } |
63 | ||
f87f9497 | 64 | static int add_excludes_from_file_1(const char *fname, |
fee88256 JH |
65 | const char *base, |
66 | int baselen, | |
67 | struct exclude_list *which) | |
9ff768e9 NP |
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); | |
f87f9497 | 82 | return 0; |
9ff768e9 NP |
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') { | |
f87f9497 | 92 | if (entry != buf + i && entry[0] != '#') { |
9ff768e9 | 93 | buf[i] = 0; |
fee88256 | 94 | add_exclude(entry, base, baselen, which); |
9ff768e9 NP |
95 | } |
96 | entry = buf + i + 1; | |
97 | } | |
98 | } | |
f87f9497 JH |
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 | { | |
fee88256 JH |
109 | if (add_excludes_from_file_1(fname, "", 0, |
110 | &exclude_list[EXC_FILE]) < 0) | |
f87f9497 JH |
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]; | |
fee88256 JH |
117 | struct exclude_list *el = &exclude_list[EXC_DIRS]; |
118 | int current_nr = el->nr; | |
f87f9497 JH |
119 | |
120 | if (exclude_per_dir) { | |
121 | memcpy(exclude_file, base, baselen); | |
122 | strcpy(exclude_file + baselen, exclude_per_dir); | |
fee88256 | 123 | add_excludes_from_file_1(exclude_file, base, baselen, el); |
f87f9497 JH |
124 | } |
125 | return current_nr; | |
126 | } | |
9ff768e9 | 127 | |
f87f9497 JH |
128 | static void pop_exclude_per_directory(int stk) |
129 | { | |
fee88256 JH |
130 | struct exclude_list *el = &exclude_list[EXC_DIRS]; |
131 | ||
132 | while (stk < el->nr) | |
133 | free(el->excludes[--el->nr]); | |
9ff768e9 NP |
134 | } |
135 | ||
fee88256 JH |
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) | |
9ff768e9 NP |
142 | { |
143 | int i; | |
f87f9497 | 144 | |
fee88256 JH |
145 | if (el->nr) { |
146 | for (i = el->nr - 1; 0 <= i; i--) { | |
147 | struct exclude *x = el->excludes[i]; | |
f87f9497 JH |
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 | } | |
9ff768e9 | 182 | } |
fee88256 JH |
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 | } | |
9ff768e9 NP |
199 | return 0; |
200 | } | |
201 | ||
6ca45943 JH |
202 | struct nond_on_fs { |
203 | int len; | |
204 | char name[0]; | |
205 | }; | |
206 | ||
207 | static struct nond_on_fs **dir; | |
8695c8bf LT |
208 | static int nr_dir; |
209 | static int dir_alloc; | |
210 | ||
211 | static void add_name(const char *pathname, int len) | |
212 | { | |
6ca45943 | 213 | struct nond_on_fs *ent; |
8695c8bf LT |
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); | |
6ca45943 | 220 | dir = xrealloc(dir, dir_alloc*sizeof(ent)); |
8695c8bf | 221 | } |
6ca45943 JH |
222 | ent = xmalloc(sizeof(*ent) + len + 1); |
223 | ent->len = len; | |
224 | memcpy(ent->name, pathname, len); | |
225 | dir[nr_dir++] = ent; | |
8695c8bf LT |
226 | } |
227 | ||
228 | /* | |
229 | * Read a directory tree. We currently ignore anything but | |
a15c1c60 JH |
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. | |
8695c8bf | 233 | * |
f87f9497 | 234 | * Also, we ignore the name ".git" (even if it is not a directory). |
aebb2679 | 235 | * That likely will not change. |
8695c8bf LT |
236 | */ |
237 | static void read_directory(const char *path, const char *base, int baselen) | |
238 | { | |
239 | DIR *dir = opendir(path); | |
240 | ||
241 | if (dir) { | |
f87f9497 | 242 | int exclude_stk; |
8695c8bf LT |
243 | struct dirent *de; |
244 | char fullname[MAXPATHLEN + 1]; | |
245 | memcpy(fullname, base, baselen); | |
246 | ||
f87f9497 JH |
247 | exclude_stk = push_exclude_per_directory(base, baselen); |
248 | ||
8695c8bf LT |
249 | while ((de = readdir(dir)) != NULL) { |
250 | int len; | |
251 | ||
c4ee2952 JH |
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"))) | |
8695c8bf LT |
256 | continue; |
257 | len = strlen(de->d_name); | |
258 | memcpy(fullname + baselen, de->d_name, len+1); | |
f87f9497 JH |
259 | if (excluded(fullname) != show_ignored) |
260 | continue; | |
8695c8bf | 261 | |
b6829693 | 262 | switch (DTYPE(de)) { |
8695c8bf LT |
263 | struct stat st; |
264 | default: | |
265 | continue; | |
266 | case DT_UNKNOWN: | |
267 | if (lstat(fullname, &st)) | |
268 | continue; | |
a15c1c60 | 269 | if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) |
8695c8bf LT |
270 | break; |
271 | if (!S_ISDIR(st.st_mode)) | |
272 | continue; | |
273 | /* fallthrough */ | |
274 | case DT_DIR: | |
275 | memcpy(fullname + baselen + len, "/", 2); | |
20d37ef6 PB |
276 | read_directory(fullname, fullname, |
277 | baselen + len + 1); | |
8695c8bf LT |
278 | continue; |
279 | case DT_REG: | |
a15c1c60 | 280 | case DT_LNK: |
8695c8bf LT |
281 | break; |
282 | } | |
283 | add_name(fullname, baselen + len); | |
284 | } | |
285 | closedir(dir); | |
f87f9497 JH |
286 | |
287 | pop_exclude_per_directory(exclude_stk); | |
8695c8bf LT |
288 | } |
289 | } | |
290 | ||
291 | static int cmp_name(const void *p1, const void *p2) | |
292 | { | |
6ca45943 JH |
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 | } | |
8695c8bf | 299 | |
e99d59ff | 300 | static void show_killed_files(void) |
6ca45943 JH |
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 | } | |
8695c8bf LT |
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 */ | |
6ca45943 | 356 | if (show_others || show_killed) { |
8695c8bf | 357 | read_directory(".", "", 0); |
6ca45943 JH |
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(); | |
8695c8bf | 366 | } |
aee46198 | 367 | if (show_cached | show_stage) { |
8695c8bf LT |
368 | for (i = 0; i < active_nr; i++) { |
369 | struct cache_entry *ce = active_cache[i]; | |
9ff768e9 NP |
370 | if (excluded(ce->name) != show_ignored) |
371 | continue; | |
eec8c633 LT |
372 | if (show_unmerged && !ce_stage(ce)) |
373 | continue; | |
aee46198 | 374 | if (!show_stage) |
20d37ef6 PB |
375 | printf("%s%s%c", |
376 | ce_stage(ce) ? tag_unmerged : | |
377 | tag_cached, | |
378 | ce->name, line_terminator); | |
aee46198 | 379 | else |
2eab945e | 380 | printf("%s%06o %s %d\t%s%c", |
20d37ef6 PB |
381 | ce_stage(ce) ? tag_unmerged : |
382 | tag_cached, | |
aee46198 JH |
383 | ntohl(ce->ce_mode), |
384 | sha1_to_hex(ce->sha1), | |
385 | ce_stage(ce), | |
aee46198 | 386 | ce->name, line_terminator); |
8695c8bf LT |
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; | |
9ff768e9 NP |
393 | if (excluded(ce->name) != show_ignored) |
394 | continue; | |
8ae0a8c5 | 395 | if (!lstat(ce->name, &st)) |
8695c8bf | 396 | continue; |
20d37ef6 PB |
397 | printf("%s%s%c", tag_removed, ce->name, |
398 | line_terminator); | |
8695c8bf LT |
399 | } |
400 | } | |
8695c8bf LT |
401 | } |
402 | ||
4d1f1190 | 403 | static const char ls_files_usage[] = |
667bb59b | 404 | "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* " |
f87f9497 JH |
405 | "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] " |
406 | "[ --exclude-per-directory=<filename> ]"; | |
cf9a113d | 407 | |
8695c8bf LT |
408 | int main(int argc, char **argv) |
409 | { | |
410 | int i; | |
fee88256 | 411 | int exc_given = 0; |
8695c8bf LT |
412 | |
413 | for (i = 1; i < argc; i++) { | |
414 | char *arg = argv[i]; | |
415 | ||
b83c8345 JH |
416 | if (!strcmp(arg, "-z")) { |
417 | line_terminator = 0; | |
20d37ef6 PB |
418 | } else if (!strcmp(arg, "-t")) { |
419 | tag_cached = "H "; | |
420 | tag_unmerged = "M "; | |
421 | tag_removed = "R "; | |
422 | tag_other = "? "; | |
6ca45943 | 423 | tag_killed = "K "; |
cf9a113d | 424 | } else if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) { |
8695c8bf | 425 | show_cached = 1; |
cf9a113d | 426 | } else if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) { |
8695c8bf | 427 | show_deleted = 1; |
cf9a113d | 428 | } else if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) { |
8695c8bf | 429 | show_others = 1; |
cf9a113d | 430 | } else if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { |
8695c8bf | 431 | show_ignored = 1; |
cf9a113d | 432 | } else if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { |
aee46198 | 433 | show_stage = 1; |
6ca45943 JH |
434 | } else if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) { |
435 | show_killed = 1; | |
cf9a113d | 436 | } else if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) { |
20d37ef6 PB |
437 | /* There's no point in showing unmerged unless |
438 | * you also show the stage information. | |
439 | */ | |
eec8c633 LT |
440 | show_stage = 1; |
441 | show_unmerged = 1; | |
cf9a113d | 442 | } else if (!strcmp(arg, "-x") && i+1 < argc) { |
fee88256 JH |
443 | exc_given = 1; |
444 | add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]); | |
cf9a113d | 445 | } else if (!strncmp(arg, "--exclude=", 10)) { |
fee88256 JH |
446 | exc_given = 1; |
447 | add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]); | |
cf9a113d | 448 | } else if (!strcmp(arg, "-X") && i+1 < argc) { |
fee88256 | 449 | exc_given = 1; |
9ff768e9 | 450 | add_excludes_from_file(argv[++i]); |
cf9a113d | 451 | } else if (!strncmp(arg, "--exclude-from=", 15)) { |
fee88256 | 452 | exc_given = 1; |
9ff768e9 | 453 | add_excludes_from_file(arg+15); |
f87f9497 | 454 | } else if (!strncmp(arg, "--exclude-per-directory=", 24)) { |
fee88256 | 455 | exc_given = 1; |
f87f9497 | 456 | exclude_per_dir = arg + 24; |
cf9a113d | 457 | } else |
17710391 | 458 | usage(ls_files_usage); |
9ff768e9 NP |
459 | } |
460 | ||
fee88256 | 461 | if (show_ignored && !exc_given) { |
20d37ef6 PB |
462 | fprintf(stderr, "%s: --ignored needs some exclude pattern\n", |
463 | argv[0]); | |
9ff768e9 | 464 | exit(1); |
8695c8bf LT |
465 | } |
466 | ||
467 | /* With no flags, we default to showing the cached files */ | |
6ca45943 | 468 | if (!(show_stage | show_deleted | show_others | show_unmerged | show_killed)) |
8695c8bf LT |
469 | show_cached = 1; |
470 | ||
471 | read_cache(); | |
472 | show_files(); | |
473 | return 0; | |
474 | } |