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:

ModePointerStaging areaWorking files
--softMoves backKeeps the changes stagedUntouched
--mixed (default)Moves backEmpties the changesUntouched
--hardMoves backEmpties the changesOverwritten — 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 --hard to “clean up” — it silently destroys uncommitted work; check git status and prefer --soft first.
  • 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

  1. Make a commit with a deliberate typo, then git reset --soft HEAD~1 and re-commit with a better message.
  2. Repeat with --mixed and --hard in a throwaway branch, comparing git status after each.
  3. Revert a commit with git revert and confirm with git log --oneline that both the original and the revert exist.
  4. Run git rebase main on a feature branch and watch the hashes change with git log --oneline.
  5. 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.

Quiz: Correcting History: Reset, Revert and Rebase

5 questions · 70% to pass

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.