builtin-ls-files.c 11.7 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

13 14 15 16 17 18 19 20 21
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;
22
static int line_terminator = '\n';
23

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

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

37

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

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

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

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

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

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

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 94 95
		/* We should not have a matching entry, but we
		 * may have an unmerged entry for this path.
		 */
96
		struct dir_entry *ent = dir->entries[i];
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
		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);
	}
}

112
static void show_killed_files(struct dir_struct *dir)
113 114
{
	int i;
115 116
	for (i = 0; i < dir->nr; i++) {
		struct dir_entry *ent = dir->entries[i];
117 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
		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)
157
			show_dir_entry(tag_killed, dir->entries[i]);
158
	}
159 160
}

161 162 163 164 165 166 167 168
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");

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

172 173
	if (tag && *tag && show_valid_bit &&
	    (ce->ce_flags & htons(CE_VALID))) {
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
		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;
	}

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

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

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

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 227
		for (i = 0; i < active_nr; i++) {
			struct cache_entry *ce = active_cache[i];
228
			if (excluded(dir, ce->name) != dir->show_ignored)
229
				continue;
230 231
			if (show_unmerged && !ce_stage(ce))
				continue;
232
			show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
233 234
		}
	}
235
	if (show_deleted | show_modified) {
236 237 238
		for (i = 0; i < active_nr; i++) {
			struct cache_entry *ce = active_cache[i];
			struct stat st;
239
			int err;
240
			if (excluded(dir, ce->name) != dir->show_ignored)
241
				continue;
242 243 244
			err = lstat(ce->name, &st);
			if (show_deleted && err)
				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 253
		}
	}
}

/*
 * Prune the index to only contain stuff starting with "prefix"
 */
254
static void prune_cache(const char *prefix)
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
{
	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;
271
		}
272 273 274 275 276
		last = next;
	}
	active_nr = last;
}

277
static const char *verify_pathspec(const char *prefix)
278
{
279 280 281 282 283 284 285 286 287 288 289 290
	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;
291
			if (!c || c == '*' || c == '?')
292 293 294 295 296 297 298 299 300 301
				break;
			if (c == '/')
				len = i+1;
		}
		prev = n;
		if (len < max) {
			max = len;
			if (!max)
				break;
		}
302
	}
303 304 305 306 307 308 309 310 311 312

	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;
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>] "
E
Eric Wong 已提交
320 321
	"[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
	"[--] [<file>]*";
322

323
int cmd_ls_files(int argc, const char **argv, const char *prefix)
324 325
{
	int i;
326
	int exc_given = 0, require_work_tree = 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++) {
J
Junio C Hamano 已提交
335
		const char *arg = argv[i];
336

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

454 455 456 457
	if (require_work_tree &&
			(is_bare_repository() || is_inside_git_dir()))
		die("This operation must be run in a work tree");

458 459 460 461
	pathspec = get_pathspec(prefix, argv + i);

	/* Verify that the pathspec matches the prefix */
	if (pathspec)
462
		prefix = verify_pathspec(prefix);
463

464 465 466 467 468 469 470 471
	/* Treat unmatching pathspec elements as errors */
	if (pathspec && error_unmatch) {
		int num;
		for (num = 0; pathspec[num]; num++)
			;
		ps_matched = xcalloc(1, num);
	}

472
	if (dir.show_ignored && !exc_given) {
473 474
		fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
			argv[0]);
475
		exit(1);
476 477 478
	}

	/* With no flags, we default to showing the cached files */
479 480
	if (!(show_stage | show_deleted | show_others | show_unmerged |
	      show_killed | show_modified))
481 482 483
		show_cached = 1;

	read_cache();
484
	if (prefix)
485 486
		prune_cache(prefix);
	show_files(&dir, prefix);
487 488 489 490 491 492 493 494 495

	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;
496
			error("pathspec '%s' did not match any file(s) known to git.",
497
			      pathspec[num] + prefix_offset);
498
			errors++;
499
		}
500 501 502 503

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

504 505 506
		return errors ? 1 : 0;
	}

507 508
	return 0;
}