Skip to content

Commit d20b422

Browse files
committed
Add -vcs_ignore flag via libgit2
1 parent 7034123 commit d20b422

20 files changed

+479
-4
lines changed

build/pkgconf.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ for LIB; do
7171
libcap)
7272
LDLIB=-lcap
7373
;;
74+
libgit2)
75+
LDLIB=-lgit2
76+
;;
7477
libselinux)
7578
LDLIB=-lselinux
7679
;;

build/prelude.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ VCAT := ${VCAT,${XV}}
6363
ALL_PKGS := \
6464
libacl \
6565
libcap \
66+
libgit2 \
6667
libselinux \
6768
liburing \
6869
oniguruma

build/with/libgit2.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <git2.h>
2+
3+
int main(void) {
4+
return git_libgit2_init();
5+
}

configure

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ External dependencies are auto-detected by default, but you can build --with or
5050
5151
--with-libacl --without-libacl
5252
--with-libcap --without-libcap
53+
--with-libgit2 --without-libgit2
5354
--with-libselinux --without-libselinux
5455
--with-liburing --without-liburing
5556
--with-oniguruma --without-oniguruma
@@ -144,7 +145,7 @@ for arg; do
144145
case "$arg" in
145146
--enable-*|--disable-*)
146147
case "$name" in
147-
libacl|libcap|libselinux|liburing|oniguruma)
148+
libacl|libcap|libgit2|libselinux|liburing|oniguruma)
148149
old="$arg"
149150
case "$arg" in
150151
--enable-*) arg="--with-${arg#--*-}" ;;
@@ -175,7 +176,7 @@ for arg; do
175176

176177
--with-*|--without-*)
177178
case "$name" in
178-
libacl|libcap|libselinux|liburing|oniguruma)
179+
libacl|libcap|libgit2|libselinux|liburing|oniguruma)
179180
set -- "$@" "WITH_$NAME=$yn"
180181
;;
181182
*)

src/bftw.c

Lines changed: 205 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
#include <sys/stat.h>
4040
#include <sys/types.h>
4141

42+
#if BFS_WITH_LIBGIT2
43+
# include <git2.h>
44+
#endif
45+
4246
/** Initialize a bftw_stat cache. */
4347
static void bftw_stat_init(struct bftw_stat *bufs, struct bfs_stat *stat_buf, struct bfs_stat *lstat_buf) {
4448
bufs->stat_buf = stat_buf;
@@ -247,6 +251,8 @@ struct bftw_file {
247251

248252
/** Cached bfs_stat() info. */
249253
struct bftw_stat stat_bufs;
254+
/** Structured path metadata. */
255+
struct bfs_path path;
250256

251257
/** The offset of this file in the full path. */
252258
size_t nameoff;
@@ -755,6 +761,7 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil
755761

756762
file->namelen = namelen;
757763
memcpy(file->name, name, namelen + 1);
764+
bfs_path_init(&file->path, parent ? &parent->path : NULL, file->name, namelen);
758765

759766
return file;
760767
}
@@ -795,6 +802,7 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) {
795802
bftw_file_close(cache, file);
796803
}
797804

805+
bfs_path_reset(&file->path);
798806
bftw_stat_recycle(cache, file);
799807

800808
varena_free(&cache->files, file, file->namelen + 1);
@@ -845,6 +853,8 @@ struct bftw_state {
845853
struct bftw_file *file;
846854
/** The previous file. */
847855
struct bftw_file *previous;
856+
/** Temporary path metadata for the current callback. */
857+
struct bfs_path pathinfo;
848858

849859
/** The currently open directory. */
850860
struct bfs_dir *dir;
@@ -970,6 +980,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg
970980
state->path = NULL;
971981
state->file = NULL;
972982
state->previous = NULL;
983+
bfs_path_init(&state->pathinfo, NULL, NULL, 0);
973984

974985
state->dir = NULL;
975986
state->de = NULL;
@@ -1723,6 +1734,19 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) {
17231734
ftwbuf->nameoff = xbaseoff(ftwbuf->path);
17241735
}
17251736

1737+
struct bfs_path *ftw_path;
1738+
if (de || !file) {
1739+
const struct bfs_path *parent_path = file ? &file->path : NULL;
1740+
const char *name = ftwbuf->path + ftwbuf->nameoff;
1741+
size_t namelen = strlen(name);
1742+
bfs_path_init(&state->pathinfo, parent_path, name, namelen);
1743+
ftw_path = &state->pathinfo;
1744+
} else {
1745+
ftw_path = &file->path;
1746+
}
1747+
1748+
ftwbuf->pathinfo = ftw_path;
1749+
17261750
ftwbuf->stat_flags = bftw_stat_flags(state, ftwbuf->depth);
17271751

17281752
if (ftwbuf->error != 0) {
@@ -2082,6 +2106,7 @@ static void bftw_drain(struct bftw_state *state, struct bftw_queue *queue) {
20822106
*/
20832107
static int bftw_state_destroy(struct bftw_state *state) {
20842108
dstrfree(state->path);
2109+
bfs_path_reset(&state->pathinfo);
20852110

20862111
struct ioq *ioq = state->ioq;
20872112
if (ioq) {
@@ -2102,6 +2127,184 @@ static int bftw_state_destroy(struct bftw_state *state) {
21022127
return state->error ? -1 : 0;
21032128
}
21042129

2130+
#if BFS_WITH_LIBGIT2
2131+
2132+
/** Data for the gitignore feature that gets attached to a bfs_path. */
2133+
struct bfs_git_data {
2134+
/** The repository this path is in. */
2135+
struct git_repository *repo;
2136+
/** The git data for the root of the repository. */
2137+
struct bfs_git_data *root_data;
2138+
/** The path associated with this data. */
2139+
const struct bfs_path *path;
2140+
/** Whether this path is the owner of the repo pointer. */
2141+
bool is_owner;
2142+
/** Whether this path is ignored. */
2143+
bool is_ignored;
2144+
/** Whether we have checked the git status of this path. */
2145+
bool is_checked;
2146+
};
2147+
2148+
/** Destructor for bfs_git_data. */
2149+
static void bfs_git_data_free(void *ptr) {
2150+
struct bfs_git_data *data = ptr;
2151+
if (!data) {
2152+
return;
2153+
}
2154+
if (data->is_owner && data->repo) {
2155+
git_repository_free(data->repo);
2156+
}
2157+
free(data);
2158+
}
2159+
2160+
/** Get the git data for a path, allocating if necessary. */
2161+
static struct bfs_git_data *bfs_git_data(const struct bfs_path *path) {
2162+
if (!path) {
2163+
return NULL;
2164+
}
2165+
2166+
struct bfs_git_data *data = bfs_path_data(path);
2167+
if (!data) {
2168+
data = ZALLOC(struct bfs_git_data);
2169+
if (data) {
2170+
data->path = path;
2171+
bfs_path_set_data(path, data, bfs_git_data_free);
2172+
}
2173+
}
2174+
return data;
2175+
}
2176+
2177+
/** Build the full path string for a bfs_path. */
2178+
static dchar *bfs_path_str(const struct bfs_path *path) {
2179+
if (!path) {
2180+
return NULL;
2181+
}
2182+
2183+
dchar *str = bfs_path_str(path->parent);
2184+
if (!str) {
2185+
return dstrndup(path->name, path->namelen);
2186+
}
2187+
2188+
if (dstrapp(&str, '/') != 0) {
2189+
dstrfree(str);
2190+
return NULL;
2191+
}
2192+
if (dstrncat(&str, path->name, path->namelen) != 0) {
2193+
dstrfree(str);
2194+
return NULL;
2195+
}
2196+
2197+
return str;
2198+
}
2199+
2200+
/** Get the path of a file relative to the git repo root. */
2201+
static dchar *bfs_git_relative_path(const struct bfs_path *path, const struct bfs_git_data *data) {
2202+
dchar *repo_root_str = bfs_path_str(data->root_data->path);
2203+
if (!repo_root_str) {
2204+
return NULL;
2205+
}
2206+
2207+
dchar *full_path = bfs_path_str(path);
2208+
if (!full_path) {
2209+
dstrfree(repo_root_str);
2210+
return NULL;
2211+
}
2212+
2213+
const char *relative_path_start = full_path;
2214+
size_t repo_root_len = strlen(repo_root_str);
2215+
2216+
// Strip the repository root directory from the full path to make it relative.
2217+
if (strcmp(repo_root_str, ".") != 0 && strncmp(full_path, repo_root_str, repo_root_len) == 0) {
2218+
relative_path_start += repo_root_len;
2219+
if (*relative_path_start == '/') {
2220+
relative_path_start++;
2221+
}
2222+
}
2223+
2224+
// Skip "./" prefix if present.
2225+
if (strncmp(relative_path_start, "./", 2) == 0) {
2226+
relative_path_start += 2;
2227+
}
2228+
2229+
dchar *relative_path = dstrdup(relative_path_start);
2230+
dstrfree(full_path);
2231+
dstrfree(repo_root_str);
2232+
return relative_path;
2233+
}
2234+
2235+
/** Update the is_ignored and is_checked values for a path. Returns is_ignored. */
2236+
static bool bfs_update_gitignore(const struct bfs_path *path, struct bfs_git_data *data, const struct bfs_ctx *ctx) {
2237+
dchar *git_path = bfs_git_relative_path(path, data);
2238+
if (!git_path) {
2239+
return false;
2240+
}
2241+
2242+
int ignored = 0;
2243+
if (git_ignore_path_is_ignored(&ignored, data->repo, git_path) == 0 && ignored) {
2244+
data->is_ignored = true;
2245+
}
2246+
2247+
dstrfree(git_path);
2248+
return data->is_ignored;
2249+
}
2250+
2251+
/** Recursively check git status for a path and its parents. */
2252+
static void bfs_path_update_git_status(const struct bfs_path *path, const struct bfs_ctx *ctx) {
2253+
if (!path) {
2254+
return;
2255+
}
2256+
2257+
struct bfs_git_data *data = bfs_git_data(path);
2258+
if (!data || data->is_checked) {
2259+
return;
2260+
}
2261+
2262+
// Recurse to ensure parent is checked first.
2263+
bfs_path_update_git_status(path->parent, ctx);
2264+
2265+
// Inherit from parent
2266+
if (path->parent) {
2267+
struct bfs_git_data *parent_data = bfs_path_data(path->parent);
2268+
if (parent_data) {
2269+
data->repo = parent_data->repo;
2270+
data->root_data = parent_data->root_data;
2271+
}
2272+
}
2273+
2274+
if (data->repo && bfs_update_gitignore(path, data, ctx)) {
2275+
return;
2276+
}
2277+
2278+
// Check for a new repository at the current path.
2279+
dchar *search_path_str = bfs_path_str(path);
2280+
if (search_path_str) {
2281+
struct git_repository *opened_repo = NULL;
2282+
// Only the roots of the search need to check for git repos above the search dir.
2283+
unsigned int repo_open_flags = path->parent ? GIT_REPOSITORY_OPEN_NO_SEARCH : 0;
2284+
if (git_repository_open_ext(&opened_repo, search_path_str, repo_open_flags, NULL) != 0) {
2285+
opened_repo = NULL;
2286+
}
2287+
2288+
if (opened_repo) {
2289+
data->repo = opened_repo;
2290+
data->is_owner = true;
2291+
data->root_data = data;
2292+
}
2293+
}
2294+
2295+
dstrfree(search_path_str);
2296+
2297+
data->is_checked = true;
2298+
}
2299+
2300+
bool bftw_is_gitignored(const struct BFTW *ftwbuf, const struct bfs_ctx *ctx) {
2301+
bfs_path_update_git_status(ftwbuf->pathinfo, ctx);
2302+
struct bfs_git_data *data = bfs_git_data(ftwbuf->pathinfo);
2303+
return data && data->is_ignored;
2304+
}
2305+
2306+
#endif // BFS_WITH_LIBGIT2
2307+
21052308
/**
21062309
* Shared implementation for all search strategies.
21072310
*/
@@ -2293,7 +2496,7 @@ static int bftw_ids(const struct bftw_args *args) {
22932496
}
22942497
}
22952498

2296-
done:
2499+
done:
22972500
return bftw_ids_destroy(&state);
22982501
}
22992502

@@ -2325,7 +2528,7 @@ static int bftw_eds(const struct bftw_args *args) {
23252528
bftw_impl(&state.nested);
23262529
}
23272530

2328-
done:
2531+
done:
23292532
return bftw_ids_destroy(&state);
23302533
}
23312534

0 commit comments

Comments
 (0)