1 // SPDX-License-Identifier: GPL-2.0-only
3 // Traverse the source tree, parsing all .gitignore files, and print file paths
4 // that are ignored by git.
5 // The output is suitable to the --exclude-from option of tar.
6 // This is useful until the --exclude-vcs-ignores option gets working correctly.
8 // Copyright (C) 2023 Masahiro Yamada <masahiroy@kernel.org>
9 // (a lot of code imported from GIT)
22 #include <sys/types.h>
25 // Imported from commit 23c56f7bd5f1667f8b793d796bf30e39545920f6 in GIT
27 //---------------------------(IMPORT FROM GIT BEGIN)---------------------------
29 // Copied from environment.c
31 static bool ignore_case;
33 // Copied from git-compat-util.h
35 /* Sane ctype - no locale, and works with signed chars */
50 static const unsigned char sane_ctype[256];
51 #define GIT_SPACE 0x01
52 #define GIT_DIGIT 0x02
53 #define GIT_ALPHA 0x04
54 #define GIT_GLOB_SPECIAL 0x08
55 #define GIT_REGEX_SPECIAL 0x10
56 #define GIT_PATHSPEC_MAGIC 0x20
57 #define GIT_CNTRL 0x40
58 #define GIT_PUNCT 0x80
59 #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
60 #define isascii(x) (((x) & ~0x7f) == 0)
61 #define isspace(x) sane_istest(x,GIT_SPACE)
62 #define isdigit(x) sane_istest(x,GIT_DIGIT)
63 #define isalpha(x) sane_istest(x,GIT_ALPHA)
64 #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
65 #define isprint(x) ((x) >= 0x20 && (x) <= 0x7e)
66 #define islower(x) sane_iscase(x, 1)
67 #define isupper(x) sane_iscase(x, 0)
68 #define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
69 #define iscntrl(x) (sane_istest(x,GIT_CNTRL))
70 #define ispunct(x) sane_istest(x, GIT_PUNCT | GIT_REGEX_SPECIAL | \
71 GIT_GLOB_SPECIAL | GIT_PATHSPEC_MAGIC)
72 #define isxdigit(x) (hexval_table[(unsigned char)(x)] != -1)
73 #define tolower(x) sane_case((unsigned char)(x), 0x20)
74 #define toupper(x) sane_case((unsigned char)(x), 0)
76 static inline int sane_case(int x, int high)
78 if (sane_istest(x, GIT_ALPHA))
79 x = (x & ~0x20) | high;
83 static inline int sane_iscase(int x, int is_lower)
85 if (!sane_istest(x, GIT_ALPHA))
89 return (x & 0x20) != 0;
91 return (x & 0x20) == 0;
94 // Copied from ctype.c
100 G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */
101 R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */
102 P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */
105 Z = GIT_CNTRL | GIT_SPACE
108 static const unsigned char sane_ctype[256] = {
109 X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */
110 X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */
111 S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */
112 D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */
113 P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
114 A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */
115 P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
116 A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */
117 /* Nothing in the 128.. range */
122 static const signed char hexval_table[256] = {
123 -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */
124 -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */
125 -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */
126 -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */
127 -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */
128 -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */
129 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */
130 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */
131 -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */
132 -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */
133 -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */
134 -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */
135 -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */
136 -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */
137 -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */
138 -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */
139 -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */
140 -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */
141 -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */
142 -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */
143 -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */
144 -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */
145 -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */
146 -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */
147 -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */
148 -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */
149 -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */
150 -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */
151 -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */
152 -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */
153 -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */
154 -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */
157 // Copied from wildmatch.h
159 #define WM_CASEFOLD 1
160 #define WM_PATHNAME 2
164 #define WM_ABORT_ALL -1
165 #define WM_ABORT_TO_STARSTAR -2
167 // Copied from wildmatch.c
169 typedef unsigned char uchar;
171 // local modification: remove NEGATE_CLASS(2)
173 #define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \
174 && *(class) == *(litmatch) \
175 && strncmp((char*)class, litmatch, len) == 0)
177 // local modification: simpilify macros
178 #define ISBLANK(c) ((c) == ' ' || (c) == '\t')
179 #define ISGRAPH(c) (isprint(c) && !isspace(c))
180 #define ISPRINT(c) isprint(c)
181 #define ISDIGIT(c) isdigit(c)
182 #define ISALNUM(c) isalnum(c)
183 #define ISALPHA(c) isalpha(c)
184 #define ISCNTRL(c) iscntrl(c)
185 #define ISLOWER(c) islower(c)
186 #define ISPUNCT(c) ispunct(c)
187 #define ISSPACE(c) isspace(c)
188 #define ISUPPER(c) isupper(c)
189 #define ISXDIGIT(c) isxdigit(c)
191 /* Match pattern "p" against "text" */
192 static int dowild(const uchar *p, const uchar *text, unsigned int flags)
195 const uchar *pattern = p;
197 for ( ; (p_ch = *p) != '\0'; text++, p++) {
198 int matched, match_slash, negated;
200 if ((t_ch = *text) == '\0' && p_ch != '*')
202 if ((flags & WM_CASEFOLD) && ISUPPER(t_ch))
203 t_ch = tolower(t_ch);
204 if ((flags & WM_CASEFOLD) && ISUPPER(p_ch))
205 p_ch = tolower(p_ch);
208 /* Literal match with following character. Note that the test
209 * in "default" handles the p[1] == '\0' failure case. */
217 /* Match anything but '/'. */
218 if ((flags & WM_PATHNAME) && t_ch == '/')
223 const uchar *prev_p = p - 2;
224 while (*++p == '*') {}
225 if (!(flags & WM_PATHNAME))
226 /* without WM_PATHNAME, '*' == '**' */
228 else if ((prev_p < pattern || *prev_p == '/') &&
229 (*p == '\0' || *p == '/' ||
230 (p[0] == '\\' && p[1] == '/'))) {
232 * Assuming we already match 'foo/' and are at
233 * <star star slash>, just assume it matches
234 * nothing and go ahead match the rest of the
235 * pattern with the remaining string. This
236 * helps make foo/<*><*>/bar (<> because
237 * otherwise it breaks C comment syntax) match
238 * both foo/bar and foo/a/bar.
241 dowild(p + 1, text, flags) == WM_MATCH)
244 } else /* WM_PATHNAME is set */
247 /* without WM_PATHNAME, '*' == '**' */
248 match_slash = flags & WM_PATHNAME ? 0 : 1;
250 /* Trailing "**" matches everything. Trailing "*" matches
251 * only if there are no more slash characters. */
253 if (strchr((char *)text, '/'))
257 } else if (!match_slash && *p == '/') {
259 * _one_ asterisk followed by a slash
260 * with WM_PATHNAME matches the next
263 const char *slash = strchr((char*)text, '/');
266 text = (const uchar*)slash;
267 /* the slash is consumed by the top-level for loop */
274 * Try to advance faster when an asterisk is
275 * followed by a literal. We know in this case
276 * that the string before the literal
277 * must belong to "*".
278 * If match_slash is false, do not look past
279 * the first slash as it cannot belong to '*'.
281 if (!is_glob_special(*p)) {
283 if ((flags & WM_CASEFOLD) && ISUPPER(p_ch))
284 p_ch = tolower(p_ch);
285 while ((t_ch = *text) != '\0' &&
286 (match_slash || t_ch != '/')) {
287 if ((flags & WM_CASEFOLD) && ISUPPER(t_ch))
288 t_ch = tolower(t_ch);
296 if ((matched = dowild(p, text, flags)) != WM_NOMATCH) {
297 if (!match_slash || matched != WM_ABORT_TO_STARSTAR)
299 } else if (!match_slash && t_ch == '/')
300 return WM_ABORT_TO_STARSTAR;
308 /* Assign literal 1/0 because of "matched" comparison. */
309 negated = p_ch == '!' ? 1 : 0;
311 /* Inverted character class. */
325 } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {
332 if (t_ch <= p_ch && t_ch >= prev_ch)
334 else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) {
335 uchar t_ch_upper = toupper(t_ch);
336 if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch)
339 p_ch = 0; /* This makes "prev_ch" get set to 0. */
340 } else if (p_ch == '[' && p[1] == ':') {
343 for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/
347 if (i < 0 || p[-1] != ':') {
348 /* Didn't find ":]", so treat like a normal set. */
355 if (CC_EQ(s,i, "alnum")) {
358 } else if (CC_EQ(s,i, "alpha")) {
361 } else if (CC_EQ(s,i, "blank")) {
364 } else if (CC_EQ(s,i, "cntrl")) {
367 } else if (CC_EQ(s,i, "digit")) {
370 } else if (CC_EQ(s,i, "graph")) {
373 } else if (CC_EQ(s,i, "lower")) {
376 } else if (CC_EQ(s,i, "print")) {
379 } else if (CC_EQ(s,i, "punct")) {
382 } else if (CC_EQ(s,i, "space")) {
385 } else if (CC_EQ(s,i, "upper")) {
388 else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch))
390 } else if (CC_EQ(s,i, "xdigit")) {
393 } else /* malformed [:class:] string */
395 p_ch = 0; /* This makes "prev_ch" get set to 0. */
396 } else if (t_ch == p_ch)
398 } while (prev_ch = p_ch, (p_ch = *++p) != ']');
399 if (matched == negated ||
400 ((flags & WM_PATHNAME) && t_ch == '/'))
406 return *text ? WM_NOMATCH : WM_MATCH;
409 /* Match the "pattern" against the "text" string. */
410 static int wildmatch(const char *pattern, const char *text, unsigned int flags)
412 // local modification: move WM_CASEFOLD here
414 flags |= WM_CASEFOLD;
416 return dowild((const uchar*)pattern, (const uchar*)text, flags);
421 #define PATTERN_FLAG_NODIR 1
422 #define PATTERN_FLAG_ENDSWITH 4
423 #define PATTERN_FLAG_MUSTBEDIR 8
424 #define PATTERN_FLAG_NEGATIVE 16
428 static int fspathncmp(const char *a, const char *b, size_t count)
430 return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
433 static int simple_length(const char *match)
438 unsigned char c = *match++;
440 if (c == '\0' || is_glob_special(c))
445 static int no_wildcard(const char *string)
447 return string[simple_length(string)] == '\0';
450 static void parse_path_pattern(const char **pattern,
455 const char *p = *pattern;
460 *flags |= PATTERN_FLAG_NEGATIVE;
464 if (len && p[len - 1] == '/') {
466 *flags |= PATTERN_FLAG_MUSTBEDIR;
468 for (i = 0; i < len; i++) {
473 *flags |= PATTERN_FLAG_NODIR;
474 *nowildcardlen = simple_length(p);
476 * we should have excluded the trailing slash from 'p' too,
477 * but that's one more allocation. Instead just make sure
478 * nowildcardlen does not exceed real patternlen
480 if (*nowildcardlen > len)
481 *nowildcardlen = len;
482 if (*p == '*' && no_wildcard(p + 1))
483 *flags |= PATTERN_FLAG_ENDSWITH;
488 static void trim_trailing_spaces(char *buf)
490 char *p, *last_space = NULL;
492 for (p = buf; *p; p++)
511 static int match_basename(const char *basename, int basenamelen,
512 const char *pattern, int prefix, int patternlen,
515 if (prefix == patternlen) {
516 if (patternlen == basenamelen &&
517 !fspathncmp(pattern, basename, basenamelen))
519 } else if (flags & PATTERN_FLAG_ENDSWITH) {
520 /* "*literal" matching against "fooliteral" */
521 if (patternlen - 1 <= basenamelen &&
522 !fspathncmp(pattern + 1,
523 basename + basenamelen - (patternlen - 1),
527 // local modification: call wildmatch() directly
528 if (!wildmatch(pattern, basename, flags))
534 static int match_pathname(const char *pathname, int pathlen,
535 const char *base, int baselen,
536 const char *pattern, int prefix, int patternlen)
538 // local modification: remove local variables
541 * match with FNM_PATHNAME; the pattern has base implicitly
544 if (*pattern == '/') {
551 * baselen does not count the trailing slash. base[] may or
552 * may not end with a trailing slash though.
554 if (pathlen < baselen + 1 ||
555 (baselen && pathname[baselen] != '/') ||
556 fspathncmp(pathname, base, baselen))
559 // local modification: simplified because always baselen > 0
560 pathname += baselen + 1;
561 pathlen -= baselen + 1;
565 * if the non-wildcard part is longer than the
566 * remaining pathname, surely it cannot match.
568 if (prefix > pathlen)
571 if (fspathncmp(pattern, pathname, prefix))
574 patternlen -= prefix;
579 * If the whole pattern did not have a wildcard,
580 * then our prefix match is all we need; we
581 * do not need to call fnmatch at all.
583 if (!patternlen && !pathlen)
587 // local modification: call wildmatch() directly
588 return !wildmatch(pattern, pathname, WM_PATHNAME);
591 // Copied from git/utf8.c
593 static const char utf8_bom[] = "\357\273\277";
595 //----------------------------(IMPORT FROM GIT END)----------------------------
605 static struct pattern **pattern_list;
606 static int nr_patterns, alloced_patterns;
608 // Remember the number of patterns at each directory level
609 static int *nr_patterns_at;
610 // Track the current/max directory level;
611 static int depth, max_depth;
612 static bool debug_on;
613 static FILE *out_fp, *stat_fp;
614 static char *prefix = "";
615 static char *progname;
617 static void __attribute__((noreturn)) perror_exit(const char *s)
624 static void __attribute__((noreturn)) error_exit(const char *fmt, ...)
628 fprintf(stderr, "%s: error: ", progname);
631 vfprintf(stderr, fmt, args);
637 static void debug(const char *fmt, ...)
645 fprintf(stderr, "[DEBUG] ");
647 for (i = 0; i < depth * 2; i++)
651 vfprintf(stderr, fmt, args);
655 static void *xrealloc(void *ptr, size_t size)
657 ptr = realloc(ptr, size);
659 perror_exit(progname);
664 static void *xmalloc(size_t size)
666 return xrealloc(NULL, size);
669 // similar to last_matching_pattern_from_list() in GIT
670 static bool is_ignored(const char *path, int pathlen, int dirlen, bool is_dir)
674 // Search in the reverse order because the last matching pattern wins.
675 for (i = nr_patterns - 1; i >= 0; i--) {
676 struct pattern *p = pattern_list[i];
677 unsigned int flags = p->flags;
678 const char *gitignore_dir = p->pattern + p->patternlen + 1;
681 if ((flags & PATTERN_FLAG_MUSTBEDIR) && !is_dir)
684 if (flags & PATTERN_FLAG_NODIR) {
685 if (!match_basename(path + dirlen + 1,
686 pathlen - dirlen - 1,
693 if (!match_pathname(path, pathlen,
694 gitignore_dir, p->dirlen,
701 debug("%s: matches %s%s%s (%s/.gitignore)\n", path,
702 flags & PATTERN_FLAG_NEGATIVE ? "!" : "", p->pattern,
703 flags & PATTERN_FLAG_MUSTBEDIR ? "/" : "",
706 ignored = (flags & PATTERN_FLAG_NEGATIVE) == 0;
708 debug("Ignore: %s\n", path);
713 debug("%s: no match\n", path);
718 static void add_pattern(const char *string, const char *dir, int dirlen)
721 int patternlen, nowildcardlen;
724 parse_path_pattern(&string, &patternlen, &flags, &nowildcardlen);
729 p = xmalloc(sizeof(*p) + patternlen + dirlen + 2);
731 memcpy(p->pattern, string, patternlen);
732 p->pattern[patternlen] = 0;
733 memcpy(p->pattern + patternlen + 1, dir, dirlen);
734 p->pattern[patternlen + 1 + dirlen] = 0;
736 p->patternlen = patternlen;
737 p->nowildcardlen = nowildcardlen;
741 debug("Add pattern: %s%s%s\n",
742 flags & PATTERN_FLAG_NEGATIVE ? "!" : "", p->pattern,
743 flags & PATTERN_FLAG_MUSTBEDIR ? "/" : "");
745 if (nr_patterns >= alloced_patterns) {
746 alloced_patterns += 128;
747 pattern_list = xrealloc(pattern_list,
748 sizeof(*pattern_list) * alloced_patterns);
751 pattern_list[nr_patterns++] = p;
754 // similar to add_patterns_from_buffer() in GIT
755 static void add_patterns_from_gitignore(const char *dir, int dirlen)
758 char path[PATH_MAX], *buf, *entry;
762 pathlen = snprintf(path, sizeof(path), "%s/.gitignore", dir);
763 if (pathlen >= sizeof(path))
764 error_exit("%s: too long path was truncated\n", path);
766 fd = open(path, O_RDONLY | O_NOFOLLOW);
769 return perror_exit(path);
773 if (fstat(fd, &st) < 0)
778 buf = xmalloc(size + 1);
779 if (read(fd, buf, st.st_size) != st.st_size)
782 buf[st.st_size] = '\n';
786 debug("Parse %s\n", path);
791 if (!strncmp(entry, utf8_bom, strlen(utf8_bom)))
792 entry += strlen(utf8_bom);
794 for (i = entry - buf; i < size; i++) {
795 if (buf[i] == '\n') {
796 if (entry != buf + i && entry[0] != '#') {
797 buf[i - (i && buf[i-1] == '\r')] = 0;
798 trim_trailing_spaces(entry);
799 add_pattern(entry, dir, dirlen);
808 // Save the current number of patterns and increment the depth
809 static void increment_depth(void)
811 if (depth >= max_depth) {
813 nr_patterns_at = xrealloc(nr_patterns_at,
814 sizeof(*nr_patterns_at) * max_depth);
817 nr_patterns_at[depth] = nr_patterns;
821 // Decrement the depth, and free up the patterns of this directory level.
822 static void decrement_depth(void)
827 while (nr_patterns > nr_patterns_at[depth])
828 free(pattern_list[--nr_patterns]);
831 static void print_path(const char *path)
833 // The path always starts with "./"
834 assert(strlen(path) >= 2);
836 // Replace the root directory with a preferred prefix.
837 // This is useful for the tar command.
838 fprintf(out_fp, "%s%s\n", prefix, path + 2);
841 static void print_stat(const char *path, struct stat *st)
846 if (!S_ISREG(st->st_mode) && !S_ISLNK(st->st_mode))
849 assert(strlen(path) >= 2);
851 fprintf(stat_fp, "%c %9ld %10ld %s\n",
852 S_ISLNK(st->st_mode) ? 'l' : '-',
853 st->st_size, st->st_mtim.tv_sec, path + 2);
856 // Traverse the entire directory tree, parsing .gitignore files.
857 // Print file paths that are not tracked by git.
859 // Return true if all files under the directory are ignored, false otherwise.
860 static bool traverse_directory(const char *dir, int dirlen)
862 bool all_ignored = true;
865 debug("Enter[%d]: %s\n", depth, dir);
868 add_patterns_from_gitignore(dir, dirlen);
889 if (!strcmp(d->d_name, "..") || !strcmp(d->d_name, "."))
892 pathlen = snprintf(path, sizeof(path), "%s/%s", dir, d->d_name);
893 if (pathlen >= sizeof(path))
894 error_exit("%s: too long path was truncated\n", path);
896 if (lstat(path, &st) < 0)
899 if ((!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode)) ||
900 is_ignored(path, pathlen, dirlen, S_ISDIR(st.st_mode))) {
903 if (S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode))
904 // If all the files in a directory are ignored,
905 // let's ignore that directory as well. This
906 // will avoid empty directories in the tarball.
907 ignored = traverse_directory(path, pathlen);
915 print_stat(path, &st);
924 debug("Leave[%d]: %s\n", depth, dir);
929 static void usage(void)
932 "usage: %s [options]\n"
934 "Show files that are ignored by git\n"
937 " -d, --debug print debug messages to stderr\n"
938 " -e, --exclude PATTERN add the given exclude pattern\n"
939 " -h, --help show this help message and exit\n"
940 " -i, --ignore-case Ignore case differences between the patterns and the files\n"
941 " -o, --output FILE output the ignored files to a file (default: '-', i.e. stdout)\n"
942 " -p, --prefix PREFIX prefix added to each path (default: empty string)\n"
943 " -r, --rootdir DIR root of the source tree (default: current working directory)\n"
944 " -s, --stat FILE output the file stat of non-ignored files to a file\n",
948 static void open_output(const char *pathname, FILE **fp)
950 if (strcmp(pathname, "-")) {
951 *fp = fopen(pathname, "w");
953 perror_exit(pathname);
959 static void close_output(const char *pathname, FILE *fp)
964 error_exit("not all data was written to the output\n");
967 perror_exit(pathname);
970 int main(int argc, char *argv[])
972 const char *output = "-";
973 const char *rootdir = ".";
974 const char *stat = NULL;
976 progname = strrchr(argv[0], '/');
983 static struct option long_options[] = {
984 {"debug", no_argument, NULL, 'd'},
985 {"help", no_argument, NULL, 'h'},
986 {"ignore-case", no_argument, NULL, 'i'},
987 {"output", required_argument, NULL, 'o'},
988 {"prefix", required_argument, NULL, 'p'},
989 {"rootdir", required_argument, NULL, 'r'},
990 {"stat", required_argument, NULL, 's'},
991 {"exclude", required_argument, NULL, 'x'},
995 int c = getopt_long(argc, argv, "dhino:p:r:s:x:", long_options, NULL);
1023 add_pattern(optarg, ".", strlen("."));
1033 open_output(output, &out_fp);
1034 if (stat && stat[0])
1035 open_output(stat, &stat_fp);
1038 perror_exit(rootdir);
1040 add_pattern(".git/", ".", strlen("."));
1042 if (traverse_directory(".", strlen(".")))
1047 while (nr_patterns > 0)
1048 free(pattern_list[--nr_patterns]);
1050 free(nr_patterns_at);
1052 close_output(output, out_fp);
1054 close_output(stat, stat_fp);