<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://ktherage.github.io/xsl/atom.xsl" media="all"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://ktherage.github.io/tags/git/</id>
  <title>Kévin THÉRAGE | Expert Symfony Developer - Git</title>
  <subtitle><![CDATA[Technical blog over web development, Symfony, PHP and best practices. Find tutorials and advices for developers.]]></subtitle>
  <link href="https://ktherage.github.io/tags/git/atom.xml" rel="self" type="application/atom+xml" />
  <link href="https://ktherage.github.io/tags/git/" rel="alternate" type="text/html" />
  <updated>2026-04-08T10:33:25+00:00</updated>
  <author>
    <name>Kévin THÉRAGE</name>
    <uri>https://ktherage.github.io/</uri>
  </author>
  <entry xml:lang="en">
    <id>https://ktherage.github.io/blog/gitignore-blacklisting-whitelisting/</id>
    <title>Debugging Git&#039;s .gitignore: Why Whitelisting Files in Subdirectories Fails</title>
    <published>2026-03-25T00:00:00+00:00</published>
    <link href="https://ktherage.github.io/blog/gitignore-blacklisting-whitelisting/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<h2 id="introduction">Introduction</h2>
<p>When working with Git, it's common to use <code>.gitignore</code> to exclude files and directories. But sometimes, even well-intentioned rules can lead to unexpected behavior—especially when dealing with nested directories. In this post, I'll walk through a real-world example of how a <code>.gitignore</code> rule intended to keep a project clean ended up hiding important files, and how we fixed it.</p>
<hr>
<h2 id="the-setup">The Setup</h2>
<p>I wanted to keep the <code>.tools/</code> directory clean, tracking only <code>composer.json</code>, <code>composer.lock</code>, and the <code>.gitignore</code> file itself. My initial <code>.tools/.gitignore</code> looked like this:</p>
<pre><code class="language-gitignore">*
!.gitignore
!composer.json
!composer.lock</code></pre>
<p>Goal: Track only composer.json, composer.lock, and .gitignore in .tools/ and its subdirectories, ignoring everything else.</p>
<hr>
<h2 id="the-problem">The Problem</h2>
<p>After pushing this change, a colleague reported that their composer.lock file in <code>.tools/rector/</code> was being ignored. We used the following command to debug:</p>
<pre><code class="language-bash hljs bash">$ git check-ignore -v .tools/rector/composer.lock
.tools/.gitignore:1:*     .tools/rector/composer.lock</code></pre>
<hr>
<h2 id="root-cause">Root Cause</h2>
<p>Git's rule: <em>"It is not possible to re-include a file if a parent directory of that file is excluded."</em></p>
<p>The <code>*</code> pattern ignores both files and directories, which means Git never even looks inside <code>.tools/rector/</code>—so the whitelist rules for <code>composer.json</code> and <code>composer.lock</code> never apply.</p>
<hr>
<h2 id="the-solution">The Solution</h2>
<p>After debugging, we updated the <code>.gitignore</code> to explicitly allow directory traversal and re-include the necessary files:</p>
<pre><code class="language-gitignore"># Ignore all files and directories at this level
*

# But allow Git to inspect subdirectories
!*/

# Explicitly ignore vendor directories
vendor

# Whitelist composer.json in any subdirectory
!*/composer.json

# Whitelist composer.lock in any subdirectory
!*/composer.lock

# Always keep this .gitignore file
!.gitignore</code></pre>
<hr>
<h2 id="key-takeaways">Key Takeaways</h2>
<table>
<thead>
<tr>
<th>Directory/File</th>
<th>Rule Applied</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>.tools/</td>
<td><code>*</code></td>
<td>Ignored</td>
</tr>
<tr>
<td>.tools/rector/</td>
<td><code>!*/</code></td>
<td>Inspected</td>
</tr>
<tr>
<td>.tools/rector/vendor</td>
<td><code>vendor</code></td>
<td>Ignored</td>
</tr>
<tr>
<td>.tools/rector/composer.json</td>
<td><code>!*/composer.json</code></td>
<td>Tracked</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>Git's Directory Traversal:</strong> When you use <code>*</code> to ignore everything, Git won't look inside directories unless you explicitly allow it with <code>!*/</code>.</li>
<li><strong>Testing Your Rules:</strong> Always test your <code>.gitignore</code> with <code>git check-ignore -v &lt;file&gt;</code> and <code>git status</code> to ensure the expected files are tracked.</li>
<li><strong>Order Matters:</strong> Place general exclusions first, then re-include specific files or directories.</li>
<li><strong>Common Pitfalls:</strong> Remember to re-exclude directories like <code>vendor</code> after whitelisting, or they'll be included in your repository.</li>
</ul>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>Debugging <code>.gitignore</code> issues can be tricky, but understanding how Git evaluates directory traversal and pattern matching makes it much easier. Always test your rules with nested directories before committing, and don't hesitate to use <code>git check-ignore</code> to verify your setup.</p>]]>
    </content>
  </entry>
</feed>
