Lesson 4 of 15

Staging & Committing

What the Staging Area Is Really For

The staging area — Git also calls it the index — is the workbench between your folder and your history. Nothing reaches a commit without passing through it. Most other version control tools have no equivalent, which is why it feels like an extra chore at first, and why it turns out to be one of Git's most useful ideas once you have used it under pressure.

Here is the situation it was built for. You are three hours into a deadline. You fixed the bug in auth.js that was the point of the evening. On the way you also renamed a confusing variable in utils.js, deleted some dead code, and added a console.log to server.js that is still there. Without staging, your only choice is one commit called "fixes" containing all of it, including the debug line. With staging you commit auth.js alone under an honest message, commit the cleanup separately, and leave the console.log out of history entirely.

The staging area holds a snapshot of the file at the moment you added it, not a live link to it. Edit the file again after staging and you now have two different versions in play: the staged one and the newer one on disk. git status will list the same filename under both "changes to be committed" and "changes not staged for commit". That is not a bug, and it is worth pausing on, because it is the clearest possible demonstration that the three areas are genuinely separate.

Example
# Stage specific files — the deliberate, reviewable way
git add auth.js
git add css/style.css index.html

# Stage everything changed under the current folder
git add .

# Stage every change in the whole repository, wherever you are standing
git add -A

# Stage modifications and deletions of TRACKED files only (ignores new files)
git add -u

# The same file in two states at once
vim auth.js       # edit
git add auth.js   # stage version 1
vim auth.js       # edit again
git status --short
# MM auth.js       <- staged version 1, working copy is now different
Notes
  • git add . is convenient and it is how most accidents happen. It happily stages the .env file you forgot to ignore, a 200 MB dataset, and yesterday's half-finished experiment. Before committing, always read git status or git diff --staged — that habit costs five seconds and prevents the worst mistakes in this course.

Undoing a Stage, and the Command That Destroys Work

Two commands undo things at this stage of the workflow, and they are dangerously similar to type while doing completely different amounts of damage. Modern Git splits them clearly with git restore.

git restore --staged file.js takes a file out of the staging area. Your edits are untouched — the file on disk is exactly as you left it, it is simply no longer queued for the next commit. This is completely safe and you can run it as often as you like. Older tutorials write this as git reset HEAD file.js, which does the same job; both still work, and you will see the old form everywhere.

git restore file.js, with no --staged, is a different animal. It throws away your edits and replaces the file with the last committed version. Those edits are gone permanently. Git has no record of uncommitted work, so there is no reflog to rescue you and no undo. The old spelling of this command is git checkout -- file.js, and the fact that one checkout command could either switch branches or silently delete an afternoon's work is precisely why switch and restore were introduced.

So before you discard anything, ask whether you might want it later. If there is any doubt, git stash puts the changes somewhere retrievable instead of deleting them, and the Stash & Reset lesson covers it in full.

Example
# Unstage — completely safe, your edits stay
git restore --staged auth.js
git reset HEAD auth.js        # the older equivalent

# Discard your edits — DESTRUCTIVE, no undo
git restore auth.js
git checkout -- auth.js       # the older equivalent, same danger

# Discard edits in the whole project — very destructive
git restore .

# The safer instinct when you are unsure:
git stash                     # tucks the changes away, retrievable later

# Remove untracked files too (also destructive) — always dry-run first
git clean -n                  # -n only LISTS what would be deleted
git clean -fd                 # -f force, -d include directories
Notes
  • Uncommitted work is the only work Git genuinely cannot recover. Once something is committed, even on a branch you later delete, it is almost always retrievable through git reflog. Committing often is not just tidiness — it is what makes mistakes reversible.

Staging Part of a File with git add -p

Sometimes the mixed-up changes are inside a single file. You edited app.js to fix a bug at line 40 and, forty lines later, started a new feature. Staging the whole file forces those into one commit. git add -p — p for patch — walks you through the file one chunk of changes at a time and asks what to do with each.

Git shows you a hunk and waits for a single key. Press y to stage this hunk, n to skip it, s to split a large hunk into smaller ones, q to stop, and ? to see the full list of options. Nothing you press here can destroy anything; the worst case is that you stage something you did not mean to, and git restore --staged undoes that.

Beyond building tidy commits, this is one of the best code review habits you can develop. Walking through your own diff hunk by hunk forces you to actually look at what you changed, and it is remarkable how often you catch a leftover debug line, a commented-out block, or a typo in a variable name that you would otherwise have shipped.

Example
git add -p app.js

# @@ -38,7 +38,7 @@ function login(user) {
# -  if (user.password = input.password) {
# +  if (user.password === input.password) {
#
# (1/2) Stage this hunk [y,n,q,a,d,s,e,?]? y
#
# @@ -95,0 +96,12 @@
# +function exportToCsv(rows) {
# +  // half-finished, not ready
# ...
# (2/2) Stage this hunk [y,n,q,a,d,s,e,?]? n

git commit -m "Fix assignment used instead of comparison in login check"
# the unfinished CSV feature is still in your working directory, uncommitted

# Review what you are about to commit — do this every time
git diff --staged

Writing Commit Messages People Can Use

A commit message is a short note to whoever reads this history later, and that is usually you. The convention almost every project follows is the imperative mood: write Add password validation, not Added password validation or adding validation. The reason is that Git's own generated messages are phrased that way, and the message reads as an instruction — this commit will add password validation to the code.

Keep the first line under about fifty characters and treat it as a subject line. If it needs more explanation, leave a blank line and then write freely underneath. That blank line is not decoration: Git treats the first line as the summary shown in git log --oneline and on GitHub, and everything after the blank line as the body.

Spend the body on why, not how. The diff already shows how — anyone can read the code. What the diff cannot tell them is that you had to disable caching on this route because the payment gateway sends duplicate callbacks. That sentence, written once, saves the next person an hour.

A note on git commit -am: it stages and commits in one step, but only for files Git is already tracking. Brand-new files are untouched by it. Students use -am, see "1 file changed", assume everything went in, push, and then their teammate clones the project and finds a file missing. When new files are involved, stage explicitly.

Example
# Short and clear
git commit -m "Fix login button alignment on mobile"

# With a body explaining the reasoning
git commit -m "Disable caching on the payment callback route" -m "The gateway retries callbacks, and a cached response caused the second retry to be treated as a fresh payment. Issue #42."

# Or open the editor for a longer message
git commit

# Stage-and-commit shortcut — TRACKED files only, new files are skipped
git commit -am "Update header styles"

# Fix the message of the commit you just made (before pushing)
git commit --amend -m "Better message"

# Add a forgotten file to the previous commit, keeping its message
git add forgotten-file.css
git commit --amend --no-edit
Notes
  • git commit --amend does not edit the old commit — it replaces it with a new one that has a different hash. That is harmless while the commit is only on your machine. Once you have pushed, amending rewrites history that other people may already have pulled, which is the situation the Remotes lesson warns about. Rule of thumb: amend freely before pushing, think hard afterwards.

How Big Should One Commit Be?

The guideline is that one commit should be one logical change — something you can describe in a single sentence without using the word "and". If your message needs "and", you probably have two commits.

Commits that are too big are the common failure. A single commit called "final submission" containing three weeks of work is unusable: you cannot revert part of it, cannot review it, and cannot find which change broke something. Commits that are too small are a much rarer problem and a much cheaper one — nobody has ever suffered because a history was too easy to read.

This matters practically, not just aesthetically. Reverting a bad change is trivial when it lives in its own commit and impossible when it is tangled with nine unrelated edits. Tools like git bisect, which finds the commit that introduced a bug by testing halfway points, only work if your commits are small enough for the answer to be useful. And when a reviewer on a team project looks at your pull request, small commits with honest messages are the difference between a five-minute review and a request to explain yourself.

  • Use the imperative mood: Add, Fix, Remove, Refactor — not Added or Fixing
  • First line under about 50 characters, no full stop at the end, then a blank line before any body text
  • Explain the reason in the body; the code already shows the mechanism
  • Reference issues where relevant — writing Closes #42 in a message makes GitHub close that issue when the commit reaches the default branch
  • One logical change per commit; if the message needs the word "and", split it
  • Never commit code you know is broken to a shared branch — use a feature branch for work in progress
  • Read git diff --staged before every commit, without exception
Notes
  • Many teams follow Conventional Commits, a convention that prefixes each message with a type: feat:, fix:, docs:, refactor:, test:, chore:. It is not required by Git, but it lets tools generate changelogs automatically, and it is common enough in industry that recognising it is worthwhile.
Ask AI