1057 lines · 25.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only//// Traverse the source tree, parsing all .gitignore files, and print file paths// that are ignored by git.// The output is suitable to the --exclude-from option of tar.// This is useful until the --exclude-vcs-ignores option gets working correctly.//// Copyright (C) 2023 Masahiro Yamada <masahiroy@kernel.org>//                      (a lot of code imported from GIT) #include <assert.h>#include <dirent.h>#include <errno.h>#include <fcntl.h>#include <getopt.h>#include <stdarg.h>#include <stdbool.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h>#include <sys/types.h>#include <unistd.h> // Imported from commit 23c56f7bd5f1667f8b793d796bf30e39545920f6 in GIT////---------------------------(IMPORT FROM GIT BEGIN)--------------------------- // Copied from environment.c static bool ignore_case; // Copied from git-compat-util.h /* Sane ctype - no locale, and works with signed chars */#undef isascii#undef isspace#undef isdigit#undef isalpha#undef isalnum#undef isprint#undef islower#undef isupper#undef tolower#undef toupper#undef iscntrl#undef ispunct#undef isxdigit static const unsigned char sane_ctype[256];#define GIT_SPACE 0x01#define GIT_DIGIT 0x02#define GIT_ALPHA 0x04#define GIT_GLOB_SPECIAL 0x08#define GIT_REGEX_SPECIAL 0x10#define GIT_PATHSPEC_MAGIC 0x20#define GIT_CNTRL 0x40#define GIT_PUNCT 0x80#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)#define isascii(x) (((x) & ~0x7f) == 0)#define isspace(x) sane_istest(x,GIT_SPACE)#define isdigit(x) sane_istest(x,GIT_DIGIT)#define isalpha(x) sane_istest(x,GIT_ALPHA)#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)#define isprint(x) ((x) >= 0x20 && (x) <= 0x7e)#define islower(x) sane_iscase(x, 1)#define isupper(x) sane_iscase(x, 0)#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)#define iscntrl(x) (sane_istest(x,GIT_CNTRL))#define ispunct(x) sane_istest(x, GIT_PUNCT | GIT_REGEX_SPECIAL | \		GIT_GLOB_SPECIAL | GIT_PATHSPEC_MAGIC)#define isxdigit(x) (hexval_table[(unsigned char)(x)] != -1)#define tolower(x) sane_case((unsigned char)(x), 0x20)#define toupper(x) sane_case((unsigned char)(x), 0) static inline int sane_case(int x, int high){	if (sane_istest(x, GIT_ALPHA))		x = (x & ~0x20) | high;	return x;} static inline int sane_iscase(int x, int is_lower){	if (!sane_istest(x, GIT_ALPHA))		return 0; 	if (is_lower)		return (x & 0x20) != 0;	else		return (x & 0x20) == 0;} // Copied from ctype.c enum {	S = GIT_SPACE,	A = GIT_ALPHA,	D = GIT_DIGIT,	G = GIT_GLOB_SPECIAL,	/* *, ?, [, \\ */	R = GIT_REGEX_SPECIAL,	/* $, (, ), +, ., ^, {, | */	P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */	X = GIT_CNTRL,	U = GIT_PUNCT,	Z = GIT_CNTRL | GIT_SPACE}; static const unsigned char sane_ctype[256] = {	X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X,		/*   0.. 15 */	X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,		/*  16.. 31 */	S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P,		/*  32.. 47 */	D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G,		/*  48.. 63 */	P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A,		/*  64.. 79 */	A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P,		/*  80.. 95 */	P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A,		/*  96..111 */	A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X,		/* 112..127 */	/* Nothing in the 128.. range */}; // Copied from hex.c static const signed char hexval_table[256] = {	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 00-07 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 08-0f */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 10-17 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 18-1f */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 20-27 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 28-2f */	  0,  1,  2,  3,  4,  5,  6,  7,		/* 30-37 */	  8,  9, -1, -1, -1, -1, -1, -1,		/* 38-3f */	 -1, 10, 11, 12, 13, 14, 15, -1,		/* 40-47 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 48-4f */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 50-57 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 58-5f */	 -1, 10, 11, 12, 13, 14, 15, -1,		/* 60-67 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 68-67 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 70-77 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 78-7f */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 80-87 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 88-8f */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 90-97 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* 98-9f */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* a0-a7 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* a8-af */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* b0-b7 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* b8-bf */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* c0-c7 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* c8-cf */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* d0-d7 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* d8-df */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* e0-e7 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* e8-ef */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* f0-f7 */	 -1, -1, -1, -1, -1, -1, -1, -1,		/* f8-ff */}; // Copied from wildmatch.h #define WM_CASEFOLD 1#define WM_PATHNAME 2 #define WM_NOMATCH 1#define WM_MATCH 0#define WM_ABORT_ALL -1#define WM_ABORT_TO_STARSTAR -2 // Copied from wildmatch.c typedef unsigned char uchar; // local modification: remove NEGATE_CLASS(2) #define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \				    && *(class) == *(litmatch) \				    && strncmp((char*)class, litmatch, len) == 0) // local modification: simpilify macros#define ISBLANK(c) ((c) == ' ' || (c) == '\t')#define ISGRAPH(c) (isprint(c) && !isspace(c))#define ISPRINT(c) isprint(c)#define ISDIGIT(c) isdigit(c)#define ISALNUM(c) isalnum(c)#define ISALPHA(c) isalpha(c)#define ISCNTRL(c) iscntrl(c)#define ISLOWER(c) islower(c)#define ISPUNCT(c) ispunct(c)#define ISSPACE(c) isspace(c)#define ISUPPER(c) isupper(c)#define ISXDIGIT(c) isxdigit(c) /* Match pattern "p" against "text" */static int dowild(const uchar *p, const uchar *text, unsigned int flags){	uchar p_ch;	const uchar *pattern = p; 	for ( ; (p_ch = *p) != '\0'; text++, p++) {		int matched, match_slash, negated;		uchar t_ch, prev_ch;		if ((t_ch = *text) == '\0' && p_ch != '*')			return WM_ABORT_ALL;		if ((flags & WM_CASEFOLD) && ISUPPER(t_ch))			t_ch = tolower(t_ch);		if ((flags & WM_CASEFOLD) && ISUPPER(p_ch))			p_ch = tolower(p_ch);		switch (p_ch) {		case '\\':			/* Literal match with following character.  Note that the test			 * in "default" handles the p[1] == '\0' failure case. */			p_ch = *++p;			/* FALLTHROUGH */		default:			if (t_ch != p_ch)				return WM_NOMATCH;			continue;		case '?':			/* Match anything but '/'. */			if ((flags & WM_PATHNAME) && t_ch == '/')				return WM_NOMATCH;			continue;		case '*':			if (*++p == '*') {				const uchar *prev_p = p - 2;				while (*++p == '*') {}				if (!(flags & WM_PATHNAME))					/* without WM_PATHNAME, '*' == '**' */					match_slash = 1;				else if ((prev_p < pattern || *prev_p == '/') &&				    (*p == '\0' || *p == '/' ||				     (p[0] == '\\' && p[1] == '/'))) {					/*					 * Assuming we already match 'foo/' and are at					 * <star star slash>, just assume it matches					 * nothing and go ahead match the rest of the					 * pattern with the remaining string. This					 * helps make foo/<*><*>/bar (<> because					 * otherwise it breaks C comment syntax) match					 * both foo/bar and foo/a/bar.					 */					if (p[0] == '/' &&					    dowild(p + 1, text, flags) == WM_MATCH)						return WM_MATCH;					match_slash = 1;				} else /* WM_PATHNAME is set */					match_slash = 0;			} else				/* without WM_PATHNAME, '*' == '**' */				match_slash = flags & WM_PATHNAME ? 0 : 1;			if (*p == '\0') {				/* Trailing "**" matches everything.  Trailing "*" matches				 * only if there are no more slash characters. */				if (!match_slash) {					if (strchr((char *)text, '/'))						return WM_NOMATCH;				}				return WM_MATCH;			} else if (!match_slash && *p == '/') {				/*				 * _one_ asterisk followed by a slash				 * with WM_PATHNAME matches the next				 * directory				 */				const char *slash = strchr((char*)text, '/');				if (!slash)					return WM_NOMATCH;				text = (const uchar*)slash;				/* the slash is consumed by the top-level for loop */				break;			}			while (1) {				if (t_ch == '\0')					break;				/*				 * Try to advance faster when an asterisk is				 * followed by a literal. We know in this case				 * that the string before the literal				 * must belong to "*".				 * If match_slash is false, do not look past				 * the first slash as it cannot belong to '*'.				 */				if (!is_glob_special(*p)) {					p_ch = *p;					if ((flags & WM_CASEFOLD) && ISUPPER(p_ch))						p_ch = tolower(p_ch);					while ((t_ch = *text) != '\0' &&					       (match_slash || t_ch != '/')) {						if ((flags & WM_CASEFOLD) && ISUPPER(t_ch))							t_ch = tolower(t_ch);						if (t_ch == p_ch)							break;						text++;					}					if (t_ch != p_ch)						return WM_NOMATCH;				}				if ((matched = dowild(p, text, flags)) != WM_NOMATCH) {					if (!match_slash || matched != WM_ABORT_TO_STARSTAR)						return matched;				} else if (!match_slash && t_ch == '/')					return WM_ABORT_TO_STARSTAR;				t_ch = *++text;			}			return WM_ABORT_ALL;		case '[':			p_ch = *++p;			if (p_ch == '^')				p_ch = '!';			/* Assign literal 1/0 because of "matched" comparison. */			negated = p_ch == '!' ? 1 : 0;			if (negated) {				/* Inverted character class. */				p_ch = *++p;			}			prev_ch = 0;			matched = 0;			do {				if (!p_ch)					return WM_ABORT_ALL;				if (p_ch == '\\') {					p_ch = *++p;					if (!p_ch)						return WM_ABORT_ALL;					if (t_ch == p_ch)						matched = 1;				} else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {					p_ch = *++p;					if (p_ch == '\\') {						p_ch = *++p;						if (!p_ch)							return WM_ABORT_ALL;					}					if (t_ch <= p_ch && t_ch >= prev_ch)						matched = 1;					else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) {						uchar t_ch_upper = toupper(t_ch);						if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch)							matched = 1;					}					p_ch = 0; /* This makes "prev_ch" get set to 0. */				} else if (p_ch == '[' && p[1] == ':') {					const uchar *s;					int i;					for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/					if (!p_ch)						return WM_ABORT_ALL;					i = p - s - 1;					if (i < 0 || p[-1] != ':') {						/* Didn't find ":]", so treat like a normal set. */						p = s - 2;						p_ch = '[';						if (t_ch == p_ch)							matched = 1;						continue;					}					if (CC_EQ(s,i, "alnum")) {						if (ISALNUM(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "alpha")) {						if (ISALPHA(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "blank")) {						if (ISBLANK(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "cntrl")) {						if (ISCNTRL(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "digit")) {						if (ISDIGIT(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "graph")) {						if (ISGRAPH(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "lower")) {						if (ISLOWER(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "print")) {						if (ISPRINT(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "punct")) {						if (ISPUNCT(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "space")) {						if (ISSPACE(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "upper")) {						if (ISUPPER(t_ch))							matched = 1;						else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch))							matched = 1;					} else if (CC_EQ(s,i, "xdigit")) {						if (ISXDIGIT(t_ch))							matched = 1;					} else /* malformed [:class:] string */						return WM_ABORT_ALL;					p_ch = 0; /* This makes "prev_ch" get set to 0. */				} else if (t_ch == p_ch)					matched = 1;			} while (prev_ch = p_ch, (p_ch = *++p) != ']');			if (matched == negated ||			    ((flags & WM_PATHNAME) && t_ch == '/'))				return WM_NOMATCH;			continue;		}	} 	return *text ? WM_NOMATCH : WM_MATCH;} /* Match the "pattern" against the "text" string. */static int wildmatch(const char *pattern, const char *text, unsigned int flags){	// local modification: move WM_CASEFOLD here	if (ignore_case)		flags |= WM_CASEFOLD; 	return dowild((const uchar*)pattern, (const uchar*)text, flags);} // Copied from dir.h #define PATTERN_FLAG_NODIR 1#define PATTERN_FLAG_ENDSWITH 4#define PATTERN_FLAG_MUSTBEDIR 8#define PATTERN_FLAG_NEGATIVE 16 // Copied from dir.c static int fspathncmp(const char *a, const char *b, size_t count){	return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);} static int simple_length(const char *match){	int len = -1; 	for (;;) {		unsigned char c = *match++;		len++;		if (c == '\0' || is_glob_special(c))			return len;	}} static int no_wildcard(const char *string){	return string[simple_length(string)] == '\0';} static void parse_path_pattern(const char **pattern,			       int *patternlen,			       unsigned *flags,			       int *nowildcardlen){	const char *p = *pattern;	size_t i, len; 	*flags = 0;	if (*p == '!') {		*flags |= PATTERN_FLAG_NEGATIVE;		p++;	}	len = strlen(p);	if (len && p[len - 1] == '/') {		len--;		*flags |= PATTERN_FLAG_MUSTBEDIR;	}	for (i = 0; i < len; i++) {		if (p[i] == '/')			break;	}	if (i == len)		*flags |= PATTERN_FLAG_NODIR;	*nowildcardlen = simple_length(p);	/*	 * we should have excluded the trailing slash from 'p' too,	 * but that's one more allocation. Instead just make sure	 * nowildcardlen does not exceed real patternlen	 */	if (*nowildcardlen > len)		*nowildcardlen = len;	if (*p == '*' && no_wildcard(p + 1))		*flags |= PATTERN_FLAG_ENDSWITH;	*pattern = p;	*patternlen = len;} static void trim_trailing_spaces(char *buf){	char *p, *last_space = NULL; 	for (p = buf; *p; p++)		switch (*p) {		case ' ':			if (!last_space)				last_space = p;			break;		case '\\':			p++;			if (!*p)

Lines 1–500 of 1057.