Run git status and Git compares the HEAD commit, the index, and the working tree against each other. You get back a list of paths, sorted by whether the next commit will pick them up or leave them behind.
Most people run it right before staging or committing, and again before they switch branches. It only reads, so running it too often costs nothing.
The Git manual describes the long output as a commit template comment, written to be pasted into the message editor. Pro Git shows this in action, since git commit opens the editor with that status text already included as comments.
The long output uses up to four headings. Changes to be committed, Changes not staged for commit, and Untracked files are the regular ones. Unmerged paths only shows up during a merge conflict.
What Is git status?

No files and no commit history get touched when you run it. Git reports which paths differ between the HEAD commit, the index, and the working tree, and it never edits file content.
Each call makes three comparisons. HEAD against the index gives you the paths a commit would record right now. The index against the working tree turns up tracked files that were edited or deleted since the last git add. And anything in the working tree that Git doesn’t track, once ignored paths are set aside, is reported as untracked.
The index doubles as the staging area, the snapshot your next commit will record. The Git manual splits the output along the same line. The first group is what a commit would take, and the other two are what git add could still bring in.
Pro Git frames the command as the way to learn which of four states each file is in: untracked, unmodified, modified, or staged.
How to Read the git status Output

Each section of the long output answers one question. What is staged for the next commit, what is modified but unstaged, what is untracked, and what is in conflict. A section only prints when it has entries.
Run the command from a subfolder and paths show relative to your current directory, so you can paste them straight into the next command. The hint lines in parentheses name the commands that move or discard each group.
The long format is written for people, and its wording can change at any time (the manual says so itself). Pro Git’s own screens show the drift. One example suggests git restore --staged to unstage, while others still print git reset HEAD.
That’s why scripts parse porcelain format instead.
Changes to Be Committed

These files are staged. The index holds a version that differs from HEAD, and the next commit records exactly that version.
You’ll see labels like new file, modified, deleted, and renamed here. A rename prints as old name -> new name.
Git stages a file as it was at the moment of git add. Edit the file afterwards and it shows up here and under the unstaged heading at the same time (Pro Git).
Changes Not Staged for Commit
Tracked files that were edited or removed in the working tree land here. The next commit skips them until they get staged.
Modified means the content differs from the index. Deleted means the file was removed from disk without git rm.
Be careful with git commit -a. It stages every tracked change in this group at once, and Pro Git warns that it can pull in edits you never meant to include.
Untracked Files
An untracked file is absent from the last snapshot and absent from the index.
Git leaves these out of commits until told otherwise, which keeps build output and other generated files out of history by accident. Pro Git recommends writing patterns for them into a .gitignore file before the first commit.
Once a pattern matches, the file stops appearing here. Rules you only want on your own machine go in .git/info/exclude instead.
Unmerged Paths During a Merge Conflict

This heading appears when a merge stops on a conflict, and every file listed carries a label such as both modified.
While the conflict is open, the banner reads You have unmerged paths, and the hint tells you to fix the conflicts and then commit (Pro Git). After you stage the fixes, it changes to All conflicts fixed but you are still merging, and git commit finishes the merge.
Open each listed file, resolve the conflict markers, and stage it with git add.
What a Clean Working Tree Looks Like
The message reads nothing to commit, working tree clean. Nothing is staged, no tracked file has changed, and there are no untracked files.
Ignored files don’t count against it. Atlassian adds a practical point. Commands such as git merge want a clean working directory so uncommitted edits don’t get overwritten.
What Do the Branch Lines in git status Mean?

The opening lines name the checked-out branch and compare it with the branch it tracks. Ahead means you have unpushed local commits. Behind means the upstream has commits you haven’t pulled, and diverged means each side carries commits the other lacks.
The first line reads On branch followed by a name. According to Pro Git, GitHub changed its default branch name from master to main in mid-2020 and other hosts followed, so new repositories usually print main.
The comparison runs against the upstream branch recorded in your local repository. git status never queries the server, so the counts are only as fresh as your last fetch.
What to do about each state is fairly obvious. Up to date means nothing is pending. Ahead means push when the commits are ready. Behind means pull or merge the upstream changes before pushing, and diverged means merge or rebase first, then push.
Short format drops these lines unless you add -b, which tells the Git manual’s short format to show branch and tracking info.
Detached HEAD State
When HEAD points at a commit instead of a branch, the first line reports a detached HEAD in place of a branch name.
Checking out a tag or an old commit leads there, and the detached HEAD state is normal for read-only poking around. Commits made in it belong to no branch until you create one.
Move into that state with git checkout or git switch and Git prints its own advice, controlled by the advice.detachedHead setting, on how to create a local branch after the fact.
Short Format and Porcelain Format Status Codes
Short format prints one line per path as XY followed by the path, where X is the index state and Y is the working tree state. Porcelain format keeps that layout but stays frozen across Git versions, which is why scripts should parse it.
| Format | Stable across versions | Best for |
|---|---|---|
| Long (default) | No, wording can change | Reading at the terminal |
| Short (-s) | No, follows user config | Quick scans of many files |
| Porcelain (–porcelain) | Yes | Scripts and tooling |
Reading the XY Columns
Outside a merge, the left column reports the index against HEAD and the right column reports the working tree against the index. A blank in either column means no change on that side.
| Code | Meaning | Long format section |
|---|---|---|
| M (blank, then M) | Modified in working tree, not staged | Changes not staged for commit |
| M (M, then blank) | Modified and staged | Changes to be committed |
| MM | Staged, then modified again | Both sections |
| A (A, then blank) | New file staged | Changes to be committed |
| D (blank, then D) | Deleted in working tree, not staged | Changes not staged for commit |
| UU | Unmerged, both modified | Unmerged paths |
| ?? | Untracked | Untracked files |
| !! | Ignored (only with –ignored) | Ignored files |
During a conflict, X and Y show each side of the merge relative to the common ancestor, so UU means both sides changed the file. Untracked and ignored entries repeat the same character in both columns, because the index doesn’t know the path.
Renames print as R entries with old path -> new path. For submodules, an M, m, or ? reports a new commit, modified content, or untracked files inside the submodule.
Porcelain v1 Versus v2
Version 1 is the default when --porcelain has no version. It uses the same two-letter codes as short format and ignores color.status and status.relativePaths, so paths are always relative to the repository root. Pair it with -z for NUL-terminated entries when file names contain unusual characters.
Version 2 does more. With --branch it adds branch headers (branch.oid, branch.head, branch.upstream, and branch.ab for ahead and behind counts), and it prints a stash line when --show-stash is used and the stash isn’t empty. An unchanged state is written as a dot instead of a space, each entry gets file modes and object names, and renames carry a similarity score such as R100.
Pick v1 for simple parsers. Pick v2 when a script needs ahead and behind counts or file modes.
As of the 2.56.0 documentation, git-scm.com lists the git-status manual as unchanged since its 2.53.0 edit on February 2, 2026, so the formats above reflect the current manual.
Which git status Options Change the Output?
The options split by what they do to the output. Some pick the format (-s, –long, –porcelain), some decide what gets listed (-u, –ignored, –ignore-submodules), some add detail (-v, –show-stash, –column), and a few change how comparisons work (–ahead-behind, –renames). A flag on the command line wins over the matching config variable for that run.
Untracked File Modes
The -u flag (long form --untracked-files) takes a mode, and the mode must be attached to the flag, so -uno works while -u no does not.
- no hides untracked files completely
- normal lists untracked files and directories, with one entry per untracked directory
- all also lists every file inside those untracked directories
Plain -u with no mode means all, while a run without -u uses normal.
The status.showUntrackedFiles variable changes that default, and it also governs the untracked list that git commit prints.
Ignored Files
Ignored paths stay hidden until you ask for them with --ignored, and the mode decides how they’re grouped.
--ignored=traditional is the default when no mode is given. It shows ignored files and directories, and it lists individual files inside ignored directories only when --untracked-files=all is also set.
--ignored=matching shows only paths that match an ignore pattern directly.
A directory that matches a pattern appears as one entry without its contents. A directory with no pattern of its own, but whose contents are all ignored, is skipped and its contents are listed instead.
The last value, no, shows nothing.
Detail and Comparison Flags
- -v adds the staged diff under the file list, and a second -v adds the unstaged diff too
- –show-stash prints how many entries sit in the stash
- –column lays untracked files out in columns
- –ignore-submodules takes none, untracked, dirty, or all, and sets how much submodule change counts as a modification
- –no-ahead-behind skips the detailed commit counts against the upstream branch
- –no-renames turns off rename detection for one run, while –find-renames=<n> sets the similarity threshold
Configuration Variables
Any flag you type daily can become a default.
Set it with git config, and the command-line flag still wins for that run.
| Variable | Default | Effect |
|---|---|---|
| status.showUntrackedFiles | normal | Sets the untracked mode; -u overrides it |
| status.relativePaths | true | false prints paths from the repository root |
| status.short | off | true makes -s the default; –no-short overrides |
| status.branch | off | true makes -b the default; –no-branch overrides |
| status.aheadBehind | true | false drops detailed counts in non-porcelain formats |
| status.showStash | false | true prints the stash entry count |
| status.submoduleSummary | false | Adds a commit summary for modified submodules |
| status.renames | follows diff.renames | false disables rename detection, copies adds copy detection |
The Git source documentation on the master branch (checked October 1, 2026) also lists status.compareBranches, which accepts @{upstream} and @{push} to show a second comparison. Whether your installed version has it depends on its release, so run git --version first.
How git status Differs From git diff and git log

Status tells you which paths changed. Diff shows the line-level changes inside them, and log shows committed history. I run status first, diff second, and log only when the question is about the past.
| Command | Compares | Output |
|---|---|---|
| git status | HEAD, index, working tree | Paths and their states |
| git diff | Working tree against index | Line-level patch |
| git diff –staged | Index against HEAD | Line-level patch |
| git log | Commit history | List of commits |
Why git diff Can Print Nothing
A file can sit under Changes to be committed while a plain git diff stays empty. Confusing the first time it happens.
Pro Git explains it. git diff covers only unstaged changes, so once everything is staged it prints nothing. Add --staged (or its synonym --cached) to see the staged patch, and this guide to how git diff works goes deeper.
Where git log Fits
Status reads exactly one commit from history, the one HEAD points to. Log reads every commit reachable from it, and that’s where authors, dates, and messages live.
The Git manual and Atlassian’s tutorial don’t conflict on this. Status borrows a single commit as its baseline and shows nothing of the history behind it, a job that belongs to git log.
What to Run After Reading git status

Each group maps to one command. git add stages a file, git restore --staged unstages it, and git restore discards working tree edits. git commit records the staged snapshot, and git stash sets unfinished work aside.
- Run
git statusand note which group each file sits in. - Stage the files you want with
git add <file>, and rerun it after any later edit to the same file. - Run
git statusagain and confirm only the intended files appear under Changes to be committed. - Record the snapshot with
git commit. - Run
git statusa last time. The clean tree message confirms nothing was left behind.
A directory path works too, and git add stages everything inside it recursively, according to Pro Git.
Unstage or Discard
Git 2.23.0 introduced git restore, and newer status hints point to it instead of git reset and git checkout (Pro Git).
To unstage, use git restore --staged <file>. The older hint was git reset HEAD <file>. To discard edits, use git restore <file>, which replaced git checkout -- <file>.
Treat the second one as destructive. Pro Git warns that uncommitted work discarded this way is usually unrecoverable.
Setting Work Aside With git stash
Reach for a stash when you want to keep the edits but clear the tree. Pro Git rates stashing and branching above discarding in that case.
Once git stash has run, git status --show-stash prints the number of stashed entries, and status.showStash makes that line the default.
Where git status Slows Down or Stops Short
Slowness mostly comes from the search for untracked files in a large working tree. On the other side, the command stops short of showing stash contents, submodule summaries, and ignored paths. Each limit has a flag or a setting.
Large Repositories
Git prints a warning when listing untracked files takes longer than 2 seconds, and the advice.statusUoption setting silences it (Git manual).
| Setting | Effect on speed | Trade-off |
|---|---|---|
| -uno or status.showUntrackedFiles=no | Fastest, skips the untracked search | New files stay invisible until staged |
| core.untrackedCache=true | Searches only directories changed since the last run | Larger .git/index file |
| Untracked cache plus core.fsmonitor | Enumerates only recently changed directories | Needs warm-up runs and a monitor |
The GitHub Blog (2022) measured roughly a 2x speedup in the untracked search from the cache alone and about 10x with a file system monitor added.
A Chromium developer’s benchmark on chromium/src (chromium-dev group, 2023) landed lower, at 2.81 times faster with both features than with neither.
The gap shows the gain depends on the repository, so time git status before and after. The manual adds that a file system monitor without the untracked cache delivers much smaller benefits.
For daily work, the untracked cache beats -uno. Hiding untracked files is how new files get forgotten, as the manual itself cautions.
Background Runs and Index Locks
Every run refreshes the index and writes the updated stat data back to disk, an optimization the command doesn’t strictly need.
That write touches only cached stat data, not your files or history.
The catch shows up with background polling. A status run in the background holds the index lock, and other Git processes started at the same moment can fail. Scripts that poll status should call git --no-optional-locks status, which avoids taking the optional lock (Git manual).
What the Default Output Leaves Out
Stash contents never appear, since --show-stash adds only the entry count. Submodule commit summaries stay off until status.submoduleSummary is set, and ignored paths remain hidden until you pass --ignored.
git status FAQ
Why do untracked files keep showing up in git status?
A file keeps appearing while no ignore pattern matches it and no commit tracks it. Add a pattern to .gitignore for generated files, or stage the file with git add to start tracking it.
Running with -uno only hides the entries. It does not remove the cause.
Why is git status not showing my changes?

Usually the file is ignored, the edit is already committed, the change sits in the stash, or you ran the command in another repository or with a path limit.
Run git status --ignored and git status --show-stash to rule out the first and third. New files also vanish when -uno or status.showUntrackedFiles=no is active.
How often should I run git status?
Before every git add, before every git commit, and before switching branches. It’s the cheapest check for a file sitting in the wrong state.
How do I show git status information in my shell prompt?
Copy git-prompt.sh from the contrib/completion folder of Git’s source, source it in .bashrc, and call \_\_git\_ps1 inside PS1.
Setting GIT\_PS1\_SHOWDIRTYSTATE=1 adds working directory state next to the branch name, per Pro Git.
Do graphical Git clients show the same information as git status?

Yes, in substance. Visual Studio Code, GitHub Desktop, lazygit, and Magit report the same repository state, so staged, unstaged, and untracked paths match the command line.
Each tool names and groups them in its own way.
Setting Up git status for Daily Use
A routine starts with one rule. Run the command before every commit and after every staging step. Changing defaults comes second, and only settings that remove a recurring blind spot earn a place.
Turn on status.showStash early, since a forgotten stash stays invisible in the default output. Move to status.short and status.branch once the XY codes feel familiar, but know that short format drops the hint lines that name the unstage and discard commands.
Trimming the output trades guidance for speed, which suits anyone who already reads M, A, and ?? at a glance. Once the staged list matches your intent, git commit records the staged snapshot and the cycle restarts.
- Notepad++ Android Options: Apps That Come Close - October 21, 2026
- Data Management Services: A Practical Guide for Growing Companies - October 21, 2026
- MongoDB Cheat Sheet - October 20, 2026




