This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

Ignoring files with <c>.gitignore</c>

Description

<h>Introduction</h> This article defines concepts like <i>repository</i> and <i>working tree</i> and then discusses how you can use <c>.gitignore</c> files to determine the files and folders that Git considers during operations. <h>Concepts</h> <img attachment="git-logo.jpg" align="right">From a command line, you can run <c>git init</c> in any folder to make any folder a Git repository. Doing so creates a <c>.git</c> folder with a database and configuration files for the local repository. Git considers any folder that contains a <c>.git</c> folder with these configuration and database files in it to be a Git repository. The <i>working tree</i> of such a folder is all files and subfolders in that folder other than the <c>.git</c> folder. Any non-trivial repository will contain at least some files that should not be committed to the Git repository. Commonly, these are build artifacts produced from the source files that <i>are</i> included in the repository. <h><c>.gitignore</c></h> A repository may include multiple <c>.gitignore</c> files. From the <a href="https://git-scm.com/docs/gitignore">official documentation]</a>: <bq>Patterns read from a .gitignore file in the same directory as the path, or in any parent directory (up to the top-level of the working tree), with patterns in the higher level files being overridden by those in lower level files down to the directory containing the file. These patterns match relative to the location of the <c>.gitignore</c> file.</bq> Git determines whether a given file is included in the working tree by collecting the patterns from any <c>.gitignore</c> file anywhere in that file's path. Patterns defined "closer" to the file override those defined "farther" away. For example, <code> [Root] 📄 .gitignore 📁 ProductMedia 📁 Win10 📁 Content 📄 .gitignore 📁 Deploy 📁 Control 📄 .gitignore 📄 readme.txt </code> Git determines whether <c>readme.txt</c> is included in the working tree by collecting the rules from files in the following order: <ul> <c>.gitignore</c> <c>ProductMedia/Win10/Content/.gitignore</c> <c>ProductMedia/Win10/Content/Deploy/Control/.gitignore</c> </ul> <h>Patterns</h> <info>This section provides a brief overview with examples for common and more advanced cases that have been needed at Uster. For more information, see <a href="https://git-scm.com/docs/gitignore#_pattern_format">all supported patterns</a> in the official documentation.</info> A <c>.gitignore</c> file contains zero or more <i>patterns</i>. <ul> Each pattern excludes the affected files from the working tree Each pattern affects only files in the sub-tree defined by the sub-folder in which the pattern is declared. Each pattern is interpreted <i>relative</i> to its containing folder (i.e., the <i>root folder</i> for a pattern is its declaration folder).</ul> The following examples illustrate common patterns. The column "Disables subsequent patterns" indicates whether subsequent patterns that target files in the ignored sub-tree will have an effect. As <a href="#difference-between-%60/%60-and-%60/*%60">detailed below</a>, the presence of a trailing <c>*</c> determines whether Git considers subsequent rules. This only becomes relevant for <a href="#re-including-deeply-nested-files">re-including deeply nested files</a>. <dl dt_class="field"> <c>obj/</c> <div>All files in any folder named <c>obj</c> anywhere in the sub-tree of the current folder or any sub-folder thereof. Disables subsequent pattern: <i>Yes</i></div> <c>/obj/</c> <div>All files in the <i>root folder</i> named <c>obj</c> or any sub-folder thereof. Disables subsequent pattern: <i>Yes</i></div> <c>/obj/*</c> <div>All files in the <i>root folder</i> named <c>obj</c> or any sub-folder thereof. Disables subsequent pattern: <i>No</i></div> <c>*.bin</c> <div>All files ending in <c>.bin</c> anywhere in the sub-tree of the current folder. Disables subsequent pattern: <i>Yes, but irrelevant</i></div> <c>src/*/out/bin</c> <div>All files in any path named <c>out/bin</c> found in any single sub-folder of any folder named <c>src</c> anywhere in the sub-tree of the current folder. Disables subsequent pattern: <i>Yes</i></div> <c>src/**/out/bin</c> <div>All files in any path named <c>out/bin</c> found anywhere in any combination of sub-folders of the sub-tree of any folder named <c>src</c> anywhere in the sub-tree of the current folder. Disables subsequent pattern: <i>Yes</i></div> </dl> <h level="3">Re-including files</h> You can also <i>re-include</i> files with the <c>!</c> operator. For example, you might want to exclude everything in a folder but a single configuration file. The following example ignores everything in the <c>/out</c> folder <i>except for</i> the file <c>settings.json</c>. <code> /out !/out/settings.json </code> <h level="3">Difference between <c>/</c> and <c>/*</c></h> As very nicely explained in the answer on StackOverflow <a href="https://stackoverflow.com/a/5534865/178874">.gitignore exclude folder but include specific subfolder</a>, <bq><ul>The pattern <c>dir/</c> excludes a directory named <c>dir</c> and (implicitly) everything under it. With <c>dir/</c>, Git will never look at anything under <c>dir</c>, and thus will never apply any of the "un-exclude" patterns to anything under <c>dir</c>. The pattern <c>dir/*</c> says nothing about <c>dir</c> itself; it just excludes everything under <c>dir</c>. With <c>dir/*</c>, Git will process the direct contents of <c>dir</c>, giving other patterns a chance to "un-exclude" some bit of the content (<c>!dir/sub/</c>).</ul></bq> For example, the following patterns ignore all files in <c>obj</c> and <c>bin</c> folders but files named <c>readme.md</c> in those folders should be included. The following patterns achieve this for <c>obj</c> but not for <c>bin</c> folders (because the missing <c>*</c> prevents Git from considering the rule re-including <c>readme.md</c> for <c>bin</c> folders). <code> /obj/* !/obj/Readme.md /bin/ !/bin/Readme.md </code> This distinction is highly relevant for the next section. <h level="3">Re-including deeply nested files</h> <info>As linked above, the answer on StackOverflow <a href="https://stackoverflow.com/a/5534865/178874">.gitignore exclude folder but include specific subfolder</a> was invaluable in determining how to do accomplish the task below.</info> Suppose we have a folder structure as shown below. <code> [Root] 📁 Folder1 📁 Folder2 📁 Folder3 📁 ProductMedia 📁 Win10 📁 Content 📄 Other files... 📁 Deploy 📁 Control 📄 CustomerSettings.ini 📄 Bootstrap.ini 📄 Settings.xml 📄 Other files... 📁 Folder1 📁 Folder2 📁 Folder3 📁 Other folders... 📁 Win11 📁 Content 📄 Other files... 📁 Deploy 📁 Control 📄 CustomerSettings.ini 📄 Bootstrap.ini 📄 Settings.xml 📄 Other files... 📁 Folder1 📁 Folder2 📁 Folder3 📁 Other folders... 📁 Folder1 📁 Folder2 📁 Folder3 📁 Other folders... </code> We want the patterns that will ignore everything in any files in the <c>Content</c> folder of any sub-folder of <c>ProductMedia</c> except for the <c>CustomerSettings.ini</c>, <c>Bootstrap.ini</c>, and <c>Settings.xml</c> files under the path <c>Content/Deploy/Control/</c> of any sub-folder of <c>ProductMedia/</c>. You might think that the following would do the trick: <code> <span class="comment"># Ignore everything in the "Content" folder of any subfolder of "ProductMedia"</span> ProductMedia/*/Content/* <span class="comment"># Except for the specified files under the path "Content/Deploy/Control/"</span> !ProductMedia/*/Content/Deploy/Control/CustomerSettings.ini !ProductMedia/*/Content/Deploy/Control/Bootstrap.ini !ProductMedia/*/Content/Deploy/Control/Settings.xml </code> Microsoft Copilot certainly thought so. This does not have the intended effect. The pattern correctly uses the <c>*</c> to indicate that Git should continue processing patterns for sub-trees of the excluded folder. It also correctly ensures that the rule applies both to <c>Win10</c> and <c>Win11</c> folders by using a <c>*</c>. However, while patterns affecting the path <c>Content</c> will be considered, those affecting <c>Content/Deploy</c> or any sub-folder thereof <i>will not</i>. The trick, as outlined in the <a href="https://stackoverflow.com/a/5534865/178874">StackOverflow answer</a>, is to re-include, then exclude each individual sub-folder in the path, as shown below. <code> <span class="comment"># We ignore the generated "Content" subfolders in the ProductMedia folder</span> ProductMedia/**/Content/* <span class="comment"># Allow subsequent processing</span> # Except for certain configuration files !ProductMedia/**/Content/Deploy/ <span class="comment"># Include "Deploy", allowing subsequent processing</span> ProductMedia/**/Content/Deploy/* <span class="comment"># Ignore everything in "Deploy"</span> !ProductMedia/**/Content/Deploy/Control/ <span class="comment"># Include "Content", allowing subsequent processing</span> ProductMedia/**/Content/Deploy/Control/* <span class="comment"># Ignore everything in "Content"</span> <span class="comment"># Finally, include the desired individual files</span> !ProductMedia/**/Content/Deploy/Control/CustomSettings.ini !ProductMedia/**/Content/Deploy/Control/Bootstrap.ini !ProductMedia/**/Content/Deploy/Control/Settings.xml </code> This pattern of re-including, then re-excluding each sub-folder suffices to allow Git to consider the file patterns at the end, while still ignoring all other files in any sub-trees that would otherwise have been included.