Skip to content

Commit 001f759

Browse files
committed
Add -vcs_ignore flag via libgit2
1 parent 96f98ce commit 001f759

File tree

10 files changed

+144
-3
lines changed

10 files changed

+144
-3
lines changed

build/pkgconf.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ for LIB; do
8080
oniguruma)
8181
LDLIB=-lonig
8282
;;
83+
libgit2)
84+
LDLIB=-lgit2
85+
;;
8386
*)
8487
printf 'error: Unknown package %s\n' "$LIB" >&2
8588
exit 1

build/prelude.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ ALL_PKGS := \
6767
libcap \
6868
libselinux \
6969
liburing \
70-
oniguruma
70+
oniguruma \
71+
libgit2

build/with/libgit2.c

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

configure

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ External dependencies are auto-detected by default, but you can build --with or
5353
--with-libselinux --without-libselinux
5454
--with-liburing --without-liburing
5555
--with-oniguruma --without-oniguruma
56+
--with-libgit2 --without-libgit2
5657
5758
Packaging:
5859
@@ -146,7 +147,7 @@ for arg; do
146147
case "$arg" in
147148
--enable-*|--disable-*)
148149
case "$name" in
149-
libacl|libcap|libselinux|liburing|oniguruma)
150+
libacl|libcap|libselinux|liburing|oniguruma|libgit2)
150151
old="$arg"
151152
case "$arg" in
152153
--enable-*) arg="--with-${arg#--*-}" ;;
@@ -177,7 +178,7 @@ for arg; do
177178

178179
--with-*|--without-*)
179180
case "$name" in
180-
libacl|libcap|libselinux|liburing|oniguruma)
181+
libacl|libcap|libselinux|liburing|oniguruma|libgit2)
181182
set -- "$@" "WITH_$NAME=$yn"
182183
;;
183184
*)

src/ctx.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
#include <time.h>
2525
#include <unistd.h>
2626

27+
#ifdef BFS_WITH_LIBGIT2
28+
#include <git2.h>
29+
#endif
30+
2731
struct bfs_ctx *bfs_ctx_new(void) {
2832
struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx);
2933
if (!ctx) {
@@ -69,6 +73,10 @@ struct bfs_ctx *bfs_ctx_new(void) {
6973
goto fail;
7074
}
7175

76+
#ifdef BFS_WITH_LIBGIT2
77+
git_libgit2_init();
78+
#endif
79+
7280
return ctx;
7381

7482
fail:
@@ -288,8 +296,67 @@ int bfs_ctx_free(struct bfs_ctx *ctx) {
288296

289297
free(ctx->kinds);
290298
free(ctx->argv);
299+
300+
#ifdef BFS_WITH_LIBGIT2
301+
if (ctx->git_repo) {
302+
git_repository_free(ctx->git_repo);
303+
}
304+
git_libgit2_shutdown();
305+
#endif
306+
291307
free(ctx);
292308
}
293309

294310
return ret;
295311
}
312+
313+
#ifdef BFS_WITH_LIBGIT2
314+
/** Find the git repository for a given path. */
315+
static struct git_repository *bfs_git_repository_open(const struct bfs_ctx *ctx, const char *path) {
316+
bfs_debug(ctx, DEBUG_SEARCH, "Opening git repository for '%s'\n", path);
317+
struct git_repository *repo = NULL;
318+
int error = git_repository_open_ext(&repo, path,
319+
GIT_REPOSITORY_OPEN_FROM_ENV,
320+
NULL);
321+
if (error != 0) {
322+
if (error != GIT_ENOTFOUND) {
323+
bfs_warning(ctx, "git_repository_open_ext('%s'): %s.\n",
324+
path, git_error_last()->message);
325+
}
326+
bfs_debug(ctx, DEBUG_SEARCH, "Failed to open git repository for '%s': %s\n", path, git_error_last()->message);
327+
return NULL;
328+
}
329+
bfs_debug(ctx, DEBUG_SEARCH, "Successfully opened git repository for '%s'\n", path);
330+
return repo;
331+
}
332+
333+
/** Check if a path is ignored by git. */
334+
bool bfs_git_ignored(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf) {
335+
struct bfs_ctx *mut = (struct bfs_ctx *)ctx;
336+
if (strcmp(ftwbuf->path, ".") == 0) {
337+
return false;
338+
}
339+
340+
if (!mut->git_repo) {
341+
mut->git_repo = bfs_git_repository_open(ctx, ftwbuf->root);
342+
if (!mut->git_repo) {
343+
return false;
344+
}
345+
}
346+
347+
int ignored = 0;
348+
const char *git_path = ftwbuf->path;
349+
if (git_path[0] == '.' && git_path[1] == '/') {
350+
git_path += 2; // Slice off "./"
351+
}
352+
int error = git_ignore_path_is_ignored(&ignored, mut->git_repo, git_path);
353+
if (error != 0) {
354+
bfs_warning(ctx, "%pP: git_ignore_check_path(): %s.\n",
355+
ftwbuf, git_error_last()->message);
356+
return false;
357+
}
358+
359+
bfs_debug(ctx, DEBUG_SEARCH, "%pP: Ignored by git: %s\n", ftwbuf, ignored ? "true" : "false");
360+
return ignored;
361+
}
362+
#endif // BFS_WITH_LIBGIT2

src/ctx.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <time.h>
2121

2222
struct CFILE;
23+
struct git_repository;
2324

2425
/**
2526
* The execution context for bfs.
@@ -62,6 +63,8 @@ struct bfs_ctx {
6263
int optlevel;
6364
/** Debugging flags (-D). */
6465
enum debug_flags debug;
66+
/** Whether to ignore files ignored by VCS (e.g. git). */
67+
bool ignore_vcs;
6568
/** Whether to ignore deletions that race with bfs (-ignore_readdir_race). */
6669
bool ignore_races;
6770
/** Whether to follow POSIXisms more closely ($POSIXLY_CORRECT). */
@@ -120,6 +123,11 @@ struct bfs_ctx {
120123

121124
/** The current time. */
122125
struct timespec now;
126+
127+
#ifdef BFS_WITH_LIBGIT2
128+
/** The git repository for the current root. */
129+
struct git_repository *git_repo;
130+
#endif
123131
};
124132

125133
/**
@@ -181,4 +189,7 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag);
181189
*/
182190
int bfs_ctx_free(struct bfs_ctx *ctx);
183191

192+
#ifdef BFS_WITH_LIBGIT2
193+
bool bfs_git_ignored(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf);
194+
#endif // BFS_WITH_LIBGIT2
184195
#endif // BFS_CTX_H

src/eval.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
#include <unistd.h>
5050
#include <wchar.h>
5151

52+
#ifdef BFS_WITH_LIBGIT2
53+
#include <git2.h>
54+
#endif
55+
5256
struct bfs_eval {
5357
/** Data about the current file. */
5458
const struct BFTW *ftwbuf;
@@ -1505,6 +1509,13 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
15051509
}
15061510
}
15071511

1512+
#ifdef BFS_WITH_LIBGIT2
1513+
if (ctx->ignore_vcs && bfs_git_ignored(ctx, ftwbuf)) {
1514+
state.action = BFTW_PRUNE;
1515+
goto done;
1516+
}
1517+
#endif
1518+
15081519
if (eval_expr(ctx->exclude, &state)) {
15091520
state.action = BFTW_PRUNE;
15101521
goto done;

src/parse.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,14 @@ static struct bfs_expr *parse_hidden(struct bfs_parser *parser, int arg1, int ar
15841584
return parse_nullary_test(parser, eval_hidden);
15851585
}
15861586

1587+
/**
1588+
* Parse -ignore-vcs.
1589+
*/
1590+
static struct bfs_expr *parse_ignore_vcs(struct bfs_parser *parser, int arg1, int arg2) {
1591+
parser->ctx->ignore_vcs = true;
1592+
return parse_nullary_option(parser);
1593+
}
1594+
15871595
/**
15881596
* Parse -(no)?ignore_readdir_race.
15891597
*/
@@ -2774,6 +2782,8 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2
27742782
cfprintf(cout, " Search the NUL ('\\0')-separated paths from ${bld}FILE${rs} (${bld}-${rs} for standard input).\n");
27752783
cfprintf(cout, " ${blu}-follow${rs}\n");
27762784
cfprintf(cout, " Follow all symbolic links (same as ${cyn}-L${rs})\n");
2785+
cfprintf(cout, " ${blu}-ignore_vcs${rs}\n");
2786+
cfprintf(cout, " Ignore files and directories ignored by version control systems (e.g. git)\n");
27772787
cfprintf(cout, " ${blu}-ignore_readdir_race${rs}\n");
27782788
cfprintf(cout, " ${blu}-noignore_readdir_race${rs}\n");
27792789
cfprintf(cout, " Whether to report an error if ${ex}%s${rs} detects that the file tree is modified\n",
@@ -3088,6 +3098,7 @@ static const struct table_entry parse_table[] = {
30883098
{"-group", BFS_TEST, parse_group},
30893099
{"-help", BFS_ACTION, parse_help},
30903100
{"-hidden", BFS_TEST, parse_hidden},
3101+
{"-ignore_vcs", BFS_OPTION, parse_ignore_vcs},
30913102
{"-ignore_readdir_race", BFS_OPTION, parse_ignore_races, true},
30923103
{"-ilname", BFS_TEST, parse_lname, true},
30933104
{"-iname", BFS_TEST, parse_name, true},
@@ -3807,6 +3818,10 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) {
38073818
cfprintf(cerr, " ${blu}-xdev${rs}");
38083819
}
38093820

3821+
if (ctx->ignore_vcs) {
3822+
cfprintf(cerr, " ${blu}-ignore_vcs${rs}");
3823+
}
3824+
38103825
fputs("\n", stderr);
38113826

38123827
bfs_debug(ctx, flag, "(${red}-exclude${rs}\n");
@@ -3838,6 +3853,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) {
38383853
}
38393854

38403855
ctx->argc = argc;
3856+
ctx->ignore_vcs = false;
38413857
ctx->argv = xmemdup(argv, sizeof_array(char *, argc + 1));
38423858
if (!ctx->argv) {
38433859
perror("xmemdup()");

tests/bfs/vcs-ignore.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
./tracked_file

tests/bfs/vcs-ignore.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
cd "$TEST"
2+
3+
# Require git to set up a repository
4+
command -v git >/dev/null 2>&1 || skip
5+
6+
# Initialize a repo in the temp test dir
7+
git init -q || skip
8+
9+
# Ignore names starting with "ignored"
10+
echo "ignored*" > .gitignore
11+
12+
# Create files: one ignored by git, one not
13+
"$XTOUCH" ignored_file tracked_file || skip
14+
15+
# Detect whether -ignore_vcs has any effect (i.e., built with libgit2)
16+
invoke_bfs . -name '*_file' -print >"$OUT.base" || fail
17+
invoke_bfs . -ignore_vcs -name '*_file' -print >"$OUT.ign" || fail
18+
if cmp -s "$OUT.base" "$OUT.ign"; then
19+
# No change => no libgit2 support (or feature disabled). Skip.
20+
skip
21+
fi
22+
23+
# Now snapshot-test the behavior
24+
bfs_diff . -ignore_vcs -name '*_file' -print

0 commit comments

Comments
 (0)