Git & GitHub — Commits, Branches, PRs
Understand Git objects, the index and references; practice branches, private history edits, recovery and a bounded pull-request workflow.
Original publication date unrecordedUpdated
Lesson preparation & details
Level: beginner
By the end, you should be able to
- Git snapshots, the index and working tree are distinct
Bring with you
Sources checked: . A source check does not change the publication or update date.
Editorial review: · What review means
In this article · 22 sections
Review boundary and corrected contracts
Git snapshots, the index and working tree are distinct. Content hashes include an object header; packfiles may delta-compress storage without changing snapshot semantics. Branch refs may be packed rather than loose files. Replaced destructive/global-config/public-push workshop with a disposable, local-only exercise; reflogs are not permanent backups.
The examples below teach the stated input contracts; fragments and deliberately incorrect counterexamples are exercises, not production implementations. Complexity counts elementary operations unless a bit-cost or allocation caveat is stated. Practice times are suggestions, not measured performance or hiring guarantees.
Learning objective: Use Git as a save-point machine you trust: initialise a repo, branch, commit, push, and open your first pull request without googling any command.
Why this session exists
Git is the second most important tool a working engineer touches, after the editor. It is also the tool that most beginners fake their way through — copy-pasting git add . && git commit -m "stuff" for a year without ever understanding what a commit is. That gap catches up with you the first time you get a merge conflict, or the first time you have to explain to a reviewer why your PR has 47 unrelated files in it. This session closes that gap: a real mental model of commits, branches, and the remote — plus the muscle memory to ship your first pull request.
You will be able to
-
Explain in one sentence what a commit, a branch, and a remote actually are — with no analogies that break under pressure.
-
Initialise a repo, stage, commit, branch, push, and open a pull request on GitHub — end to end, without looking anything up.
-
Read
git log --oneline --graph --alland explain what each line means to a coworker. -
Diagnose wrong-branch commits, staging mistakes, detached HEAD, local resets and remote overwrites; preserve recoverable commits before changing references.
-
Write a commit message that a stranger reading it in three years still understands.
Prerequisites
- S001 — Dev Environment — you need a working shell,
gitinstalled, and a folder under~/projects/….
Pre-read · skim before the session (30 min total)
Further reading: Missing Semester · Version Control (Git)
Further reading: Pro Git · Recording Changes to the Repository
Further reading: How to Write a Git Commit Message
(a) Intuition · 5 min
What Git actually is
Real: Imagine writing a novel and hitting Save As after every paragraph — novel_v1.doc, novel_v2.doc, novel_final.doc, novel_final_FINAL.doc. Now imagine you can also branch: novel_final_but_what_if_the_dog_dies.doc. And revisit a retained snapshot without keeping a separate manual copy of every version. And email the whole history to a friend so they can add their own chapters without touching yours. That is Git. It's a save-point machine with branching and multi-player mode, invented by the guy who wrote Linux.
More precisely: Git stores content-addressed objects and a directed acyclic graph (DAG) of commits. An object ID hashes its type/length header plus payload using the repository's object format (commonly SHA-1; SHA-256 is also supported). A commit points to a tree and zero or more parents.
That is the whole trick. Branches are movable labels on the DAG. Merging is walking two paths back to their common ancestor. Nothing about Git is magic once you see the graph.
The three concepts that unlock everything
If these three don't feel solid, nothing else in Git will
-
A commit records the indexed snapshot, not every current working-tree edit. It references a tree, zero parents for a root, normally one parent, or multiple parents for a merge; it also includes author/committer metadata and a message.
-
A branch is a movable pointer to a commit.
mainandfeature/logincan point to the same commit or different commits in the DAG. -
The staging area (aka ‘index’) is a draft of your next commit.
git addcopies the selected current content into it;git commitrecords that indexed state. Later edits remain unstaged until added again.
The index-to-snapshot mental model
Beginners think Git tracks diffs. It does not. It tracks snapshots. When you run git commit without path/-a shortcuts, Git records the index. Unstaged edits are not included; unchanged blobs can be reused instead of copied. Metadata still consumes storage. Diffs are computed on the fly when you ask for them. Once you internalise this, git rebase, cherry-pick, and merge conflicts all stop being scary.
A quick history so you know why the world looks like this
The important shift from centralized version control to Git is that a normal clone has a local object database and history, so commits and branches do not require a live server. Shallow or partial clones deliberately omit some history or objects. A hosting service adds collaboration around that model: pull requests, permissions, reviews and automated checks are not themselves Git objects. Do not confuse a hosting platform’s database backups with clones of the Git repositories it hosts.
(b) From edited files to shared history · 15 min
The three areas Git shuffles files between
Before choosing a branching strategy, separate the places a change can exist. The first three boxes are local; the remote is where you publish the commits you have already made.
Figure text equivalent — labels and relationships
- Node “Working tree (your files on disk)” (square).
- Node “Staging area (index — draft of next commit)” (square).
- Node “Local repo (.git — the DAG of commits)” (square).
- Node “Remote (GitHub / GitLab / Gitea)” (square).
- “Working tree (your files on disk)” leads to “Staging area (index — draft of next commit)”: git add. Line: normal; ends: arrow point.
- “Staging area (index — draft of next commit)” leads to “Local repo (.git — the DAG of commits)”: git commit. Line: normal; ends: arrow point.
- “Local repo (.git — the DAG of commits)” leads to “Remote (GitHub / GitLab / Gitea)”: git push. Line: normal; ends: arrow point.
- “Remote (GitHub / GitLab / Gitea)” leads to “Local repo (.git — the DAG of commits)”: git fetch. Line: normal; ends: arrow point.
- “Local repo (.git — the DAG of commits)” leads to “Working tree (your files on disk)”: git checkout / restore. Line: normal; ends: arrow point.
The diagram covers ordinary content movement, not every command. git pull first fetches and then integrates into the current branch using the selected merge/rebase/fast-forward policy; it may change the working tree or stop on conflicts. git status only inspects state.
The commit graph — what git log --graph is actually showing you
The storage areas explain where a change is; the commit graph explains which snapshots share history. Follow the feature branch away from main and back through the merge, rather than reading the drawing as a list of file edits.
Figure text equivalent — labels and relationships
- 1. Commit “a1b2 init” on “main”; root commit.
- 2. Commit “c3d4 add readme” on “main”; parents “a1b2 init”.
- 3. Commit “e5f6 wip login” on “feature/login”; parents “c3d4 add readme”.
- 4. Commit “g7h8 finish login” on “feature/login”; parents “e5f6 wip login”.
- 5. Commit “i9j0 fix typo” on “main”; parents “c3d4 add readme”.
- 6. Commit “k1l2 merge” on “main”; parents “i9j0 fix typo”, “g7h8 finish login”; merged branch feature/login into main.
- 7. Commit “m3n4 release” on “main”; parents “k1l2 merge”.
- Branch “main” points to “m3n4 release”.
- Branch “feature/login” points to “g7h8 finish login”.
- Each dot is a commit (a snapshot).
- Each arrow points from a child commit to its parent(s) — this pictured merge commit has two; octopus merges can have more.
- main and feature/login are branches — just labels attached to a specific dot.
- Reflogs record local reference movements when available. Retention is configurable; common defaults expire entries after 90 days, or 30 days when unreachable. Pruning can later remove unreachable objects. Reflog is not an indefinite backup and cannot recover every unstaged edit.
The lifecycle of a change
Edit files
Desc: Change code in your editor. git status shows them as ‘modified’ (red).
git add <files>
Desc: Copy the selected current file content into the staging area. git status shows them as ‘to be committed’ (green).
git commit -m "…"
Desc: Freeze the staging area into a new commit on the current branch. A new commit object ID is created if a commit is made; its format follows the repository.
git push origin <branch>
Desc: Upload local commits to the remote (GitHub). Others can now see them.
Open a Pull Request
Desc: Ask maintainers to merge your branch into main. Reviewers comment, CI runs, you iterate.
Merge + delete branch
Desc: PR gets approved and merged. Delete the branch locally + on the remote. Repeat.
Branch strategies you'll see in the wild
Name: Trunk-based
Tagline: Short-lived integration branches
-
One long-lived branch: main
-
Feature branches live <1 day
-
Feature flags gate incomplete work
-
CI must be green on main, always
-
Fit depends on testing, release needs and coordination, not a five-person threshold
Name: GitHub Flow
Tagline: Lightweight branch-and-review workflow
-
main is always deployable
-
Branch → PR → merge → deploy
-
No release branches
-
Simple, forgiving
-
Where you should start
Name: Git Flow
Tagline: Separate development, release and hotfix lines
-
main + develop + release/* + hotfix/*
-
Heavy ceremony, many long-lived branches
-
Made sense in 2010 for shrink-wrapped software
-
Often adds unnecessary ceremony for frequent single-line delivery
-
Learn it only if a job requires it
The mental model to hold
Every Git problem is a graph problem
Draw the DAG on paper. Where is HEAD? Where is main? Where is origin/main? Which commit are you trying to move to? Once the picture is clear, the command is obvious. Reach for git log --oneline --graph --all --decorate before you reach for Stack Overflow.
Myth: "A Git commit is a diff. The repo is a stack of patches, and git log shows me the patches in the order they were applied."
Git models commits as snapshots referencing trees and parents. Packfiles may store delta-compressed representations of objects; this is a storage optimization, not patch-based commit semantics.
Why: Because the diff view is the only view you ever see, and because patch-oriented views and patch queues make differences more visible than snapshots. The illusion holds until you do something that only makes sense for snapshots: checking out a commit restoring its recorded tree subject to local changes and I/O cost, or two branches sharing identical blobs with zero extra storage, or a cherry-pick producing a different SHA for the "same" change. That last one confuses everyone until you accept that the SHA covers the tree plus the parent plus author/committer metadata, timestamps and message — change any of those and it is a different commit object, even if the diff is byte-identical.
Test: In the local workshop repository, inspect objects without adding new ones:
git cat-file -p HEAD
git ls-tree -r HEAD
git hash-object README.md
git rev-parse HEAD:README.mdThe last two IDs agree while the working README bytes match the committed blob. git cat-file shows the commit’s tree and parents, not a patch; git ls-tree lists the tree’s entries. An unstaged README change changes hash-object without changing HEAD:README.md.
Question: What does a trusted commit ID let you check, and why does rewriting an ancestor change descendant IDs?
Git object IDs hash an object header (type and length) followed by content, not the bare file bytes alone.
Because: content addressing gives free deduplication and a free integrity check in one mechanism
Premise: A tree object lists filenames together with the hashes of their blobs and subtrees. So the tree's own hash depends on every byte of every file beneath it.
Because: the tree's content is those hashes, and its name is the hash of its content
Premise: A commit object contains the tree hash, the parent commit hash(es), author, and message. So the commit hash depends on the entire snapshot and on the whole ancestry.
Because: the parent hash is part of the commit's content, recursively covering all history
Premise: Therefore a single commit hash transitively commits to every file and every ancestor commit that ever led to it. Changing one character in a 3-year-old file changes that blob's hash, its tree's hash, its commit's hash, and every descendant hash.
Because: content addressing detects changes under the hash’s integrity assumptions; it is not authentication, and SHA-1 is not a modern collision-resistant security primitive
Conclusion: A trusted tip ID binds the reachable object graph under Git’s hash/integrity assumptions. An attacker who can replace both the history and the advertised tip can still present a self-consistent different history. Establish trusted references or verified signatures through a trusted channel when authenticity matters. Rewriting an ancestor generally changes descendants that reference the rewritten ancestry. Unreachable old objects can remain until expiry/pruning, which permits some local recovery but is not a backup strategy.
Name: DAG of snapshots, branches are sticky notes
Branch names refer to commit objects; refs may be loose or packed. HEAD is often a symbolic reference to a branch, and hash length depends on the repository hash format.
Existing commit objects are immutable, but unreachable objects and reflog entries can expire. Keep independent backups and do not treat reflog as indefinite recovery.
-
resetcan move the current branch and may change index/working tree depending on mode.switchchanges the checked-out branch (or detaches HEAD). A merge may fast-forward without a new commit, or create a multi-parent commit. Rebase replays selected changes onto a base; an already up-to-date operation may be a no-op, and empty/redundant commits may be skipped. -
The three areas — working tree, index (staging), HEAD — are three different trees. Almost every confusing Git message is telling you which pair disagrees.
-
Local commands can discard uncommitted work;
restore,reset --hardandcleanare not universally reversible. Inspect status and save work first. Pushing additionally affects other users and needs the project’s authorization and policy.
Trigger: Fire this model the moment you see: "detached HEAD" · a rebase conflict that reappears commit after commit · git pull creating a surprise merge commit · a force-push argument in code review · "I think I lost my work".
Decision: Your feature branch has fallen behind main. Do you merge main into it, or rebase onto it?
Name: Merge main in
Gain: preserves existing commits and records the integration when a merge commit is made; resolve conflicts for that integration and retest. Shared-branch policy still applies, and later integrations can produce new conflicts.
Cost: history becomes a braid rather than a line; git log interleaves unrelated work; on long-lived branches you accumulate merge commits that carry no information about the feature
Pick: the branch is shared with anyone else, or it is long-lived, or you genuinely care about the forensic record of when integration happened
Name: Rebase onto main
Gain: linear, readable history; each commit is a clean self-contained change reviewable in isolation; bisect over a straight line is easy to reason about
Cost: replayed commits generally have new IDs; collaborators may need to reconcile their branches. Conflicts can recur per replayed commit, and the new states must be tested. No-op rebases need not rewrite anything.
Pick: the branch is yours alone and unpushed (or push is --force-with-lease and the team agreed), and you want a reviewable stack of commits
Name: Squash on merge
Gain: one commit per unit of review, so main's history matches the PR list exactly; no need to police intermediate commit hygiene during development
Cost: the target branch receives one combined commit rather than the feature’s intermediate ancestry. Original commits may remain in another branch/clone, but do not rely on indefinite retention after those references are removed.
Pick: the team treats a PR as the atomic unit of change and PRs are kept small — the common default in large orgs for good reason
Verdict: The operational rule that actually matters: rebase private history, merge public history. Rewriting commits nobody has pulled is free; rewriting commits others have pulled transfers your cleanup cost onto every teammate. One workable convention is rebase-locally plus squash-on-merge: it trades intermediate ancestry on main for one reviewed unit per PR. Whichever you pick, pick one and encode it in branch protection rather than in tribal knowledge — mixed conventions are what produce the unreadable histories people blame Git for.
(c) Hands-on · 25 min
Read and run this with Bash in a disposable workspace. It creates a local repo, branches and commits, then makes a two-parent merge. It never uses GH_USER, configures a remote or publishes anything. Review any pre-existing Git hooks/configuration before running a workshop on your own machine.
# Local-only disposable workshop. No reset, force push or global configuration.
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/git-workshop.XXXXXX")
cd "$WORK_DIR"
git init -b main
git config user.name 'Workshop Student'
git config user.email 'student@example.invalid'
printf '# Local Git workshop
' > README.md
git add README.md
git diff --cached
git commit -m 'Add workshop README'
git switch -c feature/hello
printf 'print("hello")
' > hello.py
git add hello.py
git commit -m 'Add greeting'
git switch main
git merge --no-ff feature/hello -m 'Merge local greeting'
git log --oneline --graph --all
# For a PR: choose your own repository visibility and remote deliberately.
# No remote is configured and nothing above publishes a repository.What each block does
Compare rebase with the merge you just made. Continue only inside the disposable workshop, with a clean working tree and no remote collaborators. This retained exercise creates one feature change and a separate main-branch change:
git switch main
git switch -c feature/bye
echo "def bye(name): return f'bye, {name}!'" >> hello.py
git add hello.py && git commit -m "feat: add bye()"
# meanwhile main gets a change
git switch main
echo "# updated" >> README.md
git add README.md && git commit -m "docs: update readme"
# rebase feature/bye on top of the new main
git switch feature/bye
git rebase main
git log --oneline --graph --all --decorateThe replayed feature/bye commit now has the updated main commit as its parent; its ID differs because that parent changed. The earlier --no-ff merge remains in the repository—“linear” describes this new replay segment, not every line in --all output. This fixture uses nonconflicting files; an overlapping edit can stop rebase for conflict resolution, so inspect status and follow git rebase --continue or --abort only after reviewing the situation.
The revised workshop starts in a new temporary directory, uses project-local settings, and prints enough state to inspect what happened. Existing projects, global shell configuration and remote repositories are not modified. The old destructive reset/install walkthrough is intentionally not executable guidance; use the documented version-specific tools only after reviewing the proposed action.
Keep commits focused, and amend only private work. A .gitignore can list .venv/, __pycache__/, *.log and .DS_Store for this exercise. It affects untracked matching paths, not already committed files. Inspect the staged diff before committing.
The original greeting helper remains a useful source file to version:
def greet(name: str) -> str:
return f"hello, {name}!"
if __name__ == "__main__":
print(greet("git"))Its output is hello, git!. In a clean disposable branch, create amend-demo.txt, stage it and make a private commit. After correcting that file, stage the correction and run git commit --amend --no-edit. The replacement commit has the same message but a new tree/ID; the old object is not edited in place. Inspect git show and preserve needed old tips before any rewrite. Amending shared history needs coordination, just like rebasing it. This is a concrete alternative to the original hard-reset “disaster” demonstration, not permission to rewrite a team branch.
From a local branch to a pull request
A remote is a named repository URL, not a second working directory inside this repo. Before connecting GitHub, choose an account/repository you control, its visibility, the destination branch and authentication through GitHub’s supported flow. Inspect git status, git diff --cached and the commits for secrets. For the workshop’s follow-up rebase exercise, feature/bye has an unmerged change; feature/hello was already merged and would give an empty comparison. In a deliberately chosen empty remote, publish the base with git push -u origin main, then git push -u origin feature/bye after adding the reviewed URL as origin. For an existing team repository, start from its agreed base instead of overwriting it. These commands are illustrative, not part of the GitHub fixture.
On GitHub open Compare & pull request, choose base main and compare feature/bye, inspect the diff and write purpose, test evidence and known limitations. A PR does not itself merge code. Required review/checks and repository merge policy decide whether and how it lands. If checks fail, fix on the same branch, commit and push again; do not bypass branch protection. The local fixture uses a filesystem bare remote and a second clone to demonstrate push/fetch only; it is not a GitHub PR or authentication test.
The commit message rules that reviewers judge you by
A practical team convention (not a verbatim list of Chris Beams’ seven rules)
-
Subject line ≤ 50 characters. Imperative mood: ‘add login’, not ‘added login’ or ‘adds login’.
-
Capitalise the subject. No trailing period.
-
Blank line between subject and body.
-
Body wrapped at 72 chars. Explain WHY and WHAT, not HOW (the diff shows how).
-
If the team uses Conventional Commits, follow its feat/fix/docs convention; this is optional and separate from Beams’ guidance.
-
On GitHub, supported closing keywords such as
Fixes #123can close an issue when the qualifying change reaches the default branch; host rules and cross-repository permissions matter. -
A stranger reading your commit in three years must still understand it. Write for them.
(d) Production reality · 15 min
Recovery starts by preserving evidence. These are illustrative failures, not claimed company incident reports.
- A forced remote overwrite does not mean every copy vanished: pause further writes, coordinate with teammates, find a clone/ref that still holds the desired commit, and preserve it on a recovery branch. A remote reflog may not be available to you. Restore shared history only with maintainer agreement.
- A leaked key remains compromised after deleting its file or rewriting history. Revoke/rotate it with the provider, audit use and follow the repository’s removal guidance. Ignore rules prevent accidental addition of untracked files; they do not untrack committed secrets. Never use a real key in a demo.
- A Git clone is not a backup of every hosting-service database, issue, attachment or secret. Back up each required state store and test restores; no specific historic outage size or response time is asserted here.
Five local diagnosis patterns. Unstage a mistakenly added path with git restore --staged path (working bytes remain). Save uncommitted work before using git restore path, which otherwise discards it. For wrong-branch commits create the correct branch at the current tip before moving any old ref. For detached HEAD create a named branch before switching away. After an accidental reset, inspect git reflog, verify the candidate with git show, then create git branch rescue <candidate> rather than immediately resetting again. The remote overwrite case requires coordinated recovery, not an automatic “undo force push”.
Where this shows up in the rest of the plan
Root: Git is the backbone of everything you'll build in the next six months
Label: S003 · Command line
Desc: Every Git command is a shell command. The pipes, aliases, and grep-fu you learn next apply directly.
Label: S004 · Reading docs
Desc: Uses git help \<cmd> and man git-\<cmd> — the largest official manual you'll grep through.
Label: S055 · CI/CD basics
Desc: GitHub Actions can be configured for push/PR events; workflow triggers, filters and permissions decide whether a job runs.
Label: S056 · Docker
Desc: Dockerfiles live in Git; image tags often use commit SHAs.
Label: Further practice · Code review
Desc: Practice writing focused changes and review explanations; no percentage of team communication is implied.
Label: Further practice · System design reviews
Desc: Designing a code host also requires permissions, storage, availability and collaboration state; a DAG is only one part.
(e) Recall + stretch · 10 min
Self-check (answer follows each question).
Recall — questions and worked answers
Q: In one sentence, what is a commit?
A: A commit is an immutable object referencing a tree snapshot and zero or more parent commits, with author/committer metadata and a message. Its ID follows the repository hash format. Plain git commit records the index, not arbitrary unstaged edits.
Q: What is a branch, actually?
A: A branch is a movable named reference to a commit. A ref can be stored loose under .git/refs/heads/ or in a packed/other supported ref backend; do not assume it is always an individual file. Creating a branch does not copy every file.
Q: What is the difference between git fetch and git pull?
A: git fetch obtains objects and updates configured remote-tracking refs; ordinary fetch does not integrate them into your current working tree. git pull fetches then integrates according to merge/rebase/fast-forward options and may conflict or refuse. Fetch still changes repository state and is not a promise of zero risk.
Q: You made 3 commits on the wrong branch. How do you move them to a new branch without losing them?
A: First save uncommitted work and verify that these are exactly the three private tip commits. git switch -c right-branch preserves the current tip. While on that new branch, git branch -f wrong-branch HEAD~3 can move the old, unshared branch back without discarding the current working tree. Do not use this if that branch is checked out elsewhere or shared; coordinate a revert/cherry-pick strategy instead. Confirm both tips with git log --all --graph.
Q: What does the staging area buy you that ‘just commit everything modified’ would not?
A: Surgical commits. You edited 5 files fixing 3 unrelated bugs — the staging area lets you commit each bug's files separately (using git add -p for hunk-level control) so history stays readable and reviewers can look at one concern at a time.
Q: Why should you almost never git push --force on a shared branch?
A: A force push can move the remote branch away from collaborators’ commits. Prefer ordinary pushes; when an agreed rewrite is necessary, an explicit --force-with-lease=refs/heads/branch:<expected-id> checks the expected remote tip. The default lease relies on local remote-tracking refs, which background fetches can refresh. Even a passing lease does not prove you preserved everyone’s intended work.
Q: You accidentally ran git reset --hard HEAD\~5 and lost 5 commits. What's your first move?
A: Stop further changes. Read git reflog, inspect the candidate commit with git show, and save it with git branch rescue <candidate-id>. Inspect that branch before any reset. Entry positions and retention vary; unreachable entries commonly expire sooner than reachable ones. Reflog cannot generally restore discarded uncommitted edits.
Q: What's the difference between git merge and git rebase — and when do you use each?
A: A merge preserves existing commits and may either fast-forward or create a merge commit. Rebase replays changes onto a chosen base, generally creating new IDs for replayed commits. Follow team policy: do not rewrite shared history without agreement, and retest the resulting states in either case.
Q: What does a pull request actually do, technically?
A: A PR is hosting-service collaboration state requesting integration of a compare branch into a base branch. Reviews, checks and permissions surround the eventual merge/squash/rebase operation. Opening or approving a PR does not by itself guarantee that the target branch changed.
Q: Stretch — you're on main, you have uncommitted changes, and you need to urgently switch to another branch to fix a bug. What are your two options and their trade-offs?
Stretch: true
A: Option 1: git stash push -u -m "wip" includes untracked files (not ignored ones); inspect the stash, switch, and later use git stash apply before dropping it after verification. Apply can conflict. Option 2: create a temporary branch, stage only reviewed work and commit a private WIP snapshot. git reset --soft HEAD~1 later moves the tip back while leaving changes staged; git restore --staged . additionally unstages them. Neither option justifies staging secrets with a blind git add -A.
Explain-out-loud test
If you can't teach these three without notes, redo the session:
- What is a commit? (one sentence, no analogies)
- What does the staging area do? (one concrete example)
- How do you recover from a bad
git reset --hard? (name the command)
Model answers: (1) A commit records a tree, parents and metadata under an object ID. (2) The index lets you record one reviewed change while leaving another edit unstaged. (3) Inspect git reflog and git show, then preserve an available candidate on a rescue branch; recovery depends on retained objects and does not cover arbitrary discarded working-tree edits.
What comes next
Hub: The 6-Month Learning Plan
Part of a 130-session learning series. Suggested session: 70 minutes across the five labeled blocks, plus 20 minutes for setup, questions and retesting. Optional pre-reading is separate; these are pacing suggestions, not completion guarantees.
Corrective source notes
- Git - Recording Changes to the Repository — Git distinguishes staged snapshot content from working-tree modifications. Checked 2026-10-07.
Governing references: Git objects, reflog expiry, reset modes, push leases, merge semantics, and GitHub Flow.
These citations support the named claims, not blanket certification of the lesson. Worked proofs are derived in the text; execution scope and finite-test limits are recorded separately.
Pause / Recall / Apply
Can you explain it without the page?
Close the example. Reconstruct the core idea, then change one assumption. Mark complete when you’re ready; you can always undo it.
Stored in this browser only. No account, no sync. Clearing browser data removes your record.