
Git Staging and Commits
Git does not record every saved file automatically. It gives you a preparation layer—the staging area—where you decide exactly what the next commit should contain.
This extra layer is one of Git’s greatest strengths, but it is also one of the most confusing concepts for beginners. A file can be committed, modified, partly staged, and modified again at the same time. Without a clear mental model, commands such as git add, git diff, git restore, and git commit can appear inconsistent.
This guide explains the staging area in depth and builds a disciplined commit workflow. You will learn how Git compares the working tree, index, and latest commit; how to stage whole files or selected hunks; how to unstage safely; how commits are constructed; how to write useful messages; and when amending or rewriting a commit is appropriate.
The Three States You Must Understand
A normal Git workflow involves three important representations of the project:
HEAD: The snapshot stored by the current commit.
Index: The proposed snapshot for the next commit, commonly called the staging area.
Working tree: The files currently visible and editable on disk.
A useful diagram is:
Latest Commit (HEAD)
|
| compared by: git diff --staged
v
Staging Area (Index)
|
| compared by: git diff
v
Working TreeEach layer can contain a different version of the same file.
For example:
The latest commit contains version 1.
The staging area contains version 2.
The working tree contains version 3.
This is not an error. It is how Git lets you prepare a precise commit while continuing to edit.
What Is the Staging Area?
The staging area is Git’s proposed next snapshot. Internally, Git stores its information in the index.
When you run:
git add app.phpGit copies the current content of app.php into the index. It does not merely mark the filename as selected.
If you edit app.php again after staging it, the index still contains the earlier staged version. The working tree contains the newer edit.
This distinction allows you to:
Separate unrelated changes into different commits.
Review exactly what the next commit will contain.
Stage only part of a file.
Leave experimental changes outside a stable commit.
Prepare a clean history even when the working directory is messy.
Why Git Has a Staging Area
Suppose you modify three files while fixing a login bug:
LoginController.phpcontains the actual bug fix.README.mdcontains an unrelated documentation improvement.config.phpcontains temporary debugging changes.
Without a staging area, you might have to commit all three changes together or manually undo some work before committing.
With Git, you can stage only the bug fix:
git add LoginController.php
git diff --staged
git commit -m "Reject expired login tokens"The documentation and debugging changes remain in the working tree for later decisions.
The staging area therefore acts as an editorial boundary between current work and permanent history.
Tracked, Untracked, Modified, and Staged Files
Files in a repository commonly move through these states:
| State | Meaning | Typical Action |
|---|---|---|
| Untracked | The file exists but is not part of Git’s index or history | Stage it or ignore it |
| Tracked and unchanged | The working version matches the index and current commit | No action required |
| Modified | The working version differs from the index | Review and stage selected work |
| Staged | The index differs from the current commit | Review and commit |
| Staged and modified again | The index and working tree contain different new versions | Decide whether to stage the later edit |
Use:
git statusfor a detailed explanation, or:
git status --shortfor a compact summary.
Reading Short Status Correctly
The short format uses two columns:
XY PATHX: Difference between
HEADand the index.Y: Difference between the index and the working tree.
Common examples:
?? notes.txt Untracked
A notes.txt New file staged
M app.php Modified but not staged
M app.php Modified version staged
MM app.php Staged version plus newer unstaged changes
D old.php Deletion staged
R old.php -> new.php
Rename stagedThe spaces matter. M and M describe different layers.
Creating a Practice Repository
Create a temporary project for the examples.
macOS, Linux, or Git Bash
mkdir git-staging-demo
cd git-staging-demo
git init -b main
cat > calculator.php <<'EOF'
<?php
function add(int $a, int $b): int
{
return $a + $b;
}
EOF
cat > README.md <<'EOF'
# Calculator Demo
A small project for practicing Git staging and commits.
EOF
git add calculator.php README.md
git commit -m "Create calculator demo"
PowerShell
New-Item -ItemType Directory git-staging-demo
Set-Location git-staging-demo
git init -b main
@"
<?php
function add(int `$a, int `$b): int
{
return `$a + `$b;
}
"@ | Set-Content calculator.php
@"
# Calculator Demo
A small project for practicing Git staging and commits.
"@ | Set-Content README.md
git add calculator.php README.md
git commit -m "Create calculator demo"
Verify the initial state:
git status
git log --onelineThe working tree should be clean.
How git add Works
git add updates the index with content from the working tree.
Its name can be slightly misleading because the command is not limited to new files. It stages:
New files.
Modifications to tracked files.
Resolved merge conflicts.
File deletions when selected through suitable path or update options.
Stage one file:
git add calculator.phpStage several named paths:
git add calculator.php README.mdStage a directory:
git add src/Stage changes under the current directory:
git add .Stage all additions, modifications, and deletions across the repository:
git add -AStage modifications and deletions to tracked files, but not new untracked files:
git add -ugit add ., git add -A, and git add -u
These commands overlap, but their intent differs.
| Command | New Files | Modified Files | Deleted Files | Scope |
|---|---|---|---|---|
git add . | Yes | Yes | Yes within the selected path scope | Current directory and descendants |
git add -A | Yes | Yes | Yes | Entire repository by default |
git add -u | No | Yes | Yes | Tracked paths in the selected scope |
In many simple repositories, git add . and git add -A appear to produce the same result. The difference becomes clearer when you run the command from a subdirectory or deliberately want to exclude untracked files.
Regardless of the command, review the staged result before committing.
Review Before Staging
Modify the calculator:
<?php
function add(int $a, int $b): int
{
return $a + $b;
}
function subtract(int $a, int $b): int
{
return $a - $b;
}
Then run:
git status
git diffgit diff shows changes in the working tree that are not staged.
Use a path when you want to inspect only one file:
git diff -- calculator.phpThe double dash marks the end of command options and the beginning of path arguments.
Review After Staging
Stage the file:
git add calculator.phpNow normal git diff may show nothing because there are no additional unstaged edits.
Inspect the proposed commit with:
git diff --staged--cached is a synonym:
git diff --cachedThis comparison shows the index relative to HEAD.
A disciplined workflow uses both comparisons:
git diff
git add calculator.php
git diff --staged
git commit -m "Add subtraction operation"A File Can Be Staged and Modified at the Same Time
Stage a change:
git add calculator.phpThen edit the same file again and add a multiplication function:
function multiply(int $a, int $b): int
{
return $a * $b;
}
Run:
git status --shortYou may see:
MM calculator.phpThe index contains the subtraction change. The working tree contains subtraction plus multiplication.
Compare each layer:
# Working tree compared with index
git diff
# Index compared with HEAD
git diff --stagedIf multiplication should be part of the same commit, stage the file again:
git add calculator.phpIf it should belong to a later commit, leave it unstaged and commit only the version already in the index.
Partial Staging with git add -p
Sometimes one file contains several logically unrelated edits. Partial staging lets you select individual sections, called hunks.
Run:
git add -p calculator.phpGit displays one hunk at a time and asks what to do.
Common choices include:
| Key | Meaning |
|---|---|
y | Stage this hunk |
n | Do not stage this hunk |
q | Quit and leave remaining hunks undecided |
a | Stage this and all later hunks in the file |
d | Skip this and all later hunks in the file |
s | Split the current hunk into smaller hunks when possible |
e | Manually edit the patch before staging |
? | Show available help |
After selecting hunks, inspect both sides:
git diff --staged
git diffThe first command shows what will be committed. The second shows what remains outside the commit.
Example: Separate Refactoring from a Feature
Suppose calculator.php contains two changes:
Rename a variable for clarity.
Add a new division operation.
These changes may belong in separate commits because one is refactoring and the other changes behavior.
Use:
git add -p calculator.phpStage only the rename, then commit:
git diff --staged
git commit -m "Clarify calculator parameter names"The division code remains unstaged. Stage and commit it afterward:
git add calculator.php
git diff --staged
git commit -m "Add division operation"This produces a history that is easier to review and reverse.
Interactive Staging with git add -i
Git also provides an interactive staging interface:
git add -iIt can help you:
View status.
Add updates.
Revert staged selections.
Add untracked files.
Stage patches.
Inspect diffs.
Many developers use git add -p more frequently because it goes directly to hunk selection, but -i is useful when several staging operations must be coordinated.
Intent to Add
The option:
git add -N new-file.phpor:
git add --intent-to-add new-file.phprecords that the path is intended to be added without staging its full content as a normal addition.
This can make the new file appear in diff-based workflows before its final content is staged. It is an advanced convenience and is not required for normal beginner workflows.
What a Commit Records
A commit records the snapshot prepared in the index and connects it to the existing history.
A commit object includes information such as:
A reference to the top-level tree representing the snapshot.
One or more parent commits, except for the initial commit.
Author identity and author timestamp.
Committer identity and committer timestamp.
The commit message.
Git then gives the commit an object identifier.
The commit does not contain every unstaged edit in the working tree. It records the index.
Author vs Committer
Git stores both an author and a committer.
Author: The person who originally created the change.
Committer: The person who recorded that version of the commit in the current history.
They are usually the same during ordinary local work.
They may differ when:
A maintainer applies another developer’s patch.
A commit is rebased.
A cherry-pick reproduces a change in another history.
A commit is amended by a different person.
Inspect both values with:
git log -1 --format=fullerCreating a Commit
Commit staged content with an inline message:
git commit -m "Add subtraction operation"Or open the configured editor:
git commitThe editor form is useful for messages that need a short subject followed by a detailed explanation.
Before committing, inspect:
git status
git diff --stagedAfter committing, inspect:
git status
git log -1 --statWriting Strong Commit Messages
A commit message should explain the purpose of the recorded change.
Weak examples:
update
changes
fix stuff
new code
final versionStronger examples:
Reject division by zero
Validate email format before registration
Remove duplicate payment retry
Document local database setupThe stronger messages identify a meaningful action and make the history searchable.
A Practical Message Structure
For a simple commit, one clear subject line is often enough:
Add subtraction operationFor a complex change, use:
Add expiration checks to password reset tokens
Reject tokens older than 30 minutes before loading the user.
This prevents expired reset links from reaching the password form.A common convention is:
Write a concise subject.
Use an imperative style such as
Add,Fix, orRemove.Leave a blank line before the body.
Explain why the change was needed and important implementation context.
Avoid repeating information that the diff already makes obvious.
Repository or organization rules may define additional formatting requirements.
Commit the Why, Not Only the What
The diff already shows which lines changed. The message should preserve reasoning that may not be visible later.
Less useful:
Change timeout from 10 to 30More useful:
Increase payment callback timeout for slow providers
Some providers require more than ten seconds during peak traffic.
Thirty seconds prevents premature retries without exceeding the API limit.The second message records the operational reason behind the number.
Commit Message Templates
A team can provide a message template containing reminders for authors.
Create a template such as:
# Summary
# Why is this change needed?
# How was it tested?
# Related issue:Configure it:
git config --global commit.template ~/.gitmessage.txtWhen git commit opens the editor, the template helps developers include consistent context.
Lines treated as comments are normally removed according to Git’s message cleanup behavior.
Review the Commit Without Creating It
Use a dry run:
git commit --dry-runThis shows what would be included and what would remain outside the commit without creating the commit.
You can also combine status formats:
git commit --dry-run --shortThe most reliable content review remains:
git diff --stagedCommitting Tracked Changes with git commit -a
The shortcut:
git commit -a -m "Update calculator operations"automatically stages modifications and deletions to files that are already tracked, then creates the commit.
It does not normally include new untracked files.
This shortcut can be useful for a small, focused change, but it bypasses the explicit review step of git add. It may include modifications you forgot were present.
A safer deliberate sequence is:
git status
git diff
git add -u
git diff --staged
git commit -m "Update calculator operations"Commit Selected Paths Carefully
Git supports commit forms that name paths directly, but they can behave differently from the normal index-based workflow.
For clarity and predictable review, especially while learning, prefer:
git add path/to/file
git diff --staged
git commit -m "Describe the change"This makes the proposed snapshot visible before history is updated.
How to Unstage a File
Staging is reversible. If a file was added to the index by mistake, unstage it without discarding the working changes.
Use:
git restore --staged calculator.phpThis restores the index version from HEAD by default while leaving the working file unchanged.
Check the result:
git status
git diff
git diff --stagedThe changes should move from the staged comparison back to the unstaged comparison.
Older reset Syntax
You will also encounter:
git reset HEAD calculator.phpWhen used with a path in this way, it updates the index and normally keeps the working-tree edits. Modern Git provides git restore --staged as a clearer command for this specific task.
Partially Unstage Changes
Use patch mode:
git restore --staged -p calculator.phpThis lets you remove selected hunks from the index while leaving other hunks staged.
It is the reverse counterpart of partial staging:
git add -pselects hunks to add to the index.git restore --staged -pselects hunks to remove from the index.
Unstaging Is Not the Same as Discarding
This command:
git restore --staged calculator.phpchanges the index but keeps the working edit.
This command:
git restore calculator.phprestores the working file from the index by default and can discard unstaged work.
Before running a working-tree restore, inspect:
git diff -- calculator.phpUncommitted working changes may not be recoverable after a destructive restore.
Amending the Most Recent Commit
If the latest local commit has a mistake, you can replace it with an amended commit.
Common cases include:
A required file was forgotten.
The commit message contains an error.
A small correction belongs logically in the same commit.
Add a Forgotten File
git add forgotten-test.php
git commit --amend --no-edit--no-edit keeps the existing message.
Change the Last Commit Message
git commit --amendEdit the message, save, and close the editor.
Amend with a New Inline Message
git commit --amend -m "Add subtraction and validation tests"Amending creates a new commit object. The commit identifier changes because the snapshot or metadata changed.
Do Not Amend Shared History Casually
Amending a commit that has already been pushed and used by other people rewrites history. Updating the remote may require a force push, which can disrupt collaborators.
A practical rule is:
Amend freely while the commit is private and local.
Be cautious after publishing it.
Follow the team’s policy for shared branches.
Prefer a new corrective commit when history is already in active use.
Empty Commits
Git normally stops when there are no staged changes.
An explicit empty commit can be created with:
git commit --allow-empty -m "Trigger deployment pipeline"Possible uses include:
Triggering automation tied to commit events.
Marking a project event without changing tracked content.
Testing repository integrations.
Empty commits should be intentional and clearly explained.
Sign-Off vs Cryptographic Signing
These concepts are different.
Developer Sign-Off
The option:
git commit --signoff -m "Add division operation"adds a Signed-off-by trailer using the committer identity. Projects may use this to record agreement with a contribution policy such as a Developer Certificate of Origin.
Cryptographic Commit Signing
A cryptographically signed commit uses signing configuration and keys to attach a verifiable signature:
git commit -S -m "Add division operation"This requires prior configuration and is separate from the textual sign-off trailer.
Do not describe --signoff as cryptographic verification.
Commit Trailers
Structured metadata can appear at the end of a commit message:
Fix duplicate invoice generation
Prevent the retry worker from creating an invoice after the first
request already completed successfully.
Reported-by: Jane Developer <jane@example.com>
Reviewed-by: Alex Maintainer <alex@example.com>Some projects define specific trailers and automated policies. Use only trailers required or recognized by the project.
Fixup and Squash Commits
During local development, you may discover that a new change belongs to an earlier commit.
Create a fixup commit:
git add calculator.php
git commit --fixup=<target-commit>Git creates a message beginning with fixup!. Later, an interactive rebase with autosquash can combine it with the target commit.
A squash commit can preserve an additional message:
git commit --squash=<target-commit>These workflows rewrite history and are best used before commits are shared broadly.
They will be covered more deeply in the workflow and history-rewriting articles.
Commit Hooks and Validation
Git hooks are programs that run at specific points in Git operations.
Commit-related hooks can be used for tasks such as:
Formatting staged files.
Running tests.
Rejecting secrets.
Checking commit-message structure.
Adding or validating metadata.
Examples include:
pre-commitprepare-commit-msgcommit-msgpost-commit
A local hook can block a commit, but local hooks alone should not be treated as the only enforcement layer because they may not be installed or enabled on every machine. Critical checks should also run in shared CI or server-side workflows.
A Complete Clean-Commit Workflow
Use the following sequence for most daily work:
1. Inspect the Repository
git status2. Review Unstaged Changes
git diff3. Stage One Logical Change
git add path/to/fileOr select hunks:
git add -p4. Verify the Proposed Snapshot
git diff --staged
git status5. Run Relevant Tests
# Example only; use the project’s actual test command
php vendor/bin/phpunit6. Commit with a Useful Message
git commit -m "Reject division by zero"7. Verify the Result
git status
git log -1 --stat8. Repeat for Remaining Work
If git status still shows unrelated edits, prepare the next focused commit instead of combining everything automatically.
Manual Run: Separate Two Changes in One File
This example demonstrates partial staging from start to finish.
Initial Committed File
<?php
function add(int $a, int $b): int
{
return $a + $b;
}
Working-Tree Version
<?php
function add(int $left, int $right): int
{
return $left + $right;
}
function subtract(int $left, int $right): int
{
return $left - $right;
}
There are two logical changes:
Rename parameters from
$aand$bto clearer names.Add subtraction behavior.
Inspect
git diff -- calculator.phpStage Selected Hunks
git add -p calculator.phpSelect only the parameter rename. Split the hunk with s if Git can divide it. Use manual patch editing only when you understand the displayed patch format.
Verify Both Sides
git diff --staged
git diffThe staged diff should show the rename. The unstaged diff should show the new subtraction function.
Create the First Commit
git commit -m "Clarify calculator parameter names"Stage the Remaining Feature
git add calculator.php
git diff --stagedCreate the Second Commit
git commit -m "Add subtraction operation"Inspect the History
git log --oneline --stat -2The result is two understandable commits instead of one mixed snapshot.
Manual Run: Unstage Without Losing Work
Assume both files were staged accidentally:
git add calculator.php README.md
git status --shortExpected state:
M README.md
M calculator.phpUnstage the README:
git restore --staged README.mdInspect again:
git status --shortExpected structure:
M README.md
M calculator.phpThe README change still exists in the working tree. It was not deleted.
Commit only the calculator:
git diff --staged
git commit -m "Add calculator validation"The README remains available for another commit.
Manual Run: Amend a Forgotten Test
Create a commit:
git add calculator.php
git commit -m "Reject division by zero"Then notice that the related test was not included:
git add tests/CalculatorTest.php
git diff --staged
git commit --amend --no-editInspect the amended commit:
git show --stat
git log -1 --format=fullerThe amended commit now includes both implementation and test, and its identifier differs from the original commit.
Common Staging Mistakes
Staging Everything Without Reviewing
git add . can include debug files, generated output, unrelated edits, or credentials.
Assuming git add Tracks Future Edits Automatically
Git stages the file content at the moment the command runs. Later edits must be staged again if they belong in the same commit.
Using Only git status
Status shows file-level state, not the complete line-level content. Review diffs as well.
Confusing Unstage with Discard
git restore --staged updates the index. git restore without --staged may overwrite working changes.
Using git commit -a for New Files
The shortcut handles tracked modifications and deletions, not ordinary untracked additions.
Creating Mixed Commits
One commit should not combine unrelated refactoring, features, debugging, and documentation unless they form one inseparable change.
Amending a Published Commit Without Coordination
Amend changes the commit identifier and can require a history-rewriting push.
Treating Sign-Off as Cryptographic Signing
A textual Signed-off-by trailer and a verified cryptographic signature serve different purposes.
Common Commit-Message Mistakes
Using vague subjects such as
update.Describing only filenames instead of the purpose.
Including several unrelated changes under one message.
Writing a long body without a clear first-line summary.
Copying ticket titles that do not explain the technical change.
Adding sensitive information to the message.
Using inconsistent conventions within the same repository.
Best Practices for Clean Commits
Start with
git status.Review unstaged work before selecting it.
Use path-specific or patch staging when changes are mixed.
Review
git diff --stagedevery time.Run focused tests before committing.
Keep each commit buildable when practical.
Write a clear subject that describes the change.
Use the body to preserve reasoning and constraints.
Amend only private history unless the team agrees otherwise.
Keep secrets and generated files outside the index.
Check the final status after every commit.
Useful Command Reference
| Command | Purpose |
|---|---|
git status | Explain the current working-tree and index state |
git status --short | Show compact two-column state codes |
git diff | Review unstaged changes |
git diff --staged | Review the proposed next commit |
git add file | Stage the current content of one file |
git add -p | Stage selected hunks interactively |
git add -i | Open the interactive staging interface |
git add -u | Stage tracked modifications and deletions |
git add -A | Stage additions, modifications, and deletions |
git restore --staged file | Unstage a path while preserving working changes |
git restore --staged -p | Partially unstage selected hunks |
git commit | Create a commit from the index using an editor |
git commit -m "message" | Create a commit with an inline message |
git commit --dry-run | Preview commit inclusion without recording it |
git commit -a | Stage tracked modifications and deletions, then commit |
git commit --amend | Replace the latest commit |
git commit --amend --no-edit | Replace the latest commit while retaining its message |
git log -1 --format=fuller | Inspect author and committer metadata |
Staging and Commit Checklist
Before creating a commit, verify:
The current branch is correct.
The staged files belong to one logical change.
No credentials or private data are staged.
Generated files are excluded when appropriate.
git diff --stagedmatches your intention.Relevant tests pass.
The subject explains the change clearly.
The body records important reasoning when needed.
After committing, verify:
The commit exists in the log.
The author information is correct.
The intended files are present in the commit.
Remaining working changes are understood.
The repository is clean when the task is complete.
Frequently Asked Questions
Is the Staging Area Required?
The index is central to Git’s normal snapshot model. Some commit options can select tracked paths directly, but the explicit stage-review-commit workflow is clearer and safer for most development.
Does git add Save My Work Permanently?
No. It prepares content in the index. A commit records that snapshot in repository history.
Why Does git diff Show Nothing?
Your changes may already be staged. Run git diff --staged. The working tree may also be clean.
Can I Stage Only Part of a File?
Yes. Use git add -p path/to/file and select individual hunks.
Can I Unstage Without Losing Changes?
Yes. Use git restore --staged path/to/file.
What Does MM Mean in git status --short?
The file has a staged modification and an additional unstaged modification.
Does git commit Include Untracked Files?
Not unless they were staged first. Even git commit -a does not normally add untracked files.
Should Every Commit Be Small?
A commit should be focused, not artificially tiny. It may modify many files when they are all required for one coherent change.
Can I Change the Last Commit?
Yes, with git commit --amend. Avoid amending shared history without coordination.
Why Did the Commit Hash Change After Amend?
Amend creates a replacement commit. Because its content or metadata differs, it receives a different object identifier.
Is Signed-off-by a Verified Signature?
No. It is a textual trailer. Cryptographic signing uses signing keys and options such as -S.
Conclusion
The staging area is not an unnecessary obstacle between editing and committing. It is the mechanism that lets you design the next snapshot deliberately.
The working tree contains current edits, the index contains the proposed commit, and HEAD represents the latest recorded snapshot. Once this three-layer model is clear, status and diff commands become predictable.
A professional commit workflow is simple: inspect the repository, review working changes, stage one logical unit, inspect the staged diff, run relevant tests, create a meaningful commit, and verify the final state.
Clean history does not come from committing more often or using complex commands. It comes from making clear decisions about what belongs together and preserving the reason behind each change.
The next article will focus on Git history, differences, and help commands, including advanced git log filters, commit inspection, file history, blame, revisions, and techniques for finding when a change entered the project.
Official References and Further Reading

