## Why ignore files?

Your project directory will almost always contain files that don't belong in your Git repository. Build artifacts, dependency folders, secret configuration, editor settings, OS-generated junk — committing any of these creates noise for collaborators and can even leak credentials.

Common examples include:

- **Dependency directories** installed by package managers (`node_modules/`, `vendor/`, `venv/`)
- **Build output** generated by compilers or bundlers (`dist/`, `build/`, `*.o`, `*.pyc`)
- **Environment and secret files** containing API keys or database credentials (`.env`, `credentials.json`)
- **OS files** that your operating system creates automatically (`.DS_Store`, `Thumbs.db`)
- **Editor and IDE folders** with personal workspace settings (`.vscode/`, `.idea/`, `*.swp`)
- **Logs and error reports** generated at runtime (`*.log`, `npm-debug.log*`)

You *could* just avoid staging these files, but that's error-prone and they'll still clutter `git status` output. A better approach is to tell Git to ignore them automatically.

## Creating a `.gitignore` file

Create a file called `.gitignore` in the root of your repository. Inside it, list patterns — one per line — that describe which files and directories Git should ignore.

```bash
# Create the file
touch .gitignore

# Open it in your editor and add patterns
```

Once you've saved your `.gitignore`, commit it to the repository so the rules are shared with everyone who works on the project:

```bash
git add .gitignore
git commit -m "Add .gitignore"
```

**Tip:** You should commit your `.gitignore` early — ideally in the first commit of a new project, before you accidentally commit files you don't want tracked.

## Pattern syntax

Each line in a `.gitignore` file is a pattern. Here are the rules Git uses to match them:

### Basic patterns

| Pattern | What it matches |
|---------|----------------|
| `config.yml` | Any file or directory named `config.yml`, at any depth |
| `*.log` | Any file ending in `.log` |
| `debug?.log` | `debug0.log`, `debuga.log`, etc. (`?` matches one character) |
| `debug[0-9].log` | `debug0.log` through `debug9.log` |

### Directory patterns

Append a trailing slash to match directories only:

| Pattern | What it matches |
|---------|----------------|
| `logs/` | Any directory named `logs`, at any depth |
| `build/` | Any directory named `build`, at any depth |

### Anchoring with a leading slash

Without a leading slash, a pattern matches at any level in the directory tree. Adding a leading `/` anchors the pattern to the directory where the `.gitignore` file lives:

| Pattern | What it matches |
|---------|----------------|
| `config.yml` | `config.yml`, `src/config.yml`, `app/config.yml` |
| `/config.yml` | Only `config.yml` in the project root |
| `/build/` | Only the `build/` directory in the project root |

### Double asterisk (`**`)

The `**` wildcard matches across directory boundaries:

| Pattern | What it matches |
|---------|----------------|
| `**/logs` | `logs`, `src/logs`, `app/src/logs` |
| `**/logs/*.log` | `logs/debug.log`, `src/logs/error.log` |
| `src/**` | Everything inside `src/`, at any depth |
| `a/**/b` | `a/b`, `a/x/b`, `a/x/y/b` |

### Negation (exceptions)

Prefix a pattern with `!` to re-include a file that would otherwise be ignored:

```
# Ignore all .env files
*.env

# But keep the example file
!.env.example
```

**Important caveat:** You cannot re-include a file if its parent directory is already ignored. Git skips ignored directories entirely for performance, so it never looks inside them:

```
# This will NOT work as expected
build/
!build/important.js
```

To work around this, ignore the directory's *contents* instead of the directory itself:

```
# Ignore everything in build/ but allow exceptions
build/*
!build/important.js
```

### Comments and blank lines

Lines starting with `#` are comments. Blank lines are ignored and can be used to organise your patterns into logical groups:

```
# Dependencies
node_modules/
vendor/

# Build output
dist/
build/

# Environment
.env
.env.local
```

## Real-world `.gitignore` examples

### Node.js

```
node_modules/
dist/
build/
.env
.env.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.yarn/cache
.yarn/unplugged
coverage/
```

### Python

```
__pycache__/
*.py[cod]
*.so
venv/
.env
*.egg-info/
dist/
build/
.pytest_cache/
htmlcov/
.coverage
```

### Ruby

```
vendor/bundle/
.bundle/
*.gem
Gemfile.lock
log/
tmp/
.env
coverage/
```

### PHP / Laravel

```
vendor/
node_modules/
.env
storage/*.key
public/hot
public/storage
*.cache
```

### WordPress

```
wp-content/uploads/
wp-content/cache/
wp-content/upgrade/
wp-config.php
.htaccess
*.log
```

### General patterns (add to any project)

```
# OS files
.DS_Store
Thumbs.db
Desktop.ini

# Editors and IDEs
.vscode/
.idea/
*.swp
*.swo
*~
.project
.classpath
.settings/

# Misc
*.bak
*.tmp
```

**Tip:** The [github/gitignore](https://github.com/github/gitignore) repository maintains a comprehensive collection of `.gitignore` templates for virtually every language and framework. It's a great starting point when setting up a new project.

## Ignoring files that are already tracked

This is the most common gotcha with `.gitignore`: **it only affects untracked files**. If you've already committed a file, adding it to `.gitignore` won't make Git forget about it.

To stop tracking a file that's already been committed:

```bash
# Remove from Git's index but keep the file on disk
git rm --cached .env

# For an entire directory
git rm -r --cached node_modules/

# Then commit the removal
git commit -m "Stop tracking .env"
```

After this, the file stays on your local machine but Git treats it as untracked, and your `.gitignore` pattern will now take effect.

**Warning:** When other team members pull this commit, the file *will* be deleted from their working directory. If it's something like `.env`, make sure everyone knows to back up their copy first, or provide a `.env.example` template in the repo.

## Global `.gitignore`

Some files — like `.DS_Store` on macOS or `Thumbs.db` on Windows — should be ignored everywhere, not just in one project. Instead of adding them to every repository's `.gitignore`, configure a global ignore file:

```bash
# Create the global ignore file
touch ~/.gitignore_global

# Tell Git to use it
git config --global core.excludesFile ~/.gitignore_global
```

A typical global `.gitignore` contains patterns for your OS and editor:

```
# macOS
.DS_Store
.AppleDouble
.LSOverride

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# Linux
*~
.directory

# Editors
.vscode/
.idea/
*.swp
*.swo
*.sublime-workspace
*.sublime-project
```

The global file applies to every repository on your machine, but it is *not* committed anywhere — it only affects you. Project-specific patterns should still go in the repository's `.gitignore` so they're shared with collaborators.

## Local-only rules with `.git/info/exclude`

If you need to ignore files in a specific repository but don't want to modify the shared `.gitignore`, you can add patterns to `.git/info/exclude`. This file works exactly like `.gitignore` but is never committed.

This is useful for personal tooling or editor files that only affect your workflow:

```bash
# Add patterns directly
echo "my-scratch-notes.txt" >> .git/info/exclude
echo ".env.local.bak" >> .git/info/exclude
```

## Nested `.gitignore` files

You can place a `.gitignore` file in any subdirectory of your repository, not just the root. Patterns in a nested `.gitignore` are relative to the directory where the file lives, and they only apply from that directory downward.

```
project/
├── .gitignore          # Project-wide rules
├── docs/
│   └── .gitignore      # Rules specific to docs/
└── src/
    └── .gitignore      # Rules specific to src/
```

When multiple `.gitignore` files exist, the rules from the most specific (deepest) file take precedence. In practice, most projects only need a single root-level `.gitignore`. Use nested files when a subdirectory has meaningfully different ignore requirements — for example, a `docs/` folder that generates HTML output you don't want tracked.

## Debugging with `git check-ignore`

When a file is being ignored and you can't figure out why — or it's *not* being ignored and you think it should be — use `git check-ignore` to debug:

```bash
# Check if a file is ignored and show which rule matched
git check-ignore -v path/to/file
```

Output example:

```
.gitignore:3:*.log    debug.log
```

This tells you that line 3 of `.gitignore` (the pattern `*.log`) is causing `debug.log` to be ignored.

To check multiple files at once:

```bash
git check-ignore -v *.log src/*.tmp
```

If the command produces no output, the file is not being ignored by any rule.

## Keeping empty directories

Git only tracks files, not directories. If you need an empty directory to exist in the repository — for example, a `logs/` folder that should be present but always empty — the convention is to add a `.gitkeep` file inside it:

```bash
mkdir logs
touch logs/.gitkeep
```

Then in your `.gitignore`:

```
# Ignore log files but keep the directory
logs/*
!logs/.gitkeep
```

**Note:** `.gitkeep` is not a Git feature — it's a community convention. The file name doesn't matter; it could be called anything. `.gitkeep` is simply the most widely recognised name for this purpose.

## Quick reference

| I want to... | Pattern |
|---|---|
| Ignore all `.log` files | `*.log` |
| Ignore `node_modules/` everywhere | `node_modules/` |
| Ignore `.env` only in the root | `/.env` |
| Ignore everything in `build/` except one file | `build/*` then `!build/keep.js` |
| Ignore all `.tmp` files in any `cache/` folder | `**/cache/*.tmp` |
| Ignore a directory but keep it in the repo | Add `dir/*` + `!dir/.gitkeep` |
| Stop tracking a file already committed | `git rm --cached <file>` |
| Find out why a file is ignored | `git check-ignore -v <file>` |
| Ignore files globally (all repos) | `git config --global core.excludesFile ~/.gitignore_global` |

---

Once your `.gitignore` is set up and your repository is clean, [DeployHQ](https://www.deployhq.com) can deploy your code automatically — only the files that matter reach your server, while ignored files stay out of the deployment.
