vcs-ignore: Query Git ignore rules and traverse visible repository entries.

[ bsd3, development, library, program ] [ Propose Tags ] [ Report a vulnerability ]

vcs-ignore provides lazy Git ignore queries and a pruning, early-stopping repository traversal built on one shared rule engine.


[Skip to Readme]

Modules

[Last Documentation]

  • Data
    • VCS
      • Data.VCS.Ignore
        • Data.VCS.Ignore.Git
        • Data.VCS.Ignore.Types

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.0.1.0, 0.0.2.0, 0.1.0.0
Change log CHANGELOG.md
Dependencies base (>=4.20 && <4.23), containers (>=0.6 && <0.9), directory (>=1.3.8 && <1.4), filepath (>=1.4.2 && <1.6), Glob (>=0.10.2 && <0.11), optparse-applicative (>=0.18 && <0.19), text (>=2.0 && <2.2), vcs-ignore [details]
License BSD-3-Clause
Copyright Copyright (c) 2020-2026 Vaclav Svejcar
Author Vaclav Svejcar
Maintainer vaclav.svejcar@gmail.com
Uploaded by xwinus at 2026-08-31T19:42:46Z
Category Development
Home page https://github.com/xwinus/vcs-ignore
Bug tracker https://github.com/xwinus/vcs-ignore/issues
Source repo head: git clone https://github.com/xwinus/vcs-ignore
Distributions LTSHaskell:0.0.2.0, Stackage:0.0.2.0
Reverse Dependencies 1 direct, 0 indirect [details]
Executables ignore
Downloads 904 total (6 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs not available [build log]
All reported builds failed as of 2026-08-31 [all 2 reports]

Readme for vcs-ignore-0.1.0.0

[back to package description]

vcs-ignore

CI Hackage version Stackage version

vcs-ignore is a Haskell library for querying Git ignore rules and traversing the visible part of a working tree. It uses one lazy repository session for both operations: opening a repository never scans its working tree, and per-directory .gitignore files are loaded only when a visible branch needs them.

Contents

Opening a repository

Use openGitRepository when the working-tree root is already known:

import Data.VCS.Ignore

openProject :: IO GitRepository
openProject = openGitRepository "/path/to/project"

Use findGitRepository to search from a file or directory towards its parents. It returns Nothing when no enclosing Git worktree exists.

import Data.VCS.Ignore

findProject :: IO (Maybe GitRepository)
findProject = findGitRepository "/path/to/project/src/Main.hs"

Both regular .git directories and gitfiles used by linked worktrees and submodules are supported.

Checking a path

Queries take a repository-relative path and an explicit PathKind. The path does not need to exist, and the library never guesses its kind from the filesystem or a trailing slash.

import Data.VCS.Ignore

checkBuildDirectory :: IO Bool
checkBuildDirectory = do
    repo <- openGitRepository "/path/to/project"
    isIgnored repo Directory "dist"

checkGeneratedFile :: IO Bool
checkGeneratedFile = do
    repo <- openGitRepository "/path/to/project"
    isIgnored repo RegularFile "generated/output.log"

Absolute paths and paths containing a .. component are rejected. . denotes the repository root and is never ignored.

Listing visible entries

listRepo returns non-ignored files and directories. Each path is relative to the repository root, and the root itself is not included.

import Data.VCS.Ignore

listVisibleFiles :: IO [FilePath]
listVisibleFiles = do
    repo <- openGitRepository "/path/to/project"
    entries <- listRepo repo
    pure
        [ entryPath entry
        | entry <- entries
        , entryKind entry == RegularFile
        ]

listRepo intentionally materializes its result. Use forRepo_ or foldRepo for large repositories when a complete list is unnecessary.

Processing without building a list

forRepo_ performs an action for every non-ignored entry without accumulating the results.

import Data.VCS.Ignore
import System.FilePath ((</>))

printVisiblePaths :: IO ()
printVisiblePaths = do
    repo <- openGitRepository "/path/to/project"
    forRepo_ repo $ \entry ->
        putStrLn (repositoryRoot repo </> entryPath entry)

The callback receives the kind already discovered by the walker. Directory symlinks are reported as SymbolicLink and are never followed.

Pruning and stopping early

foldRepo is the underlying traversal primitive. Prune skips a visible directory for caller-specific reasons, while Stop terminates the entire traversal immediately.

import Data.VCS.Ignore
import System.FilePath (takeFileName)

countAtMost :: Int -> GitRepository -> IO (WalkResult Int)
countAtMost limit repo =
    foldRepo repo 0 $ \count entry ->
        if entryKind entry == Directory
            && takeFileName (entryPath entry) == ".cache"
            then pure (count, Prune)
            else
                if count >= limit
                    then pure (count, Stop)
                    else pure (count + 1, Continue)

Traversal is depth-first and pre-order. Sibling order is intentionally unspecified. Prune on a non-directory is equivalent to Continue.

Traversal and cache semantics

  • Ignored entries are not passed to callbacks.
  • Ignored directories are never opened or scanned.
  • Git metadata belonging to the opened repository is never emitted.
  • The default XDG global ignore file and info/exclude are loaded when the repository is opened.
  • A visible directory's .gitignore is loaded on first use and at most once per repository session.
  • A nested .gitignore is never loaded below an ignored or caller-pruned directory.
  • Later rule changes become visible after opening a new GitRepository.
  • Missing, symlinked, and non-regular .gitignore files are treated as empty. Other I/O failures are reported instead of being silently ignored.

These rules ensure that individual queries and traversal use the same Git precedence and ignored-parent behavior.

The current matcher intentionally does not evaluate Git configuration. In particular, core.excludesFile, core.ignoreCase, conditional config includes, and index-backed ignore files in sparse checkouts are not supported. It reads the default $XDG_CONFIG_HOME/git/ignore (or its platform equivalent) and uses case-sensitive matching. Paths use Haskell FilePath; exact round-tripping of arbitrary byte-string filenames is not guaranteed.

Migrating from 0.0.x

Version 0.1.0.0 replaces the eager and lazy APIs with one repository session:

Before Now
scanRepo @Git root openGitRepository root
openGitIgnoreMatcher root openGitRepository root
isIgnored repo path isIgnored repo kind path
isIgnoredPath matcher kind path isIgnored repo kind path
repoRoot repo repositoryRoot repo
listRepo :: IO [FilePath] listRepo :: IO [Entry]
list-returning callback traversal foldRepo, walkRepo, or forRepo_

The Repo type class, eager Git value, scanRepo, and public pattern and filesystem helpers have been removed. Paths returned in Entry remain repository-relative.

Using the executable

The package includes an executable named ignore. Run it from inside a Git working tree to check a path:

$ ignore --help
vcs-ignore, v0.1.0.0 :: https://github.com/xwinus/vcs-ignore

Usage: ignore (-p|--path PATH) [--debug] [-v|--version] [--numeric-version]

The command exits with status 0 when the path is ignored and status 1 when it is visible or no repository is found.

$ ignore -p generated/output.log
Found repository at: /path/to/project
Path 'generated/output.log' IS ignored

$ echo $?
0