From d20b422d4cad3474fcd77ce6f7c7160b1d17cd83 Mon Sep 17 00:00:00 2001 From: losnappas Date: Wed, 21 May 2025 20:06:55 +0300 Subject: [PATCH] Add `-vcs_ignore` flag via libgit2 --- build/pkgconf.sh | 3 + build/prelude.mk | 1 + build/with/libgit2.c | 5 + configure | 5 +- src/bftw.c | 207 ++++++++++++++++++++++++++- src/bftw.h | 61 ++++++++ src/ctx.c | 14 ++ src/ctx.h | 2 + src/eval.c | 7 + src/parse.c | 15 ++ tests/bfs/vcs-ignore-multi-root.out | 6 + tests/bfs/vcs-ignore-multi-root.sh | 19 +++ tests/bfs/vcs-ignore-nested.out | 12 ++ tests/bfs/vcs-ignore-nested.sh | 43 ++++++ tests/bfs/vcs-ignore-other-root.out | 9 ++ tests/bfs/vcs-ignore-other-root.sh | 23 +++ tests/bfs/vcs-ignore-parent-repo.out | 5 + tests/bfs/vcs-ignore-parent-repo.sh | 24 ++++ tests/bfs/vcs-ignore.out | 3 + tests/bfs/vcs-ignore.sh | 19 +++ 20 files changed, 479 insertions(+), 4 deletions(-) create mode 100644 build/with/libgit2.c create mode 100644 tests/bfs/vcs-ignore-multi-root.out create mode 100644 tests/bfs/vcs-ignore-multi-root.sh create mode 100644 tests/bfs/vcs-ignore-nested.out create mode 100644 tests/bfs/vcs-ignore-nested.sh create mode 100644 tests/bfs/vcs-ignore-other-root.out create mode 100644 tests/bfs/vcs-ignore-other-root.sh create mode 100644 tests/bfs/vcs-ignore-parent-repo.out create mode 100644 tests/bfs/vcs-ignore-parent-repo.sh create mode 100644 tests/bfs/vcs-ignore.out create mode 100644 tests/bfs/vcs-ignore.sh diff --git a/build/pkgconf.sh b/build/pkgconf.sh index decf7069..cd67fcba 100755 --- a/build/pkgconf.sh +++ b/build/pkgconf.sh @@ -71,6 +71,9 @@ for LIB; do libcap) LDLIB=-lcap ;; + libgit2) + LDLIB=-lgit2 + ;; libselinux) LDLIB=-lselinux ;; diff --git a/build/prelude.mk b/build/prelude.mk index 6250d73d..ba5e596c 100644 --- a/build/prelude.mk +++ b/build/prelude.mk @@ -63,6 +63,7 @@ VCAT := ${VCAT,${XV}} ALL_PKGS := \ libacl \ libcap \ + libgit2 \ libselinux \ liburing \ oniguruma diff --git a/build/with/libgit2.c b/build/with/libgit2.c new file mode 100644 index 00000000..b0bf1cc7 --- /dev/null +++ b/build/with/libgit2.c @@ -0,0 +1,5 @@ +#include + +int main(void) { + return git_libgit2_init(); +} diff --git a/configure b/configure index 51b543ec..dd79ff11 100755 --- a/configure +++ b/configure @@ -50,6 +50,7 @@ External dependencies are auto-detected by default, but you can build --with or --with-libacl --without-libacl --with-libcap --without-libcap + --with-libgit2 --without-libgit2 --with-libselinux --without-libselinux --with-liburing --without-liburing --with-oniguruma --without-oniguruma @@ -144,7 +145,7 @@ for arg; do case "$arg" in --enable-*|--disable-*) case "$name" in - libacl|libcap|libselinux|liburing|oniguruma) + libacl|libcap|libgit2|libselinux|liburing|oniguruma) old="$arg" case "$arg" in --enable-*) arg="--with-${arg#--*-}" ;; @@ -175,7 +176,7 @@ for arg; do --with-*|--without-*) case "$name" in - libacl|libcap|libselinux|liburing|oniguruma) + libacl|libcap|libgit2|libselinux|liburing|oniguruma) set -- "$@" "WITH_$NAME=$yn" ;; *) diff --git a/src/bftw.c b/src/bftw.c index a884e927..49dd6f5b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -39,6 +39,10 @@ #include #include +#if BFS_WITH_LIBGIT2 +# include +#endif + /** Initialize a bftw_stat cache. */ static void bftw_stat_init(struct bftw_stat *bufs, struct bfs_stat *stat_buf, struct bfs_stat *lstat_buf) { bufs->stat_buf = stat_buf; @@ -247,6 +251,8 @@ struct bftw_file { /** Cached bfs_stat() info. */ struct bftw_stat stat_bufs; + /** Structured path metadata. */ + struct bfs_path path; /** The offset of this file in the full path. */ size_t nameoff; @@ -755,6 +761,7 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->namelen = namelen; memcpy(file->name, name, namelen + 1); + bfs_path_init(&file->path, parent ? &parent->path : NULL, file->name, namelen); return file; } @@ -795,6 +802,7 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bftw_file_close(cache, file); } + bfs_path_reset(&file->path); bftw_stat_recycle(cache, file); varena_free(&cache->files, file, file->namelen + 1); @@ -845,6 +853,8 @@ struct bftw_state { struct bftw_file *file; /** The previous file. */ struct bftw_file *previous; + /** Temporary path metadata for the current callback. */ + struct bfs_path pathinfo; /** The currently open directory. */ struct bfs_dir *dir; @@ -970,6 +980,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->path = NULL; state->file = NULL; state->previous = NULL; + bfs_path_init(&state->pathinfo, NULL, NULL, 0); state->dir = NULL; state->de = NULL; @@ -1723,6 +1734,19 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { ftwbuf->nameoff = xbaseoff(ftwbuf->path); } + struct bfs_path *ftw_path; + if (de || !file) { + const struct bfs_path *parent_path = file ? &file->path : NULL; + const char *name = ftwbuf->path + ftwbuf->nameoff; + size_t namelen = strlen(name); + bfs_path_init(&state->pathinfo, parent_path, name, namelen); + ftw_path = &state->pathinfo; + } else { + ftw_path = &file->path; + } + + ftwbuf->pathinfo = ftw_path; + ftwbuf->stat_flags = bftw_stat_flags(state, ftwbuf->depth); if (ftwbuf->error != 0) { @@ -2082,6 +2106,7 @@ static void bftw_drain(struct bftw_state *state, struct bftw_queue *queue) { */ static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); + bfs_path_reset(&state->pathinfo); struct ioq *ioq = state->ioq; if (ioq) { @@ -2102,6 +2127,184 @@ static int bftw_state_destroy(struct bftw_state *state) { return state->error ? -1 : 0; } +#if BFS_WITH_LIBGIT2 + +/** Data for the gitignore feature that gets attached to a bfs_path. */ +struct bfs_git_data { + /** The repository this path is in. */ + struct git_repository *repo; + /** The git data for the root of the repository. */ + struct bfs_git_data *root_data; + /** The path associated with this data. */ + const struct bfs_path *path; + /** Whether this path is the owner of the repo pointer. */ + bool is_owner; + /** Whether this path is ignored. */ + bool is_ignored; + /** Whether we have checked the git status of this path. */ + bool is_checked; +}; + +/** Destructor for bfs_git_data. */ +static void bfs_git_data_free(void *ptr) { + struct bfs_git_data *data = ptr; + if (!data) { + return; + } + if (data->is_owner && data->repo) { + git_repository_free(data->repo); + } + free(data); +} + +/** Get the git data for a path, allocating if necessary. */ +static struct bfs_git_data *bfs_git_data(const struct bfs_path *path) { + if (!path) { + return NULL; + } + + struct bfs_git_data *data = bfs_path_data(path); + if (!data) { + data = ZALLOC(struct bfs_git_data); + if (data) { + data->path = path; + bfs_path_set_data(path, data, bfs_git_data_free); + } + } + return data; +} + +/** Build the full path string for a bfs_path. */ +static dchar *bfs_path_str(const struct bfs_path *path) { + if (!path) { + return NULL; + } + + dchar *str = bfs_path_str(path->parent); + if (!str) { + return dstrndup(path->name, path->namelen); + } + + if (dstrapp(&str, '/') != 0) { + dstrfree(str); + return NULL; + } + if (dstrncat(&str, path->name, path->namelen) != 0) { + dstrfree(str); + return NULL; + } + + return str; +} + +/** Get the path of a file relative to the git repo root. */ +static dchar *bfs_git_relative_path(const struct bfs_path *path, const struct bfs_git_data *data) { + dchar *repo_root_str = bfs_path_str(data->root_data->path); + if (!repo_root_str) { + return NULL; + } + + dchar *full_path = bfs_path_str(path); + if (!full_path) { + dstrfree(repo_root_str); + return NULL; + } + + const char *relative_path_start = full_path; + size_t repo_root_len = strlen(repo_root_str); + + // Strip the repository root directory from the full path to make it relative. + if (strcmp(repo_root_str, ".") != 0 && strncmp(full_path, repo_root_str, repo_root_len) == 0) { + relative_path_start += repo_root_len; + if (*relative_path_start == '/') { + relative_path_start++; + } + } + + // Skip "./" prefix if present. + if (strncmp(relative_path_start, "./", 2) == 0) { + relative_path_start += 2; + } + + dchar *relative_path = dstrdup(relative_path_start); + dstrfree(full_path); + dstrfree(repo_root_str); + return relative_path; +} + +/** Update the is_ignored and is_checked values for a path. Returns is_ignored. */ +static bool bfs_update_gitignore(const struct bfs_path *path, struct bfs_git_data *data, const struct bfs_ctx *ctx) { + dchar *git_path = bfs_git_relative_path(path, data); + if (!git_path) { + return false; + } + + int ignored = 0; + if (git_ignore_path_is_ignored(&ignored, data->repo, git_path) == 0 && ignored) { + data->is_ignored = true; + } + + dstrfree(git_path); + return data->is_ignored; +} + +/** Recursively check git status for a path and its parents. */ +static void bfs_path_update_git_status(const struct bfs_path *path, const struct bfs_ctx *ctx) { + if (!path) { + return; + } + + struct bfs_git_data *data = bfs_git_data(path); + if (!data || data->is_checked) { + return; + } + + // Recurse to ensure parent is checked first. + bfs_path_update_git_status(path->parent, ctx); + + // Inherit from parent + if (path->parent) { + struct bfs_git_data *parent_data = bfs_path_data(path->parent); + if (parent_data) { + data->repo = parent_data->repo; + data->root_data = parent_data->root_data; + } + } + + if (data->repo && bfs_update_gitignore(path, data, ctx)) { + return; + } + + // Check for a new repository at the current path. + dchar *search_path_str = bfs_path_str(path); + if (search_path_str) { + struct git_repository *opened_repo = NULL; + // Only the roots of the search need to check for git repos above the search dir. + unsigned int repo_open_flags = path->parent ? GIT_REPOSITORY_OPEN_NO_SEARCH : 0; + if (git_repository_open_ext(&opened_repo, search_path_str, repo_open_flags, NULL) != 0) { + opened_repo = NULL; + } + + if (opened_repo) { + data->repo = opened_repo; + data->is_owner = true; + data->root_data = data; + } + } + + dstrfree(search_path_str); + + data->is_checked = true; +} + +bool bftw_is_gitignored(const struct BFTW *ftwbuf, const struct bfs_ctx *ctx) { + bfs_path_update_git_status(ftwbuf->pathinfo, ctx); + struct bfs_git_data *data = bfs_git_data(ftwbuf->pathinfo); + return data && data->is_ignored; +} + +#endif // BFS_WITH_LIBGIT2 + /** * Shared implementation for all search strategies. */ @@ -2293,7 +2496,7 @@ static int bftw_ids(const struct bftw_args *args) { } } -done: + done: return bftw_ids_destroy(&state); } @@ -2325,7 +2528,7 @@ static int bftw_eds(const struct bftw_args *args) { bftw_impl(&state.nested); } -done: + done: return bftw_ids_destroy(&state); } diff --git a/src/bftw.h b/src/bftw.h index 8b3ed7f0..4ec56407 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -13,6 +13,54 @@ #include +/** Destructor for bfs_path-associated data. */ +typedef void (*bfs_path_data_dtor)(void *data); + +/** A node in a bfs traversal path. */ +struct bfs_path { + const struct bfs_path *parent; + const char *name; + size_t namelen; + void *data; + bfs_path_data_dtor dtor; +}; + +static inline void bfs_path_init(struct bfs_path *path, const struct bfs_path *parent, + const char *name, size_t namelen) { + path->parent = parent; + path->name = name; + path->namelen = namelen; + path->data = NULL; + path->dtor = NULL; +} + +static inline void *bfs_path_data(const struct bfs_path *path) { + return path ? path->data : NULL; +} + +static inline void bfs_path_set_data(const struct bfs_path *path, void *data, bfs_path_data_dtor dtor) { + struct bfs_path *mut = (struct bfs_path *)path; + if (!mut) { + return; + } + if (mut->dtor && mut->data && mut->data != data) { + mut->dtor(mut->data); + } + mut->data = data; + mut->dtor = dtor; +} + +static inline void bfs_path_reset(struct bfs_path *path) { + if (!path) { + return; + } + if (path->dtor && path->data) { + path->dtor(path->data); + } + path->data = NULL; + path->dtor = NULL; +} + /** * Possible visit occurrences. */ @@ -45,6 +93,8 @@ struct BFTW { const char *path; /** The string offset of the filename. */ size_t nameoff; + /** Structured metadata for the current path component. */ + const struct bfs_path *pathinfo; /** The root path passed to bftw(). */ const char *root; @@ -69,6 +119,11 @@ struct BFTW { enum bfs_stat_flags stat_flags; /** Cached bfs_stat() info. */ struct bftw_stat stat_bufs; + +#if BFS_WITH_LIBGIT2 + /** For internal use by bftw_is_gitignored(). */ + const void *internal; +#endif }; /** @@ -135,6 +190,12 @@ enum bftw_action { */ typedef enum bftw_action bftw_callback(const struct BFTW *ftwbuf, void *ptr); +#if BFS_WITH_LIBGIT2 +struct bfs_ctx; +/** Check if a file is ignored by git. */ +bool bftw_is_gitignored(const struct BFTW *ftwbuf, const struct bfs_ctx *ctx); +#endif + /** * Flags that control bftw() behavior. */ diff --git a/src/ctx.c b/src/ctx.c index 05baa1d6..012dc2ea 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -24,6 +24,11 @@ #include #include +#if BFS_WITH_LIBGIT2 +# include + +#endif + struct bfs_ctx *bfs_ctx_new(void) { struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx); if (!ctx) { @@ -69,6 +74,10 @@ struct bfs_ctx *bfs_ctx_new(void) { goto fail; } +#if BFS_WITH_LIBGIT2 + git_libgit2_init(); +#endif + return ctx; fail: @@ -288,6 +297,11 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { free(ctx->kinds); free(ctx->argv); + +#if BFS_WITH_LIBGIT2 + git_libgit2_shutdown(); +#endif + free(ctx); } diff --git a/src/ctx.h b/src/ctx.h index 908338f7..a12080e8 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -62,6 +62,8 @@ struct bfs_ctx { int optlevel; /** Debugging flags (-D). */ enum debug_flags debug; + /** Whether to ignore files ignored by VCS (e.g. git). */ + bool ignore_vcs; /** Whether to ignore deletions that race with bfs (-ignore_readdir_race). */ bool ignore_races; /** Whether to follow POSIXisms more closely ($POSIXLY_CORRECT). */ diff --git a/src/eval.c b/src/eval.c index 8b624198..5bbd78f6 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1505,6 +1505,13 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) { } } +#if BFS_WITH_LIBGIT2 + if (ctx->ignore_vcs && bftw_is_gitignored(ftwbuf, ctx)) { + state.action = BFTW_PRUNE; + goto done; + } +#endif + if (eval_expr(ctx->exclude, &state)) { state.action = BFTW_PRUNE; goto done; diff --git a/src/parse.c b/src/parse.c index febab7f6..2f185a76 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1611,6 +1611,14 @@ static struct bfs_expr *parse_hidden(struct bfs_parser *parser, int arg1, int ar return parse_nullary_test(parser, eval_hidden); } +/** + * Parse -ignore_vcs. + */ +static struct bfs_expr *parse_ignore_vcs(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->ignore_vcs = true; + return parse_nullary_option(parser); +} + /** * Parse -(no)?ignore_readdir_race. */ @@ -2801,6 +2809,8 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2 cfprintf(cout, " Search the NUL ('\\0')-separated paths from ${bld}FILE${rs} (${bld}-${rs} for standard input).\n"); cfprintf(cout, " ${blu}-follow${rs}\n"); cfprintf(cout, " Follow all symbolic links (same as ${cyn}-L${rs})\n"); + cfprintf(cout, " ${blu}-ignore_vcs${rs}\n"); + cfprintf(cout, " Ignore files and directories ignored by version control systems (e.g. git)\n"); cfprintf(cout, " ${blu}-ignore_readdir_race${rs}\n"); cfprintf(cout, " ${blu}-noignore_readdir_race${rs}\n"); cfprintf(cout, " Whether to report an error if ${ex}%s${rs} detects that the file tree is modified\n", @@ -3115,6 +3125,7 @@ static const struct table_entry parse_table[] = { {"-group", BFS_TEST, parse_group}, {"-help", BFS_ACTION, parse_help}, {"-hidden", BFS_TEST, parse_hidden}, + {"-ignore_vcs", BFS_OPTION, parse_ignore_vcs}, {"-ignore_readdir_race", BFS_OPTION, parse_ignore_races, true}, {"-ilname", BFS_TEST, parse_lname, true}, {"-iname", BFS_TEST, parse_name, true}, @@ -3833,6 +3844,9 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { if ((ctx->flags & (BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) == BFTW_PRUNE_MOUNTS) { cfprintf(cerr, " ${blu}-xdev${rs}"); } + if (ctx->ignore_vcs) { + cfprintf(cerr, " ${blu}-ignore_vcs${rs}"); + } fputs("\n", stderr); @@ -3865,6 +3879,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } ctx->argc = argc; + ctx->ignore_vcs = false; ctx->argv = xmemdup(argv, sizeof_array(char *, argc + 1)); if (!ctx->argv) { perror("xmemdup()"); diff --git a/tests/bfs/vcs-ignore-multi-root.out b/tests/bfs/vcs-ignore-multi-root.out new file mode 100644 index 00000000..3e7a6eb8 --- /dev/null +++ b/tests/bfs/vcs-ignore-multi-root.out @@ -0,0 +1,6 @@ +repo1 +repo1/.gitignore +repo1/keep1 +repo2 +repo2/.gitignore +repo2/keep2 diff --git a/tests/bfs/vcs-ignore-multi-root.sh b/tests/bfs/vcs-ignore-multi-root.sh new file mode 100644 index 00000000..fc829015 --- /dev/null +++ b/tests/bfs/vcs-ignore-multi-root.sh @@ -0,0 +1,19 @@ +cd "$TEST" || exit + +invoke_bfs -quit -ignore_vcs || skip + +command -v git >/dev/null 2>&1 || skip + +for repo in repo1 repo2; do + mkdir "$repo" || skip + cd "$repo" || skip + + git init -q || skip + echo "ignored" > .gitignore + keep="keep${repo#repo}" + "$XTOUCH" ignored "$keep" || skip + + cd .. +done + +bfs_diff repo1 repo2 -ignore_vcs -print diff --git a/tests/bfs/vcs-ignore-nested.out b/tests/bfs/vcs-ignore-nested.out new file mode 100644 index 00000000..bc6d9591 --- /dev/null +++ b/tests/bfs/vcs-ignore-nested.out @@ -0,0 +1,12 @@ +./outer-keep +./repo +./repo/.gitignore +./repo/.gitmodules +./repo/dir_content_ignore +./repo/outer-keep +./repo/subrepo +./repo/subrepo/.gitignore +./repo/subrepo/subrepo-keep +./subrepo +./subrepo/.gitignore +./subrepo/subrepo-keep diff --git a/tests/bfs/vcs-ignore-nested.sh b/tests/bfs/vcs-ignore-nested.sh new file mode 100644 index 00000000..7c8e6937 --- /dev/null +++ b/tests/bfs/vcs-ignore-nested.sh @@ -0,0 +1,43 @@ +cd "$TEST" || exit + +invoke_bfs -quit -ignore_vcs || skip + +# Require git for repository setup +command -v git >/dev/null 2>&1 || skip + +( + mkdir subrepo + cd subrepo || fail + git init -q + git config user.email "a@a.com" >/dev/null + git config user.name "a" >/dev/null + echo subrepo-ignore > .gitignore + "$XTOUCH" subrepo-keep + git add .; git commit -q -m "abc" +) + +( + mkdir repo + cd repo || fail + + git init -q + echo "outer-ignore" > .gitignore + "$XTOUCH" outer-ignore outer-keep + + env GIT_ALLOW_PROTOCOL=file git submodule add -q ../subrepo subrepo + + echo "ignored_repo" >> .gitignore + mkdir ignored_repo + cd ignored_repo || fail + git init -q + "$XTOUCH" file + cd .. + + mkdir dir_content_ignore + echo '/*' > dir_content_ignore/.gitignore + "$XTOUCH" dir_content_ignore/file +) + +"$XTOUCH" outer-keep + +bfs_diff . -ignore_vcs -mindepth 1 -print diff --git a/tests/bfs/vcs-ignore-other-root.out b/tests/bfs/vcs-ignore-other-root.out new file mode 100644 index 00000000..8c70ef47 --- /dev/null +++ b/tests/bfs/vcs-ignore-other-root.out @@ -0,0 +1,9 @@ +.. +../repo1 +../repo1/.gitignore +../repo1/keep1 +../repo2 +../repo2/.gitignore +../repo2/keep2 +../somewhere +../somewhere/file diff --git a/tests/bfs/vcs-ignore-other-root.sh b/tests/bfs/vcs-ignore-other-root.sh new file mode 100644 index 00000000..0cee6dbd --- /dev/null +++ b/tests/bfs/vcs-ignore-other-root.sh @@ -0,0 +1,23 @@ +cd "$TEST" || exit + +invoke_bfs -quit -ignore_vcs || skip + +command -v git >/dev/null 2>&1 || skip + +for repo in repo1 repo2; do + mkdir "$repo" || skip + cd "$repo" || skip + + git init -q || skip + echo "ignored" > .gitignore + keep="keep${repo#repo}" + "$XTOUCH" ignored "$keep" || skip + + cd .. +done + +mkdir somewhere +cd somewhere || fail +"$XTOUCH" file + +bfs_diff .. -ignore_vcs -print diff --git a/tests/bfs/vcs-ignore-parent-repo.out b/tests/bfs/vcs-ignore-parent-repo.out new file mode 100644 index 00000000..48b88a2d --- /dev/null +++ b/tests/bfs/vcs-ignore-parent-repo.out @@ -0,0 +1,5 @@ +./file +./level2 +./level2/file +./level2_sibling +./level2_sibling/file diff --git a/tests/bfs/vcs-ignore-parent-repo.sh b/tests/bfs/vcs-ignore-parent-repo.sh new file mode 100644 index 00000000..7583b5ac --- /dev/null +++ b/tests/bfs/vcs-ignore-parent-repo.sh @@ -0,0 +1,24 @@ +cd "$TEST" || exit 1 + +invoke_bfs -quit -ignore_vcs || skip + +# Require git to set up a repository +command -v git >/dev/null 2>&1 || skip + +# Initialize a repo in the temp test dir +git init -q + +# Ignore names starting with "ignored" +echo "ignored*" > .gitignore + +# subdir and sibling subdir +mkdir -p search_in_dir/level2 search_in_dir/level2_sibling +"$XTOUCH" ignored_file tracked_file \ + search_in_dir/file search_in_dir/ignored_file \ + search_in_dir/level2/file search_in_dir/level2/ignored_file \ + search_in_dir/level2_sibling/file search_in_dir/level2_sibling/ignored_file + +# cd into subdir, so the repo root is on parent dir +cd search_in_dir || fail +# Now snapshot-test the behavior +bfs_diff . -mindepth 1 -ignore_vcs -print diff --git a/tests/bfs/vcs-ignore.out b/tests/bfs/vcs-ignore.out new file mode 100644 index 00000000..ac5e09c8 --- /dev/null +++ b/tests/bfs/vcs-ignore.out @@ -0,0 +1,3 @@ +./.gitignore +./tracked_dir +./tracked_file diff --git a/tests/bfs/vcs-ignore.sh b/tests/bfs/vcs-ignore.sh new file mode 100644 index 00000000..f12777c3 --- /dev/null +++ b/tests/bfs/vcs-ignore.sh @@ -0,0 +1,19 @@ +cd "$TEST" || exit + +invoke_bfs -quit -ignore_vcs || skip + +# Require git to set up a repository +command -v git >/dev/null 2>&1 || skip + +# Initialize a repo in the temp test dir +git init -q + +# Ignore names starting with "ignored" +echo "ignored*" > .gitignore + +mkdir ignored_dir tracked_dir ignored_dir_empty +# Create files: one ignored by git, one not, one in ignored dir +"$XTOUCH" ignored_file tracked_file ignored_dir/file + +# Now snapshot-test the behavior +bfs_diff . -mindepth 1 -ignore_vcs -print