TechEarl

The Git Staging Area Explained

The Git staging area (the index) is the in-between layer where you assemble exactly what goes into your next commit. Here is what git add really does, and why it exists.

Ishan Karunaratne⏱️ 9 min readUpdated
Share thisCopied
Understanding the Git staging area and what git add does to the index before a commit

The Git staging area, also called the index, is the snapshot of what will go into your next commit. When you run git add <path>, Git copies the current content of that file into the index, marking that exact version for the next commit. git commit then records the index as a new commit, and nothing else. So the flow is: working tree to (git add) the staging area / index to (git commit) the repository. git status is what shows you the split between what is staged (already in the index) and what is still unstaged, and git restore --staged <path> (or, on older Git, git reset HEAD <path>) takes a file back out of the index without touching your edits.

That one extra step trips up almost everyone new to Git. Most tools have two states: a file is either saved or unsaved. Git has three. Understanding the third one is the difference between fighting Git and working with it. The official Git documentation calls it the index throughout; the Pro Git chapter on recording changes and the git-add reference are the authoritative source if you want the canonical wording.

The three places a file can live

Every tracked file in a Git project exists in up to three states at once:

AreaWhat it isHow a file gets there
Working directoryThe actual files on disk that you editYou edit them in your editor
Staging area (index)The snapshot queued for the next commitgit add <file>
Repository (commit history)The permanent, recorded snapshotsgit commit

The flow is one-directional when you save work: edit in the working directory, stage with git add, record with git commit. The staging area is the gate between "stuff I changed" and "stuff I am committing."

If you have just started a project, the setup walkthrough for a new repo covers getting to this point, and this is the same staging step you would use when first adding Git to an existing project. If git add or git status greets you with fatal: not a git repository instead, you are standing outside an initialized repo: the not-a-git-repository fix explains why and how to get back inside one. If you are completely new, start with the Git for beginners guide, which frames all of this from scratch.

What git add actually does

git add takes the current contents of a file and writes a snapshot of it into the index. Run git status after editing a file and Git tells you it is modified but not staged:

bash
git status
git status showing a file moving from unstaged to staged after git add
git add moves a change into the staging area: red becomes green.
text
On branch 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:   app.js

no changes added to commit (use "git add" to commit)

Stage it, and the same file moves into the "Changes to be committed" group:

bash
git add app.js
git status
text
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   app.js

The key thing to understand: git add snapshots the file at the moment you run it. If you edit app.js again after staging it, that newer edit is not staged. Git will now show the file in both groups at once: the staged snapshot in "Changes to be committed" and the fresh edit in "Changes not staged for commit." You have to git add again to update the staged copy. This catches people constantly, so it is worth seeing once on purpose.

To stage everything at once:

bash
git add .

That stages all changes in the current directory and below, including new files. New files are otherwise invisible to Git until you add them, since untracked files are not part of any commit yet. If a file keeps showing up that you never want committed, that is what a .gitignore file is for.

Why does staging exist at all?

This is the question worth answering, because the staging area feels like pure overhead until you see what it buys you.

The point is that a commit and a set of edits are not the same thing. You might make five unrelated changes in one editing session: a bug fix, a typo correction, a new feature, and two debug console.log lines you will delete later. If commits were just "save everything I changed," you would be forced to lump all five into one messy commit.

The staging area lets you compose commits deliberately. Stage only the bug fix, commit it with a clear message, then stage the feature work and commit that separately. Your history ends up as a series of focused, single-purpose commits instead of one "fixed stuff" blob. That pays off every time you read a diff, hunt a regression, or write a clear commit message, since each commit is about exactly one thing. And if your edits got messier than your history should be, you can always squash several focused commits into one with an interactive rebase after the fact.

It also gives you a review checkpoint. The gap between "I edited files" and "this is now permanent history" is a moment to look at precisely what you are about to record:

bash
git diff --staged

git diff on its own shows working-directory changes that are not yet staged. git diff --staged (also spelled --cached) shows what you have staged, which is exactly what the next commit will contain. Get into the habit of running it right before you commit. You can automate the same checkpoint too: a pre-commit hook can check what you have staged before it becomes a commit, catching debug lines or secrets before they land in history.

Staging part of a file with git add -p

Here is where the staging area stops being a chore and starts being a tool. Suppose you fixed a bug and added a debug line in the same file. You want to commit the fix but not the debug line. git add app.js would stage both. Instead, stage interactively:

bash
git add -p app.js

The -p flag (short for --patch) walks you through the file one "hunk" (a contiguous block of changes) at a time and asks what to do with each:

text
Stage this hunk [y,n,q,a,d,s,e,?]?

The choices you will actually use:

KeyAction
yStage this hunk
nDo not stage this hunk
sSplit the hunk into smaller pieces (when two changes are close together)
qQuit; stop reviewing hunks
?Show help for all the options

Press y on the bug fix, n on the debug line, and only the fix gets staged. Commit, and the debug line stays in your working directory, unstaged, ready to be removed or kept. This is the cleanest way I know to keep commits focused when your edits got tangled together. It is also a gentle nudge to review your own diff line by line before it becomes history.

How to unstage a file

You staged something you did not mean to. Nothing is committed yet, so this is completely safe to undo. The modern command is:

bash
git restore --staged app.js

This removes the file from the staging area and leaves your working-directory edits untouched. The file goes back to "Changes not staged for commit." Your actual changes are not lost; only the staged snapshot is dropped.

On older Git (before 2.23, when git restore was introduced), the equivalent is:

bash
git reset HEAD app.js

Both do the same job here: unstage without touching your edits. git status even prints the right one for your version in its hints, so you can copy it straight from there.

Be careful not to confuse unstaging with discarding. Unstaging keeps your edits; discarding throws them away. If you actually want to throw the edits away, that is a different operation covered in discarding local changes in Git. And if you want to set work aside temporarily without committing or losing it, git stash is the tool for that.

Staging area vs working directory vs commit

To pin the mental model down, here is the same change at each stage:

StateWhere the change livesCommand to move it forwardSafe to undo?
ModifiedWorking directory onlygit add to stageYes, edits are yours
StagedIndex, queued for commitgit commit to recordYes, git restore --staged
CommittedRepository historygit push to shareYes, but it is now history

Once a change is committed it is part of history, and undoing it means rewriting that history rather than just dropping a staged snapshot. If you committed too early, undoing the last commit walks through the options, and the broader reset vs revert comparison explains which approach fits which situation. Even then nothing is truly gone: you can recover commits you thought you lost via the reflog.

A typical staging workflow

Putting it together, a normal session looks like this:

bash
# See what changed
git status

# Stage the specific files for this commit
git add app.js styles.css

# Or stage selectively within a file
git add -p utils.js

# Review exactly what will be committed
git diff --staged

# Record it
git commit -m "Fix price rounding on the checkout total"

The staging area is the step where you slow down for a second and decide what belongs together. Skip it (with shortcuts like git commit -am) once you understand it and the situation is simple. But knowing it is there, and what it does, is what lets you build a clean history instead of a pile of save points.

FAQ

git add copies a snapshot of your changes into the staging area, marking them for inclusion in the next commit. git commit takes everything currently staged and records it as a permanent snapshot in the repository history. Nothing is committed until you run git commit, and only staged changes get committed.

Yes. Staging area, index, and the older term cache all refer to the same thing in Git. The official documentation uses index, while most everyday explanations say staging area. That is why some commands use the word cached, such as git diff cached, while others say staged. They mean the same place.

Run git restore staged followed by the file name. This removes the file from the staging area but keeps your edits in the working directory. On Git versions before 2.23 the equivalent command is git reset HEAD followed by the file name. Neither one throws away your actual changes.

Yes, with git add patch (the dash p flag), which walks through your changes one block at a time and asks whether to stage each one. Press y to stage a block and n to skip it, then commit. The skipped changes stay in your working directory, unstaged, so you can commit or remove them later.

Sources

Authoritative references this article was fact-checked against.

Tagsgit staging areaGitVersion Controlgit addgit index

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

The PCRE (*ACCEPT) backtracking control verb: how it forces an immediate successful regex match, how capturing groups are closed when it fires, which engines support it, and the backtracking control verb family.

The Regex (*ACCEPT) Control Verb, Explained

What the PCRE (*ACCEPT) backtracking control verb does, how it forces an immediate successful match, how it behaves inside capturing groups, which engines support it, and where it is genuinely useful.