Skip to main content
Git & GitHub · lesson 1 of 12
Git & GitHub beginner 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 version-control

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

  1. Create a third file, stage it, then run git status -s before and after staging. Compare the compact output to the verbose git status output 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.

  2. Edit app.py again, stage it, then edit it once more without staging. Run git status and 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.py

    The index holds the first edit; the working directory holds the second. git diff shows only the second edit; git diff --staged shows only the first.

  3. Run git diff --staged after staging a change but before committing it. Confirm it shows the same hunk git diff showed before you staged.

  4. Use git log -p -1 to show the full patch of the most recent commit, not just its message.

  5. Run git show <hash> on the first commit’s hash from this lesson’s log output. Confirm it prints app.py with only the greet function — the farewell function does not exist yet at that snapshot.

Frequently Asked Questions

Why does git add app.py need to run again after editing a tracked file?
Git's staging area is a snapshot you build by hand, not a live mirror of your working directory. Editing a tracked file changes the working copy, but the staged snapshot still points at the old blob until you run git add again.
What's the difference between git status and git status -s?
git status prints the full explanation with hints ("use git add to include..."). git status -s prints a compact two-column form — one line per file, a letter for staged state and a letter for unstaged state — meant for scripts and quick glances, not first-time readers.
Does git diff show staged changes?
No. Plain git diff compares the working directory against the index (staging area). Once you git add a file, diff goes quiet for it — use git diff --staged to compare the index against the last commit instead.
Why is core.autocrlf mentioned in this lesson?
On Windows, Git can rewrite line endings between LF (stored) and CRLF (checked out), which adds warning noise and can make identical-looking files diff. Setting core.autocrlf false keeps files byte-for-byte, which is what these examples assume.