builtin-ls-files.c 11.5 KB
Newer Older
1 2 3 4 5 6 7
/*
 * This merges the file listing in the directory cache index
 * with the actual working directory list, and shows different
 * combinations of the two.
 *
 * Copyright (C) Linus Torvalds, 2005
 */
8
#include <fnmatch.h>
9 10

#include "cache.h"
11
#include "quote.h"
12
#include "dir.h"
P
Peter Eriksen 已提交
13
#include "builtin.h"
14

E
Eric Wong 已提交
15
static int abbrev = 0;
16 17 18
static int show_deleted = 0;
static int show_cached = 0;
static int show_others = 0;
19
static int show_stage = 0;
20
static int show_unmerged = 0;
21
static int show_modified = 0;
22
static int show_killed = 0;
23
static int show_valid_bit = 0;
24
static int line_terminator = '\n';
25

26
static int prefix_len = 0, prefix_offset = 0;
27
static const char **pathspec = NULL;
28 29
static int error_unmatch = 0;
static char *ps_matched = NULL;
30

31 32 33 34
static const char *tag_cached = "";
static const char *tag_unmerged = "";
static const char *tag_removed = "";
static const char *tag_other = "";
35
static const char *tag_killed = "";
36
static const char *tag_modified = "";
37

38

39 40 41 42
/*
 * Match a pathspec against a filename. The first "len" characters
 * are the common prefix
 */
43 44
static int match(const char **spec, char *ps_matched,
		 const char *filename, int len)
45 46 47 48 49 50 51
{
	const char *m;

	while ((m = *spec++) != NULL) {
		int matchlen = strlen(m + len);

		if (!matchlen)
52
			goto matched;
53 54
		if (!strncmp(m + len, filename + len, matchlen)) {
			if (m[len + matchlen - 1] == '/')
55
				goto matched;
56 57
			switch (filename[len + matchlen]) {
			case '/': case '\0':
58
				goto matched;
59 60 61
			}
		}
		if (!fnmatch(m + len, filename + len, 0))
62 63 64 65 66 67 68 69
			goto matched;
		if (ps_matched)
			ps_matched++;
		continue;
	matched:
		if (ps_matched)
			*ps_matched = 1;
		return 1;
70 71 72 73
	}
	return 0;
}

74
static void show_dir_entry(const char *tag, struct dir_entry *ent)
75 76 77 78 79 80 81
{
	int len = prefix_len;
	int offset = prefix_offset;

	if (len >= ent->len)
		die("git-ls-files: internal error - directory entry not superset of prefix");

82
	if (pathspec && !match(pathspec, ps_matched, ent->name, len))
83 84
		return;

85
	fputs(tag, stdout);
86
	write_name_quoted("", 0, ent->name + offset, line_terminator, stdout);
87
	putchar(line_terminator);
88 89
}

90
static void show_other_files(struct dir_struct *dir)
91 92
{
	int i;
93
	for (i = 0; i < dir->nr; i++) {
94 95 96
		/* We should not have a matching entry, but we
		 * may have an unmerged entry for this path.
		 */
97
		struct dir_entry *ent = dir->entries[i];
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
		int pos = cache_name_pos(ent->name, ent->len);
		struct cache_entry *ce;
		if (0 <= pos)
			die("bug in show-other-files");
		pos = -pos - 1;
		if (pos < active_nr) { 
			ce = active_cache[pos];
			if (ce_namelen(ce) == ent->len &&
			    !memcmp(ce->name, ent->name, ent->len))
				continue; /* Yup, this one exists unmerged */
		}
		show_dir_entry(tag_other, ent);
	}
}

113
static void show_killed_files(struct dir_struct *dir)
114 115
{
	int i;
116 117
	for (i = 0; i < dir->nr; i++) {
		struct dir_entry *ent = dir->entries[i];
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
		char *cp, *sp;
		int pos, len, killed = 0;

		for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
			sp = strchr(cp, '/');
			if (!sp) {
				/* If ent->name is prefix of an entry in the
				 * cache, it will be killed.
				 */
				pos = cache_name_pos(ent->name, ent->len);
				if (0 <= pos)
					die("bug in show-killed-files");
				pos = -pos - 1;
				while (pos < active_nr &&
				       ce_stage(active_cache[pos]))
					pos++; /* skip unmerged */
				if (active_nr <= pos)
					break;
				/* pos points at a name immediately after
				 * ent->name in the cache.  Does it expect
				 * ent->name to be a directory?
				 */
				len = ce_namelen(active_cache[pos]);
				if ((ent->len < len) &&
				    !strncmp(active_cache[pos]->name,
					     ent->name, ent->len) &&
				    active_cache[pos]->name[ent->len] == '/')
					killed = 1;
				break;
			}
			if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
				/* If any of the leading directories in
				 * ent->name is registered in the cache,
				 * ent->name will be killed.
				 */
				killed = 1;
				break;
			}
		}
		if (killed)
158
			show_dir_entry(tag_killed, dir->entries[i]);
159
	}
160 161
}

162 163 164 165 166 167 168 169
static void show_ce_entry(const char *tag, struct cache_entry *ce)
{
	int len = prefix_len;
	int offset = prefix_offset;

	if (len >= ce_namelen(ce))
		die("git-ls-files: internal error - cache entry not superset of prefix");

170
	if (pathspec && !match(pathspec, ps_matched, ce->name, len))
171 172
		return;

173 174
	if (tag && *tag && show_valid_bit &&
	    (ce->ce_flags & htons(CE_VALID))) {
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
		static char alttag[4];
		memcpy(alttag, tag, 3);
		if (isalpha(tag[0]))
			alttag[0] = tolower(tag[0]);
		else if (tag[0] == '?')
			alttag[0] = '!';
		else {
			alttag[0] = 'v';
			alttag[1] = tag[0];
			alttag[2] = ' ';
			alttag[3] = 0;
		}
		tag = alttag;
	}

190 191
	if (!show_stage) {
		fputs(tag, stdout);
192 193
		write_name_quoted("", 0, ce->name + offset,
				  line_terminator, stdout);
194 195 196 197
		putchar(line_terminator);
	}
	else {
		printf("%s%06o %s %d\t",
198 199
		       tag,
		       ntohl(ce->ce_mode),
E
Eric Wong 已提交
200 201
		       abbrev ? find_unique_abbrev(ce->sha1,abbrev)
				: sha1_to_hex(ce->sha1),
202
		       ce_stage(ce));
203 204
		write_name_quoted("", 0, ce->name + offset,
				  line_terminator, stdout);
205 206
		putchar(line_terminator);
	}
207 208
}

209
static void show_files(struct dir_struct *dir, const char *prefix)
210 211 212 213
{
	int i;

	/* For cached/deleted files we don't need to even do the readdir */
214
	if (show_others || show_killed) {
215 216 217
		const char *path = ".", *base = "";
		int baselen = prefix_len;

218
		if (baselen)
219
			path = base = prefix;
220
		read_directory(dir, path, base, baselen);
221
		if (show_others)
222
			show_other_files(dir);
223
		if (show_killed)
224
			show_killed_files(dir);
225
	}
226
	if (show_cached | show_stage) {
227 228
		for (i = 0; i < active_nr; i++) {
			struct cache_entry *ce = active_cache[i];
229
			if (excluded(dir, ce->name) != dir->show_ignored)
230
				continue;
231 232
			if (show_unmerged && !ce_stage(ce))
				continue;
233
			show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
234 235
		}
	}
236
	if (show_deleted | show_modified) {
237 238 239
		for (i = 0; i < active_nr; i++) {
			struct cache_entry *ce = active_cache[i];
			struct stat st;
240
			int err;
241
			if (excluded(dir, ce->name) != dir->show_ignored)
242
				continue;
243 244 245
			err = lstat(ce->name, &st);
			if (show_deleted && err)
				show_ce_entry(tag_removed, ce);
246
			if (show_modified && ce_modified(ce, &st, 0))
247
				show_ce_entry(tag_modified, ce);
248 249 250 251 252 253 254
		}
	}
}

/*
 * Prune the index to only contain stuff starting with "prefix"
 */
255
static void prune_cache(const char *prefix)
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
{
	int pos = cache_name_pos(prefix, prefix_len);
	unsigned int first, last;

	if (pos < 0)
		pos = -pos-1;
	active_cache += pos;
	active_nr -= pos;
	first = 0;
	last = active_nr;
	while (last > first) {
		int next = (last + first) >> 1;
		struct cache_entry *ce = active_cache[next];
		if (!strncmp(ce->name, prefix, prefix_len)) {
			first = next+1;
			continue;
272
		}
273 274 275 276 277
		last = next;
	}
	active_nr = last;
}

278
static const char *verify_pathspec(const char *prefix)
279
{
280 281 282 283 284 285 286 287 288 289 290 291
	const char **p, *n, *prev;
	char *real_prefix;
	unsigned long max;

	prev = NULL;
	max = PATH_MAX;
	for (p = pathspec; (n = *p) != NULL; p++) {
		int i, len = 0;
		for (i = 0; i < max; i++) {
			char c = n[i];
			if (prev && prev[i] != c)
				break;
292
			if (!c || c == '*' || c == '?')
293 294 295 296 297 298 299 300 301 302
				break;
			if (c == '/')
				len = i+1;
		}
		prev = n;
		if (len < max) {
			max = len;
			if (!max)
				break;
		}
303
	}
304 305 306 307 308 309 310 311 312 313

	if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
		die("git-ls-files: cannot generate relative filenames containing '..'");

	real_prefix = NULL;
	prefix_len = max;
	if (max) {
		real_prefix = xmalloc(max + 1);
		memcpy(real_prefix, prev, max);
		real_prefix[max] = 0;
314
	}
315
	return real_prefix;
316 317
}

318
static const char ls_files_usage[] =
319
	"git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
320
	"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
E
Eric Wong 已提交
321 322
	"[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
	"[--] [<file>]*";
323

324
int cmd_ls_files(int argc, const char **argv, const char *prefix)
325 326
{
	int i;
327
	int exc_given = 0;
328
	struct dir_struct dir;
329

330
	memset(&dir, 0, sizeof(dir));
331
	if (prefix)
332
		prefix_offset = strlen(prefix);
333
	git_config(git_default_config);
334

335
	for (i = 1; i < argc; i++) {
J
Junio C Hamano 已提交
336
		const char *arg = argv[i];
337

338 339 340 341
		if (!strcmp(arg, "--")) {
			i++;
			break;
		}
342 343
		if (!strcmp(arg, "-z")) {
			line_terminator = 0;
344 345
			continue;
		}
346
		if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
347 348 349
			tag_cached = "H ";
			tag_unmerged = "M ";
			tag_removed = "R ";
350
			tag_modified = "C ";
351
			tag_other = "? ";
352
			tag_killed = "K ";
353 354
			if (arg[1] == 'v')
				show_valid_bit = 1;
355 356 357
			continue;
		}
		if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
358
			show_cached = 1;
359 360 361
			continue;
		}
		if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
362
			show_deleted = 1;
363 364
			continue;
		}
365 366 367 368
		if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
			show_modified = 1;
			continue;
		}
369
		if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
370
			show_others = 1;
371 372 373
			continue;
		}
		if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
374
			dir.show_ignored = 1;
375 376 377
			continue;
		}
		if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
378
			show_stage = 1;
379 380 381
			continue;
		}
		if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
382
			show_killed = 1;
383 384
			continue;
		}
385
		if (!strcmp(arg, "--directory")) {
386
			dir.show_other_directories = 1;
387 388
			continue;
		}
389
		if (!strcmp(arg, "--no-empty-directory")) {
390
			dir.hide_empty_directories = 1;
391 392
			continue;
		}
393
		if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
394 395 396
			/* There's no point in showing unmerged unless
			 * you also show the stage information.
			 */
397 398
			show_stage = 1;
			show_unmerged = 1;
399 400 401
			continue;
		}
		if (!strcmp(arg, "-x") && i+1 < argc) {
402
			exc_given = 1;
403
			add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
404 405 406
			continue;
		}
		if (!strncmp(arg, "--exclude=", 10)) {
407
			exc_given = 1;
408
			add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
409 410 411
			continue;
		}
		if (!strcmp(arg, "-X") && i+1 < argc) {
412
			exc_given = 1;
413
			add_excludes_from_file(&dir, argv[++i]);
414 415 416
			continue;
		}
		if (!strncmp(arg, "--exclude-from=", 15)) {
417
			exc_given = 1;
418
			add_excludes_from_file(&dir, arg+15);
419 420 421
			continue;
		}
		if (!strncmp(arg, "--exclude-per-directory=", 24)) {
422
			exc_given = 1;
423
			dir.exclude_per_dir = arg + 24;
424 425 426 427 428 429
			continue;
		}
		if (!strcmp(arg, "--full-name")) {
			prefix_offset = 0;
			continue;
		}
430 431 432 433
		if (!strcmp(arg, "--error-unmatch")) {
			error_unmatch = 1;
			continue;
		}
E
Eric Wong 已提交
434 435 436 437 438 439 440 441 442 443 444 445
		if (!strncmp(arg, "--abbrev=", 9)) {
			abbrev = strtoul(arg+9, NULL, 10);
			if (abbrev && abbrev < MINIMUM_ABBREV)
				abbrev = MINIMUM_ABBREV;
			else if (abbrev > 40)
				abbrev = 40;
			continue;
		}
		if (!strcmp(arg, "--abbrev")) {
			abbrev = DEFAULT_ABBREV;
			continue;
		}
446
		if (*arg == '-')
447
			usage(ls_files_usage);
448
		break;
449 450
	}

451 452 453 454
	pathspec = get_pathspec(prefix, argv + i);

	/* Verify that the pathspec matches the prefix */
	if (pathspec)
455
		prefix = verify_pathspec(prefix);
456

457 458 459 460 461 462 463 464
	/* Treat unmatching pathspec elements as errors */
	if (pathspec && error_unmatch) {
		int num;
		for (num = 0; pathspec[num]; num++)
			;
		ps_matched = xcalloc(1, num);
	}

465
	if (dir.show_ignored && !exc_given) {
466 467
		fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
			argv[0]);
468
		exit(1);
469 470 471
	}

	/* With no flags, we default to showing the cached files */
472 473
	if (!(show_stage | show_deleted | show_others | show_unmerged |
	      show_killed | show_modified))
474 475 476
		show_cached = 1;

	read_cache();
477
	if (prefix)
478 479
		prune_cache(prefix);
	show_files(&dir, prefix);
480 481 482 483 484 485 486 487 488 489

	if (ps_matched) {
		/* We need to make sure all pathspec matched otherwise
		 * it is an error.
		 */
		int num, errors = 0;
		for (num = 0; pathspec[num]; num++) {
			if (ps_matched[num])
				continue;
			error("pathspec '%s' did not match any.",
490
			      pathspec[num] + prefix_offset);
491
			errors++;
492 493 494 495
		}
		return errors ? 1 : 0;
	}

496 497
	return 0;
}