Chapter 2 — Daily Workflow
The everyday cycle: edit → `git add` → `git commit`, plus `git log` to inspect history, `git diff` to inspect changes, `.gitignore` to leave files out, and aliases to type less.
What you'll learn
- 1Use `git add`, `git commit`, `git log`, `git diff` confidently in daily work.
- 2Write `.gitignore` so build artifacts and secrets never leak in.
- 3Configure aliases to shorten common commands.
- 4Understand `HEAD`, `HEAD~1`, `HEAD~2` as commit references.
Core Concepts
1) The everyday loop
edit a file → git add file → git commit -m "..." → repeat2) `git log` patterns
| Command | Purpose |
|---|---|
| `git log` | Full history with author/date |
| `git log --oneline` | One commit per line |
| `git log --oneline --graph --all` | All branches as graph |
| `git log -n 5` | Latest 5 commits |
| `git log --author="Name"` | By author |
3) `git diff` flavors
| Command | Shows |
|---|---|
| `git diff` | Working dir vs staging |
| `git diff --staged` | Staging vs HEAD |
| `git diff HEAD~1 HEAD` | The latest commit's change |
| `git diff main..feature` | Across branches |
4) `.gitignore`
A file at the repo root listing path patterns to ignore. Examples:
node_modules/
*.log
.env
.DS_Store
build/5) Git aliases
Save typing by binding short forms to common commands:
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --all"
git config --global alias.co checkoutExamples
Example 1 — `ex01_add_commit.sh`: working dir → staging → commit
**Output**
=== Working dir → staging → commit ===
file edited
staged
committedKey: `git status` after each step confirms which area the change is in.
Example 2 — `ex02_log_diff.sh`: inspect history and diffs
**Output**
=== Tracking changes ===
a1b2c3d feat: edit file
=== Compare across two commits ===
diff: line addedKey: `git log` is your time machine; `git diff` shows what changed where.
Example 3 — `ex03_gitignore.sh`: ignoring files
**Output**
=== Create .gitignore ===
=== .gitignore in action ===
accidentally committed file no longer stagedKey: if a file was already tracked, `.gitignore` alone won't untrack it — use `git rm --cached <file>`.
Example 4 — `ex04_alias.sh`: setup aliases
**Output**
=== Alias setup ===
=== Register alias ===
git st is now status
git lg is now graph logKey: `git config --global alias.<name> "<command>"` saves keystrokes everywhere.
Common mistakes
- **Forgetting to `git add`** after editing — the new content isn't staged.
- **Committing build artifacts** — set `.gitignore` before the first commit.
- **`.gitignore` after tracking** — the file is already tracked; remove with `git rm --cached`.
- **Cryptic commit messages** — "fix" with no context. Use `<type>: <short summary>`.
- **`git diff` confusion** — without `--staged`, it shows un-staged changes only.
Recap
- The basic cycle is `edit → add → commit`; learn it cold.
- `.gitignore` prevents accidental commits; commit it like any other file.
- Aliases (`st`, `lg`, `co`) reduce typing in everyday flows.
- `HEAD~N` references the Nth-prior commit.
Try it
cd src
chmod +x ex0*.sh
./ex01_add_commit.sh
./ex02_log_diff.sh
./ex03_gitignore.sh
./ex04_alias.sh💻 Examples
Runnable examples — see the output yourself.
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
printf 'line1\nline2\nline3\n' > file.txt
git add file.txt && git commit -q -m "init"
echo "line4" >> file.txt
git add file.txt
echo "line5" >> file.txt
echo "=== diff: working vs staging ==="
git diff
echo ""
echo "=== diff --staged: staging vs HEAD ==="
git diff --staged
git commit -q -m "add line4 (staged only)"
echo ""
echo "=== commit (after) remaining change ==="
git diff
rm -rf "$REPO"
=== Working dir → staging → commit ===
file edited
staged
committed#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
for i in 1 2 3; do
echo "v$i" > version.txt
git add version.txt
git commit -q -m "chore: version $i"
done
echo "=== git log --oneline ==="
git log --oneline
echo ""
echo "=== git log --oneline -n 2 ==="
git log --oneline -n 2
echo ""
echo "=== git log --oneline --stat ==="
git log --oneline --stat
echo ""
echo "=== git diff HEAD~2 HEAD (two commits ago vs current) ==="
git diff HEAD~2 HEAD
rm -rf "$REPO"
=== Tracking changes ===
a1b2c3d feat: edit file
=== Compare across two commits ===
diff: line added#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "secret_password" > .env
echo "print('hello')" > main.py
git add .
git commit -q -m "init: add files (including .env by mistake)"
echo "=== accidentally committed file list ==="
git ls-files
# .env at
echo ".env" > .gitignore
git rm --cached .env
git add .gitignore
git commit -m "chore: stop tracking .env"
echo ""
echo "=== edit (after) file list ==="
git ls-files
echo ""
echo "=== .env file at using ==="
ls -la .env
rm -rf "$REPO"
=== Create .gitignore ===
=== .gitignore in action ===
accidentally committed file no longer staged#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
# local repository alias register
git config alias.lg "log --oneline --graph --all --decorate"
git config alias.st "status --short --branch"
git config alias.aa "add --all"
git config alias.cm "commit -m"
for i in 1 2; do
echo "file$i" > "file$i.txt"
git aa
git cm "chore: add file$i"
done
echo "=== git st (status --short --branch) ==="
git st
echo ""
echo "=== git lg (log --oneline --graph --all) ==="
git lg
echo ""
echo "=== register alias list ==="
git config --list | grep "^alias\."
rm -rf "$REPO"
=== Alias setup ===
=== Register alias ===
git st is now status
git lg is now graph log📝 Exercises
Try them yourself first, then open the solution to compare.
Problem 1 (hw01.sh)
Goal: In a temp repo, make three commits each adding one line to `notes.md`. Then print the log in two formats and the diff between the first and last commit.
- Filename: hw01.sh
▶Toggle solution
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
printf 'todo 1\ntodo 2\ntodo 3\n' > notes.txt
git add notes.txt && git commit -q -m "init: add notes"
echo "todo 4" >> notes.txt
git add notes.txt
echo "todo 5" >> notes.txt
echo "=== diff: working vs staging ==="
git diff
echo ""
echo "=== diff --staged: staging vs HEAD ==="
git diff --staged
rm -rf "$REPO"
Problem 2 (hw02.sh)
Goal: Build a `.gitignore` that ignores `*.log`, `node_modules/`, and `.env`. Demonstrate that committing a `secret.env` no longer stages it, while `app.js` still does.
- Filename: hw02.sh
▶Toggle solution
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
for i in 1 2 3 4 5; do
echo "commit $i" > "file$i.txt"
git add "file$i.txt"
git commit -q -m "chore: add file$i"
done
echo "*.log" > .gitignore
git add .gitignore
git commit -q -m "chore: ignore *.log files"
echo "some debug info" > debug.log
echo "=== git status (.log file ) ==="
git status
echo ""
echo "=== git log --oneline ( (before)) ==="
git log --oneline
echo ""
echo "=== git log --oneline -n 3 ( 3) ==="
git log --oneline -n 3
rm -rf "$REPO"
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗