Live Demo Script: Git & GitHub, first command to last#
Run this top to bottom in one terminal. Everything happens in a throwaway folder, and the “remote” is a local bare repo, so you only need GitHub for Part 7b. Each step has a Say line (what to tell the audience) and a Show line (what to point at).
Tip: keep a second terminal open running watch -n1 git log --oneline --graph --all so the audience sees the history change live.
Part 0 · Setup (2 min)#
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global core.editor "nano"
git config --list --show-origin | grep -E "user|defaultBranch"
rm -rf ~/git-demo && mkdir ~/git-demo && cd ~/git-demo
Say: Git needs to know who you are because every commit is signed with a name and email.
Part 1 · First repository (5 min)#
git init
ls -a # show the hidden .git folder
git status # "No commits yet"
echo "# Demo App" > README.md
git status # Untracked
git add README.md
git status # Staged (green)
git commit -m "Add README"
git log
Show: the three states of a file: untracked, staged, committed.
echo "print('hello')" > app.py
echo "Built with Python" >> README.md
git status # one new, one modified
git add .
git commit -m "Add app.py and describe the stack"
git log --oneline
Say: edit, add, commit. That is the whole rhythm.
Part 2 · Looking back (4 min)#
echo "print('hello, world')" > app.py
git diff # working folder vs staging
git add app.py
git diff # empty now
git diff --staged # staging vs last commit
git commit -m "Greet the whole world"
git log --oneline --graph
git show HEAD # open the last commit
git show HEAD~1 # the one before
git blame app.py
.gitignore#
echo "SECRET=123" > .env
echo "debug" > debug.log
git status # both show as untracked
printf ".env\n*.log\n" > .gitignore
git status # only .gitignore remains
git add .gitignore && git commit -m "Ignore secrets and logs"
Part 3 · Undoing things (6 min)#
# 1. Discard edits to a file
echo "oops" >> app.py
git restore app.py
cat app.py
# 2. Un-stage
echo "print('draft')" >> app.py
git add app.py
git restore --staged app.py
git restore app.py
# 3. Amend the last commit
echo "License: MIT" >> README.md
git add README.md
git commit -m "Add licence"
git commit --amend -m "Add license note" # fixed the typo
# 4. Reset (local only)
echo "a" > a.txt && git add a.txt && git commit -m "Add a"
echo "b" > b.txt && git add b.txt && git commit -m "Add b"
git reset --soft HEAD~1 # commit gone, b.txt still staged
git status
git reset --mixed HEAD # unstage
git status
git add b.txt && git commit -m "Add b"
git reset --hard HEAD~1 # DANGER: b.txt deleted
ls
# 5. Reflog: the safety net
git reflog
git reset --hard HEAD@{1} # b is back
ls
# 6. Revert: safe undo for shared history
git revert HEAD --no-edit
git log --oneline
Say: reset rewrites history (private only). revert adds a new commit (safe for shared work).
Part 4 · Branches (5 min)#
git branch
git switch -c feature/login
echo "def login(): pass" > login.py
git add login.py && git commit -m "Add login stub"
echo "# login docs" > docs.md
git add docs.md && git commit -m "Add login docs"
git log --oneline --graph --all
git switch main
ls # login.py is gone: it lives on the other branch
git switch feature/login
ls # and it is back
Show: ls flipping between branches; HEAD moving in the graph terminal.
Part 5 · Merging and conflicts (7 min)#
Fast-forward#
git switch main
git merge feature/login # "Fast-forward"
git branch -d feature/login
Three-way merge#
git switch -c feature/footer
echo "footer" > footer.txt && git add . && git commit -m "Add footer"
git switch main
echo "header" > header.txt && git add . && git commit -m "Add header"
git merge feature/footer -m "Merge footer"
git log --oneline --graph
Conflict#
git switch -c feature/greeting-a
echo "Hello from A" > greeting.txt && git add . && git commit -m "Greeting A"
git switch main
git switch -c feature/greeting-b
echo "Hello from B" > greeting.txt && git add . && git commit -m "Greeting B"
git switch main
git merge feature/greeting-a # fine
git merge feature/greeting-b # CONFLICT
git status
cat greeting.txt # show <<<<<<< ======= >>>>>>>
echo "Hello from A and B" > greeting.txt
git add greeting.txt
git commit -m "Resolve greeting conflict"
git merge --abort 2>/dev/null; git log --oneline --graph
Say: a conflict is not an error, it is Git asking a human to decide.
Part 6 · Remotes (6 min)#
Simulate GitHub with a local bare repo first (no account needed):
cd ~ && rm -rf fake-github.git && git init --bare fake-github.git
cd ~/git-demo
git remote add origin ~/fake-github.git
git remote -v
git push -u origin main
git branch -a
Simulate a teammate:
cd ~ && rm -rf teammate && git clone ~/fake-github.git teammate
cd teammate
git config user.name "Teammate"; git config user.email "t@example.com"
echo "teammate was here" > team.txt
git add . && git commit -m "Teammate adds a file"
git push
cd ~/git-demo
git fetch # download only
git log --oneline origin/main -3
git status # "behind by 1"
git pull # fetch + merge
Rejected push:
echo "mine" > mine.txt && git add . && git commit -m "My change"
cd ~/teammate && echo "theirs" > theirs.txt && git add . && git commit -m "Their change" && git push
cd ~/git-demo
git push # REJECTED
git pull --no-rebase --no-edit
git push # works
Part 6b · Real GitHub (3 min)#
ssh-keygen -t ed25519 -C "you@example.com"
cat ~/.ssh/id_ed25519.pub # paste into GitHub > Settings > SSH keys
ssh -T git@github.com
# create an empty repo "git-demo" on github.com, then:
git remote set-url origin git@github.com:YOUR-USER/git-demo.git
git push -u origin main
Part 7 · Pull request (live on GitHub, 5 min)#
git switch -c fix/readme-typo
echo "Contributions welcome" >> README.md
git add . && git commit -m "Add contributing note"
git push -u origin fix/readme-typo
On github.com: Compare & pull request → write title and description → Create pull request → add a review comment → Squash and merge → Delete branch.
git switch main
git pull
git branch -d fix/readme-typo
git fetch --prune
Fork flow (show on the website only): Fork → clone your fork → git remote add upstream <original-url> →
git fetch upstream → git merge upstream/main.
Part 8 · Power tools (6 min)#
# Stash
echo "half done" >> app.py
git switch -c temp # may be refused if conflicts; otherwise fine
git switch main
git stash
git status # clean
git stash list
git stash pop
git restore app.py
# Cherry-pick
git switch -c hotfix
echo "fix" > fix.txt && git add . && git commit -m "Hotfix"
git switch main
git cherry-pick hotfix
git log --oneline -3
# Rebase
git switch -c feature/rebase-demo
echo "x" > x.txt && git add . && git commit -m "Feature x"
git switch main
echo "y" > y.txt && git add . && git commit -m "Main moves on"
git switch feature/rebase-demo
git rebase main
git log --oneline --graph --all
# Tags
git switch main
git tag v1.0.0
git tag -a v1.1.0 -m "Second release"
git tag
git push --tags
Say: never rebase commits other people already have.
Part 9 · Workflows hands-on (6 min)#
# Feature-branch workflow in 6 commands
git switch main && git pull
git switch -c feature/search
echo "search" > search.py && git add . && git commit -m "Add search"
git push -u origin feature/search
# open PR on GitHub, merge, then:
git switch main && git pull && git branch -d feature/search
# Trunk-based: tiny change straight to main
echo "tweak" >> README.md && git commit -am "Tweak README" && git push
Walk through the six workflow diagrams (centralized, feature branch, GitHub flow, Git Flow, forking, trunk-based) and ask the audience which fits their team.
Part 10 · Rescue missions (6 min)#
# Committed on main by mistake
echo "wip" > wip.txt && git add . && git commit -m "Oops on main"
git branch rescue-branch
git reset --hard HEAD~1
git switch rescue-branch # the commit lives on
# Detached HEAD
git switch main
git switch --detach HEAD~2
git switch -c saved-work # keep anything done here
git switch main
# Deleted a branch
git branch -D saved-work
git reflog | head
git branch saved-work <commit-id-from-reflog>
# Wrong author
git commit --amend --reset-author --no-edit
# Bisect: which commit broke it?
git bisect start
git bisect bad
git bisect good HEAD~5
# test, then mark good/bad until Git names the commit
git bisect reset
Wrap-up (2 min)#
git log --oneline --graph --all
Say: the daily loop is git pull → work → git add → git commit → git push. When in doubt, run git status.
Cleanup#
cd ~ && rm -rf git-demo fake-github.git teammate