Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Gitignore Laravel Package

civicrm/gitignore

A Laravel/PHP package for managing .gitignore rules, helping keep generated and environment-specific files out of version control. It provides a simple way to maintain consistent ignore patterns across CiviCRM projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Why reach for this. You need to answer, inside PHP, "would git ignore this path?" — or you need to read/write a pattern list that happens to share gitignore syntax. That second part is the real leverage: .gitignore, .dockerignore, .npmignore, .prettierignore, ripgrep ignore files, PHP-CS-Fixer finder excludes, and git sparse-checkout all use the same grammar. One parser covers all of them, which makes this a small but broadly applicable utility rather than a git-specific one.

Typical day-to-day triggers in a Laravel codebase:

  • A generator/scaffolder that shouldn't overwrite files the user has deliberately ignored (compiled assets, local config, uploaded fixtures).
  • A file watcher, static-analysis pass, or Finder traversal that should skip ignored trees instead of descending into node_modules and storage/framework.
  • A CI guard that fails when a build artifact is accidentally tracked even though it's matched by .gitignore.
  • Emitting exclusion lists for deploy packaging or sparse-checkout without hand-rolling a second pattern dialect.

Install and orient.

composer require civicrm/gitignore

Then read, in this order:

  1. composer.json — check the PHP constraint (CiviCRM-adjacent packages often target older runtimes) and the license field. It reports NOASSERTION on GitHub, so confirm the actual LICENSE file before shipping it in anything distributed.
  2. src/ — the package is deliberately small, likely one parser plus a rule value object. Skim the whole thing; it will take minutes and removes all guesswork.
  3. tests/ — this is the de facto spec. Whatever the README omits about **, negation, anchoring, and escaped characters will be pinned down here.

First use case: don't clobber ignored files.

The shape of the API is "feed it pattern lines (or a file's contents), get back something you can match paths against." Confirm the exact class and method names in src/ — the sketch below shows the flow, not a guaranteed signature:

// Adjust names to the real entry point found in src/.
$rules = $parser->parse(file_get_contents(base_path('.gitignore')));

if ($rules->matches('storage/app/generated/thumb.png')) {
    // leave the user's ignored file alone
    return;
}

If that's all you need, stop there. Don't build a service layer around a thirty-line call site.

Implementation Patterns

Wrap it once, cache the compiled rules. Parsing is cheap but per-request parsing is pointless. Bind a matcher as a singleton keyed on the file's hash, so edits invalidate naturally:

$this->app->singleton(IgnoreMatcher::class, fn () => IgnoreMatcher::fromFiles([
    base_path('.gitignore'),
    base_path('.git/info/exclude'),
]));

In a queued worker or long-running Octane process, key the cache on md5_file() rather than registering once at boot.

Honour the layering, not just one file. Git resolves rules in layers, with deeper files winning:

Layer Path Notes
Global core.excludesFile Not discoverable from the repo; read git config or skip
Repo-local .git/info/exclude Same syntax, never committed
Root .gitignore The one everyone thinks of
Nested path/to/.gitignore Patterns are relative to their directory

A parser returns patterns; you own the directory-relative resolution. Pass paths relative to each file's base directory and evaluate from shallow to deep, letting later rules override earlier ones.

Use it as a generator, not only a matcher. Because the grammar is shared, the same code writes sparse-checkout sets and rsync --exclude-from inputs. Keep a small emit helper alongside the parse helper — that symmetry is where the package pays for itself twice.

Integrate with Finder instead of walking manually. Convert rules to a filter callback and let Symfony do the traversal:

$finder->filter(fn (SplFileInfo $f) => !$matcher->matches(
    str_replace(base_path().'/', '', $f->getPathname())
));

Note Finder's own ->ignoreVCSIgnored(true) already shells out to git — prefer that when a git binary is present and use this package only where it isn't.

Add a CI guard command. Parse git ls-files, test each path against the matcher, and fail on any tracked file that is ignored. That single check catches the most common cause of "works on my machine" deploys — a .env or compiled asset committed by accident.

Test the patterns as data. A PHPUnit data provider over [pattern, path, expected] triples documents your team's assumptions and gives you a regression net if you ever swap implementations.

Keep an escape hatch. Define your own tiny IgnoreMatcher interface with matches(string $relativePath): bool, backed by this package. If you later need authoritative semantics, you can swap in git check-ignore or a fork without touching call sites.

Gotchas and Tips

A parser is not git. The hard parts of gitignore are not lexing patterns — they're path resolution semantics. Verify these against git check-ignore -v in a throwaway repo before trusting the library:

  • Negation can't rescue a file inside an excluded directory. foo/ plus !foo/keep.txt still ignores keep.txt, because git never descends into foo. Naive matchers get this wrong and it's the #1 source of surprise.
  • Anchoring. A pattern containing a / (other than a trailing one) is anchored to the .gitignore's directory; one without is matched at any depth.
  • Trailing slash means directory-only. Don't match files against it.
  • ** placement matters. **/logs differs from logs/**, and a/**/b matches zero or more intermediate directories.
  • Whitespace and escaping. Trailing spaces are stripped unless backslash-escaped; leading # is a comment; \! and \# are literal. CRLF line endings bite Windows contributors.
  • Case sensitivity. Git is case-sensitive on Linux, but core.ignorecase=true is common on Windows/macOS and changes the answer. Decide explicitly and document it.

Subdirectory pruning differs. Git skips reading nested .gitignore files inside already-excluded directories for performance. A recursive parser that reads everything will produce answers git wouldn't. Usually harmless; occasionally not.

Global and info excludes are out of scope. No parser can find core.excludesFile for you — it lives in git config. If your matcher only reads .gitignore, it will disagree with git status in repos with global excludes. Either read git config --get core.excludesFile once or accept the divergence and say so in the class docblock.

Zero stars, zero dependents, no release safety net. That's not disqualifying for a package this small, but it changes how you consume it:

  • Pin an exact version ("civicrm/gitignore": "1.0.0"), not a caret range.
  • Assume no security response process. If it ends up in a user-facing path, be prepared to vendor and own it.
  • Read the source before adopting. At this size it's a lunch break, and you'll understand the semantics better than the README explains them.

License is NOASSERTION. GitHub couldn't classify it. CiviCRM projects are typically AGPL-3.0-or-later, which has real obligations if you distribute the application. Check LICENSE and get a yes/no from whoever owns that question before the code lands in a shipped product.

Check the PHP constraint early. Older CiviCRM tooling often supports conservative PHP ranges and may lack typed properties or return types. If you're on PHP 8.3 with strict tooling, verify it installs and passes your static analysis before designing around it.

Debugging divergences. Keep a fixture repo with the awkward cases (nested ignores, negation inside excluded dirs, ** variants) and diff the library's verdicts against git check-ignore -v --no-index output. Do this once, in an afternoon; it turns every later "why wasn't that file skipped?" into a one-line lookup.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums