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:

  1. HEAD: The snapshot stored by the current commit.

  2. Index: The proposed snapshot for the next commit, commonly called the staging area.

  3. 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 Tree

Each 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.php

Git 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.php contains the actual bug fix.

  • README.md contains an unrelated documentation improvement.

  • config.php contains 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:

StateMeaningTypical Action
UntrackedThe file exists but is not part of Git’s index or historyStage it or ignore it
Tracked and unchangedThe working version matches the index and current commitNo action required
ModifiedThe working version differs from the indexReview and stage selected work
StagedThe index differs from the current commitReview and commit
Staged and modified againThe index and working tree contain different new versionsDecide whether to stage the later edit

Use:

git status

for a detailed explanation, or:

git status --short

for a compact summary.

Reading Short Status Correctly

The short format uses two columns:

XY PATH
  • X: Difference between HEAD and 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 staged

The 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 --oneline

The 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.php

Stage several named paths:

git add calculator.php README.md

Stage a directory:

git add src/

Stage changes under the current directory:

git add .

Stage all additions, modifications, and deletions across the repository:

git add -A

Stage modifications and deletions to tracked files, but not new untracked files:

git add -u

git add ., git add -A, and git add -u

These commands overlap, but their intent differs.

CommandNew FilesModified FilesDeleted FilesScope
git add .YesYesYes within the selected path scopeCurrent directory and descendants
git add -AYesYesYesEntire repository by default
git add -uNoYesYesTracked 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 diff

git 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.php

The double dash marks the end of command options and the beginning of path arguments.

Review After Staging

Stage the file:

git add calculator.php

Now 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 --cached

This 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.php

Then edit the same file again and add a multiplication function:

function multiply(int $a, int $b): int
{
    return $a * $b;
}

Run:

git status --short

You may see:

MM calculator.php

The 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 --staged

If multiplication should be part of the same commit, stage the file again:

git add calculator.php

If 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.php

Git displays one hunk at a time and asks what to do.

Common choices include:

KeyMeaning
yStage this hunk
nDo not stage this hunk
qQuit and leave remaining hunks undecided
aStage this and all later hunks in the file
dSkip this and all later hunks in the file
sSplit the current hunk into smaller hunks when possible
eManually edit the patch before staging
?Show available help

After selecting hunks, inspect both sides:

git diff --staged
git diff

The 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:

  1. Rename a variable for clarity.

  2. 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.php

Stage 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 -i

It 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.php

or:

git add --intent-to-add new-file.php

records 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=fuller

Creating a Commit

Commit staged content with an inline message:

git commit -m "Add subtraction operation"

Or open the configured editor:

git commit

The editor form is useful for messages that need a short subject followed by a detailed explanation.

Before committing, inspect:

git status
git diff --staged

After committing, inspect:

git status
git log -1 --stat

Writing Strong Commit Messages

A commit message should explain the purpose of the recorded change.

Weak examples:

update
changes
fix stuff
new code
final version

Stronger examples:

Reject division by zero
Validate email format before registration
Remove duplicate payment retry
Document local database setup

The 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 operation

For 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, or Remove.

  • 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 30

More 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.txt

When 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-run

This 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 --short

The most reliable content review remains:

git diff --staged

Committing 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.php

This restores the index version from HEAD by default while leaving the working file unchanged.

Check the result:

git status
git diff
git diff --staged

The changes should move from the staged comparison back to the unstaged comparison.

Older reset Syntax

You will also encounter:

git reset HEAD calculator.php

When 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.php

This lets you remove selected hunks from the index while leaving other hunks staged.

It is the reverse counterpart of partial staging:

  • git add -p selects hunks to add to the index.

  • git restore --staged -p selects hunks to remove from the index.

Unstaging Is Not the Same as Discarding

This command:

git restore --staged calculator.php

changes the index but keeps the working edit.

This command:

git restore calculator.php

restores the working file from the index by default and can discard unstaged work.

Before running a working-tree restore, inspect:

git diff -- calculator.php

Uncommitted 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 --amend

Edit 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-commit

  • prepare-commit-msg

  • commit-msg

  • post-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 status

2. Review Unstaged Changes

git diff

3. Stage One Logical Change

git add path/to/file

Or select hunks:

git add -p

4. Verify the Proposed Snapshot

git diff --staged
git status

5. Run Relevant Tests

# Example only; use the project’s actual test command
php vendor/bin/phpunit

6. Commit with a Useful Message

git commit -m "Reject division by zero"

7. Verify the Result

git status
git log -1 --stat

8. 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:

  1. Rename parameters from $a and $b to clearer names.

  2. Add subtraction behavior.

Inspect

git diff -- calculator.php

Stage Selected Hunks

git add -p calculator.php

Select 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 diff

The 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 --staged

Create the Second Commit

git commit -m "Add subtraction operation"

Inspect the History

git log --oneline --stat -2

The 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 --short

Expected state:

M  README.md
M  calculator.php

Unstage the README:

git restore --staged README.md

Inspect again:

git status --short

Expected structure:

 M README.md
M  calculator.php

The 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-edit

Inspect the amended commit:

git show --stat
git log -1 --format=fuller

The 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 --staged every 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

CommandPurpose
git statusExplain the current working-tree and index state
git status --shortShow compact two-column state codes
git diffReview unstaged changes
git diff --stagedReview the proposed next commit
git add fileStage the current content of one file
git add -pStage selected hunks interactively
git add -iOpen the interactive staging interface
git add -uStage tracked modifications and deletions
git add -AStage additions, modifications, and deletions
git restore --staged fileUnstage a path while preserving working changes
git restore --staged -pPartially unstage selected hunks
git commitCreate a commit from the index using an editor
git commit -m "message"Create a commit with an inline message
git commit --dry-runPreview commit inclusion without recording it
git commit -aStage tracked modifications and deletions, then commit
git commit --amendReplace the latest commit
git commit --amend --no-editReplace the latest commit while retaining its message
git log -1 --format=fullerInspect 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 --staged matches 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