Once you have a repository, the next thing to understand is how Git monitors the files inside it. Not every file in your project is automatically tracked — Git requires you to be deliberate about what it watches and what goes into each commit. That deliberateness is by design, and understanding it is what separates developers who use Git confidently from those who feel like they're guessing.
The File Lifecycle
Every file in your working directory is in one of two broad states: tracked or untracked.
- Tracked files are files Git knows about — they were part of the last snapshot. A tracked file can be in one of three substates: unmodified, modified, or staged.
- Untracked files are everything else — files Git sees in the directory but has never been told to include.
When you first clone a repository, all files are tracked and unmodified. As you edit them, they become modified. When you run git add, they become staged. When you commit, they become unmodified again — and the cycle repeats.
Untracked → Staged → Committed (Unmodified)
↑
Unmodified → Modified → Staged
Checking File Status — git status
git status is the command you will run more than any other. It shows you which files are in which state at any given moment.
A clean working directory looks like this:
git status
On branch main
nothing to commit, working tree clean
When you create a new file, Git immediately notices it but does not track it:
Untracked files:
(use "git add <file>..." to include in what will be committed)
app.js
Git is explicit: it sees the file but will not include it in any commit until you tell it to.
Short Status
The full git status output can be verbose. For a compact view, use the -s or --short flag:
git status -s
M README.md
MM app.js
A styles.css
?? notes.txt
The output uses two columns. The left column reflects the staging area; the right column reflects the working directory.
| Symbol | Meaning |
|---|---|
?? | Untracked file |
A | New file added to the staging area |
M | Modified file |
MM | Modified, staged, then modified again — has both staged and unstaged changes |
D | Deleted file |
Staging Files — git add
git add is a multipurpose command. It serves three distinct purposes:
- Begin tracking a new file — tells Git to start watching an untracked file.
- Stage a modified file — queues an already-tracked file's changes for the next commit.
- Mark merge conflicts as resolved — covered in the branching topics.
The most accurate mental model for git add is not "add this file to the project" but rather "add precisely this content to the next commit."
Staging a single file
git add app.js
Staging multiple files
git add app.js styles.css README.md
Staging a directory (all files inside it recursively)
git add src/
Staging everything in the current directory
git add .
After staging, git status moves the file under "Changes to be committed":
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: app.js
The staging gotcha
git add captures the file as it exists at the moment you run the command. If you stage a file and then edit it again, git status will show the file in both sections — staged (the version from when you ran git add) and modified (the newer unsaved version):
Changes to be committed:
modified: app.js
Changes not staged for commit:
modified: app.js
If you commit at this point, the older staged version goes into the commit — not the current one. To include the latest edits, run git add again before committing.
Removing Files — git rm
To stop tracking a file and remove it from the project entirely, use git rm. This removes the file from both the staging area and the working directory:
git rm PROJECTS.md
After the next commit, the file will be gone from the repository and will no longer appear as untracked.
If you simply delete a file from your filesystem without using git rm, Git will notice the deletion and show it as an unstaged change — but it won't be included in the commit until you stage the removal explicitly.
Keeping the file on disk but removing it from tracking
Sometimes you accidentally staged a file that should be ignored — a large log file, a compiled output, or a credentials file. To untrack it without deleting it from your computer:
git rm --cached .env
git rm --cached -r node_modules/
This removes the file from Git's tracking but leaves it in your working directory. After this, add it to .gitignore so it doesn't get accidentally staged again.
Renaming and Moving Files — git mv
Git does not explicitly track file movement. However, it provides git mv as a convenience command for renaming files:
git mv old-name.js new-name.js
Running git status after this shows:
Changes to be committed:
renamed: old-name.js -> new-name.js
Under the hood, git mv is equivalent to three separate commands:
mv old-name.js new-name.js
git rm old-name.js
git add new-name.js
Git figures out the rename from the combination of a deletion and an addition. Either approach works — git mv is simply faster.
Ignoring Files — .gitignore
Some files should never be tracked: dependency folders, build output, log files, environment files containing secrets, and operating system metadata. The .gitignore file tells Git to completely ignore these files — they won't appear as untracked and won't be accidentally staged.
Create a .gitignore file in the root of your repository:
# Dependencies
node_modules/
# Build output
dist/
.next/
# Environment files
.env
.env.local
.env.*.local
# Logs
*.log
npm-debug.log*
# OS files
.DS_Store
Thumbs.db
.gitignore pattern rules
| Pattern | What It Matches |
|---|---|
*.log | All files ending in .log anywhere in the project |
build/ | The entire build directory |
/TODO | Only TODO in the root directory, not subdirectories |
doc/*.txt | .txt files directly inside doc/, but not nested deeper |
doc/**/*.pdf | All .pdf files inside doc/ and any subdirectory within it |
!lib.a | Track lib.a even though other .a files are ignored |
# | Comment — ignored by Git |
Important: .gitignore only works on untracked files
If a file has already been committed, adding it to .gitignore will not stop Git from tracking it. You need to explicitly untrack it first:
git rm --cached filename
Then add it to .gitignore and commit the change. From that point on, Git will ignore it.
.gitignore templates
GitHub maintains a collection of ready-made .gitignore templates for most languages and frameworks at https://github.com/github/gitignore. Starting from one of these is strongly recommended — they cover all the common files for a given stack that beginners might not think to exclude.
Summary
| Command | What It Does |
|---|---|
git status | Shows the state of all files in the working directory and staging area |
git status -s | Compact version of git status |
git add <file> | Stages a file for the next commit |
git add . | Stages all changes in the current directory |
git rm <file> | Removes a file from tracking and deletes it from the working directory |
git rm --cached <file> | Removes a file from tracking without deleting it from disk |
git mv <old> <new> | Renames or moves a file and stages the change |