The Three Trees: Working Directory, Index, and Repository¶
Git organizes your work into three distinct areas, commonly called the "three trees." Understanding how changes flow between them is the single most important concept in Git. Every command you run either moves data between these areas, inspects the differences between them, or manipulates what they contain.
The Three Areas¶
Working Directory (Working Tree)¶
The working directory is the actual directory of files on your filesystem. When you open a file in your editor, you're editing the working copy. This is the only area you interact with directly - the other two are managed by Git.
Your working directory contains every tracked file at its current state, plus any new files you've created that Git doesn't know about yet.
Staging Area (Index)¶
The staging area - also called the index - is a holding zone between your working directory and the repository. When you run git add, you're copying the current state of a file into the index. The index represents what will go into your next commit.
The index is actually a binary file at .git/index. It doesn't store file contents directly - it stores references to blob objects and metadata (file paths, permissions, timestamps). You can think of it as a manifest of what the next commit will look like.
Why have a staging area at all? Because it gives you precise control over commits. If you've changed five files but only two changes are related, you can stage just those two and commit them with a focused message. The other three changes stay in your working directory, ready for a separate commit.
Repository (Committed History)¶
The repository is the .git directory at the root of your project. It contains the complete history of every committed snapshot, all branches and tags, configuration, and the object database. When you run git commit, Git takes everything in the staging area, creates a permanent snapshot, and adds it to the repository.
Once something is committed, it's safe. Git's content-addressable storage means committed data is checksummed and extremely difficult to lose (even if you try).
The File Lifecycle¶
Every file in a Git repository is in one of these states:
| State | Where it lives | What it means |
|---|---|---|
| Untracked | Working directory only | Git sees the file but isn't tracking it |
| Tracked, unmodified | Working directory = repository | File matches the last commit, no changes |
| Modified | Working directory differs from index | You've changed the file but haven't staged the changes |
| Staged | Index differs from repository | Changes are queued for the next commit |
| Committed | Repository | Changes are permanently recorded |
A file can be in multiple states simultaneously. If you modify a file, stage it, then modify it again, the file is both staged (the first set of changes) and modified (the second set). This is normal and useful - it means your next commit captures only the changes you explicitly staged.
flowchart LR
A[Untracked] -->|git add| B[Staged]
B -->|git commit| C[Unmodified]
C -->|edit file| D[Modified]
D -->|git add| B
C -->|git rm| A
D -->|git restore| C
B -->|git restore --staged| D
Creating a Repository: git init¶
Every Git project starts with initialization. git init creates the .git directory with all the internal structures Git needs:
Or initialize in an existing directory:
After git init, your directory looks like this:
myproject/
└── .git/
├── HEAD # Points to the current branch
├── config # Repository-specific configuration
├── description # Used by GitWeb (rarely relevant)
├── hooks/ # Sample hook scripts
├── info/ # Global exclude patterns
├── objects/ # All content (blobs, trees, commits, tags)
└── refs/ # Branch and tag pointers
├── heads/ # Local branches
└── tags/ # Tags
The important pieces: HEAD tells Git what branch you're on. objects/ stores all your data. refs/ stores branch and tag pointers. The Object Model and Refs, the Reflog, and the DAG guides cover these in depth.
Checking Status: git status¶
git status is the command you'll run most often. It shows you exactly where things stand across all three areas:
The output groups files by state:
- Changes to be committed - files in the staging area (ready for commit)
- Changes not staged for commit - tracked files that have been modified but not staged
- Untracked files - files Git doesn't know about
For a compact view:
This shows two-column status codes. The left column is the staging area status, the right column is the working tree status:
| Code | Meaning |
|---|---|
?? |
Untracked |
A |
New file, staged |
M |
Modified, staged |
M |
Modified, not staged |
MM |
Modified, staged, then modified again |
D |
Deleted, staged |
D |
Deleted, not staged |
Staging Changes: git add¶
git add copies the current state of files from the working directory into the staging area:
# Stage a specific file
git add README.md
# Stage multiple files
git add file1.txt file2.txt
# Stage all changes in a directory
git add src/
# Stage all changes in the entire working tree
git add .
# Stage parts of a file interactively
git add -p README.md
git add -p (patch mode) is particularly powerful. It shows you each change in a file and lets you choose which ones to stage. This means you can make several unrelated edits to one file and commit them separately.
Stage with intention
Get in the habit of staging specific files rather than using git add . for everything. This forces you to review what you're about to commit and produces cleaner, more focused commits.
Unstaging and Restoring: git restore¶
Made a mistake? git restore (introduced in Git 2.23) provides clear commands for undoing changes:
# Discard changes in working directory (restore from index)
git restore README.md
# Unstage a file (restore the index from HEAD, keep working changes)
git restore --staged README.md
# Restore a file from a specific commit
git restore --source=HEAD~2 README.md
git restore discards changes
git restore README.md (without --staged) permanently discards your uncommitted working directory changes to that file. There is no undo. If you haven't committed or stashed those changes, they are gone. Use git stash first if you're unsure.
Before Git 2.23, the same operations used git checkout and git reset:
| Modern command | Legacy equivalent |
|---|---|
git restore file |
git checkout -- file |
git restore --staged file |
git reset HEAD file |
The modern restore commands are clearer about what they do, but you'll see the legacy forms in older documentation and tutorials.
Removing Files: git rm¶
To remove a tracked file from both the working directory and the staging area:
This deletes the file from disk and stages the deletion. Your next commit will record the removal.
To stop tracking a file but keep it on disk (useful for files that should have been in .gitignore):
The file stays in your working directory but Git stops tracking it. Add it to .gitignore to prevent accidentally re-adding it.
The Init-to-Commit Workflow¶
Here's the complete workflow from creating a repository through your first commit, with each step showing the state of the three trees:
HEAD: Your Current Position¶
HEAD is a special reference that points to whatever you currently have checked out - usually a branch, which in turn points to a commit. Think of HEAD as "you are here" on the commit graph.
When HEAD points to a branch name (the normal case), it's a symbolic reference:
When you make a new commit, the branch pointer moves forward and HEAD follows it.
Detached HEAD¶
If you check out a specific commit (by hash, tag, or remote branch) rather than a branch name, HEAD points directly to that commit. This is called detached HEAD state:
In detached HEAD, you can look around and even make commits, but those commits aren't on any branch. If you switch away without creating a branch, those commits become unreachable and will eventually be garbage collected.
To get out of detached HEAD and keep any commits you made:
git branch my-new-branch # Create a branch at the current commit
git switch my-new-branch # Switch to it (HEAD is now attached)
The .gitignore File¶
Not every file in your working directory should be tracked. Build artifacts, dependency directories, editor configs, OS files, and secrets should be excluded. The .gitignore file tells Git which files to ignore.
Create .gitignore in your repository root:
# Compiled output
*.o
*.pyc
__pycache__/
# Dependencies
node_modules/
vendor/
# Build directories
dist/
build/
*.egg-info/
# IDE and editor files
.idea/
.vscode/
*.swp
*~
# OS files
.DS_Store
Thumbs.db
# Environment and secrets
.env
.env.local
*.key
*.pem
Pattern Syntax¶
| Pattern | Matches | Example |
|---|---|---|
*.log |
All .log files in any directory |
error.log, src/debug.log |
/build |
build directory in the repo root only |
build/, but not src/build/ |
build/ |
Any directory named build |
build/, src/build/ |
doc/*.txt |
.txt files in doc/ (not subdirs) |
doc/notes.txt, but not doc/sub/notes.txt |
doc/**/*.txt |
.txt files anywhere under doc/ |
doc/notes.txt, doc/sub/notes.txt |
!important.log |
Exception - track this file even if *.log matches |
Negation must come after the pattern it overrides |
# |
Comment line | Ignored by Git |
Global gitignore
For OS-specific files (.DS_Store, Thumbs.db) and editor files (.idea/, *.swp) that apply to all your projects, use a global gitignore instead of adding them to every repository:
Then put your personal patterns in ~/.gitignore_global. This keeps each repository's .gitignore focused on project-specific patterns.
Checking What's Ignored¶
To verify your .gitignore patterns work:
# Check if a specific file would be ignored
git check-ignore -v debug.log
# List all ignored files
git status --ignored
Putting It All Together¶
Further Reading¶
- Pro Git - Chapter 2: Git Basics - covers init, clone, status, add, commit, rm, and gitignore
- Official git-status documentation - complete reference for status output formats
- Official gitignore documentation - full pattern syntax specification
- github/gitignore - a collection of
.gitignoretemplates for many languages and frameworks
Previous: Introduction: Why Git, and Why Version Control | Next: Commits and History | Back to Index