packages feed

vcs-ignore-0.1.0.0: README.md

# vcs-ignore

[![CI](https://github.com/xwinus/vcs-ignore/actions/workflows/ci.yml/badge.svg)](https://github.com/xwinus/vcs-ignore/actions/workflows/ci.yml)
[![Hackage version](https://img.shields.io/hackage/v/vcs-ignore.svg)](https://hackage.haskell.org/package/vcs-ignore)
[![Stackage version](https://www.stackage.org/package/vcs-ignore/badge/lts?label=Stackage)](https://www.stackage.org/package/vcs-ignore)

`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](#opening-a-repository)
- [Checking a path](#checking-a-path)
- [Listing visible entries](#listing-visible-entries)
- [Processing without building a list](#processing-without-building-a-list)
- [Pruning and stopping early](#pruning-and-stopping-early)
- [Traversal and cache semantics](#traversal-and-cache-semantics)
- [Migrating from 0.0.x](#migrating-from-00x)
- [Using the executable](#using-the-executable)

## Opening a repository

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

```haskell
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.

```haskell
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.

```haskell
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.

```haskell
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.

```haskell
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.

```haskell
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:

```console
$ 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.

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

$ echo $?
0
```