|
Home - Old Man Programmer
| Displaying projects/tree/filter.c
/* $Copyright: $
* Copyright (c) 1996 - 2026 by Steve Baker (steve.baker.llc@gmail.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "tree.h"
extern char xpattern[PATH_MAX];
struct ignorefile *filterstack = NULL;
void gittrim(char *s)
{
ssize_t i, e = (ssize_t)strlen(s)-1;
if (e < 0) return;
while (e > 0 && (s[e] == '\n' || s[e] == '\r')) e--;
for(i = e; i >= 0; i--) {
if (s[i] != ' ') break;
if (i && s[i-1] != '\\') e--;
}
s[e+1] = '\0';
for(i = e = 0; s[i] != '\0';) {
if (s[i] == '\\') i++;
s[e++] = s[i++];
}
s[e] = '\0';
}
struct pattern *new_pattern(char *pattern)
{
struct pattern *p = xmalloc(sizeof(struct pattern));
char *sl;
p->pattern = scopy(pattern + ((pattern[0] == '/')? 1 : 0));
sl = strchr(pattern, '/');
p->relative = (sl == NULL || (sl && !*(sl+1)));
p->next = NULL;
return p;
}
bool is_file(const char *path)
{
struct stat st;
if (stat(path, &st) < 0) return false;
return S_ISREG(st.st_mode);
}
bool is_dir(const char *path)
{
struct stat st;
if (stat(path, &st) < 0) return false;
return S_ISDIR(st.st_mode);
}
/**
* Search up the directory tree for .gitignore files, stopping at a directory
* that contains a .git directory, or at /, whichever occurs first. The depth
* parameter is just a sanity check to insure we can't get into a loop somehow,
* even though that should be impossible.
*/
struct ignorefile *gitignore_search(const char *startpath, int depth)
{
struct ignorefile *pign = NULL, *ign = NULL;
char path[PATH_MAX+1], rpath[PATH_MAX+1];
if (realpath(startpath, rpath) == NULL) return NULL;
// Stop when we hit a directory with a .git directory. we'll assume it's the
// git root:
snprintf(path, PATH_MAX, "%.*s/.git", PATH_MAX-6, rpath);
if (is_dir(path)) {
// Add it's .git/config/exclude
snprintf(path, PATH_MAX, "%.*s/.git/info/exclude", PATH_MAX-21, rpath);
if (is_file(path)) push_filterstack(pign = new_ignorefile(path, false));
} else if (strcmp(rpath, "/") != 0 && depth < 2048) {
// Otherwise if we haven't reached /, then keep searching upward:
snprintf(path, PATH_MAX, "%.*s/..", PATH_MAX-4, rpath);
pign = gitignore_search(path, depth+1);
}
snprintf(path, PATH_MAX, "%.*s/.gitignore", PATH_MAX-12, rpath);
if (is_file(path)) push_filterstack(ign = new_ignorefile(path, false));
return ign == NULL? pign : ign;
}
struct ignorefile *new_ignorefile(const char *path, bool checkparents)
{
char buf[PATH_MAX];
struct ignorefile *ig;
struct pattern *remove = NULL, *remend, *p;
struct pattern *reverse = NULL, *revend;
int rev;
FILE *fp;
if (!is_file(path)) {
snprintf(buf, PATH_MAX, "%s/.gitignore", path);
fp = fopen(buf, "r");
// This probably will never actually happen anymore:
if (fp == NULL && checkparents) {
return gitignore_search(path, 0);
}
} else fp = fopen(path, "r");
if (fp == NULL) return NULL;
while (fgets(buf, PATH_MAX, fp) != NULL) {
if (buf[0] == '#') continue;
rev = (buf[0] == '!');
gittrim(buf);
if (strlen(buf) == 0) continue;
p = new_pattern(buf + (rev? 1 : 0));
if (rev) {
if (reverse == NULL) reverse = revend = p;
else {
revend->next = p;
revend = p;
}
} else {
if (remove == NULL) remove = remend = p;
else {
remend->next = p;
remend = p;
}
}
}
fclose(fp);
ig = xmalloc(sizeof(struct ignorefile));
ig->remove = remove;
ig->reverse = reverse;
ig->path = scopy(path);
ig->next = NULL;
return ig;
}
void push_filterstack(struct ignorefile *ig)
{
if (ig == NULL) return;
ig->next = filterstack;
filterstack = ig;
}
struct ignorefile *pop_filterstack(void)
{
struct ignorefile *ig;
struct pattern *p, *c;
ig = filterstack;
if (ig == NULL) return NULL;
filterstack = filterstack->next;
for(p=c=ig->remove; p != NULL; c = p) {
p=p->next;
free(c->pattern);
}
for(p=c=ig->reverse; p != NULL; c = p) {
p=p->next;
free(c->pattern);
}
free(ig->path);
free(ig);
return NULL;
}
struct ignorefile *flush_filterstack(void)
{
while (filterstack != NULL) pop_filterstack();
return NULL;
}
/**
* true if remove filter matches and no reverse filter matches.
*/
bool filtercheck(const char *path, const char *name, int isdir)
{
bool filter = false;
struct ignorefile *ig;
struct pattern *p;
for(ig = filterstack; !filter && ig; ig = ig->next) {
int fpos = sprintf(xpattern, "%s/", ig->path);
for(p = ig->remove; p != NULL; p = p->next) {
if (p->relative) {
if (patmatch(name, p->pattern, isdir) == 1) {
filter = true;
break;
}
} else {
sprintf(xpattern + fpos, "%s", p->pattern);
if (patmatch(path, xpattern, isdir) == 1) {
filter = true;
break;
}
}
}
}
if (!filter) return false;
for(ig = filterstack; ig; ig = ig->next) {
int fpos = sprintf(xpattern, "%s/", ig->path);
for(p = ig->reverse; p != NULL; p = p->next) {
if (p->relative) {
if (patmatch(name, p->pattern, isdir) == 1) return false;
} else {
sprintf(xpattern + fpos, "%s", p->pattern);
if (patmatch(path, xpattern, isdir) == 1) return false;
}
}
}
return true;
}
|