|<<>>|4 of 282 Show listMobile Mode

Ignoring files with .gitignore

Published by marco on

Introduction

This article defines concepts like repository and working tree and then discusses how you can use .gitignore files to determine the files and folders that Git considers during operations.

Concepts

 From a command line, you can run git init in any folder to make any folder a Git repository. Doing so creates a .git folder with a database and configuration files for the local repository. Git considers any folder that contains a .git folder with these configuration and database files in it to be a Git repository.

The working tree of such a folder is all files and subfolders in that folder other than the .git 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 are included in the repository.

.gitignore

A repository may include multiple .gitignore files. From the official documentation]:

“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 .gitignore file.”

Git determines whether a given file is included in the working tree by collecting the patterns from any .gitignore file anywhere in that file’s path. Patterns defined “closer” to the file override those defined “farther” away.

For example,

[Root]
  πŸ“„ .gitignore
  πŸ“ ProductMedia
     πŸ“ Win10
        πŸ“ Content
           πŸ“„ .gitignore
           πŸ“ Deploy
              πŸ“ Control
                 πŸ“„ .gitignore
                 πŸ“„ readme.txt

Git determines whether readme.txt is included in the working tree by collecting the rules from files in the following order:

  • .gitignore
  • ProductMedia/Win10/Content/.gitignore
  • ProductMedia/Win10/Content/Deploy/Control/.gitignore

Patterns

This section provides a brief overview with examples for common and more advanced cases that have been needed at Uster. For more information, see all supported patterns in the official documentation.

A .gitignore file contains zero or more patterns.

  • 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 relative to its containing folder (i.e., the root folder for a pattern is its declaration folder).

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 detailed below, the presence of a trailing * determines whether Git considers subsequent rules. This only becomes relevant for re-including deeply nested files.

obj/

All files in any folder named obj anywhere in the sub-tree of the current folder or any sub-folder thereof.

Disables subsequent pattern: Yes

/obj/

All files in the root folder named obj or any sub-folder thereof.

Disables subsequent pattern: Yes

/obj/*

All files in the root folder named obj or any sub-folder thereof.

Disables subsequent pattern: No

*.bin

All files ending in .bin anywhere in the sub-tree of the current folder.

Disables subsequent pattern: Yes, but irrelevant

src/*/out/bin

All files in any path named out/bin found in any single sub-folder of any folder named src anywhere in the sub-tree of the current folder.

Disables subsequent pattern: Yes

src/**/out/bin

All files in any path named out/bin found anywhere in any combination of sub-folders of the sub-tree of any folder named src anywhere in the sub-tree of the current folder.

Disables subsequent pattern: Yes

Re-including files

You can also re-include files with the ! operator. For example, you might want to exclude everything in a folder but a single configuration file.

The following example ignores everything in the /out folder except for the file settings.json.

/out
!/out/settings.json

Difference between / and /*

As very nicely explained in the answer on StackOverflow .gitignore exclude folder but include specific subfolder,

  • The pattern dir/ excludes a directory named dir and (implicitly) everything under it. With dir/, Git will never look at anything under dir, and thus will never apply any of the “un-exclude” patterns to anything under dir.
  • The pattern dir/* says nothing about dir itself; it just excludes everything under dir. With dir/*, Git will process the direct contents of dir, giving other patterns a chance to “un-exclude” some bit of the content (!dir/sub/).

For example, the following patterns ignore all files in obj and bin folders but files named readme.md in those folders should be included. The following patterns achieve this for obj but not for bin folders (because the missing * prevents Git from considering the rule re-including readme.md for bin folders).

/obj/*
!/obj/Readme.md
/bin/
!/bin/Readme.md

This distinction is highly relevant for the next section.

Re-including deeply nested files

As linked above, the answer on StackOverflow .gitignore exclude folder but include specific subfolder was invaluable in determining how to do accomplish the task below.

Suppose we have a folder structure as shown below.

[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…

We want the patterns that will ignore everything in any files in the Content folder of any sub-folder of ProductMedia except for the CustomerSettings.ini, Bootstrap.ini, and Settings.xml files under the path Content/Deploy/Control/ of any sub-folder of ProductMedia/.

You might think that the following would do the trick:

# Ignore everything in the "Content" folder of any subfolder of "ProductMedia"
ProductMedia/*/Content/*

# Except for the specified files under the path "Content/Deploy/Control/"
!ProductMedia/*/Content/Deploy/Control/CustomerSettings.ini
!ProductMedia/*/Content/Deploy/Control/Bootstrap.ini
!ProductMedia/*/Content/Deploy/Control/Settings.xml

Microsoft Copilot certainly thought so. This does not have the intended effect.

The pattern correctly uses the * 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 Win10 and Win11 folders by using a *.

However, while patterns affecting the path Content will be considered, those affecting Content/Deploy or any sub-folder thereof will not.

The trick, as outlined in the StackOverflow answer, is to re-include, then exclude each individual sub-folder in the path, as shown below.

# We ignore the generated "Content" subfolders in the ProductMedia folder
ProductMedia/**/Content/*                    # Allow subsequent processing
# Except for certain configuration files
!ProductMedia/**/Content/Deploy/             # Include "Deploy", allowing subsequent processing
ProductMedia/**/Content/Deploy/*             # Ignore everything in "Deploy"

!ProductMedia/**/Content/Deploy/Control/     # Include "Content", allowing subsequent processing
ProductMedia/**/Content/Deploy/Control/*     # Ignore everything in "Content"

# Finally, include the desired individual files

!ProductMedia/**/Content/Deploy/Control/CustomSettings.ini
!ProductMedia/**/Content/Deploy/Control/Bootstrap.ini
!ProductMedia/**/Content/Deploy/Control/Settings.xml

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.