Chapter 9 of 10 · Git and GitHub for Beginners — Complete Free Tutorial
Correcting History: Reset, Revert and Rebase
Sometimes a commit should never have existed. This ninth chapter of the Git and GitHub for Beginners series covers the three tools that rewrite or undo history — reset, revert and rebase — and, more importantly, the safety rule that decides which one you are allowed to use.
The golden rule of history
Never rewrite history that other people have already pulled. Once a commit is on a shared branch, teammates' clones reference its hash; changing it means their repositories disagree with yours and every subsequent push turns into a negotiation. Keep the rule simple: local commits are yours to fix, published commits are only safe to reverse. With that rule in place, the three tools stop being scary and become a tidy toolkit.
git reset: moving the branch pointer
reset moves your current branch pointer backwards to an earlier commit, then chooses what to do with the changes in between. The mode flag controls everything:
| Mode | Pointer | Staging area | Working files |
|---|---|---|---|
--soft | Moves back | Keeps the changes staged | Untouched |
--mixed (default) | Moves back | Empties the changes | Untouched |
--hard | Moves back | Empties the changes | Overwritten — work is discarded |
Use --soft to re-do a commit properly, --mixed to keep the edits and restage deliberately, and --hard only when you genuinely want the changes gone. Anything moved by --hard is recoverable for a while through the reflog, but behave as if it is not. A common beginner form is HEAD~1 — “one commit before the current one”.
git revert: the safe undo
revert does the opposite of rewriting: it creates a new commit whose content undoes an earlier one, leaving the original untouched. Because history only grows, nothing references a vanished hash, so revert is safe on any branch at any time — it is the correct tool for a bad commit that has been pushed, and the reason “just revert it” is standard team advice. A revert of a revert is also perfectly legal when you change your mind again.
git rebase: replaying your work
rebase moves your commits to a new base by copying them one by one onto the tip of another branch, producing fresh hashes and a straight line of history. The everyday form is git rebase main while working on a feature branch: your changes replay on top of the latest main, so the eventual merge is a clean fast-forward. The interactive form (git rebase -i) opens a list where you can reorder, squash or reword your own commits before publishing. Two limits keep you safe: only rebase commits nobody else has seen, and after rebasing a pushed branch you will need --force-with-lease — which is exactly the situation the golden rule warns about.
Recovery: the reflog safety net
Even if you pick the wrong tool, Git usually remembers. The reflog records where your branch pointer has been for roughly 90 days: run git reflog, find the hash from before the mistake, and git switch -c recovery <hash> to stand on that state again. Between revert for shared work, soft reset for local redo, and reflog for panic, almost nothing is permanently lost — provided you act before the entry expires.
git reset --soft HEAD~1
git status --short
Expected output:
A contact.html
M index.html
git revert 4c3b2a1
Expected output:
[main 5b6c7d8] Revert "Fix the broken link in the footer"
1 file changed, 2 insertions(+), 2 deletions(-)
git rebase main
Expected output:
Successfully rebased and updated refs/heads/feature-online-orders.
Common mistakes
- Running
git reset --hardto “clean up” — it silently destroys uncommitted work; checkgit statusand prefer--softfirst. - Amending or rebasing commits that are already pushed — teammates' clones diverge and the next push demands a force that history was not meant to take.
- Using reset when they meant revert — reset erases the record; revert adds to it. Shared branches get revert.
- Rebasing a whole team's branch — only rebase your own commits; rebasing someone else's work multiplies confusion.
- Assuming a reset deleted the commit forever — the reflog still points at it for weeks; check before mourning.
Practice checklist
- Make a commit with a deliberate typo, then
git reset --soft HEAD~1and re-commit with a better message. - Repeat with
--mixedand--hardin a throwaway branch, comparinggit statusafter each. - Revert a commit with
git revertand confirm withgit log --onelinethat both the original and the revert exist. - Run
git rebase mainon a feature branch and watch the hashes change withgit log --oneline. - Delete a commit by accident, recover it via
git reflog, and save the hash somewhere safe.
What's next?
You now know the whole toolbox — the final piece is deciding when to reach for which. Continue to Git Workflows and Project Best Practices to tie everything together.
Frequently Asked Questions
Git is a distributed version control system: it records snapshots of your project over time, lets you jump back to any point, branch off to try ideas safely, and merge other people's work. It is the industry standard — nearly every developer job assumes basic Git fluency — and it is the best insurance you can have against lost or broken code.
Git is the tool that runs on your machine and stores history locally; it works fully offline. GitHub is one of many websites (GitLab, Bitbucket) that hosts Git repositories in the cloud and adds collaboration features: pull requests, code review, issues and CI. You can use Git forever without GitHub — GitHub needs Git to be useful.
A repository (repo) is your project folder plus a hidden .git directory containing the entire history: every commit, branch and tag. It can be local (only on your computer) or remote (cloned on GitHub). Deleting .git loses the history but keeps your files; deleting the files while keeping .git leaves history with nothing to show.
git add moves changed files into the staging area — your draft of the next snapshot — so you can review exactly what will be recorded. git commit writes the staged snapshot permanently with a message describing why the change happened. Two steps by design: stage deliberately, commit intentionally.
A branch is a movable pointer to a commit — creating one costs almost nothing. Use a branch for every unit of work (feature, fix, experiment): it keeps unfinished code out of main, lets you review changes in a pull request, and makes abandoning a bad idea as simple as deleting the branch.
It depends on where the change lives. Unstaged edits: git restore file. Staged but uncommitted: git restore --staged file. Committed but not pushed: git reset --soft to keep changes staged. Already pushed: git revert commit — it writes a new commit that undoes the old one instead of rewriting shared history. The undoing chapter covers each case with commands.
A conflict happens when two branches changed the same lines and Git cannot choose automatically. Your file gets marked up with <<<<<<<, ======= and >>>>>>> sections; you edit it down to the correct result, remove the markers, then git add and git commit. Conflicts are normal, small and entirely fixable — they are not an error state.
A pull request (PR) is GitHub's proposal to merge one branch into another. It shows the diff, runs checks, and lets teammates comment line by line before the code lands. It is the centre of modern collaboration: work on a branch, open a PR, review, approve, merge.
Write the subject in imperative mood, under about 50 characters: "Add email validation to signup". Explain why, not what, in the body when it is not obvious. Many teams use Conventional Commits (feat:, fix:, docs:) so tools can generate changelogs — the workflows chapter shows the format in practice.
Secrets (API keys, .env files), dependencies (node_modules), build output (dist), and operating-system junk (.DS_Store, Thumbs.db). A .gitignore file excludes them from the start; the common mistakes chapter shows what to do if something already slipped into history.