Chapter 15: Ignoring Files in Git to Keep them Out
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the purpose and importance of ignoring files in a Git repository.
- Create and use
.gitignore
files to specify patterns for files and directories that Git should ignore. - Master the syntax and various pattern rules for
.gitignore
files. - Set up and use a global
.gitignore
file for user-specific ignore patterns. - Learn how to make Git ignore files that were previously tracked.
- Debug
.gitignore
rules usinggit check-ignore
. - Understand and apply the
.gitkeep
convention for tracking empty directories.
Introduction
When working on any software project, you’ll inevitably generate files that you don’t want to include in your Git repository’s history. These might be build artifacts, log files, temporary files created by your editor or IDE, local configuration overrides, or sensitive information like API keys (though .gitignore
is not a foolproof security measure for already committed data). Including such files in your repository can bloat its size, create unnecessary merge conflicts, and clutter your git status
output, making it harder to see meaningful changes.
Git provides a simple yet powerful mechanism to specify intentionally untracked files that Git should ignore: the .gitignore
file. By defining patterns in .gitignore
files, you can tell Git to disregard certain files and directories, keeping your repository clean, focused on source code and essential project assets, and easier for collaborators to work with.
This chapter will guide you through the process of creating effective .gitignore
files, understanding their syntax, managing global ignore rules, and handling scenarios where you need to ignore files that were previously tracked. We’ll also cover how to debug your ignore rules and a common convention for tracking otherwise empty directories.
Theory
What is .gitignore
?
A .gitignore
file is a plain text file where each line contains a pattern for files or directories that Git should ignore. Git checks these patterns when determining which files are “untracked.” If an untracked file matches a pattern in a relevant .gitignore
file, Git will not list it in git status
(unless explicitly asked to show ignored files) and will not include it in git add .
operations.
Why Ignore Files?
Common types of files you’ll want to ignore include:
- Compiled code/Build artifacts:
.class
files,.o
files, executables,bin/
,build/
,dist/
directories. - Log files:
.log
files, application logs. - Runtime files: PID files, temporary outputs.
- Dependency directories:
node_modules/
,vendor/
(though this depends on the project’s dependency management strategy; some prefer to commit dependencies). - Editor/IDE configuration files:
.idea/
(IntelliJ),.vscode/
(VS Code, though some parts likelaunch.json
might be shared),.project
(Eclipse), editor backup files like*~
or*.swp
. - OS-generated files:
.DS_Store
(macOS),Thumbs.db
(Windows). - Sensitive information: Files containing API keys, passwords, or other credentials (e.g.,
credentials.json
,config.local.php
). Important:.gitignore
only prevents newly untracked files from being added. If sensitive data has already been committed, it’s in the history and requires more advanced techniques (likegit filter-repo
from Chapter 13) to remove.
.gitignore
File Location and Scope
- Repository Root: The most common place for a
.gitignore
file is in the root directory of your Git repository. Patterns in this file apply to the entire repository. - Subdirectories: You can also place
.gitignore
files in subdirectories. Patterns in a subdirectory’s.gitignore
file are relative to that directory and apply to that directory and its subdirectories. These rules are evaluated in addition to rules from parent directories. - Precedence: Rules in a
.gitignore
file in a subdirectory take precedence over rules in a parent directory for paths within that subdirectory.
.gitignore
Syntax and Patterns
The rules for patterns in .gitignore
files are as follows:
Pattern Element / Rule | Description | Example(s) |
---|---|---|
Blank Lines | Ignored; can be used for readability. | (Just an empty line) |
# (Hash) | Lines starting with # are comments and are ignored. |
# This is a comment |
Trailing Spaces | Ignored unless quoted with a backslash (\ ). |
file_with_space\ |
* (Asterisk) | Matches zero or more characters, but not directory separators (/ ). |
*.log (matches debug.log , app.log )temp* (matches tempfile , temporary ) |
? (Question Mark) | Matches any single character, except / . |
file?.txt (matches file1.txt , fileA.txt ) |
[set] or [range] | Matches any one character from the set (e.g., [abc] ) or range (e.g., [0-9] ). |
image[1-3].png (matches image1.png , image2.png , image3.png ) |
/ (Forward Slash) – Beginning | Anchors the pattern to the directory where the .gitignore file resides. Only matches items in that specific directory. |
/TODO (matches ./TODO , not src/TODO ) |
/ (Forward Slash) – Middle | Treated as a directory separator. | logs/debug.log (matches debug.log inside any logs directory relative to the .gitignore file, or ./logs/debug.log if not starting with ** ) |
/ (Forward Slash) – End | Pattern only matches directories. Everything inside the directory will also be ignored. | build/ (ignores the build directory and its contents) |
** (Double Asterisk) | Matches zero or more directories (i.e., matches across directory levels).
– **/foo : matches foo file or directory in any subdirectory.
– a/**/b : matches b inside a at any depth.
– a/** : matches everything inside directory a .
|
**/logs (matches logs , src/logs , a/b/logs )
*.tmp/** (matches any .tmp directory and everything within it)
|
! (Exclamation Mark) | Negates the pattern. Re-includes a file if it was excluded by a previous pattern. Precedence matters: an earlier exclusion is necessary for a negation to have an effect. A later exclusion can override an earlier inclusion. | *.log !important.log (ignore all .log files except important.log ) |
- Blank Lines: Blank lines are ignored and can be used for readability.
- Comments: Lines starting with a hash (
#
) are treated as comments and are ignored. - Trailing Spaces: Trailing spaces are ignored unless they are quoted with a backslash (
\
). - Glob Patterns: Git uses glob patterns, similar to those used in shell environments, to match file and directory names.
*
: Matches zero or more characters, but does not match directory separators (/
). For example,*.log
matchesdebug.log
anderror.log
but notlogs/debug.log
.?
: Matches any single character, except/
. For example,file?.txt
matchesfile1.txt
andfileA.txt
.[abc]
: Matches any one character from the seta
,b
, orc
.[0-9]
: Matches any digit from 0 to 9.
- Directory Separator (
/
):- If a pattern starts with a
/
, it anchors the pattern to the directory where the.gitignore
file resides. For example,/TODO
in the root.gitignore
matchesTODO
in the root, but notsrc/TODO
. - If a pattern contains a
/
elsewhere, it’s treated as a directory separator. For example,logs/debug.log
matchesdebug.log
inside alogs
directory. - If a pattern ends with a
/
, it only matches directories. For example,build/
matches thebuild
directory and everything inside it, but not a file namedbuild
.
- If a pattern starts with a
- Double Asterisk (
**
):**/logs
: Matcheslogs
directories anywhere (e.g.,logs/
,src/logs/
).foo/**/bar
: Matchesbar
files or directories insidefoo
at any depth (e.g.,foo/bar
,foo/a/bar
,foo/a/b/bar
).abc/**
: Matches everything inside theabc
directory.
- Negation (
!
):- An optional prefix
!
negates the pattern. Any matching file excluded by a previous pattern will become included again if it also matches a negated pattern. - Precedence is important: For a
!
pattern to re-include a file, the file must first be excluded by a non-!
pattern defined earlier in the.gitignore
files or by a pattern in a.gitignore
file in a parent directory. A later exclusion rule can override an earlier inclusion rule. - Example:
*.log # Ignore all .log files !important.log # But do track important.log
Another example:logs/ # Ignore the logs directory !logs/.gitkeep # But track the .gitkeep file in it
- An optional prefix
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph LR subgraph "Project Directory Structure" direction LR P["ProjectRoot/"] P --> F1[".gitignore"] P --> F2["app.py"] P --> D1["build/"] D1 --> DF1["output.exe"] D1 --> DF2["temp.o"] P --> D2["logs/"] D2 --> LF1["debug.log"] D2 --> LF2["trace.log"] P --> D3["src/"] D3 --> SF1["main.c"] D3 --> SD1["vendor/"] SD1 --> SVF1["library.lib"] P --> F3["config.ini"] P --> F4["IMPORTANT.md"] P --> F5[".DS_Store"] class F1,F2,F3,F4,F5,DF1,DF2,LF1,LF2,SF1,SVF1 fileNode; class D1,D2,D3,SD1 dirNode; end subgraph ".gitignore Contents & Effects" direction TB R1["Pattern: <b>build/</b>"] --> E1["Effect: Ignores <i>build/</i> directory and all its contents (<i>output.exe</i>, <i>temp.o</i>)"] R2["Pattern: <b>*.log</b>"] --> E2["Effect: Ignores <i>debug.log</i> and <i>trace.log</i>"] R3["Pattern: <b>src/vendor/</b>"] --> E3["Effect: Ignores <i>src/vendor/</i> and its contents (<i>library.lib</i>)"] R4["Pattern: <b>**/*.o</b>"] --> E4["Effect: Ignores <i>temp.o</i> in <i>build/</i> (and any <i>.o</i> file in any subdirectory)"] R5["Pattern: <b>.DS_Store</b>"] --> E5["Effect: Ignores <i>.DS_Store</i> file"] R6["Pattern: <b>!IMPORTANT.md</b><br>(Assuming <i>*.md</i> was ignored earlier or by a broader rule)"] --> E6["Effect: Re-includes <i>IMPORTANT.md</i> even if other <i>*.md</i> files are ignored"] R7["Pattern: <b>config.ini</b>"] --> E7["Effect: Ignores <i>config.ini</i> specifically"] class R1,R2,R3,R4,R5,R6,R7 patternNode; class E1,E2,E3,E4,E5,E6,E7 effectNode; end %% Styling classDef fileNode fill:#DBEAFE,stroke:#2563EB,color:#1E40AF; classDef dirNode fill:#E0E7FF,stroke:#4338CA,color:#3730A3,font-weight:bold; classDef patternNode fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6,font-size:12px; classDef effectNode fill:#F3F4F6,stroke:#9CA3AF,color:#374151,font-size:11px,padding:5px; %% Linking examples to file structure (conceptual) E1 -.-> D1; E2 -.-> LF1; E2 -.-> LF2; E3 -.-> SD1; E4 -.-> DF2; E5 -.-> F5; E6 -.-> F4; E7 -.-> F3; style P fill:#FFFBEB,stroke:#FBBF24,font-weight:bold;
Global .gitignore
Sometimes you have ignore patterns that apply to all your repositories on your machine, such as files generated by your personal editor setup or OS-specific files (e.g., .DS_Store
on macOS, Thumbs.db
on Windows). Instead of adding these to every project’s .gitignore
file, you can use a global .gitignore
file.
- Create a global ignore file (e.g.,
~/.gitignore_global
or~/.config/git/ignore
). The name and location are up to you. - Tell Git where to find this file using the
core.excludesFile
configuration option:git config --global core.excludesFile ~/.gitignore_global
(Replace~/.gitignore_global
with the actual path to your global ignore file.)
Patterns in this global file apply to all repositories for your user account.
Category / Operating System | Common Files / Directories to Ignore Globally | Notes |
---|---|---|
macOS |
.DS_Store ._* .Spotlight-V100 .Trashes __MACOSX
|
Finder metadata, resource forks, Spotlight index, trash items, archive metadata. |
Windows |
Thumbs.db ehthumbs.db Desktop.ini $RECYCLE.BIN/
|
Thumbnail cache, desktop configuration, recycle bin. |
Linux (General) |
*~ (common editor backup suffix)*.swp , *.swo (Vim swap files)
|
Backup files from editors like Emacs, Vim, Gedit. |
Common IDEs / Editors |
.idea/ (IntelliJ IDEA, WebStorm, etc. – some files within might be shared, like runConfigurations) .vscode/ (VS Code – user-specific settings like tasks.json, workspaceStorage; some like launch.json might be shared)*.sublime-workspace , *.sublime-project (Sublime Text).project , .classpath , .settings/ (Eclipse)nbproject/ (NetBeans)
|
User-specific workspace, project settings, history, or cache files. Check IDE documentation for recommended per-user ignores. |
Personal Build/Tool Outputs |
*.bak *.tmp local_env_setup.*
|
Generic backup files, temporary files, or personal scripts not meant for sharing. |
Ignoring Previously Tracked Files
It’s crucial to understand that .gitignore
files only prevent untracked files from being added to Git’s tracking (the index/staging area). If a file is already tracked by Git (i.e., it has been committed), adding a pattern for it to .gitignore
will not make Git ignore it. git status
will still show modifications to that file.
To stop tracking a file that is already in the repository and then ignore it:
- Remove the file from Git’s tracking (the index/staging area), but keep it in your working directory:
git rm --cached <file_or_directory>
For a directory, use the-r
(recursive) option:git rm -r --cached <directory_name>
The--cached
option is key here; it removes the file from the index, but leaves your local copy intact. Without--cached
,git rm
would also delete the file from your working directory. - Add the file’s pattern to your .gitignore file.For example, if you removed config.local.ini, add config.local.ini or a more general pattern like *.local.ini to .gitignore.
- Commit the changes:This will involve committing the removal of the file from tracking (recorded as a deletion in the index) and committing the updated .gitignore file.
git add .gitignore git commit -m "Stop tracking <file_or_directory> and add to .gitignore"
Warning: This procedure removes the file from being tracked going forward. The file will still exist in the repository’s history in commits made before this change. To completely remove a file from all history (e.g., if sensitive data was committed), you need to use more advanced history rewriting tools like
git filter-repo
(as discussed in Chapter 13), which is a destructive operation and should be done with extreme caution, especially on shared repositories.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph TD A["Start: File *config.log* is currently tracked by Git (committed)."] --> B{"Goal: Stop tracking *config.log* and ignore future changes."}; B --> C["<b>Step 1:</b> Remove file from Git's tracking (index/staging area)<br>but keep it in the working directory."]; C --> D["Command: *git rm --cached config.log*"]; D --> E["Result: *config.log* is no longer staged for commit (Git sees it as 'deleted' from index).<br>The actual file *config.log* remains in your local working directory."]; E --> F["<b>Step 2:</b> Add the file (or a pattern matching it) to *.gitignore*."]; F --> G["Edit *.gitignore* file, add line:<br>*config.log* (or **.log* if appropriate)"]; G --> H["<b>Step 3:</b> Stage and commit these changes."]; H --> I["Command: *git add .gitignore* (and *git add config.log* if it appears as deleted in status, due to *rm --cached*)<br>Alternatively, *git add .gitignore config.log* or *git add .*"]; I --> J["Command: *git commit -m \Stop tracking config.log and add to .gitignore\*"]; J --> K["<b>Outcome:</b><br>- *config.log* is no longer tracked by Git.<br>- Future changes to *config.log* will be ignored.<br>- The file still exists in your local working directory.<br>- *Important:* The file's content from previous commits remains in the repository history."]; K --> Z["End: File is untracked and ignored going forward."]; subgraph "Important Note on History" Note1["*.gitignore* only prevents future tracking of untracked files."] Note2["To remove a file from <b>all</b> past history (e.g., sensitive data),<br>more advanced tools like *git filter-repo* are needed (see Chapter 13).<br>This is a destructive operation and should be handled with extreme care."] class Note1,Note2 warningNote; end K -.-> Note1; %% Styling classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef commandNode fill:#E0E7FF,stroke:#4338CA,stroke-width:1px,color:#3730A3,font-family:monospace; classDef resultNode fill:#F3F4F6,stroke:#9CA3AF,color:#374151,font-size:11px; classDef outcomeNode fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef warningNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E,font-style:italic; classDef decisionNode fill:#FEF9C3,stroke:#F59E0B,stroke-width:1px,color:#78350F; class A startNode; class B decisionNode; class C,F,H processNode; class D,G,I,J commandNode; class E,K resultNode; class Z outcomeNode; class Note1,Note2 warningNode;
Debugging .gitignore
Sometimes, it’s not clear why a file is being ignored or, conversely, why it’s not being ignored when you expect it to be. Git provides a command to help debug this: git check-ignore
.
- git check-ignore <pathspec>…:This command checks the given path(s) against your .gitignore rules (from all applicable .gitignore files and the global excludes file). If a path is ignored, the command will output the path. If not, it outputs nothing.
git check-ignore myapp.log # Output if ignored: myapp.log
- git check-ignore -v <pathspec>… or git check-ignore –verbose <pathspec>…:This is the most useful mode for debugging. For each path that is ignored, it outputs three pieces of information, colon-separated:
- The path to the
.gitignore
file containing the matching pattern. - The line number of the matching pattern within that file.
- The pattern itself.
git check-ignore -v myapp.log node_modules/somepackage/file.js # Example output: # .gitignore:1:*.log myapp.log # .gitignore:5:node_modules/ node_modules/somepackage/file.js
If a path is not ignored, there will be no output for that path. - The path to the
- git check-ignore -n <pathspec>… or git check-ignore –non-matching <pathspec>…:This option, often used with -v, will also print details for paths that are not ignored. For non-matching paths, the second and third fields (line number and pattern) will be empty.
- git status –ignored:This command shows untracked files, and if you provide the –ignored flag, it will also list ignored files that are currently present in your working directory. This can be a quick way to see what Git considers ignored.
The .gitkeep
Convention
Git does not track empty directories. If you git add
an empty directory, nothing happens. However, sometimes you want an empty directory structure to be present in the repository (e.g., a logs/
directory, tmp/
, or an empty src/main/resources
in a Java project before resources are added).
To work around this, developers often use a convention: placing an empty file (or a file with minimal content) named .gitkeep
inside an otherwise empty directory. This makes the directory non-empty, allowing Git to track the .gitkeep
file, and thus, the directory structure.
.gitkeep
is not an official Git feature or command. It’s purely a community convention. The name.gitkeep
is just a common choice; you could name it.keep
,placeholder.txt
, etc.- If you have a rule in
.gitignore
that would ignore all files in a directory (e.g.,logs/*
), but you want to keep thelogs/
directory structure using.gitkeep
, you would need to explicitly un-ignore.gitkeep
:# Ignore all files in logs directory logs/* # But do not ignore .gitkeep files within logs !logs/.gitkeep # Or more generally, if you use .gitkeep in many places: # !.gitkeep
Practical Examples
Setup:
Create a new Git repository for these examples.
mkdir gitignore-lab
cd gitignore-lab
git init
# Create some files and directories
touch app.py
mkdir build
touch build/output.exe
mkdir logs
touch logs/debug.log
touch logs/trace.log
mkdir data
touch data/user_data.csv
touch .DS_Store # Simulate macOS file
touch Thumbs.db # Simulate Windows file
mkdir src
touch src/main.c
mkdir src/editor_config
touch src/editor_config/.vscode_settings
touch my_notes.txt~ # Simulate editor backup file
Current git status
would show all these as untracked files.
1. Creating a Basic .gitignore
File
Create a file named .gitignore
in the root of gitignore-lab
with the following content:
# .gitignore
# Ignore build artifacts
build/
*.exe
# Ignore log files
logs/
*.log
# Ignore OS-specific files
.DS_Store
Thumbs.db
# Ignore editor backup files
*~
# Ignore specific sensitive files (example)
# credentials.secret
Now, check the status:
git status
Expected Output (files like app.py
, data/user_data.csv
, src/main.c
, src/editor_config/.vscode_settings
should still be listed as untracked, but the others should be gone from the list):
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
app.py
data/
src/
nothing added to commit but untracked files present (use "git add" to track)
The build/
, logs/
, .DS_Store
, Thumbs.db
, and my_notes.txt~
are no longer listed as untracked because they match patterns in .gitignore
.
Tip: Always commit your
.gitignore
file to the repository so that these ignore rules are shared with all collaborators.
git add .gitignore
git commit -m "Add initial .gitignore file"
2. Ignoring Specific Files and Using Negation
Let’s say we want to ignore all .csv
files in the data/
directory, but specifically track data/important_report.csv
.
Modify .gitignore
:
# ... (previous rules) ...
# Ignore CSV files in data directory
data/*.csv
# But track important_report.csv
!data/important_report.csv
Now create these files:
touch data/temp_data.csv
touch data/important_report.csv
Check status:
git status
Expected Output:
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
app.py
data/important_report.csv # This one is NOT ignored
src/
nothing added to commit but untracked files present (use "git add" to track)
data/temp_data.csv` is ignored, but `data/important_report.csv` is still listed as untracked (meaning it’s not ignored).
3. Setting up a Global .gitignore
Let’s say you always use an editor that creates *.bak
backup files, and you want to ignore these for all your projects.
- Create a global ignore file, e.g.,
~/.gitignore_global
:# ~/.gitignore_global *.bak .my_personal_editor_prefs
- Configure Git to use it:
bash git config --global core.excludesFile ~/.gitignore_global
(Adjust path if you named or placed your global file differently.)
Now, go back to your gitignore-lab
project (or any other Git project) and create a .bak
file:
# In gitignore-lab directory
touch my_document.txt.bak
git status
my_document.txt.bak should not appear in the
git status` output because it’s ignored by your global configuration.
4. Ignoring an Already Tracked File
Suppose app.py
was accidentally committed, but now we realize it’s a local script and shouldn’t be tracked.
First, let’s commit it to simulate the scenario:
git add app.py
git commit -m "Add app.py script"
# Now app.py is tracked
Now app.py is trackedNow, we want to stop tracking it and ignore it:
- Remove
app.py
from Git’s tracking (but keep the local file):git rm --cached app.py
Output:rm 'app.py'
- Add
app.py
to.gitignore
:# ... (previous rules) ... app.py
- Commit these changes:
git add .gitignore git commit -m "Stop tracking app.py and add to .gitignore"
Now, git status
will no longer show changes to app.py
, and it’s effectively ignored for future commits. It still exists in your working directory.
5. Debugging with git check-ignore -v
Let’s check why some files are ignored:
git check-ignore -v logs/debug.log build/output.exe data/important_report.csv src/main.c
Expected Output (paths and line numbers might vary slightly based on your exact .gitignore
):
.gitignore:5:logs/ logs/debug.log
.gitignore:2:build/ build/output.exe
# No output for data/important_report.csv because it's NOT ignored (due to the '!' rule)
# No output for src/main.c because it's NOT ignored
This output tells you exactly which pattern in which .gitignore
file is causing a file to be ignored. If data/important_report.csv
was checked, it wouldn’t appear in the output because it’s not ignored.
6. Using .gitkeep
Let’s say we want an empty directory tmp/
in our project.
mkdir tmp
git status # 'tmp/' won't show up as Git doesn't track empty dirs
Add a .gitkeep
file:
touch tmp/.gitkeep
git status
Expected Output:
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/
tmp/
nothing added to commit but untracked files present (use "git add" to track)
Now tmp/
(because it contains .gitkeep
) is listed. You can git add tmp/.gitkeep
to track the directory structure.
If you wanted to ignore everything else in tmp/:
Add to .gitignore:
# ...
tmp/*
!tmp/.gitkeep
OS-Specific Notes
- Path Separators: Always use forward slashes (
/
) as path separators in.gitignore
files. Git internally converts them to be platform-independent, so/
works correctly on Windows, macOS, and Linux. - Case Sensitivity:
- On case-sensitive file systems (like those typical on Linux), patterns in
.gitignore
are case-sensitive by default.MyFile.log
is different frommyfile.log
. - On case-insensitive file systems (default for Windows and macOS), Git’s behavior regarding
.gitignore
patterns can be influenced by thecore.ignorecase
setting (which defaults totrue
on these systems). Generally, patterns will match case-insensitively. For example,*.log
would ignoreDEBUG.LOG
anddebug.log
. - To ensure consistent behavior across platforms, it’s often best to write patterns in lowercase and ensure filenames also follow a consistent case if cross-platform collaboration is expected.
- On case-sensitive file systems (like those typical on Linux), patterns in
- Common OS-Specific Files to Ignore Globally:
- macOS:
.DS_Store
,._*
,.Spotlight-V100
,.Trashes
- Windows:
Thumbs.db
,ehthumbs.db
,Desktop.ini
,$RECYCLE.BIN/
- Linux (editors/misc): *~ (Emacs/Vim backups), *.swp (Vim swap files)These are excellent candidates for your global .gitignore file (core.excludesFile). Many template .gitignore files for specific languages or frameworks available online (e.g., from github.com/github/gitignore) include common OS-specific ignores.
- macOS:
Common Mistakes & Troubleshooting Tips
Issue / Symptom | Common Cause(s) | Solution / Tip |
---|---|---|
File is already tracked and .gitignore doesn’t ignore it. |
.gitignore only prevents untracked files from being added. It does not affect files already in Git’s index. |
1. Remove the file from Git’s tracking (but keep it locally): git rm --cached <file> (use -r for directories).2. Add/verify the pattern for the file in .gitignore .3. Commit both changes: git add .gitignore <file_if_rm_was_staged> (or git add . if appropriate), then git commit -m "Stop tracking <file>" .
|
Pattern in .gitignore not working as expected. |
Incorrect pattern syntax (e.g., wrong wildcards, path issues, misunderstanding of / or ** ). Typos. Case sensitivity issues on some systems. |
Review .gitignore syntax rules carefully.Use git check-ignore -v <path_to_file> to see which pattern (if any) from which .gitignore file is matching the file.Test patterns incrementally. Ensure correct use of forward slashes / for paths.
|
Global .gitignore not taking effect. |
Path to global ignore file in core.excludesFile is incorrect or file doesn’t exist at that path. Local .gitignore rules (especially ! patterns) might be overriding global rules. |
Verify the path with git config --global core.excludesFile .Ensure the global ignore file exists and has correct patterns. Use git check-ignore -v <path_to_file> to see if a global rule is being applied or if a local rule is taking precedence.
|
Forgetting to commit .gitignore file(s). |
Ignore rules work locally for the developer who made the change, but not for collaborators or in CI/CD environments after they pull. |
Always git add .gitignore and git commit any changes to .gitignore files so the rules are versioned and shared with the project.
|
Negation pattern (! ) not working. |
A negation pattern only re-includes a file if it was excluded by a previous pattern in the same file or a .gitignore file in a parent directory. A later exclusion rule can override an earlier inclusion. The order of rules matters. |
Ensure an exclusion pattern for the file (or a broader pattern covering it) appears before the negation pattern. Example: *.log !important.log (Correct)!important.log *.log (Incorrect – important.log will be ignored by the later *.log rule)
|
Trying to ignore changes to a tracked file. | A file is tracked, but you want Git to temporarily ignore local modifications to it without removing it from the repository. .gitignore is not for this. |
Use git update-index --assume-unchanged <file> to tell Git to temporarily ignore changes to this specific tracked file (local setting).To start tracking changes again: git update-index --no-assume-unchanged <file> .Use with caution as it can lead to forgetting to commit important changes. |
Exercises
- Comprehensive Project
.gitignore
:- Create a new project directory and initialize a Git repository.
- Imagine this is a Python project. Create the following files/directories:
main.py
tests/test_utils.py
venv/
(a virtual environment directory)__pycache__/some_module.cpython-39.pyc
docs/_build/html/index.html
output.log
.env
(for local environment variables)secrets.ini
(containing mock sensitive data)
- Create a
.gitignore
file in the project root to:- Ignore the
venv/
directory. - Ignore all
__pycache__/
directories. - Ignore all
.pyc
files. - Ignore the
docs/_build/
directory. - Ignore all
.log
files. - Ignore the
.env
file. - Ignore
secrets.ini
.
- Ignore the
- Run
git status
to verify that onlymain.py
,tests/test_utils.py
, and.gitignore
itself are listed as untracked. - Use
git check-ignore -v
on several of the ignored files to see which rule is causing them to be ignored.
- Selective Ignoring and
.gitkeep
:- In the same project, create a directory
temp_outputs/
. - You want to keep the
temp_outputs/
directory in the repository structure but ignore all its contents, except for a.gitkeep
file. - Add rules to your
.gitignore
to achieve this. - Place an empty
.gitkeep
file insidetemp_outputs/
. - Create another file, e.g.,
temp_outputs/report.tmp
. - Run
git status
. Verify thattemp_outputs/.gitkeep
is shown as untracked (or can be added), buttemp_outputs/report.tmp
is ignored.
- In the same project, create a directory
Summary
.gitignore
files specify intentionally untracked files that Git should ignore.- Patterns follow glob syntax (
*
,?
,**
,/
for directory,!
for negation). .gitignore
can be placed in the repository root or subdirectories. Rules are relative and can be overridden.- A global
.gitignore
(configured viacore.excludesFile
) handles user-specific ignores across all repositories. .gitignore
only affects untracked files. To ignore an already tracked file, usegit rm --cached <file>
, then add it to.gitignore
, and commit.- Debug ignore rules with
git check-ignore -v <path>
. - The
.gitkeep
convention (placing an empty.gitkeep
file) is used to make Git track otherwise empty directories.
Properly configured .gitignore
files are essential for maintaining a clean and manageable Git repository by ensuring that only relevant project files are version controlled.
Further Reading
- Official Git Documentation:
gitignore(5)
Manual Page: https://git-scm.com/docs/gitignore
- Pro Git Book:
- Chapter 2.2 Git Basics – Recording Changes to the Repository (Ignoring Files): https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository#_ignoring_files
- GitHub Documentation:
- Ignoring files: https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
- Collection of useful
.gitignore
templates: https://github.com/github/gitignore
- Atlassian Git Tutorials – .gitignore: https://www.atlassian.com/git/tutorials/saving-changes/gitignore
