GitIgnore Generator with a Path Tester

Stack templates overlap heavily, so a React and Node pairing repeats node_modules three times before you notice. Pick your stacks here and every rule is written once. Then type a real path from your repo and read back the exact line deciding its fate.

GitIgnore builder and path tester

Stacks

Click to add or drop
Languages
Frameworks
Editors and tooling
Machines and risk

Your own rules

One pattern per line

.gitignore

Nothing leaves your browser
0Stacks in
0Rules written
0Repeats merged
0Exceptions

Path tester

Runs against the file above
  • 34 stack templates
  • Duplicate rules merged
  • Last match wins, shown to you

Writing a .gitignore you will still trust in six months

Most ignore files start honest and turn into sediment. Someone pastes a Node template, someone else pastes a React template on top, a build output rule gets added twice with different spelling, and eighteen months later nobody wants to touch the file because nobody knows which line does what. The builder above solves the first half by writing each pattern once across every stack you pick. The tester solves the second half by naming the line responsible for any path you give it.

Stack templates overlap more than they look

Pick Node.js, React, Next.js and Webpack together from a template site and you get node_modules/ four times, three spellings of the build folder, and two blocks of debug logs. None of it breaks git. All of it makes the file harder to read and harder to edit safely, since changing one copy leaves the other three behind.

The merge here keeps the first occurrence of a pattern and records who else asked for it. The Repeats merged counter above shows how many lines the merge saved, and the panel under it names the patterns several stacks share. Treat those shared patterns as the spine of your file: they are the ones worth reviewing by hand.

Where the slash sits decides the scope

Three characters change the meaning of a rule, and they are the source of most confusion in ignore files.

PatternMatchesSkips
buildAny file or folder named build, at any depthNothing named differently
build/Folders named build onlyA file called build with no extension
/buildbuild at the repository rootpackages/ui/build
docs/buildOnly that one path, because the slash anchors itsite/docs/build
**/buildbuild at any depth, written explicitlyNothing extra, same result as the bare form
*.logLog files in every folderlogs/ as a folder

The rule to remember: a pattern with no slash in the middle floats and matches by name anywhere. A pattern with a slash anywhere except the end is pinned to the repository root. Type both forms into the tester with a nested path and watch the verdict flip.

Last match wins, which makes order load bearing

Git walks the whole file for every path and keeps the final rule to match. Earlier lines lose silently. This is why an exception placed above its ignore rule does nothing at all:

!config/database.local.php
config/*.local.php

Written in the other order the exception survives, because it matches last:

config/*.local.php
!config/database.local.php

The flat layout in the builder sorts patterns alphabetically and pushes every exception to the bottom for exactly this reason. Grouped layout keeps stack sections intact, which reads better, at the cost of you watching where exceptions land when you edit later.

An excluded folder is a closed door

This trips up experienced people. Once a directory is excluded, git stops descending into it, so a negation aimed at something inside never gets evaluated. The file below fails to keep the asset:

assets/
!assets/logo.svg

Git never looks inside assets/, so the second line is dead text. Re-open the path level by level instead:

assets/*
!assets/logo.svg

The tester reports this case directly. Paste assets/logo.svg against the broken version and the verdict names the folder rule as the blocker rather than the pattern you expected.

Deeper paths need every level reopened. To keep assets/brand/logo.svg alive you need assets/*, then !assets/brand/, then !assets/brand/logo.svg. Each step tells git to walk one layer further before it gives up.

An ignore rule has no power over a tracked file

The most common support question about ignore files is not a pattern problem at all. Git applies these rules to untracked files. A file already in the index keeps getting committed no matter what you write, so adding .env to a repository where .env was committed last March changes nothing.

git rm --cached .env
git commit -m "Stop tracking local environment file"

That removes the file from the index while leaving it on your disk. Everyone who pulls loses their copy, so warn the team first. For a folder use git rm -r --cached storage/logs. To check the whole working tree at once, git status --ignored lists what git is currently skipping.

A secret in history stays in history. Untracking a leaked key hides it from future commits only. The old blobs remain reachable, and on a pushed branch, mirrored. Rotate the credential first, then clean history with git filter-repo or the BFG if the repository is worth rewriting.

Three ignore files, three different jobs

Nested ignore files also work. A .gitignore inside packages/api/ applies to that subtree, with its rules taking priority over the root file for paths underneath. Monorepos read better with small local files than with one root file carrying every workspace.

What belongs in the file before the first push

Order the file by what hurts most when it escapes:

  1. Credentials..env, private keys, service account JSON, keystore files. The Secrets and keys chip covers the common shapes and keeps .env.example committed so new contributors have a template.
  2. Dependency folders.node_modules/, vendor/, venv/. Reinstallable from a lock file and worth thousands of files in a diff.
  3. Build output.dist/, target/, .next/. Regenerated on every build and a permanent source of merge conflicts otherwise.
  4. Machine noise. Editor state, thumbnail caches, swap files.
  5. Heavy junk. Database dumps and archives, which bloat clone size forever once committed.

One line deserves a second thought: lock files. The Rust template here ignores Cargo.lock, correct for a library and wrong for an application. Same tension applies to composer.lock and package-lock.json. Commit lock files when you ship a binary or a deployed service, drop them when you publish a library other projects depend on.

Reading the tester output

The tester answers a narrow question well. Give it a path relative to the repository root, with forward slashes, and it walks the path one segment at a time, applying every rule at each level the way git does. A verdict of Ignored names the winning line and its number. A verdict of Committed either names an exception rule or tells you nothing matched at all.

Add a trailing slash to test a folder: logs/ and logs get different answers when a directory-only rule is in play. That difference is the point of the trailing slash, and typing both is faster than reasoning about it.

Limits worth knowing before you rely on this

Everything runs in this tab. The templates, the merge, the file, and the matcher are JavaScript on the page, so a path from a private repository never reaches a server. Close the tab and it is gone.

GitIgnore questions that come up in review

Ordering, exceptions, tracked files, and the difference between the three ignore files git reads.

Why is my file still committed after I added it to .gitignore?

Because git was already tracking it. Ignore rules apply to untracked files only, so a file added to the index at any point in the past keeps showing up in diffs regardless of what the ignore file says. Run git rm --cached path to drop it from the index while keeping it on disk, then commit. For folders add the -r flag. Tell your team before pushing, since their next pull deletes their local copy of the file.

My negation rule does nothing. What went wrong?

Two causes account for nearly all of them. Either the exception sits above the rule it fights, and git keeps the last match rather than the first, or a parent directory is excluded with a trailing slash so git never walks inside to see the exception. The fix for the first is reordering. The fix for the second is replacing assets/ with assets/* so the folder itself stays open and only its contents are excluded.

Should I combine several stack templates or keep them separate?

Combine them into one root file for a normal project, which is what the builder does, with repeated patterns written once. Split into per directory files when you run a monorepo where each workspace has its own toolchain. A .gitignore inside packages/api applies to that subtree and its rules take priority there, which keeps the root file short and makes each package readable on its own.

What is the difference between .gitignore, .git/info/exclude and the global file?

Scope and who sees them. The .gitignore file is committed and shared, so it holds project truths such as build folders. The .git/info/exclude file lives inside your clone and is never pushed, which suits personal scratch files. The global file, pointed at by core.excludesFile, applies to every repository on your machine and is where editor and operating system noise belongs. Git reads all three, with the closest file to the path winning.

How accurate is the path tester compared to real git?

It implements the same rules for anchoring, directory-only patterns, wildcards, double asterisk segments, character classes, and last match wins, including the case where an excluded parent blocks a later exception. It evaluates one file. Real git also reads nested ignore files, your global exclude file, and .git/info/exclude, so a difference between the verdict here and your working copy usually points at one of those. Confirm with git check-ignore -v on the path.

Do I need both .DS_Store and Thumbs.db in a project file?

Only if contributors run mixed machines and you would rather not depend on everyone configuring a global exclude file. Purists keep operating system noise out of the repository file, since it describes a laptop rather than the project. Practical teams commit both blocks because one contributor without a global file pollutes every pull request. Both positions are defensible, and the second causes fewer arguments in review.

Should lock files be ignored?

Commit them for applications and services, ignore them for libraries. An application benefits from every machine and every CI run installing byte identical dependencies, which the lock file guarantees. A library should resolve fresh against its consumers, so a committed lock file adds noise without changing what users install. The Rust template here ignores Cargo.lock following the library convention, so delete that line for a binary crate.

How do I ignore everything except a few files?

Start with a wildcard, then reopen each level you need. Write /* to exclude everything at the root, then !.gitignore, then !src/ to reopen that folder. Every intermediate directory needs its own exception, because git stops descending as soon as a level is excluded. This pattern shows up in deployment branches and content repositories, and it rewards testing each path in the tester above before committing.

Does adding a rule remove a secret that was already pushed?

No. An ignore rule stops future commits and leaves history untouched, so the credential stays reachable in old commits and in every clone and fork. Rotate the key first, since that is the only step making the exposure harmless. After rotation, rewrite history with git filter-repo or the BFG Repo-Cleaner if the repository matters enough, then force push and have everyone re-clone.

Is my generated file sent anywhere?

No. Templates, merging, formatting, and path matching all run in JavaScript inside this page, with no request after the page loads. Paths you type into the tester stay in the tab, which matters when the path itself leaks a client name or an internal project. Nothing is stored between visits, so a reload gives you a clean start.