Git & GitHub · lesson 1 of 12
Git Basics: Init, Add, Commit, Diff
Learn Git's core snapshot workflow with real command output: git init, add, status, commit, log, and diff on a tiny Python project.
By SkillByExample · Updated
Git tracks content as a series of snapshots, not file-by-file diffs. Every command in
this lesson either builds a snapshot (add, commit) or inspects one (status, log,
diff).
1) An empty repository has no history yet
$ git init -b main demo
$ cd demo
$ git status
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)
git init creates a .git directory but no commits. git status says so explicitly —
this is the state before any snapshot exists.
2) A new file is untracked until you say otherwise
$ printf 'def greet(name):\n return f"Hello, {name}!"\n' > app.py
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
app.py
nothing added to commit but untracked files present (use "git add" to track)
Creating app.py doesn’t put it under version control. Git sees it exists but won’t
include it in a commit until you explicitly stage it.
3) Staging builds the next snapshot
$ git add app.py
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: app.py
git add copies the file’s content into the index — the staging area — as the
proposed content of the next commit. Nothing is permanent yet; git rm --cached would
undo it.
4) Committing seals the snapshot
$ git commit -m "Add greet function"
$ git log --oneline
86b2e4e Add greet function
A commit takes whatever is staged and writes it as a permanent, addressable snapshot
identified by a SHA-1 hash (86b2e4e here — yours will differ). git log --oneline
shows one line per commit: hash and message.
5) Diff shows what changed, relative to what you compare against
$ printf 'def greet(name):\n return f"Hello, {name}!"\n\n\ndef farewell(name):\n return f"Goodbye, {name}!"\n' > app.py
$ git diff
diff --git a/app.py b/app.py
index 097a41c..f3543c3 100644
--- a/app.py
+++ b/app.py
@@ -1,2 +1,6 @@
def greet(name):
return f"Hello, {name}!"
+
+
+def farewell(name):
+ return f"Goodbye, {name}!"
Plain git diff compares the working directory against the index — it shows
unstaged changes. Stage the file and git diff goes quiet for it; git diff --staged
would show the same hunk compared against the last commit instead.
6) A second commit, and a two-line history
$ git add app.py
$ git commit -m "Add farewell function"
$ git log --oneline
f6a618b Add farewell function
86b2e4e Add greet function
git log --oneline reads newest-first. Each hash is a permanent pointer to that exact
snapshot — git show 86b2e4e would print the greet-only version even after later
commits change the file further.
Practice
-
Create a third file, stage it, then run
git status -sbefore and after staging. Compare the compact output to the verbosegit statusoutput above.Solution
$ printf "TEMP=1\n" > .env.example $ git status -s ?? .env.example $ git add .env.example $ git status -s A .env.example??means untracked;A(staged) with a blank in the second column means added and not modified since staging. -
Edit
app.pyagain, stage it, then edit it once more without staging. Rungit statusand explain why the file appears in two sections at once.Solution
$ git status Changes to be committed: modified: app.py Changes not staged for commit: modified: app.pyThe index holds the first edit; the working directory holds the second.
git diffshows only the second edit;git diff --stagedshows only the first. -
Run
git diff --stagedafter staging a change but before committing it. Confirm it shows the same hunkgit diffshowed before you staged. -
Use
git log -p -1to show the full patch of the most recent commit, not just its message. -
Run
git show <hash>on the first commit’s hash from this lesson’s log output. Confirm it printsapp.pywith only thegreetfunction — thefarewellfunction does not exist yet at that snapshot.