ls-files.c 11.3 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 <dirent.h>
9
#include <fnmatch.h>
10 11 12 13 14 15 16

#include "cache.h"

static int show_deleted = 0;
static int show_cached = 0;
static int show_others = 0;
static int show_ignored = 0;
17
static int show_stage = 0;
18
static int show_unmerged = 0;
19
static int show_killed = 0;
20
static int line_terminator = '\n';
21

22 23 24 25
static const char *tag_cached = "";
static const char *tag_unmerged = "";
static const char *tag_removed = "";
static const char *tag_other = "";
26
static const char *tag_killed = "";
27

28
static char *exclude_per_dir = NULL;
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

/* We maintain three exclude pattern lists:
 * EXC_CMDL lists patterns explicitly given on the command line.
 * EXC_DIRS lists patterns obtained from per-directory ignore files.
 * EXC_FILE lists patterns from fallback ignore files.
 */
#define EXC_CMDL 0
#define EXC_DIRS 1
#define EXC_FILE 2
static struct exclude_list {
	int nr;
	int alloc;
	struct exclude {
		const char *pattern;
		const char *base;
		int baselen;
	} **excludes;
} exclude_list[3];

static void add_exclude(const char *string, const char *base,
			int baselen, struct exclude_list *which)
50
{
51 52 53 54 55
	struct exclude *x = xmalloc(sizeof (*x));

	x->pattern = string;
	x->base = base;
	x->baselen = baselen;
56 57 58 59
	if (which->nr == which->alloc) {
		which->alloc = alloc_nr(which->alloc);
		which->excludes = realloc(which->excludes,
					  which->alloc * sizeof(x));
60
	}
61
	which->excludes[which->nr++] = x;
62 63
}

64
static int add_excludes_from_file_1(const char *fname,
65 66 67
				    const char *base,
				    int baselen,
				    struct exclude_list *which)
68 69 70 71 72 73 74 75 76 77 78 79 80 81
{
	int fd, i;
	long size;
	char *buf, *entry;

	fd = open(fname, O_RDONLY);
	if (fd < 0)
		goto err;
	size = lseek(fd, 0, SEEK_END);
	if (size < 0)
		goto err;
	lseek(fd, 0, SEEK_SET);
	if (size == 0) {
		close(fd);
82
		return 0;
83 84 85 86 87 88 89 90 91
	}
	buf = xmalloc(size);
	if (read(fd, buf, size) != size)
		goto err;
	close(fd);

	entry = buf;
	for (i = 0; i < size; i++) {
		if (buf[i] == '\n') {
92
			if (entry != buf + i && entry[0] != '#') {
93
				buf[i] = 0;
94
				add_exclude(entry, base, baselen, which);
95 96 97 98
			}
			entry = buf + i + 1;
		}
	}
99 100 101 102 103 104 105 106 107 108
	return 0;

 err:
	if (0 <= fd)
		close(fd);
	return -1;
}

static void add_excludes_from_file(const char *fname)
{
109 110
	if (add_excludes_from_file_1(fname, "", 0,
				     &exclude_list[EXC_FILE]) < 0)
111 112 113 114 115 116
		die("cannot use %s as an exclude file", fname);
}

static int push_exclude_per_directory(const char *base, int baselen)
{
	char exclude_file[PATH_MAX];
117 118
	struct exclude_list *el = &exclude_list[EXC_DIRS];
	int current_nr = el->nr;
119 120 121 122

	if (exclude_per_dir) {
		memcpy(exclude_file, base, baselen);
		strcpy(exclude_file + baselen, exclude_per_dir);
123
		add_excludes_from_file_1(exclude_file, base, baselen, el);
124 125 126
	}
	return current_nr;
}
127

128 129
static void pop_exclude_per_directory(int stk)
{
130 131 132 133
	struct exclude_list *el = &exclude_list[EXC_DIRS];

	while (stk < el->nr)
		free(el->excludes[--el->nr]);
134 135
}

136 137 138 139 140 141
/* Scan the list and let the last match determines the fate.
 * Return 1 for exclude, 0 for include and -1 for undecided.
 */
static int excluded_1(const char *pathname,
		      int pathlen,
		      struct exclude_list *el)
142 143
{
	int i;
144

145 146 147
	if (el->nr) {
		for (i = el->nr - 1; 0 <= i; i--) {
			struct exclude *x = el->excludes[i];
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 176 177 178 179 180 181
			const char *exclude = x->pattern;
			int to_exclude = 1;

			if (*exclude == '!') {
				to_exclude = 0;
				exclude++;
			}

			if (!strchr(exclude, '/')) {
				/* match basename */
				const char *basename = strrchr(pathname, '/');
				basename = (basename) ? basename+1 : pathname;
				if (fnmatch(exclude, basename, 0) == 0)
					return to_exclude;
			}
			else {
				/* match with FNM_PATHNAME:
				 * exclude has base (baselen long) inplicitly
				 * in front of it.
				 */
				int baselen = x->baselen;
				if (*exclude == '/')
					exclude++;

				if (pathlen < baselen ||
				    (baselen && pathname[baselen-1] != '/') ||
				    strncmp(pathname, x->base, baselen))
				    continue;

				if (fnmatch(exclude, pathname+baselen,
					    FNM_PATHNAME) == 0)
					return to_exclude;
			}
		}
182
	}
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
	return -1; /* undecided */
}

static int excluded(const char *pathname)
{
	int pathlen = strlen(pathname);
	int st;

	for (st = EXC_CMDL; st <= EXC_FILE; st++) {
		switch (excluded_1(pathname, pathlen, &exclude_list[st])) {
		case 0:
			return 0;
		case 1:
			return 1;
		}
	}
199 200 201
	return 0;
}

202 203 204 205 206 207
struct nond_on_fs {
	int len;
	char name[0];
};

static struct nond_on_fs **dir;
208 209 210 211 212
static int nr_dir;
static int dir_alloc;

static void add_name(const char *pathname, int len)
{
213
	struct nond_on_fs *ent;
214 215 216 217 218 219

	if (cache_name_pos(pathname, len) >= 0)
		return;

	if (nr_dir == dir_alloc) {
		dir_alloc = alloc_nr(dir_alloc);
220
		dir = xrealloc(dir, dir_alloc*sizeof(ent));
221
	}
222 223 224 225
	ent = xmalloc(sizeof(*ent) + len + 1);
	ent->len = len;
	memcpy(ent->name, pathname, len);
	dir[nr_dir++] = ent;
226 227 228 229
}

/*
 * Read a directory tree. We currently ignore anything but
230 231 232
 * directories, regular files and symlinks. That's because git
 * doesn't handle them at all yet. Maybe that will change some
 * day.
233
 *
234
 * Also, we ignore the name ".git" (even if it is not a directory).
I
Ingo Molnar 已提交
235
 * That likely will not change.
236 237 238 239 240 241
 */
static void read_directory(const char *path, const char *base, int baselen)
{
	DIR *dir = opendir(path);

	if (dir) {
242
		int exclude_stk;
243 244 245 246
		struct dirent *de;
		char fullname[MAXPATHLEN + 1];
		memcpy(fullname, base, baselen);

247 248
		exclude_stk = push_exclude_per_directory(base, baselen);

249 250 251
		while ((de = readdir(dir)) != NULL) {
			int len;

252 253 254 255
			if ((de->d_name[0] == '.') &&
			    (de->d_name[1] == 0 ||
			     !strcmp(de->d_name + 1, ".") ||
			     !strcmp(de->d_name + 1, "git")))
256 257 258
				continue;
			len = strlen(de->d_name);
			memcpy(fullname + baselen, de->d_name, len+1);
259 260
			if (excluded(fullname) != show_ignored)
				continue;
261

262
			switch (DTYPE(de)) {
263 264 265 266 267 268
			struct stat st;
			default:
				continue;
			case DT_UNKNOWN:
				if (lstat(fullname, &st))
					continue;
269
				if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
270 271 272 273 274 275
					break;
				if (!S_ISDIR(st.st_mode))
					continue;
				/* fallthrough */
			case DT_DIR:
				memcpy(fullname + baselen + len, "/", 2);
276 277
				read_directory(fullname, fullname,
					       baselen + len + 1);
278 279
				continue;
			case DT_REG:
280
			case DT_LNK:
281 282 283 284 285
				break;
			}
			add_name(fullname, baselen + len);
		}
		closedir(dir);
286 287

		pop_exclude_per_directory(exclude_stk);
288 289 290 291 292
	}
}

static int cmp_name(const void *p1, const void *p2)
{
293 294 295 296 297 298
	const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1;
	const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2;

	return cache_name_compare(e1->name, e1->len,
				  e2->name, e2->len);
}
299

L
Linus Torvalds 已提交
300
static void show_killed_files(void)
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 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
{
	int i;
	for (i = 0; i < nr_dir; i++) {
		struct nond_on_fs *ent = dir[i];
		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)
			printf("%s%.*s%c", tag_killed,
			       dir[i]->len, dir[i]->name,
			       line_terminator);
	}
349 350 351 352 353 354 355
}

static void show_files(void)
{
	int i;

	/* For cached/deleted files we don't need to even do the readdir */
356
	if (show_others || show_killed) {
357
		read_directory(".", "", 0);
358 359 360 361 362 363 364 365
		qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
		if (show_others)
			for (i = 0; i < nr_dir; i++)
				printf("%s%.*s%c", tag_other,
				       dir[i]->len, dir[i]->name,
				       line_terminator);
		if (show_killed)
			show_killed_files();
366
	}
367
	if (show_cached | show_stage) {
368 369
		for (i = 0; i < active_nr; i++) {
			struct cache_entry *ce = active_cache[i];
370 371
			if (excluded(ce->name) != show_ignored)
				continue;
372 373
			if (show_unmerged && !ce_stage(ce))
				continue;
374
			if (!show_stage)
375 376 377 378
				printf("%s%s%c",
				       ce_stage(ce) ? tag_unmerged :
				       tag_cached,
				       ce->name, line_terminator);
379
			else
380
				printf("%s%06o %s %d\t%s%c",
381 382
				       ce_stage(ce) ? tag_unmerged :
				       tag_cached,
383 384 385 386
				       ntohl(ce->ce_mode),
				       sha1_to_hex(ce->sha1),
				       ce_stage(ce),
				       ce->name, line_terminator); 
387 388 389 390 391 392
		}
	}
	if (show_deleted) {
		for (i = 0; i < active_nr; i++) {
			struct cache_entry *ce = active_cache[i];
			struct stat st;
393 394
			if (excluded(ce->name) != show_ignored)
				continue;
395
			if (!lstat(ce->name, &st))
396
				continue;
397 398
			printf("%s%s%c", tag_removed, ce->name,
			       line_terminator);
399 400 401 402
		}
	}
}

403
static const char *ls_files_usage =
404
	"git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* "
405 406 407
	"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
	"[ --exclude-per-directory=<filename> ]";
;
408

409 410 411
int main(int argc, char **argv)
{
	int i;
412
	int exc_given = 0;
413 414 415 416

	for (i = 1; i < argc; i++) {
		char *arg = argv[i];

417 418
		if (!strcmp(arg, "-z")) {
			line_terminator = 0;
419 420 421 422 423
		} else if (!strcmp(arg, "-t")) {
			tag_cached = "H ";
			tag_unmerged = "M ";
			tag_removed = "R ";
			tag_other = "? ";
424
			tag_killed = "K ";
425
		} else if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
426
			show_cached = 1;
427
		} else if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
428
			show_deleted = 1;
429
		} else if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
430
			show_others = 1;
431
		} else if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
432
			show_ignored = 1;
433
		} else if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
434
			show_stage = 1;
435 436
		} else if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
			show_killed = 1;
437
		} else if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
438 439 440
			/* There's no point in showing unmerged unless
			 * you also show the stage information.
			 */
441 442
			show_stage = 1;
			show_unmerged = 1;
443
		} else if (!strcmp(arg, "-x") && i+1 < argc) {
444 445
			exc_given = 1;
			add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
446
		} else if (!strncmp(arg, "--exclude=", 10)) {
447 448
			exc_given = 1;
			add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]);
449
		} else if (!strcmp(arg, "-X") && i+1 < argc) {
450
			exc_given = 1;
451
			add_excludes_from_file(argv[++i]);
452
		} else if (!strncmp(arg, "--exclude-from=", 15)) {
453
			exc_given = 1;
454
			add_excludes_from_file(arg+15);
455
		} else if (!strncmp(arg, "--exclude-per-directory=", 24)) {
456
			exc_given = 1;
457
			exclude_per_dir = arg + 24;
458
		} else
459
			usage(ls_files_usage);
460 461
	}

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

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

	read_cache();
	show_files();
	return 0;
}