Three places a change lives
In Level 1 you made a repository. Now let's look at how a change ends up saved in it, because this is where people who copy commands usually get confused.
Git keeps track of three places:
- Working tree: the files you can see and edit in your folder.
- Staging area (also called the index): a holding area for what will go into the next commit.
- Commits: saved snapshots that become part of your history.
Think of packing a parcel. Your desk is the working tree, where things are scattered and half-finished. The open box is the staging area, where you put only what you mean to send. Taping the box and writing the label is the commit. The box is the useful part. It means you can finish three things at your desk and send only one of them.
Why have a middle step at all? Because real work is messy. You fix a bug, rename a variable and leave a debug print in the same file. The staging area lets you save the bug fix as its own clean commit and leave the rest for later.
Reading git status
git status is the command you'll run most often. Let's make three different kinds of change in your practice repository:
cd practice-repo
echo "# Notes" > notes.md # a brand-new file Git has never seen
echo "print('hello')" > hello.py
git add hello.py && git commit -m "Add hello script" # hello.py is now tracked
echo "print('goodbye')" >> hello.py # change a tracked file
echo "TODO" > todo.txt
git add todo.txt # stage a new file
git status
The output looks like this (your branch name may be different):
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: todo.txt
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: hello.py
Untracked files:
(use "git add <file>..." to include in what will be committed)
notes.md
Read it from the top down:
- Changes to be committed: these are staged. They're in the box.
- Changes not staged for commit: these are modified tracked files that are still on the desk.
- Untracked files: Git has never recorded these files and will ignore them until you
git addthem.
For a compact view, run git status -s:
A todo.txt # left column = staging area: A = added
M hello.py # right column = working tree: M = modified, not staged
?? notes.md # ?? = untracked
Gotcha:
git statusrepeats the hints for its own commands. Read them. They usually tell you the exact next step, including how to take a file back out of the box withgit restore --staged.
Seeing exactly what changed: git diff
git status tells you which files changed. git diff shows you which lines changed. There are two forms you should know:
git diff # working tree vs staging area: what you have NOT staged yet
git diff --staged # staging area vs last commit: what WILL go into the next commit
(--cached means the same as --staged, and you'll see both online.) Running git diff now shows this:
--- a/hello.py
+++ b/hello.py
@@ -1 +1,2 @@
print('hello')
+print('goodbye')
Lines starting with + were added and lines starting with - were removed. Make it a habit to run git diff --staged before every commit. It's the last check that the box holds only what you meant to send.
Gotcha: If plain
git diffprints nothing, that doesn't mean nothing changed. Everything may already be staged. Checkgit diff --stagedtoo.
Staging whole files or only some changes
git add <file> stages everything in that file. git add . stages every change in the current folder and the folders inside it. It's quick, but it's also how debug files and passwords end up in history. Run git status first.
To stage part of a file, use patch mode:
printf "print('hello')\nprint('goodbye')\n\nprint('DEBUG x=', 42)\n" > hello.py
git add -p hello.py
Git shows each hunk (a block of changed lines) and asks what to do with it:
Stage this hunk [y,n,q,a,d,s,e,?]?
y: stage this hunk.n: skip it.s: split the hunk into smaller ones, if Git can.q: quit.?: show help for every key.
Stage the goodbye line with y, skip the debug line with n, then check what you've done:
git diff --staged # shows only the goodbye line
git diff # shows only the DEBUG line, still on your desk
git status will now list hello.py under both "to be committed" and "not staged". That's normal: part of the file is in the box and part isn't.
Making the commit
git commit -m "Print goodbye after greeting"
Only what you staged is recorded. The debug line stays in your working tree. If you run git commit without -m, Git opens your editor so you can write a longer message.
Shortcut to be careful with:
git commit -astages every modified or deleted tracked file and commits it in one step. It skips untracked files, so a new file you forgot toaddwon't be included. It also skips the review thatgit add -pgives you.
Writing a clear message
Git's own documentation recommends a short summary line of no more than 50 characters, then a blank line, then more detail if you need it:
Fix crash when config file is empty
load_config() returned None and the caller indexed it.
Return an empty dict instead.
Write the summary as an instruction ("Fix crash...", not "fixed stuff"). A good test is whether it completes the sentence "If applied, this commit will...". Keep each commit to one logical change. Small commits are easier to review, explain and undo.
Try it and check
git add notes.md
git commit -m "Add notes and todo files"
git status -s # expect only: " M hello.py" (the debug line)
If the only line left is M hello.py, you've committed everything except the debug line, which is what you wanted.
Key takeaways
- A change moves from the working tree to the staging area (with
git add) and then into a commit (withgit commit). git statussorts files into staged, modified-but-not-staged and untracked.git diffshows changes you haven't staged.git diff --stagedshows exactly what the next commit will contain.git add -plets you stage some hunks of a file and leave others out.git commit -askips untracked files. Make small commits with a short summary line in the instruction form.