Teach a beginner Git and GitHub on their own real project, do first and explain after. Guided setup, then short lessons covering first commits, reading status and diffs, branches, remotes, pull requests, a deliberate merge conflict, first CI with GitHub Actions, and safe undo. Every lesson ends with a runnable check and a fix path for the errors beginners actually hit, and the course ends with a teach back. Use when someone wants to learn Git or GitHub hands on, asks to be taught version control on a real repo, keeps getting stuck on beginner errors, or needs an onboarding path that goes past setup into pull requests and CI.
./install.sh hands-on-github-teachingname: hands_on_github_teaching description: Teach a beginner Git and GitHub on their own real project, do first and explain after. Guided setup, then short lessons covering first commits, reading status and diffs, branches, remotes, pull requests, a deliberate merge conflict, first CI with GitHub Actions, and safe undo. Every lesson ends with a runnable check and a fix path for the errors beginners actually hit, and the course ends with a teach back. Use when someone wants to learn Git or GitHub hands on, asks to be taught version control on a real repo, keeps getting stuck on beginner errors, or needs an onboarding path that goes past setup into pull requests and CI.
Most beginner Git material stops at setup and a list of commands. The learner can repeat the commands in the tutorial repo and freezes the first time their own repo does something unexpected. This skill teaches on the learner's real project instead. Each lesson is short, starts with doing a real change to that project, explains only what the learner just saw, and ends with a check that either passes or routes to a specific fix.
The teaching method is deliberate. Doing before explaining keeps the explanation tied to something the learner already touched. The end of lesson check is retrieval practice: the learner has to produce the state or the answer from memory instead of recognizing it on a page. Retrieval practice is one of the best supported study methods in the learning research, which is why every lesson has a check and the course ends with a teach back instead of a summary.
Safety rule that shapes the whole course: the learner works on their own project, so no lesson ever uses a destructive command as a teaching shortcut. No git reset --hard, no force push, no deleting branches with unmerged work, until the undo lesson, and there only inside a throwaway practice branch created for that purpose.
git --version
No output or command not found means install Git first from git-scm.com or their package manager, then restart the terminal and recheck. Do not proceed past a failed version check.
python3 bin/check_lesson.py <lesson> --repo <path> prints PASS or the exact missing piece.Never stack two lessons into one sitting for a true beginner. One lesson, one check, one break is the default pace. Two lessons per sitting is the ceiling, and only when both checks passed first try.
Do:
git config --global user.name "Their Name"
git config --global user.email "them@example.com"
git config --global init.defaultBranch main
git config --list
Explain after: Git stamps every commit with a name and email, so this is the return address on everything they will ever commit. init.defaultBranch main means new repositories start with a branch called main. Git has supported setting that default since version 2.28. --global means it applies to every project on this machine, so they do this once, not per project.
Check (recall plus state): learner runs git config user.name and git config user.email from memory and reads back their own values.
Fix paths:
git config --global user.email command with the corrected value. It overwrites, nothing else is needed.git config --show-origin user.email inside the project, which names the file that set the winning value, then set the local value deliberately or remove it with git config --unset user.email inside that repo.Do, inside the project folder:
git init
git status
Then the learner picks one small real file, writes or edits it, and:
git add <file>
git status
git commit -m "Describe the change in plain words"
git log --oneline
Explain after: git init turned the folder into a repository, nothing more. The working folder, the staging area, and the commit history are three separate places, and git status always tells you which place each change is sitting in. git add moves a change into staging, git commit moves staged changes into history with a message. Their first commit now exists and git log --oneline proves it.
Check: python3 bin/check_lesson.py 1 --repo <path> passes when the repo exists, has at least one commit, and the working tree is clean.
Fix paths:
fatal: not a git repository: they are in the wrong folder. pwd (or cd checking on Windows), move into the project folder, rerun. Never run git init in a home directory to make the error go away.git status shows what is unstaged, then git add the file and commit again.git commit with no -m. Teach the exit for their editor, then use -m for the rest of the course.git reset --soft HEAD~1 is introduced here only as a named exception, explained as: uncommit, keep everything staged, fix the staging, commit again. If that feels too advanced for this learner, leave the commit and fix forward with a second commit. Both are acceptable, say so.Do: learner edits a tracked file in two separate places, stages only one edit if their tooling allows, then runs:
git status
git diff
git diff --staged
git log --oneline --graph --all
Explain after: git diff shows unstaged changes, git diff --staged shows what is about to be committed, and comparing the two is how you know exactly what a commit will contain before you make it. Status is the dashboard, diff is the detail, log is the history. Reading these three is 80 percent of daily Git confidence.
Check (recall): close the terminal output. Learner states, for a change they just made, which of the three places it is in and which command would prove it. Then they run that command to verify their own answer.
Fix paths:
q. Then set git config --global core.pager cat for the rest of the course if the pager keeps biting.git diff --ignore-all-space to confirm, then fix the editor setting rather than committing the noise.Do:
git switch -c try-small-change
Learner makes one real, small improvement on this branch, commits it, then:
git switch main
git log --oneline --graph --all
so they can see main does not have the change yet.
Explain after: a branch is a movable label pointing at a commit, not a copy of the project. Switching branches changes which commit their files match. Work on a branch is isolated until it is merged, which is the whole reason branches exist.
Check: python3 bin/check_lesson.py 3 --repo <path> passes when at least two branches exist and the current branch is not the only one with commits, or the topic branch exists with its own commit.
Fix paths:
git switch fails with local changes would be overwritten: the learner has uncommitted work that conflicts with switching. Commit it on the current branch first, or set it aside with git stash push -m "wip" and git stash pop after switching back. Teach stash here only if it actually happens.git switch -c try-small-change, then move main back one commit on the practice understanding from Lesson 1, or simpler for beginners, leave the commit on main and redo the lesson making the next change on the branch. Pick based on the learner, and say why.git switch -c rescue-branch. If they made none, git switch main returns to normal. Explain in one sentence: HEAD was pointing at a commit instead of a branch, so new commits had no label to move with them.Do:
git remote add origin <url they copied from GitHub>
git push -u origin main
git pull locally and finds the change on their machine.Explain after: origin is just a nickname for the GitHub address. Push sends commits up, pull fetches commits down and merges them. The -u on the first push links local main to origin main so later pushes and pulls need no arguments. GitHub itself holds no special copy, it is one remote among possible remotes, theirs is just the one everyone can reach.
Check: python3 bin/check_lesson.py 4 --repo <path> passes when origin exists and local main is not ahead of its upstream.
Authentication, taught here because this is where it bites:
ssh -T git@github.com, and success reads as a greeting with their username. All SSH connections to GitHub are made as the user git, never as their own username in the URL.Fix paths, the four errors that end most beginner sessions:
Permission denied (publickey): the key GitHub has does not match the key being offered. Run ssh -vT git@github.com, check the remote is the SSH form with git remote -v, confirm the public key is added under their GitHub settings, and confirm they did not generate or use keys under sudo, which changes whose keys are in play.non-fast-forward: the remote has commits they do not have, usually the GitHub side edit from this lesson done out of order, or a second machine. Fix is git pull first, resolve anything it raises, then push again. Git refuses the push specifically to stop history being lost, say that out loud.remote origin already exists: they added it twice. git remote set-url origin <url> corrects the address in place.git remote -v shows the truth. Fix the URL with set-url. Nothing about the local commits changes.Do, following the same shape as GitHub's own quickstart:
git switch main, git pull, and deletes the merged branch with git branch -d try-small-change.Explain after: a pull request is a proposal to merge one branch into another, with a discussion attached. It is a GitHub feature built on top of Git branches, not a Git command. Small, focused pull requests get reviewed faster and merge cleaner, so one change per pull request is the working habit. Pushing more commits to the same branch updates the open pull request, nothing needs reopening.
Check (state plus recall): the merged change is present on local main after the pull, and the learner can state the five steps (branch, commit, push, open, respond, merge) in order without looking. Missing a step in recall means re-walk it on a second tiny pull request, which is normal and expected.
Fix paths:
git push -u origin <branch> and refresh.Do not wait for a real conflict to teach this. Manufacture one in a safe place:
conflict-practice.txt, with one line of text, commits it on main, pushes.git merge main. Conflict, guaranteed.Explain after, while the conflict is on screen: Git merged everything it could and stopped exactly where two changes touched the same lines, because picking a winner there is a human decision. The markers mean: between <<<<<<< HEAD and ======= is the current branch version, between ======= and >>>>>>> main is the incoming version. Resolving means editing the file to the final text a person wants, deleting every marker, then staging and committing to finish the merge.
Do the fix together: edit the file to a deliberate final line, remove all markers, then:
git add conflict-practice.txt
git commit
git status
Check: python3 bin/check_lesson.py 6 --repo <path> passes when no merge is in progress, no conflict markers remain in any tracked file, and the working tree is clean. Then the learner repeats the whole drill a second time alone, with a different line, while the teacher only watches. The second solo run is the real check.
Fix paths:
git merge --abort returns the repo to exactly where it was before the merge started. Using it is allowed. It is a retreat, not a failure.git pull instead of merge: same markers, same resolution, then git commit to finish. The entry point differs, the skill is identical.Do:
.github/workflows/ci.yml on a branch, using the shape that fits their project. Minimal starter for a project with no test suite yet, which still teaches the full mechanism:yaml
name: CI
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: ls -la
For a Node project with tests, swap the last step for the setup and test pattern from GitHub's example workflow: actions/setup-node@v4 with a pinned node version, npm ci, then npm test.
Explain after: a workflow is a YAML file in .github/workflows that GitHub runs on machines it provides, called runners, when the events listed under on happen. Committing the workflow file and pushing is what triggers it. A red check on a pull request is CI doing its one job: reporting that this branch, tested fresh on a clean machine, does not pass.
Check: the learner's pull request shows a completed check, and the learner can answer from memory: where do workflow files live, what event ran this one, and where do you read the log when a step fails.
Fix paths:
.github/workflows, the extension is not .yml or .yaml, or a YAML indentation error stopped it before it started. Check the Actions tab for an annotation error, fix indentation with spaces not tabs, push again.pull_request is missing from on.Teach undo as a ladder, safest rung first. The learner practices each rung on the practice branch, never first on main.
git restore --staged <file>. The change stays in the working folder, it just leaves the staging area. Git added git restore in version 2.23 for exactly these undo jobs.git restore <file>. Say the cost plainly before they run it: the uncommitted change is gone, and anything never committed is generally not recoverable.git reset --soft HEAD~1. Commits removed this way leave their changes staged.git revert <commit>. Revert does not erase history, it adds a new commit that reverses the earlier one, which is why it is the safe choice on shared branches. It needs a clean working tree.git reflog lists where HEAD has been, and git branch recover-branch <hash> puts a label back on a lost commit so it is reachable again. Run this once as a drill: make a commit on a practice branch, reset it away with --soft, find it in the reflog, and recover it onto a branch.Check: the drill commit is recovered and visible with git log --oneline recover-branch, and the learner can state which rung they would use for each of: staged the wrong file, committed to the wrong branch, pushed a bad commit to a shared branch.
Fix paths:
git revert --continue, or back out with git revert --abort.git stash list and the editor's local history. If it was never committed or stashed, say plainly that it is gone and rebuild it. Do not invent a rescue that does not exist.--hard or force push as a shortcut: refuse it in this course. If a real situation seems to need one, that situation has outgrown the beginner course and gets handled case by case with a backup branch made first: git branch backup-before-fix.Close the course with two assessments, in this order:
references/teach-back-rubric.md. Anything they cannot explain is the list of what to reteach, not a grade. Fill the gaps with one more targeted lesson each, then repeat only the missed parts of the teach back.Hand over the learner's own reference at the end: references/beginner-error-map.md with their lesson log appended, so the fixes they personally hit stay findable.
A teaching run produces:
references/teach-back-rubric.md, with reteach items listed explicitly, or a clear statement that the teach back has not happened yet.reset --hard, no force push, no clean -f, no branch deletion with -D, without a backup branch first and the learner's explicit say so. Default answer to "can I just force it" is no, plus the safe alternative.git <command> --help or the official docs at git-scm.com and docs.github.com before teaching it. Version differences are real: git switch and git restore need Git 2.23 or later, the init.defaultBranch setting needs 2.28 or later.git status.publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.