Git and GitHubgit 2.54 · GitHub · macOS, Linux or Windows
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
26 small wins to finish your pathNext lesson →

.gitignore

.gitignore is a file of path patterns that tells Git which files to leave untracked, so an API key, a virtual environment, a data folder and a saved model never reach a commit.

Last updated: 12 Oct, 2026 · git 2.54

Reading status taught you that every untracked file shows up in the answer. A working project collects files that should never be tracked: the key in .env that costs money if it leaks, a venv folder of hundreds of megabytes that each person rebuilds anyway, the documents in data/, a saved model, and the checkpoint files a notebook leaves behind. One file keeps all of them out of your commits and out of your status.

Syntax: one pattern per line in a file named .gitignore, in the top folder of the repository. A blank line is skipped and a line starting with # is a comment.

text
# secrets
.env

# python
venv/
__pycache__/

# notebooks
.ipynb_checkpoints/

# data and model files
data/
*.pt
Project files used on this pageThis lesson builds on a project from earlier lessons. The code below imports this file. Click a file to see its code, or follow the link to the lesson that wrote it. To run the code yourself, keep it in the same folder.
View the code here
.gitconfig
[user]
	name = Reader
	email = reader@example.com

What a project collects in a week

This repository has two committed files and six things that should never be committed: the key, the virtual environment, compiled Python, a notebook checkpoint, the documents and a saved model.

ExampleThe key, sitting untracked in the project
mkdir ask-docs && cd ask-docs
git init -q -b main
printf 'Support bot\n' > README.md
printf 'chunk_size = 500\n' > loader.py
git add . && git commit -q -m "Start the project"
printf 'OPENAI_API_KEY=sk-live-9f2a\n' > .env    # the key, worth money
git status --short

The key is untracked, so one careless git add . would stage it. Those five lines above it are the throwaway repository for the rest of this page, and the blocks below start from them. The other five things a week of work leaves behind go in next.

ExampleFive more things Git has never seen
mkdir -p venv/lib && printf 'x\n' > venv/lib/site.py      # the virtual environment
mkdir -p __pycache__ && printf 'x\n' > __pycache__/loader.pyc
mkdir -p .ipynb_checkpoints && printf '{}\n' > .ipynb_checkpoints/explore-checkpoint.ipynb
mkdir -p data && printf 'doc one\n' > data/corpus.jsonl   # the documents
printf 'weights\n' > model.pt                             # a saved model
git status --short

Every one is untracked, so a careless git add . would stage the lot. Git prints a folder once instead of listing every file inside it, which is why venv/ is one line and not a thousand.

Writing the .gitignore file

The patterns go in the top folder of the repository, next to .git. Writing the file in an editor is the normal way; this block writes it in one command so the example runs end to end.

ExampleThe patterns, written into .gitignore
printf '# secrets\n.env\n\n# python\nvenv/\n__pycache__/\n\n# notebooks\n.ipynb_checkpoints/\n\n# data and model files\ndata/\n*.pt\n' > .gitignore
cat .gitignore

cat reads the file back so you can see what landed in it. A blank line separates the groups and each # line is a comment, so Git reads six patterns out of those thirteen lines.

ExampleThe same status, with the patterns in place
git status --short
  • The six noisy entries are gone from the status. They are still on disk, and Git has been told to leave them untracked.
  • ?? .gitignore is the one line left. The ignore file itself belongs in the repository, so that everyone working on the project ignores the same things. Commit it.

Checking which pattern matched

When a file is ignored and you cannot see why, git check-ignore -v names the file and the line that did it.

ExampleWhich line of .gitignore matched each path
git check-ignore -v .env venv/lib/site.py data/corpus.jsonl model.pt

Each line reads file:line:pattern followed by the path it matched, so .gitignore:5:venv/ says the pattern on line 5 caught that file. The other direction is git status --ignored, which lists the ignored paths instead of hiding them.

ExampleThe ignored paths, listed instead of hidden
git status --ignored --short

!! marks an ignored path and ?? marks an untracked one. A folder pattern such as data/ is reported once for the folder, not once per file.

The patterns worth knowing

PatternMatchesUse it for
.envthat exact name, in any folderthe file your keys live in
venv/a folder of that name and everything in itvirtual environments, node_modules/
__pycache__/the folder, at any depthcompiled Python
*.ptany file with that extensionsaved models, *.ckpt, *.h5
data/the whole data folderdocuments and datasets
!data/sample.jsonlan exception to an earlier patternthe one small sample you do want committed

Order matters for the last one: a ! line only rescues a file that an earlier pattern caught, and it cannot rescue a file inside an ignored folder, so data/sample.jsonl needs data/* above it rather than data/. GitHub keeps a ready-made file per language at github/gitignore, and the full rules are on the gitignore page.

The key that was committed before the pattern existed

.gitignore only decides what happens to untracked files. A file that is already in a commit stays tracked, and every change to it keeps appearing, however many patterns match its name.

ExampleAn ignore pattern does not untrack a file that is already committed
printf 'OPENAI_API_KEY=sk-live-9f2a\n' > .env
git add . && git commit -q -m "Start the project"   # the key went in
printf '.env\n' > .gitignore
printf 'OPENAI_API_KEY=sk-live-0000\n' > .env       # change it
git status --short

M .env is the answer: the pattern is in place, the file is still tracked, and the change is still reported. A pattern cannot untrack a file; one more command does that.

ExampleTaking the file out of Git's tracking
git rm --cached .env
git status --short
  • git rm --cached .env takes the file out of Git's tracking and leaves it on your disk. git rm without the flag would delete your copy as well.
  • D .env is that removal, staged and waiting for a commit, and ?? .gitignore is the ignore file waiting with it. After committing both, the pattern finally keeps the key quiet.

Removing the file does not remove the key from the history. The commit that added it is still there, git log -p prints it, and anyone who cloned the repository has it. Once such a commit is pushed, treat the key as public: scanners read new public repositories within minutes. The only fix that works is to create a new key and revoke the old one with the provider, and then keep the new one out of Git.

Watch out. Write the .gitignore and commit it before the secret file exists, not after. Keep an .env.example next to it, holding the variable names with no values, and commit that instead: the next person then knows which keys the project needs without ever seeing yours.

Where you write a .gitignore

  • At the start of a project. Start from GitHub's file for your language, add .env and your data folder, and commit it before anything else.
  • When a tool starts writing new output. A new build folder or cache folder gets a new line the day it appears.
  • Per machine, for your own files. Editor folders such as .idea/ and .vscode/ belong in your global ignore file, set with git config --global core.excludesfile, so your editor's habits stay out of the team's repository.
Try it yourself
  • Add *.log to the file, create debug.log, and watch it stay out of git status.
  • Change data/ to data/* and add !data/sample.jsonl on the next line. Create both data/corpus.jsonl and data/sample.jsonl and see only one of them come back.
  • Run git check-ignore -v README.md, a file no pattern covers. Git prints nothing and exits with a non-zero status, which is how a script asks the question.

Every expert started right here.