git status
git status is the command you will run more than any other in Git. It tells you exactly what state your repository is in right now: which branch you’re on, which files have changed, which of those changes are staged for the next commit, and which files Git doesn’t know about yet. Understanding its output is the single most useful skill for avoiding accidental commits, lost work, and confusing merge situations.
Overview / How it works
Git tracks your project through three areas: the working directory (the actual files on disk that you edit), the index (also called the staging area, a snapshot of what will go into the next commit), and the repository (the committed history, stored as objects). git status compares all three: it compares the working directory against the index to find unstaged changes, and it compares the index against the last commit (HEAD) to find staged changes.
Recall that a commit object doesn’t store files directly — it points to a tree object, which is a snapshot of the directory structure, and that tree points to blob objects, which are the actual file contents, each identified by a SHA-1 hash of their content. The index is essentially a staged tree-in-waiting: when you run git add, Git hashes the file content into a blob and records it in the index. When you run git commit, Git turns the current index into a new tree object, wraps it in a commit object, and moves the current branch pointer to that new commit. git status is the tool that shows you, before any of that happens, exactly what the next commit would contain if you ran git commit right now.
git status also tells you which branch HEAD is attached to, and whether your local branch has diverged from its upstream (the remote-tracking branch, e.g. origin/main). This makes it your first line of defense against committing to the wrong branch or pushing when you’re behind the remote.
Syntax
git status [<options>]
Common options:
| Option | What it does |
|---|---|
-s, --short |
Prints a compact, two-column format instead of the verbose default output. |
-b, --branch |
Shows branch and tracking info even in short format (short format hides it by default). |
--porcelain |
A stable, script-friendly output format that won’t change between Git versions — use this in scripts instead of parsing the human-readable format. |
-u[<mode>], --untracked-files[=<mode>] |
Controls how untracked files are shown: no hides them, normal (default) lists untracked directories without recursing, all lists every untracked file individually. |
--ignored |
Also lists files ignored by .gitignore. |
-v, --verbose |
Shows the actual diff of staged changes at the bottom of the output (and unstaged too with -vv). |
Examples
Example 1: A clean working tree
git status
Output:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
This is the state you want before you start new work and after every successful commit and push: the working directory matches the index, and the index matches the last commit. Nothing is pending.
Example 2: Modified and untracked files
echo "// TODO: rate-limit login attempts" >> src/auth.js
touch notes.txt
git status
Output:
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/auth.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
notes.txt
no changes added to commit (use "git add" and/or "git commit -a")
src/auth.js is already tracked by Git, so editing it shows up under Changes not staged for commit — Git compared the working directory copy against the blob stored in the index and found a difference. notes.txt has never been added to Git at all, so it appears under Untracked files instead; Git has no record of it in the index or any commit.
Example 3: Staging changes for commit
git add src/auth.js
git status
Output:
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/auth.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
notes.txt
After git add src/auth.js, Git hashed the current content of auth.js into a blob and recorded it in the index. Now the index differs from HEAD, so the file moves into the Changes to be committed section — this is exactly what git commit would snapshot right now. notes.txt is still untracked, because it was never staged; a commit at this point would not include it.
Short format
For a quick glance, -s compresses the same information into two-letter codes per file:
git status -s
Output:
M src/auth.js
?? notes.txt
The left column shows the index (staged) state, the right column shows the working-tree (unstaged) state. M in the left column means staged-modified; ?? means completely untracked. A file that’s both staged and further modified afterward would show as MM.
How it works step by step
When you run git status, Git performs three comparisons in sequence:
- It reads the current commit that
HEADpoints to and gets its tree (the last committed snapshot). - It reads the index file (
.git/index), which holds the staged snapshot, and diffs it against that committed tree — any difference is reported as Changes to be committed. - It walks the working directory and diffs each tracked file’s current content against what’s recorded in the index — any difference is reported as Changes not staged for commit. Files present in the working directory but absent from the index (and not matched by
.gitignore) are reported as Untracked files.
None of this writes anything to disk or to the object database — git status is purely read-only. It’s always safe to run, as often as you like.
Common Mistakes
Mistake 1: Assuming git commit -a includes new files
touch src/passwordReset.js
echo "resetPassword();" >> src/auth.js
git commit -am "feat: add password reset flow"
git status
Output:
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/passwordReset.js
nothing to commit, working tree clean is wrong here — use git status before committing to confirm
The -a flag on git commit only stages modifications to files Git already tracks — it never picks up new, untracked files. The commit above silently ships without passwordReset.js, even though the feature depends on it. The fix is to run git status before every commit and explicitly git add new files, or use git add -A when you really do want everything staged.
Mistake 2: Committing to the wrong branch
Because git status‘s first line always names the current branch (On branch main), skipping it is how people end up committing feature work directly to main. Before staging anything, glance at that first line — if it doesn’t say the branch you meant to be on, run git switch feature/login-page first. Fixing a commit made on the wrong branch after the fact requires git reset or git cherry-pick, both avoidable by checking status first.
Mistake 3: Trusting status blindly for ignored files
If a file you expect to see modified is missing from git status entirely, check whether it’s matched by .gitignore — ignored files are hidden by default. Run git status --ignored to confirm, or git check-ignore -v <file> to see exactly which rule is excluding it.
Best Practices
- Run
git statusbefore everygit addand everygit commit— make it a reflex, not an afterthought. - Use
git status -sonce you’re comfortable reading the long format; it’s faster to scan for larger changesets. - If
git statusshows a file you didn’t mean to touch, usegit restore <file>(unstaged changes) orgit restore --staged <file>(staged changes) rather than manually re-editing it back. - Prefer
git add <specific-file>overgit add .when a commit should be narrowly scoped —git statustells you exactly what’s about to be staged either way. - In scripts or CI, parse
git status --porcelain, never the human-readable default, since the default format’s wording can change between Git versions. - Check the branch-tracking line (e.g.
Your branch is ahead of 'origin/main' by 2 commits) so you know togit pushorgit pullbefore you forget.
Practice Exercises
- Create a new repository, add one file and commit it. Then modify that file, create a second new file, and run
git status. Before reading further, predict which section each file will appear under — then verify. - Stage the modified file from the previous exercise with
git add, but leave the new file untracked. Rungit statusagain and confirm the modified file has moved into Changes to be committed while the new file is still untracked. - Add a file named
debug.logto.gitignore, then create a file with that exact name in your working directory. Rungit statusand confirm it does not appear, then rungit status --ignoredand confirm it does.
Summary
git statusis a read-only command that never changes your repository — it’s always safe to run.- It compares the working directory to the index (unstaged changes) and the index to the last commit (staged changes).
- Files fall into three buckets: untracked, modified-but-unstaged, and staged-for-commit — the section headings in the output tell you which.
git status -sgives a compact two-column view once you know how to read the long format.- Checking
git statusbefore every commit prevents the most common Git mistakes: forgotten files, wrong branches, and accidental commits of ignored or unwanted content.
