Skip to content

Glob Pattern Tester

Test glob and .gitignore patterns against a list of paths and see exactly which rule matched each one — including which negation won.

Last matching rule wins, and a name with no slash matches at any depth.

.gitignore

one per line
  • node_modules matches at any depth, because it contains no slash
  • dist matches at any depth, because it contains no slash
  • build/ matches directories only; matches at any depth, because it contains no slash
  • *.log matches at any depth, because it contains no slash; `*` matches any characters except `/`
  • !important.log re-includes files an earlier rule excluded; matches at any depth, because it contains no slash
  • .env* matches at any depth, because it contains no slash; `*` matches any characters except `/`

Paths

one per line; end with / for a directory

Results

8 of 14 ignored
  • keptsrc/index.ts
  • keptsrc/lib/engines/json.ts
  • keptsrc/lib/engines/json.test.ts
  • keptsrc/components/ui/button.tsx
  • ignoreddist/index.jsby dist
  • ignorednode_modules/react/index.jsby node_modules
  • ignoredpackages/web/node_modules/lodash/index.jsby node_modules
  • ignored.envby .env*
  • ignored.env.localby .env*
  • ignoredbuild/by build/
  • ignoredbuild/assets/app.cssby build/
  • keptdocs/README.md
  • ignoreddebug.logby *.log
  • keptimportant.logby !important.log

Matching runs in your browser — no repository is read and nothing is uploaded. The rule shown against each path is the one that decided it, which in .gitignore mode is the last rule that matched.

About the Glob Pattern Tester

Paste a list of paths, paste your patterns, and see exactly which ones match — and which rule decided each one. Glob and .gitignore patterns are short enough to look obvious and subtle enough to be wrong, and the usual way to find out is to commit something you meant to exclude.

This glob pattern tester covers both dialects, because they are genuinely different languages that happen to share syntax. In a glob, a pattern matches a whole path and `*` stops at a slash. In .gitignore, a pattern with no interior slash matches a *name* at any depth, a leading slash anchors to the repository root, a trailing slash restricts to directories, a leading `!` re-includes, and the last matching rule wins. Confusing the two is why `build/*` and `build/` behave differently and why your `!keep.log` did nothing.

Every path is annotated with the rule that decided it. In .gitignore mode that is the last rule to match, which is what makes negation work — and what makes it silently fail when the negation is written above the rule it is supposed to override.

  • Both dialects: shell/build-tool globs and .gitignore semantics
  • The deciding rule shown against every path, so precedence is visible
  • Plain-English explanation of what each pattern was read as
  • **, *, ?, [abc], [!abc] and {a,b} alternation
  • Directory-only patterns, root anchoring and ! negation
  • Invalid patterns reported with a reason instead of matching nothing

How to use it

  1. Choose .gitignore rules or glob patterns.
  2. Paste your patterns, one per line. Comments and blank lines are skipped, as git does.
  3. Paste the paths to test — one per line, with a trailing slash to mark a directory.
  4. Read the results: ignored or matched paths are highlighted, with the rule that decided each one.
  5. Read the explanations under the pattern box if a rule is not doing what you expected.

Real-world use cases

Backend & full-stack developers

Work out why a build tool's include/exclude glob is picking up files it should not, by testing the exact pattern against the exact paths in question rather than rerunning the build repeatedly.

DevOps & platform engineers

Debug a .gitignore or .dockerignore that is not excluding what it should, seeing exactly which rule — or lack of one — decided each path's fate.

Open source maintainers

Check a new contributor's .gitignore addition before merging it, confirming a broad pattern like `logs/` will not also match a genuinely useful `logs/` directory nested somewhere unexpected.

Tooling & build engineers

Verify a glob used in a bundler config, a lint-staged pattern or a CI path filter matches only the files it is meant to, before it silently skips or includes the wrong set in a pipeline nobody watches closely.

New team members onboarding

Understand why a monorepo's root .gitignore excludes something in a nested package, by seeing which rule actually matched — since a name-only pattern applies at every depth, which is not always obvious.

Examples

A name with no slash matches at any depth

Input
node_modules
—
node_modules/react/index.js
packages/web/node_modules/lodash/index.js
Output
both ignored

This is the .gitignore rule that surprises people coming from globs, and it is the reason a bare `node_modules` is enough in a monorepo.

Negation, and why order matters

Input
*.log
!important.log
—
debug.log
important.log
Output
debug.log      ignored by *.log
important.log  kept by !important.log

The last matching rule wins. Swap the two lines and `important.log` is ignored again — the negation is overridden by the rule that follows it.

* does not cross a slash; ** does

Input
src/*.ts   vs   src/**/*.ts
—
src/index.ts
src/lib/deep/index.ts
Output
src/*.ts    matches only src/index.ts
src/**/*.ts matches both

In glob mode a pattern must match the whole path, so `*.ts` alone does not match `src/index.ts` either.

A trailing slash means directories only

Input
build/
—
build/       (directory)
build        (file)
Output
the directory is ignored
the file of the same name is not

And ignoring a directory ignores everything inside it — you do not need `build/**`.

Common errors

MessageCauseFix
My !negation does not workEither it is written before the rule it should override, or a parent directory is already excluded.Move the negation below. If a directory is excluded, git never looks inside it, so `!build/keep.txt` cannot re-include a file under an ignored `build/` — you have to un-ignore the directory first with `!build/`.
A file is still tracked despite being in .gitignore.gitignore only affects untracked files. A file already in the index stays there forever.`git rm --cached <path>` and commit. This is the most common .gitignore complaint and it is not a pattern problem at all.
*.ts does not match src/index.ts in glob modeA glob matches the whole path, and `*` does not cross a slash.Use `**/*.ts`. In .gitignore mode the same pattern does match, because a slash-free pattern applies to the name at any depth.
A dotfile is not matched by *Shell globs deliberately skip a leading dot, which is why `rm *` spares your dotfiles.Write `.*` or `.env*`. .gitignore does not have this rule — there, `*` matches dotfiles too, and the tool follows each dialect's own behaviour.
Unclosed [ in the patternA character class was opened and never closed, or a literal `[` was not escaped.Escape it as `\[`. An unparseable pattern is reported here rather than silently matching nothing, which is what most tools do with it.
build/* behaves differently from build/`build/*` matches the entries inside build but not build itself, so git still descends into it and a negation inside can apply. `build/` excludes the directory outright.Use `build/` unless you specifically need to re-include something underneath.

Frequently asked questions

Is anything uploaded?

No. Patterns and paths are matched in your browser; no repository is read and nothing is transmitted or logged.

What is the difference between a glob and a .gitignore pattern?

A glob describes a whole path and is what build tools, tsconfig includes and shell expansion use. A .gitignore pattern is a filter with extra rules layered on: no-slash patterns match a name at any depth, a leading slash anchors to the root, a trailing slash means directories only, `!` re-includes, and later rules override earlier ones. The same text can mean different things in each, which is why this page keeps them as separate modes.

Why does the last matching rule win in .gitignore?

Because that is what makes negation expressible at all: you write a broad exclusion and then carve exceptions out of it below. It also means a broad rule placed at the bottom of the file silently overrides the careful exceptions above it — a common result of appending to a .gitignore over time.

Does `**` work everywhere?

In .gitignore, yes — git supports it. In glob-consuming tools it varies: most modern ones do, some older ones treat `**` as a plain `*`. Where it works, `a/**/b` also matches `a/b`, which this tool follows.

Does it handle .dockerignore or .eslintignore too?

.dockerignore uses Go's filepath.Match with its own precedence rules and is close to but not identical to .gitignore. .eslintignore and .prettierignore do follow gitignore semantics. Use the .gitignore mode for those two; treat the Docker case as approximate.

Why is a path with no matching rule shown as kept?

Because in .gitignore terms an unmatched path is included — the file will be tracked. The tool labels it rather than leaving it blank so that the answer for every path is explicit.

What does {a,b} alternation match?

Brace expansion — {a,b} matches either a or b at that position, so *.{ts,tsx} matches both file.ts and file.tsx. It is supported in both glob and .gitignore modes here, though not every real-world .gitignore-consuming tool implements it; git itself does not support brace expansion in .gitignore files, which is a common source of a pattern that works in this tester's glob mode but does nothing in an actual .gitignore.

Can I test multiple patterns against multiple paths at once?

Yes — that is the normal way to use it. Paste every pattern you want to check, one per line, and every path you want to test against them, and the result table shows the outcome and the deciding rule for each path, checked against the whole pattern list at once rather than one at a time.

Last updated