added GG_GIT_ALL behavior
This commit is contained in:
@@ -26,13 +26,17 @@ It is intentionally narrow:
|
|||||||
skipped. A literal match that occurs *before* the first NUL in an
|
skipped. A literal match that occurs *before* the first NUL in an
|
||||||
otherwise-text file is still reported.
|
otherwise-text file is still reported.
|
||||||
- **Recursive by default.** Given a directory it walks the whole tree.
|
- **Recursive by default.** Given a directory it walks the whole tree.
|
||||||
|
- **Git-aware by default.** When walking a tree, `gg` honours every `.gitignore`
|
||||||
|
it encounters and always skips `.git` directories — just like `git` and `rg`.
|
||||||
|
Set `GG_GIT_ALL=1` to search everything instead (equivalent to `rg
|
||||||
|
--no-ignore`).
|
||||||
- **Self-contained.** No third-party libraries, no shelling out to other
|
- **Self-contained.** No third-party libraries, no shelling out to other
|
||||||
programs. Just `libc` + `libpthread`.
|
programs. Just `libc` + `libpthread`.
|
||||||
|
|
||||||
`gg` was built to be a clean, readable reference implementation that is also
|
`gg` was built to be a clean, readable reference implementation that is also
|
||||||
fast: on a 16 GB / ~209 000-file tree it completes the equivalent of
|
fast: on a 16 GB / ~209 000-file tree it completes the equivalent of
|
||||||
`rg --no-ignore --hidden -l` in roughly **0.4 s**, about **1.5× faster than
|
`rg --no-ignore --hidden -l` in roughly **0.4 s**, about **1.5× faster than
|
||||||
`rg`** on the same machine and query, using only portable POSIX interfaces.
|
`rg`** on the same machine and query, using only portable POSIX interfaces. (With `GG_GIT_ALL` unset, the equivalent is `rg --hidden -l`.)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -113,8 +117,10 @@ gg "def main" src/main.py
|
|||||||
|
|
||||||
- **Symlinks are not followed.** Symbolic links to files or directories are
|
- **Symlinks are not followed.** Symbolic links to files or directories are
|
||||||
skipped, which prevents infinite loops on symlink cycles.
|
skipped, which prevents infinite loops on symlink cycles.
|
||||||
- **Hidden files and directories are searched.** Unlike `rg`'s default, `gg`
|
- **Hidden files and directories are searched.** `gg` does not skip hidden
|
||||||
does not consult `.gitignore` or skip hidden paths.
|
paths. It *does* consult `.gitignore` files and skips `.git` directories by
|
||||||
|
default; set `GG_GIT_ALL=1` to disable that and search everything (including
|
||||||
|
ignored and `.git` paths).
|
||||||
- **Binary files are skipped.** A file containing a NUL byte is never reported,
|
- **Binary files are skipped.** A file containing a NUL byte is never reported,
|
||||||
even if it also contains the search term (except when the match occurs before
|
even if it also contains the search term (except when the match occurs before
|
||||||
the first NUL, in which case it is reported — see FAQ).
|
the first NUL, in which case it is reported — see FAQ).
|
||||||
@@ -214,8 +220,20 @@ against symlink cycles and avoids double-counting.
|
|||||||
|
|
||||||
### Does it respect `.gitignore`?
|
### Does it respect `.gitignore`?
|
||||||
|
|
||||||
No. `gg` searches everything it can read, including hidden files and directories.
|
Yes, by default. `gg` reads every `.gitignore` found while walking and skips
|
||||||
This is why its results match `rg --no-ignore --hidden`.
|
matching files and directories, and it always skips `.git` directories — the
|
||||||
|
same rules `git` itself uses (including `!` negation, `**` globs, and
|
||||||
|
trailing-`/` directory-only patterns). Hidden files and directories are still
|
||||||
|
searched; only `.gitignore`-matched paths and `.git` are excluded.
|
||||||
|
|
||||||
|
To search everything — ignored files, `.git`, the lot — set the `GG_GIT_ALL`
|
||||||
|
environment variable (to any value) before running `gg`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
GG_GIT_ALL=1 gg "needle" .
|
||||||
|
```
|
||||||
|
|
||||||
|
With `GG_GIT_ALL` set, `gg`'s results match `rg --no-ignore --hidden`.
|
||||||
|
|
||||||
### Why not `mmap` the files?
|
### Why not `mmap` the files?
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
* Usage: gg SEARCH_TERM [FILE_OR_DIRECTORY]
|
* Usage: gg SEARCH_TERM [FILE_OR_DIRECTORY]
|
||||||
*
|
*
|
||||||
* Created by Luxferre in 2026, released into the public domain
|
* Created by Luxferre in 2026, released into the public domain
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
#ifndef _POSIX_C_SOURCE
|
#ifndef _POSIX_C_SOURCE
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <fnmatch.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -34,16 +36,39 @@ static const char *needle;
|
|||||||
static size_t needle_len;
|
static size_t needle_len;
|
||||||
static int found_any = 0;
|
static int found_any = 0;
|
||||||
static pthread_mutex_t found_mx = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t found_mx = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static int git_all = 0; /* if set, ignore .gitignore / .git entirely */
|
||||||
|
|
||||||
|
/* ---------- .gitignore handling ---------- */
|
||||||
|
typedef struct Pattern {
|
||||||
|
int negate; /* pattern began with '!' */
|
||||||
|
int dir_only; /* pattern ended with '/' (matches dirs only) */
|
||||||
|
int has_slash; /* pattern contains a '/' (anchored to its .gitignore dir) */
|
||||||
|
char *pat; /* pattern text (without '!' / trailing '/') */
|
||||||
|
size_t pat_len;
|
||||||
|
} Pattern;
|
||||||
|
|
||||||
|
typedef struct GitIgnore {
|
||||||
|
Pattern *pats;
|
||||||
|
size_t npats;
|
||||||
|
const char *rel; /* directory this ignore governs, relative to root */
|
||||||
|
struct GitIgnore *parent;
|
||||||
|
struct GitIgnore *gnext; /* global list, for cleanup */
|
||||||
|
} GitIgnore;
|
||||||
|
|
||||||
|
static GitIgnore *gi_all = NULL;
|
||||||
|
|
||||||
/* ---------- directory work queue (parallel walk + scan) ---------- */
|
/* ---------- directory work queue (parallel walk + scan) ---------- */
|
||||||
typedef struct DirJob {
|
typedef struct DirJob {
|
||||||
char *path;
|
char *path; /* full path for opendir/open */
|
||||||
|
char *rel; /* path relative to the search root ("" for root) */
|
||||||
|
GitIgnore *gi; /* active ignore set for this directory's children */
|
||||||
struct DirJob *next;
|
struct DirJob *next;
|
||||||
} DirJob;
|
} DirJob;
|
||||||
|
|
||||||
static DirJob *dir_stack = NULL;
|
static DirJob *dir_stack = NULL;
|
||||||
static pthread_mutex_t dir_mx = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t dir_mx = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
static void push_dir(const char *path) {
|
static void push_dir(const char *path, const char *rel, GitIgnore *gi) {
|
||||||
DirJob *j = malloc(sizeof *j);
|
DirJob *j = malloc(sizeof *j);
|
||||||
if(j == NULL)
|
if(j == NULL)
|
||||||
return;
|
return;
|
||||||
@@ -52,6 +77,13 @@ static void push_dir(const char *path) {
|
|||||||
free(j);
|
free(j);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
j->rel = strdup(rel);
|
||||||
|
if(j->rel == NULL) {
|
||||||
|
free(j->path);
|
||||||
|
free(j);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
j->gi = gi;
|
||||||
pthread_mutex_lock(&dir_mx);
|
pthread_mutex_lock(&dir_mx);
|
||||||
j->next = dir_stack;
|
j->next = dir_stack;
|
||||||
dir_stack = j;
|
dir_stack = j;
|
||||||
@@ -63,17 +95,283 @@ static void die(const char *msg) {
|
|||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *pop_dir(void) {
|
static void pop_dir(char **path, char **rel, GitIgnore **gi) {
|
||||||
pthread_mutex_lock(&dir_mx);
|
pthread_mutex_lock(&dir_mx);
|
||||||
DirJob *j = dir_stack;
|
DirJob *j = dir_stack;
|
||||||
if(j != NULL)
|
if(j != NULL)
|
||||||
dir_stack = j->next;
|
dir_stack = j->next;
|
||||||
pthread_mutex_unlock(&dir_mx);
|
pthread_mutex_unlock(&dir_mx);
|
||||||
if(j == NULL)
|
if(j == NULL) {
|
||||||
return NULL;
|
*path = NULL;
|
||||||
char *p = j->path;
|
*rel = NULL;
|
||||||
|
*gi = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*path = j->path;
|
||||||
|
*rel = j->rel;
|
||||||
|
*gi = j->gi;
|
||||||
free(j);
|
free(j);
|
||||||
return p;
|
}
|
||||||
|
|
||||||
|
/* Read an entire fd into a malloc'd, NUL-terminated buffer. Returns the buffer
|
||||||
|
* (caller frees) and stores the length in *len, or NULL on error. */
|
||||||
|
static char *read_all(int fd, size_t *len) {
|
||||||
|
size_t cap = 4096, n = 0;
|
||||||
|
char *buf = malloc(cap);
|
||||||
|
if(buf == NULL)
|
||||||
|
return NULL;
|
||||||
|
for(;;) {
|
||||||
|
if(n == cap) {
|
||||||
|
cap *= 2;
|
||||||
|
char *nb = realloc(buf, cap);
|
||||||
|
if(nb == NULL) {
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf = nb;
|
||||||
|
}
|
||||||
|
ssize_t r = read(fd, buf + n, cap - n);
|
||||||
|
if(r < 0) {
|
||||||
|
if(errno == EINTR)
|
||||||
|
continue;
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(r == 0)
|
||||||
|
break;
|
||||||
|
n += (size_t)r;
|
||||||
|
}
|
||||||
|
buf = realloc(buf, n + 1);
|
||||||
|
if(buf == NULL)
|
||||||
|
return NULL;
|
||||||
|
buf[n] = '\0';
|
||||||
|
*len = n;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse a .gitignore buffer into patterns, chained under `parent`. Returns the
|
||||||
|
* new GitIgnore node (sharing `parent` if the file is empty/absent). */
|
||||||
|
static GitIgnore *load_gitignore(const char *dir, GitIgnore *parent,
|
||||||
|
const char *rel) {
|
||||||
|
char path[PATH_MAX];
|
||||||
|
int n = snprintf(path, sizeof path, "%s/.gitignore", dir);
|
||||||
|
if(n < 0 || (size_t)n >= sizeof path)
|
||||||
|
return parent;
|
||||||
|
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||||
|
if(fd < 0)
|
||||||
|
return parent;
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
char *buf = read_all(fd, &len);
|
||||||
|
close(fd);
|
||||||
|
if(buf == NULL)
|
||||||
|
return parent;
|
||||||
|
|
||||||
|
Pattern *pa = NULL;
|
||||||
|
size_t np = 0, cap = 0;
|
||||||
|
char *p = buf, *end = buf + len;
|
||||||
|
while(p < end) {
|
||||||
|
char *nl = memchr(p, '\n', (size_t)(end - p));
|
||||||
|
size_t llen = (nl != NULL) ? (size_t)(nl - p) : (size_t)(end - p);
|
||||||
|
if(llen > 0 && p[llen - 1] == '\r')
|
||||||
|
llen--;
|
||||||
|
size_t s = 0;
|
||||||
|
while(s < llen && (p[s] == ' ' || p[s] == '\t'))
|
||||||
|
s++;
|
||||||
|
size_t e = llen;
|
||||||
|
while(e > s && (p[e - 1] == ' ' || p[e - 1] == '\t'))
|
||||||
|
e--;
|
||||||
|
if(s >= e || p[s] == '#') {
|
||||||
|
p = (nl != NULL) ? nl + 1 : end;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
char *t = malloc(e - s + 1);
|
||||||
|
if(t == NULL)
|
||||||
|
break;
|
||||||
|
memcpy(t, p + s, e - s);
|
||||||
|
t[e - s] = '\0';
|
||||||
|
|
||||||
|
int negate = 0, dir_only = 0;
|
||||||
|
if(t[0] == '!') {
|
||||||
|
negate = 1;
|
||||||
|
memmove(t, t + 1, strlen(t));
|
||||||
|
}
|
||||||
|
size_t tl = strlen(t);
|
||||||
|
if(tl > 0 && t[tl - 1] == '/') {
|
||||||
|
dir_only = 1;
|
||||||
|
t[tl - 1] = '\0';
|
||||||
|
tl--;
|
||||||
|
}
|
||||||
|
int has_slash = (strchr(t, '/') != NULL);
|
||||||
|
|
||||||
|
if(np == cap) {
|
||||||
|
cap = cap ? cap * 2 : 16;
|
||||||
|
Pattern *na = realloc(pa, cap * sizeof *pa);
|
||||||
|
if(na == NULL) {
|
||||||
|
free(t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pa = na;
|
||||||
|
}
|
||||||
|
pa[np].negate = negate;
|
||||||
|
pa[np].dir_only = dir_only;
|
||||||
|
pa[np].has_slash = has_slash;
|
||||||
|
pa[np].pat = t;
|
||||||
|
pa[np].pat_len = tl;
|
||||||
|
np++;
|
||||||
|
p = (nl != NULL) ? nl + 1 : end;
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
if(np == 0) {
|
||||||
|
free(pa);
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
GitIgnore *g = malloc(sizeof *g);
|
||||||
|
if(g == NULL) {
|
||||||
|
for(size_t i = 0; i < np; i++)
|
||||||
|
free(pa[i].pat);
|
||||||
|
free(pa);
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
g->pats = pa;
|
||||||
|
g->npats = np;
|
||||||
|
g->rel = strdup(rel);
|
||||||
|
g->parent = parent;
|
||||||
|
g->gnext = gi_all;
|
||||||
|
gi_all = g;
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match a single path segment `s` (len sl) against a glob `p` (len pl) where
|
||||||
|
* '*' matches a run of non-'/' chars and '?' matches one char. */
|
||||||
|
static int seg_eq(const char *p, size_t pl, const char *s, size_t sl) {
|
||||||
|
size_t i = 0, j = 0;
|
||||||
|
size_t star = SIZE_MAX, si = 0, sj = 0;
|
||||||
|
while(i < pl || j < sl) {
|
||||||
|
if(i < pl && p[i] == '*') {
|
||||||
|
star = i;
|
||||||
|
si = i + 1;
|
||||||
|
sj = j;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(i < pl && (p[i] == '?' || (j < sl && p[i] == s[j]))) {
|
||||||
|
i++;
|
||||||
|
j++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(star != SIZE_MAX) {
|
||||||
|
/* Extend what the '*' matches by one more char of s, if possible. */
|
||||||
|
if(sj + 1 > sl)
|
||||||
|
break;
|
||||||
|
i = si;
|
||||||
|
sj++;
|
||||||
|
j = sj;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return i == pl && j == sl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match a slash-separated pattern `p` against path `s`, supporting `**`. */
|
||||||
|
static int match_path(const char *p, const char *s) {
|
||||||
|
for(;;) {
|
||||||
|
if(p[0] == '*' && p[1] == '*') {
|
||||||
|
p += 2;
|
||||||
|
while(*p == '/')
|
||||||
|
p++;
|
||||||
|
if(*p == '\0')
|
||||||
|
return 1;
|
||||||
|
const char *sp = s;
|
||||||
|
for(;;) {
|
||||||
|
if(match_path(p, sp))
|
||||||
|
return 1;
|
||||||
|
while(*sp && *sp != '/')
|
||||||
|
sp++;
|
||||||
|
if(*sp == '\0')
|
||||||
|
break;
|
||||||
|
sp++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(*p == '\0')
|
||||||
|
return *s == '\0';
|
||||||
|
if(*p == '/') {
|
||||||
|
if(*s != '/')
|
||||||
|
return 0;
|
||||||
|
p++;
|
||||||
|
s++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const char *pe = p;
|
||||||
|
while(*pe && *pe != '/')
|
||||||
|
pe++;
|
||||||
|
const char *se = s;
|
||||||
|
while(*se && *se != '/')
|
||||||
|
se++;
|
||||||
|
if(!seg_eq(p, (size_t)(pe - p), s, (size_t)(se - s)))
|
||||||
|
return 0;
|
||||||
|
p = pe;
|
||||||
|
s = se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match a gitignore pattern against a path (path relative to the owning
|
||||||
|
* .gitignore directory). A leading '/' only marks the pattern as anchored to
|
||||||
|
* that directory (it is already implied by `path`), so it is stripped before
|
||||||
|
* matching. Falls back to fnmatch for patterns without `**`. */
|
||||||
|
static int git_match(const char *pat, const char *path) {
|
||||||
|
while(*pat == '/')
|
||||||
|
pat++;
|
||||||
|
if(*pat == '\0')
|
||||||
|
return *path == '\0';
|
||||||
|
if(strstr(pat, "**") == NULL)
|
||||||
|
return fnmatch(pat, path, FNM_PATHNAME) == 0;
|
||||||
|
return match_path(pat, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decide whether an entry named `name` (full relative path `entry_rel`, a
|
||||||
|
* directory iff `is_dir`) is ignored by the active ignore set `gi`. The
|
||||||
|
* deepest matching rule wins (git semantics). */
|
||||||
|
static int is_ignored(const char *entry_rel, const char *name, GitIgnore *gi,
|
||||||
|
int is_dir) {
|
||||||
|
GitIgnore *chain[1024];
|
||||||
|
int n = 0;
|
||||||
|
for(GitIgnore *g = gi; g != NULL && n < 1024; g = g->parent)
|
||||||
|
chain[n++] = g;
|
||||||
|
|
||||||
|
int ignored = 0;
|
||||||
|
for(int i = n - 1; i >= 0; i--) { /* root -> child (deepest wins) */
|
||||||
|
GitIgnore *g = chain[i];
|
||||||
|
for(size_t p = 0; p < g->npats; p++) {
|
||||||
|
Pattern *pat = &g->pats[p];
|
||||||
|
int m;
|
||||||
|
if(pat->has_slash) {
|
||||||
|
const char *rp;
|
||||||
|
if(g->rel[0] == '\0') {
|
||||||
|
rp = entry_rel;
|
||||||
|
} else {
|
||||||
|
size_t rl = strlen(g->rel);
|
||||||
|
if(strncmp(entry_rel, g->rel, rl) != 0)
|
||||||
|
continue;
|
||||||
|
if(entry_rel[rl] != '/')
|
||||||
|
continue;
|
||||||
|
rp = entry_rel + rl + 1;
|
||||||
|
}
|
||||||
|
m = git_match(pat->pat, rp);
|
||||||
|
} else {
|
||||||
|
if(pat->dir_only && !is_dir)
|
||||||
|
continue;
|
||||||
|
m = git_match(pat->pat, name);
|
||||||
|
}
|
||||||
|
if(m)
|
||||||
|
ignored = pat->negate ? 0 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- scan one open fd (chunked read + memmem, binary-skipping) ---------- */
|
/* ---------- scan one open fd (chunked read + memmem, binary-skipping) ---------- */
|
||||||
@@ -154,10 +452,19 @@ static void *worker(void *arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
char child[PATH_MAX];
|
char child[PATH_MAX];
|
||||||
|
char relbuf[PATH_MAX];
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
char *dir = pop_dir();
|
char *dir, *rel;
|
||||||
|
GitIgnore *parent_gi;
|
||||||
|
pop_dir(&dir, &rel, &parent_gi);
|
||||||
if(dir == NULL)
|
if(dir == NULL)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
GitIgnore *my_gi = parent_gi;
|
||||||
|
if(!git_all)
|
||||||
|
my_gi = load_gitignore(dir, parent_gi, rel);
|
||||||
|
|
||||||
DIR *d = opendir(dir);
|
DIR *d = opendir(dir);
|
||||||
if(d != NULL) {
|
if(d != NULL) {
|
||||||
struct dirent *ent;
|
struct dirent *ent;
|
||||||
@@ -165,13 +472,35 @@ static void *worker(void *arg) {
|
|||||||
if(strcmp(ent->d_name, ".") == 0 ||
|
if(strcmp(ent->d_name, ".") == 0 ||
|
||||||
strcmp(ent->d_name, "..") == 0)
|
strcmp(ent->d_name, "..") == 0)
|
||||||
continue;
|
continue;
|
||||||
int n = snprintf(child, sizeof child, "%s/%s", dir, ent->d_name);
|
const char *name = ent->d_name;
|
||||||
|
|
||||||
|
int n = snprintf(child, sizeof child, "%s/%s", dir, name);
|
||||||
if(n < 0 || (size_t)n >= sizeof child)
|
if(n < 0 || (size_t)n >= sizeof child)
|
||||||
continue;
|
continue;
|
||||||
|
n = snprintf(relbuf, sizeof relbuf, "%s%s%s",
|
||||||
|
rel, (rel[0] == '\0') ? "" : "/", name);
|
||||||
|
if(n < 0 || (size_t)n >= sizeof relbuf)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int is_dir = (ent->d_type == DT_DIR);
|
||||||
|
if(ent->d_type == DT_UNKNOWN) {
|
||||||
|
struct stat st;
|
||||||
|
if(lstat(child, &st) == 0)
|
||||||
|
is_dir = S_ISDIR(st.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!git_all) {
|
||||||
|
/* Always skip .git directories, just like git. */
|
||||||
|
if(strcmp(name, ".git") == 0 && is_dir)
|
||||||
|
continue;
|
||||||
|
if(is_ignored(relbuf, name, my_gi, is_dir))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(ent->d_type == DT_REG) {
|
if(ent->d_type == DT_REG) {
|
||||||
scan_file(child, &sb);
|
scan_file(child, &sb);
|
||||||
} else if(ent->d_type == DT_DIR) {
|
} else if(ent->d_type == DT_DIR) {
|
||||||
push_dir(child);
|
push_dir(child, relbuf, my_gi);
|
||||||
} else if(ent->d_type == DT_LNK) {
|
} else if(ent->d_type == DT_LNK) {
|
||||||
continue; /* do not follow symlinks: avoids cycles */
|
continue; /* do not follow symlinks: avoids cycles */
|
||||||
} else {
|
} else {
|
||||||
@@ -179,7 +508,7 @@ static void *worker(void *arg) {
|
|||||||
if(lstat(child, &st) != 0)
|
if(lstat(child, &st) != 0)
|
||||||
continue;
|
continue;
|
||||||
if(S_ISDIR(st.st_mode))
|
if(S_ISDIR(st.st_mode))
|
||||||
push_dir(child);
|
push_dir(child, relbuf, my_gi);
|
||||||
else if(S_ISREG(st.st_mode))
|
else if(S_ISREG(st.st_mode))
|
||||||
scan_file(child, &sb);
|
scan_file(child, &sb);
|
||||||
}
|
}
|
||||||
@@ -187,12 +516,27 @@ static void *worker(void *arg) {
|
|||||||
closedir(d);
|
closedir(d);
|
||||||
}
|
}
|
||||||
free(dir);
|
free(dir);
|
||||||
|
free(rel);
|
||||||
}
|
}
|
||||||
free(sb.buf);
|
free(sb.buf);
|
||||||
free(sb.carry);
|
free(sb.carry);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void free_gitignores(void) {
|
||||||
|
GitIgnore *g = gi_all;
|
||||||
|
while(g != NULL) {
|
||||||
|
GitIgnore *next = g->gnext;
|
||||||
|
for(size_t i = 0; i < g->npats; i++)
|
||||||
|
free(g->pats[i].pat);
|
||||||
|
free(g->pats);
|
||||||
|
free((void *)g->rel);
|
||||||
|
free(g);
|
||||||
|
g = next;
|
||||||
|
}
|
||||||
|
gi_all = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
if(argc < 2 || argc > 3) {
|
if(argc < 2 || argc > 3) {
|
||||||
fprintf(stderr, "usage: %s SEARCH_TERM [FILE_OR_DIRECTORY]\n", argv[0]);
|
fprintf(stderr, "usage: %s SEARCH_TERM [FILE_OR_DIRECTORY]\n", argv[0]);
|
||||||
@@ -206,6 +550,8 @@ int main(int argc, char **argv) {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
git_all = (getenv("GG_GIT_ALL") != NULL);
|
||||||
|
|
||||||
char cwd[PATH_MAX];
|
char cwd[PATH_MAX];
|
||||||
const char *root;
|
const char *root;
|
||||||
if(argc == 3) {
|
if(argc == 3) {
|
||||||
@@ -239,7 +585,7 @@ int main(int argc, char **argv) {
|
|||||||
scan_file(root, &(ScanBufs){ malloc(IO_BUFSZ + needle_len),
|
scan_file(root, &(ScanBufs){ malloc(IO_BUFSZ + needle_len),
|
||||||
malloc(needle_len) });
|
malloc(needle_len) });
|
||||||
} else {
|
} else {
|
||||||
push_dir(root);
|
push_dir(root, "", NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
long nproc = sysconf(_SC_NPROCESSORS_ONLN);
|
long nproc = sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
@@ -259,5 +605,6 @@ int main(int argc, char **argv) {
|
|||||||
for(int i = 0; i < started; i++)
|
for(int i = 0; i < started; i++)
|
||||||
pthread_join(t[i], NULL);
|
pthread_join(t[i], NULL);
|
||||||
|
|
||||||
|
free_gitignores();
|
||||||
return found_any ? 0 : 1;
|
return found_any ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user