Skip to tool
ecech.
💻 Developer & Code

Glob Tester That Shows Where gitignore and Shell Globs Disagree

Test a pattern against your own file list in both dialects at once. node_modules and /node_modules are not the same rule, and that difference ships secrets.

Files

0

Matched — gitignore

0

Matched — shell glob

0

Disagreements

0

File by file

Advertisement

How the calculation works

One character decides whether nested files are ignored node_modules no slash → matches at ANY depth node_modules/react/index.js ignored packages/web/node_modules/x.js ignored This is what you almost always want. /node_modules leading slash → repository root only node_modules/react/index.js ignored packages/web/node_modules/x.js COMMITTED Silently, in every monorepo. A pattern containing a slash anywhere but the end is anchored to the root. That is the whole rule.

How to Use This Tool

Paste your patterns and your file paths. Both dialects run at once, and any file they disagree about is marked.

The gitignore anchoring rule

This is the one that costs people real incidents, and it is a single sentence:

If a pattern contains a slash anywhere except at the end, it is anchored to the repository root. Otherwise it matches at every depth.

  • node_modules — no slash, so it matches node_modules/, packages/web/node_modules/, everything.
  • /node_modules — leading slash, so only the one at the root. In a monorepo the nested ones are committed, and nothing warns you.
  • config/secret.env — contains a slash, so it is anchored. A file at services/api/config/secret.env is not ignored.

The last case is the dangerous one. The pattern looks specific and reads as though it means “any config/secret.env”. It does not.

The ** rule that is not what people assume

* never crosses a /. ** does — but only when it stands as a whole path segment.

  • src/**/*.js** is its own segment, so this is recursive.
  • src/**.js** is glued to .js, so it degrades to a single * and matches only files directly in src/.

That second form appears constantly in build configs written by people expecting recursion. It runs, it matches something, and it quietly skips every nested file.

Same two stars, different meaning src/**/*.js ** is its own segment → crosses directories matches src/a.js and src/a/b/c.js src/**.js ** is glued to .js → behaves as a single * matches src/a.js only — src/a/b/c.js is skipped
The second one runs without error and silently misses every nested file.

What the two columns mean

gitignore applies the anchoring rule above, treats a trailing / as “directory only”, and lets a later ! line re-include something an earlier line excluded. Order matters: the last pattern that matches wins.

shell glob is the plain form used by most build tools and by minimatch: every pattern is matched against the whole path, nothing is implicitly anchored or unanchored, and there is no negation.

When the two columns disagree, it means the same line in your config would do different things in .gitignore and in your bundler. That is worth knowing before you rely on it.

Limits worth stating

Real .gitignore resolution also depends on files on disk — whether a path is a directory, and nested .gitignore files in subfolders that apply relative to themselves. This page only sees the text you paste, so it cannot model either. For the anchoring and ** questions, which is what people actually get wrong, the text is enough.

Advertisement

Frequently Asked Questions

Why is my .gitignore ignoring node_modules at the root but not in subfolders?
Because the pattern starts with a slash. /node_modules is anchored to the repository root, so nested copies in a monorepo are committed. Removing the leading slash makes it match at every depth, which is almost always what you wanted.
What is the actual gitignore anchoring rule?
If a pattern contains a slash anywhere except at the very end, it is anchored to the repository root. Otherwise it matches at any depth. So config/secret.env only matches that exact path from the root, and a file at services/api/config/secret.env is not ignored — even though the pattern reads as though it should be.
What is the difference between * and **?
* never crosses a directory separator. ** does, but only when it stands as a whole path segment. src/**/*.js is recursive because ** is its own segment; src/**.js is not, because the ** is glued to .js and degrades to a single *, matching only files directly inside src.
Why do the two columns sometimes disagree?
Because they are different dialects. gitignore anchors patterns conditionally, treats a trailing slash as directory-only, and supports ! to re-include. Shell globs match the whole path with no implicit anchoring and no negation. The same line can therefore behave differently in .gitignore and in your bundler config.
Does ! order matter?
Yes. The last pattern that matches a path decides the outcome, so a ! line only re-includes a file if it comes after the line that excluded it. Git also will not re-include a file inside a directory that is itself excluded, which is a separate rule this page cannot model from text alone.
Is my file list uploaded?
No. The matching runs entirely in your browser. Paths from a private repository are often sensitive on their own, so nothing here is sent anywhere.

Related tools in Developer & Code

Browse all Developer & Code tools
The person who builds ecech., at the desk where the tools are written.

Made by one person

ecech. is not a content farm. Every tool here is written and checked by hand, one at a time, by someone who wanted the tool to exist and could not find a version that showed its working.

No accounts and no sign-in, and nothing you type reaches a server — every calculation on this page runs inside your browser. The ads are served by Google and do set their own cookies, which is set out in full on the privacy page. More about the site.