builtin-ls-files.c 14.2 KB
Newer Older
1 2 3 4 5 6 7 8
/*
 * 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
 */
#include "cache.h"
9
#include "quote.h"
10
#include "dir.h"
P
Peter Eriksen 已提交
11
#include "builtin.h"
12
#include "tree.h"
13

14 15 16 17 18 19 20 21 22
static int abbrev;
static int show_deleted;
static int show_cached;
static int show_others;
static int show_stage;
static int show_unmerged;
static int show_modified;
static int show_killed;
static int show_valid_bit;
23
static int line_terminator = '\n';
24

25 26 27 28 29
static int prefix_len;
static int prefix_offset;
static const char **pathspec;
static int error_unmatch;
static char *ps_matched;
30
static const char *with_tree;
31

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

39

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

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

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

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

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

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

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

90
static void show_other_files(struct dir_struct *dir)
91 92
{
	int i;
93 94 95 96 97 98 99 100 101 102 103 104


	/*
	 * Skip matching and unmerged entries for the paths,
	 * since we want just "others".
	 *
	 * (Matching entries are normally pruned during
	 * the directory tree walk, but will show up for
	 * gitlinks because we don't necessarily have
	 * dir->show_other_directories set to suppress
	 * them).
	 */
105 106
	for (i = 0; i < dir->nr; i++) {
		struct dir_entry *ent = dir->entries[i];
107
		int len, pos;
108
		struct cache_entry *ce;
109 110 111 112 113 114 115 116 117

		/*
		 * Remove the '/' at the end that directory
		 * walking adds for directory entries.
		 */
		len = ent->len;
		if (len && ent->name[len-1] == '/')
			len--;
		pos = cache_name_pos(ent->name, len);
118
		if (0 <= pos)
119
			continue;	/* exact match */
120
		pos = -pos - 1;
J
Junio C Hamano 已提交
121
		if (pos < active_nr) {
122
			ce = active_cache[pos];
123 124
			if (ce_namelen(ce) == len &&
			    !memcmp(ce->name, ent->name, len))
125 126 127 128 129 130
				continue; /* Yup, this one exists unmerged */
		}
		show_dir_entry(tag_other, ent);
	}
}

131
static void show_killed_files(struct dir_struct *dir)
132 133
{
	int i;
134 135
	for (i = 0; i < dir->nr; i++) {
		struct dir_entry *ent = dir->entries[i];
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
		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)
176
			show_dir_entry(tag_killed, dir->entries[i]);
177
	}
178 179
}

180 181 182 183 184 185 186 187
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");

188
	if (pathspec && !match(pathspec, ps_matched, ce->name, len))
189 190
		return;

191 192
	if (tag && *tag && show_valid_bit &&
	    (ce->ce_flags & htons(CE_VALID))) {
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
		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;
	}

208 209
	if (!show_stage) {
		fputs(tag, stdout);
210
	} else {
211
		printf("%s%06o %s %d\t",
212 213
		       tag,
		       ntohl(ce->ce_mode),
E
Eric Wong 已提交
214 215
		       abbrev ? find_unique_abbrev(ce->sha1,abbrev)
				: sha1_to_hex(ce->sha1),
216 217
		       ce_stage(ce));
	}
218
	write_name_quoted(ce->name + offset, stdout, line_terminator);
219 220
}

221
static void show_files(struct dir_struct *dir, const char *prefix)
222 223 224 225
{
	int i;

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

230
		if (baselen)
231
			path = base = prefix;
232
		read_directory(dir, path, base, baselen, pathspec);
233
		if (show_others)
234
			show_other_files(dir);
235
		if (show_killed)
236
			show_killed_files(dir);
237
	}
238
	if (show_cached | show_stage) {
239 240
		for (i = 0; i < active_nr; i++) {
			struct cache_entry *ce = active_cache[i];
241
			if (excluded(dir, ce->name) != dir->show_ignored)
242
				continue;
243 244
			if (show_unmerged && !ce_stage(ce))
				continue;
245 246
			if (ce->ce_flags & htons(CE_UPDATE))
				continue;
247
			show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
248 249
		}
	}
250
	if (show_deleted | show_modified) {
251 252 253
		for (i = 0; i < active_nr; i++) {
			struct cache_entry *ce = active_cache[i];
			struct stat st;
254
			int err;
255
			if (excluded(dir, ce->name) != dir->show_ignored)
256
				continue;
257 258 259
			err = lstat(ce->name, &st);
			if (show_deleted && err)
				show_ce_entry(tag_removed, ce);
260
			if (show_modified && ce_modified(ce, &st, 0))
261
				show_ce_entry(tag_modified, ce);
262 263 264 265 266 267 268
		}
	}
}

/*
 * Prune the index to only contain stuff starting with "prefix"
 */
269
static void prune_cache(const char *prefix)
270 271 272 273 274 275
{
	int pos = cache_name_pos(prefix, prefix_len);
	unsigned int first, last;

	if (pos < 0)
		pos = -pos-1;
276 277
	memmove(active_cache, active_cache + pos,
		(active_nr - pos) * sizeof(struct cache_entry *));
278 279 280 281 282 283 284 285 286
	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;
287
		}
288 289 290 291 292
		last = next;
	}
	active_nr = last;
}

293
static const char *verify_pathspec(const char *prefix)
294
{
295 296 297 298 299 300 301 302 303 304 305
	const char **p, *n, *prev;
	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;
306
			if (!c || c == '*' || c == '?')
307 308 309 310 311 312 313 314 315 316
				break;
			if (c == '/')
				len = i+1;
		}
		prev = n;
		if (len < max) {
			max = len;
			if (!max)
				break;
		}
317
	}
318 319 320 321 322

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

	prefix_len = max;
P
Pierre Habouzit 已提交
323
	return max ? xmemdupz(prev, max) : NULL;
324 325
}

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
/*
 * Read the tree specified with --with-tree option
 * (typically, HEAD) into stage #1 and then
 * squash them down to stage #0.  This is used for
 * --error-unmatch to list and check the path patterns
 * that were given from the command line.  We are not
 * going to write this index out.
 */
static void overlay_tree(const char *tree_name, const char *prefix)
{
	struct tree *tree;
	unsigned char sha1[20];
	const char **match;
	struct cache_entry *last_stage0 = NULL;
	int i;

	if (get_sha1(tree_name, sha1))
		die("tree-ish %s not found.", tree_name);
	tree = parse_tree_indirect(sha1);
	if (!tree)
		die("bad tree-ish %s", tree_name);

	/* Hoist the unmerged entries up to stage #3 to make room */
	for (i = 0; i < active_nr; i++) {
		struct cache_entry *ce = active_cache[i];
		if (!ce_stage(ce))
			continue;
		ce->ce_flags |= htons(CE_STAGEMASK);
	}

	if (prefix) {
		static const char *(matchbuf[2]);
		matchbuf[0] = prefix;
		matchbuf [1] = NULL;
		match = matchbuf;
	} else
		match = NULL;
	if (read_tree(tree, 1, match))
		die("unable to read tree entries %s", tree_name);

	for (i = 0; i < active_nr; i++) {
		struct cache_entry *ce = active_cache[i];
		switch (ce_stage(ce)) {
		case 0:
			last_stage0 = ce;
			/* fallthru */
		default:
			continue;
		case 1:
			/*
			 * If there is stage #0 entry for this, we do not
			 * need to show it.  We use CE_UPDATE bit to mark
			 * such an entry.
			 */
			if (last_stage0 &&
			    !strcmp(last_stage0->name, ce->name))
				ce->ce_flags |= htons(CE_UPDATE);
		}
	}
}

387
static const char ls_files_usage[] =
388
	"git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
389
	"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
E
Eric Wong 已提交
390 391
	"[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
	"[--] [<file>]*";
392

393
int cmd_ls_files(int argc, const char **argv, const char *prefix)
394 395
{
	int i;
396
	int exc_given = 0, require_work_tree = 0;
397
	struct dir_struct dir;
398

399
	memset(&dir, 0, sizeof(dir));
400
	if (prefix)
401
		prefix_offset = strlen(prefix);
402
	git_config(git_default_config);
403

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

407 408 409 410
		if (!strcmp(arg, "--")) {
			i++;
			break;
		}
411 412
		if (!strcmp(arg, "-z")) {
			line_terminator = 0;
413 414
			continue;
		}
415
		if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
416 417 418
			tag_cached = "H ";
			tag_unmerged = "M ";
			tag_removed = "R ";
419
			tag_modified = "C ";
420
			tag_other = "? ";
421
			tag_killed = "K ";
422 423
			if (arg[1] == 'v')
				show_valid_bit = 1;
424 425 426
			continue;
		}
		if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
427
			show_cached = 1;
428 429 430
			continue;
		}
		if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
431
			show_deleted = 1;
432 433
			continue;
		}
434 435
		if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
			show_modified = 1;
436
			require_work_tree = 1;
437 438
			continue;
		}
439
		if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
440
			show_others = 1;
441
			require_work_tree = 1;
442 443 444
			continue;
		}
		if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
445
			dir.show_ignored = 1;
446
			require_work_tree = 1;
447 448 449
			continue;
		}
		if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
450
			show_stage = 1;
451 452 453
			continue;
		}
		if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
454
			show_killed = 1;
455
			require_work_tree = 1;
456 457
			continue;
		}
458
		if (!strcmp(arg, "--directory")) {
459
			dir.show_other_directories = 1;
460 461
			continue;
		}
462
		if (!strcmp(arg, "--no-empty-directory")) {
463
			dir.hide_empty_directories = 1;
464 465
			continue;
		}
466
		if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
467 468 469
			/* There's no point in showing unmerged unless
			 * you also show the stage information.
			 */
470 471
			show_stage = 1;
			show_unmerged = 1;
472 473 474
			continue;
		}
		if (!strcmp(arg, "-x") && i+1 < argc) {
475
			exc_given = 1;
476
			add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
477 478
			continue;
		}
479
		if (!prefixcmp(arg, "--exclude=")) {
480
			exc_given = 1;
481
			add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
482 483 484
			continue;
		}
		if (!strcmp(arg, "-X") && i+1 < argc) {
485
			exc_given = 1;
486
			add_excludes_from_file(&dir, argv[++i]);
487 488
			continue;
		}
489
		if (!prefixcmp(arg, "--exclude-from=")) {
490
			exc_given = 1;
491
			add_excludes_from_file(&dir, arg+15);
492 493
			continue;
		}
494
		if (!prefixcmp(arg, "--exclude-per-directory=")) {
495
			exc_given = 1;
496
			dir.exclude_per_dir = arg + 24;
497 498 499 500 501 502
			continue;
		}
		if (!strcmp(arg, "--full-name")) {
			prefix_offset = 0;
			continue;
		}
503 504 505 506
		if (!strcmp(arg, "--error-unmatch")) {
			error_unmatch = 1;
			continue;
		}
507 508 509 510
		if (!prefixcmp(arg, "--with-tree=")) {
			with_tree = arg + 12;
			continue;
		}
511
		if (!prefixcmp(arg, "--abbrev=")) {
E
Eric Wong 已提交
512 513 514 515 516 517 518 519 520 521 522
			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;
		}
523
		if (*arg == '-')
524
			usage(ls_files_usage);
525
		break;
526 527
	}

528 529
	if (require_work_tree && !is_inside_work_tree())
		setup_work_tree();
530

531 532 533 534
	pathspec = get_pathspec(prefix, argv + i);

	/* Verify that the pathspec matches the prefix */
	if (pathspec)
535
		prefix = verify_pathspec(prefix);
536

537 538 539 540 541 542 543 544
	/* Treat unmatching pathspec elements as errors */
	if (pathspec && error_unmatch) {
		int num;
		for (num = 0; pathspec[num]; num++)
			;
		ps_matched = xcalloc(1, num);
	}

545
	if (dir.show_ignored && !exc_given) {
546 547
		fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
			argv[0]);
548
		exit(1);
549 550 551
	}

	/* With no flags, we default to showing the cached files */
552 553
	if (!(show_stage | show_deleted | show_others | show_unmerged |
	      show_killed | show_modified))
554 555 556
		show_cached = 1;

	read_cache();
557
	if (prefix)
558
		prune_cache(prefix);
559 560 561 562 563 564 565 566 567
	if (with_tree) {
		/*
		 * Basic sanity check; show-stages and show-unmerged
		 * would not make any sense with this option.
		 */
		if (show_stage || show_unmerged)
			die("ls-files --with-tree is incompatible with -s or -u");
		overlay_tree(with_tree, prefix);
	}
568
	show_files(&dir, prefix);
569 570 571 572 573 574 575

	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++) {
576 577
			int other, found_dup;

578 579
			if (ps_matched[num])
				continue;
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
			/*
			 * The caller might have fed identical pathspec
			 * twice.  Do not barf on such a mistake.
			 */
			for (found_dup = other = 0;
			     !found_dup && pathspec[other];
			     other++) {
				if (other == num || !ps_matched[other])
					continue;
				if (!strcmp(pathspec[other], pathspec[num]))
					/*
					 * Ok, we have a match already.
					 */
					found_dup = 1;
			}
			if (found_dup)
				continue;

598
			error("pathspec '%s' did not match any file(s) known to git.",
599
			      pathspec[num] + prefix_offset);
600
			errors++;
601
		}
602 603 604 605

		if (errors)
			fprintf(stderr, "Did you forget to 'git add'?\n");

606 607 608
		return errors ? 1 : 0;
	}

609 610
	return 0;
}