Your First Repository and Commit
Build a real repository from scratch. The .git directory, the three areas, reading git status, staging with add and add -p, and writing commit messages worth keeping.
Build a real repository from scratch. The .git directory, the three areas, reading git status, staging with add and add -p, and writing commit messages worth keeping.
You have a folder with some files in it, and the only record of how it got that way is your memory. That works until the afternoon you change four things at once and one of them quietly breaks another.
By the end of this lesson you will have a real repository on
disk called trip-planner, three commits inside it, and enough
grip on the staging area to use it on purpose rather than by
rote.
Start with an ordinary folder. Nothing about it is special yet.
mkdir trip-planner
cd trip-planner
ls -a. ..Empty — ls -a shows hidden entries too, and there are none.
Hand the folder to Git, then look again:
git init
ls -aInitialized empty Git repository in /home/you/trip-planner/.git/
. .. .gitOne new thing appeared. A repository is not a folder Git
watches from a distance; it is your files plus that hidden
.git directory sitting next to them. Every commit, message,
and past version of every file you ever save lives inside it.
Your visible files are only the current view.
Running git init inside a folder that already has files is
safe and extremely common. Git rewrites nothing; it only adds
.git, and your existing files sit untracked until you add
them. Run a Git command outside a repository and you get a
message worth recognising on sight:
fatal: not a git repository (or any of the parent directories): .gitThat almost always means you are in the wrong directory rather
than that anything is broken. Check where you are and cd back
in.
Get this picture straight before the first commit. The rest of Git is built on it, and almost every beginner's confusion is really a confusion about which of these three they are looking at.
Picture packing for a trip. Your things are all over the house. The suitcase on the bed holds only what you have decided is going. Zipping it shut and putting it in the car is a separate act again.
The working tree
Your folder as the editor sees it — the live files you are typing into right now. The whole house, mess included.
The staging area
Also called the index: the list of exactly what goes into your next commit. The open suitcase. Putting a shirt in it does not mean you have left yet.
The repository
The permanent history in .git. Committing zips the
suitcase shut, labels it and stores it — recording what was
inside, not what was strewn around the bedroom.
Create the project's first three files:
Now ask Git what it sees. You will run git status more than
any other command:
Read it line by line. On branch main is where you are. No commits yet means this repository has no history — true, you
have committed nothing. Then the part that matters: all three
files are untracked. Git can see them but has never been
asked to record them, so it will not save or even notice changes
to them.
Its headings map onto the three states a file can be in: untracked (never stored), modified (known to Git but different from the last commit, listed under "Changes not staged for commit"), and staged (in the suitcase, listed under "Changes to be committed").
git add is how things go into the suitcase. Three forms cover
almost everything:
The last two are stated imprecisely almost everywhere.
git add . stages new, modified, and deleted files under your
current directory, so from a subfolder it skips changes
elsewhere in the project. git add -A does the same across
the whole repository, wherever you are standing. From the
repository root they behave identically, which is why the
difference is easy to miss until it bites.
Run git add README.md on its own and git status moves it
under a new heading, "Changes to be committed", while the other
two files stay untracked. That heading is the suitcase.
Stage all three files and take the snapshot:
0e3824e is the start of that commit's unique ID, and
root-commit means it is the first, with nothing before it.
The -m flag supplies the commit message inline. Adopt the
convention from day one: a subject of about fifty characters in
the imperative mood, as if finishing the sentence "this commit
will…". Add trip planner skeleton, not Added skeleton or
skeleton stuff. Messages like update, fixes, and wip
describe every commit ever written, and so describe none of
them. A good subject tells a stranger — including you, in
November — what they are looking at without opening the diff.
Add a sight to notes.md and a heading to README.md, then
stage only the README:
Two headings, two different worlds. README.md is staged and
will be in the next commit. notes.md is modified in the
working tree only, and will not be unless you add it. The hints
mention git restore, the modern command for undoing both —
lesson 6's territory, so leave it for now.
The same state has a short format — dense, but fast to read once its two columns click. The left column is the staging area, the right is the working tree:
M — modified and staged; the M sits in the left
column. M — modified, not staged; note the leading space where
the staging column would be.MM — both. You staged a version, then kept editing, so the
staged copy and the file on disk now differ. Committing saves
the staged version, not what is on your screen.?? — untracked, as all three files were a moment ago.Stage the rest and make the second commit:
Here is the payoff for having a staging area at all. Turn
pack.sh into a real script, and while you are at it jot a
half-formed idea into notes.md. Instead of sweeping both up
with git add -A, review the script change hunk by hunk:
Git walks you through the change one hunk at a time:
Press y to stage this hunk, n to skip it, s to split a big hunk into smaller ones, q to quit, and ? for the rest of the keys. Everything you say yes to lands in the staging area; everything else stays in your working tree, untouched, for a later commit.
Run git commit with no -m and Git opens your configured
editor with an empty message and some commented help text. Write
the message, save, close the file, and the commit is made; an
empty message aborts it.
Do that now for the third commit. The shape is a short imperative subject, a blank line, then a body wrapped at about seventy-two characters:
Your notes.md scribble is still sitting unstaged in the
working tree, exactly as intended. The blank line after the
subject is not decoration: Git treats the first line as the
subject wherever it shows a summary and the rest as the body, so
dropping it turns the whole message into one long subject.
Three commits in, ask to see them:
Newest first, with full IDs, author, date, and message. Long output opens in a pager — press q to get your prompt back. That one key traps almost everyone once.
For everyday use, the compressed view is better:
One line per commit: short ID, then subject. This is where
fifty-character subjects earn their keep, and HEAD -> main
marks where you are standing. Add --graph to draw the shape of
the history down the left edge:
A straight line today, because you have one line of development.
Once branches arrive that column forks and rejoins, and
--graph earns its keep.
Commit whenever the project reaches a state you would be content
to come back to: a working change, a finished paragraph, a test
that passes. In practice that is several times an hour, not once
a day. Small commits are easier to describe, to read later, and
to undo one at a time, while a giant end-of-day commit called
stuff is one opaque block you cannot pick apart. A useful
instinct: if the subject line needs the word "and", you probably
have two commits.
You now have a repository with three commits and a working
mental model: changes move from the working tree into the
staging area into permanent history, and git status tells you
where each one currently sits.
The obvious next question is what actually changed between those
states — the contents, not only the filenames. That is the
next lesson, reading what changed: git diff for unstaged
edits, git diff --staged for what is in the suitcase, and
git show for looking inside a commit you already made.
Before moving on, keep trip-planner and use it. Edit two
files, stage one, read the status, commit it, then commit the
other separately. Half a dozen rounds of that turns the three
areas from a diagram you have read into something your hands
know.
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
notes.md
pack.sh
nothing added to commit but untracked files present (use "git add" to track)[main (root-commit) 0e3824e] Add trip planner skeleton
3 files changed, 7 insertions(+)
create mode 100644 README.md
create mode 100644 notes.md
create mode 100644 pack.shOn branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: README.md
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: notes.mdM README.md
M notes.md@@ -1 +1,4 @@
+#!/usr/bin/env bash
echo "Packing list"
+echo "- passport"
+echo "- adapters"
(1/1) Stage this hunk [y,n,q,a,d,s,e,?]?Print a real packing checklist
The script only echoed a title, which meant every trip started
with the same scramble to remember chargers and adapters.
The list is ordered the way we pack, bag by bag, rather than
alphabetically, because that is how we check it at the door.commit 319dda08f2b916b1d8c6f25d16b26fea10e0cc2c
Author: Dana Reyes <dana@example.com>
Date: Sun Jul 26 14:19:22 2026 +0000
Print a real packing checklist
The script only echoed a title, which meant every trip
started with the same scramble to remember chargers and
adapters.319dda0 (HEAD -> main) Print a real packing checklist
058159f Sketch the day-by-day plan
0e3824e Add trip planner skeleton* 319dda0 (HEAD -> main) Print a real packing checklist
* 058159f Sketch the day-by-day plan
* 0e3824e Add trip planner skeletonecho "# Trip Planner" > README.md
echo "Ideas" > notes.md
echo "echo 'Packing list'" > pack.shgit statusgit add README.md # stage exactly this path
git add . # stage everything under the current folder
git add -A # stage everything in the whole repositorygit add -A
git commit -m "Add trip planner skeleton"echo "- Belem tower" >> notes.md
echo "" >> README.md && echo "## Days" >> README.md
git add README.md
git statusgit status -s # --short is the same flag spelled outgit add -A
git commit -m "Sketch the day-by-day plan"git add -p pack.shgit loggit log --onelinegit log --oneline --graphgit init # create .git and start a repository
ls -a # confirm .git appeared
git status # untracked / modified / staged
git status -s # same, two-column short format
git add <file> # stage one path
git add . # stage everything under this folder
git add -A # stage everything in the repository
git add -p <file> # stage selected hunks: y n s q ?
git commit -m "Subject" # commit with an inline message
git commit # commit, writing message in editor
git log # full history, q to leave the pager
git log --oneline # one line per commit
git log --oneline --graph # plus the shape of the history