The staging area

Use the middle place deliberately — and stop being surprised by what ends up in a commit.

05of 16
≈ 12Min read
5Parts

The commit contains what you staged, not what you have

git add takes a copy. Anything you type afterwards is in the working tree and nowhere else until you add it again. This is not a flaw — it is what lets you keep editing while a commit is being assembled — but it catches everyone once.

the surprise that teaches the lesson
echo "draft" > notes.md
git add notes.md             # the index now holds "draft"

echo "final" > notes.md      # the working tree now says "final"
git commit -m "Notes"        # the commit contains "draft"

git status                   # notes.md: modified, not staged

Staging part of a file

One afternoon usually contains three commits' worth of work. git add -p is how you separate them afterwards, and it doubles as a review: you read every line you are about to commit, which catches debug logging more reliably than any hook.

the flag that makes small commits possible
git add -p price-list.md

# for each hunk:
#   y  stage it        n  skip it
#   s  split it into smaller hunks
#   e  edit the hunk by hand
#   q  stop

Taking things back out

the modern spellings, and what they touch
git restore --staged notes.md   # unstage: index ← HEAD. your edits are kept
git restore notes.md            # discard edits: working tree ← index. NOT recoverable
git restore --source=HEAD~2 notes.md   # bring back an older version, into both

# the older spellings, still everywhere
git reset HEAD notes.md         # = restore --staged
git checkout -- notes.md        # = restore
One of these throws work away

git restore <file> overwrites the file with the staged version. That edit was never committed, so git cannot get it back. It is the only common git command that destroys work outright.

.gitignore

A pattern with a trailing slash matches directories; does not cross a slash, * does; ! un-ignores. The file belongs in the repository — the whole team needs the same rules.

what should never be committed
node_modules/
dist/
.env
.env.*
*.log
.DS_Store
coverage/

!dist/.gitkeep          # an exception to the rule above
Ignoring does not untrack

If a file is already tracked, adding it to .gitignore changes nothing. You need git rm --cached .env and a commit — and if the secret was ever pushed, it is in the history for good: rotate it, do not just delete it.

Empty directories, and the file that is not code

Git tracks content, so an empty directory cannot be represented at all — hence the .gitkeep convention, which is just an empty file nobody reads. Large binaries (video, design files, datasets) bloat every clone forever; that is what Git LFS exists for, and it is worth setting up before the file goes in, not after.

Practice