Skip to content

Undo and stay safe ​

Almost everything in Git can be undone, as long as it was committed at some point. This lesson gives you the right undo for each situation, the safety net behind them, and the few habits that prevent the mistakes Git cannot fix.

Pick the right undo ​

You want to…CommandRewrites history?
Throw away edits to a file you have not stagedgit restore fileNo, but the edits are gone for good
Take a file out of the staging area, keep the editsgit restore --staged fileNo
Fix the last commit before pushinggit commit --amendYes, the last commit
Undo a commit that is already pushedgit revert commitNo, adds a new commit
Move your branch back to an earlier commitgit reset (soft, mixed or hard)Yes
Put unfinished work aside for a whilegit stashNo
Find a commit you "lost"git reflogNo

The column on the right matters most. Rewriting history is fine for commits only you have. For commits other people already have, use the commands that add a new commit instead.

Restore: files in the working tree and staging area ​

bash
git restore --staged packing.txt

Unstages the file. Your edits stay in the file; they will not go into the next commit.

bash
git restore packing.txt

Throws away your unstaged edits and puts back the version from the staging area (or the last commit). git status suggests this command next to every modified file.

DANGER

git restore file cannot be undone. Edits that were never staged or committed are not in Git's history, so the reflog below cannot bring them back. Read git diff file first if you are not sure.

Revert: undo a commit by adding one ​

git revert makes a new commit that does the opposite of an earlier one. The original stays in the history, so nobody's copy is broken. It is the safe undo for anything already pushed.

bash
git log --oneline -2
text
e8b0b39 Pack a map
4042b93 Merge branch 'weekend'
bash
git revert e8b0b39
text
[main 74d2e72] Revert "Pack a map"
 1 file changed, 1 deletion(-)
bash
git log --oneline -3
text
74d2e72 Revert "Pack a map"
e8b0b39 Pack a map
4042b93 Merge branch 'weekend'

Git opens your editor with the message Revert "Pack a map"; add a line saying why, then save. In BranchPilot, right-click the commit and choose Revert this commit.

Reset: move the branch back ​

git reset moves your current branch label to another commit. The commits after it drop out of the branch. The three modes differ in what happens to your files:

text
You are here, and run: git reset <mode> HEAD~2

… ◄── 4042b93 ◄── e8b0b39 ◄── 1e2b5b4
         ▲                       ▲
     main moves here       main was here

               Branch     Staging area      Working tree
--soft         moves      keeps changes     keeps changes
--mixed        moves      reset             keeps changes     (the default)
--hard         moves      reset             reset: changes are DISCARDED

HEAD~2 means "two commits before HEAD". HEAD~1 (or HEAD~) is the commit before.

Soft keeps everything staged, ready to commit again. It is a handy way to combine your last few commits into one:

bash
git reset --soft HEAD~2
git status --short
text
M  packing.txt

Mixed (the default when you give no mode) keeps the changes in your files but unstages them, so you can stage and commit them differently:

bash
git reset HEAD~1
text
Unstaged changes after reset:
M	packing.txt

Hard makes the branch, the staging area and your files all match the target commit:

bash
git reset --hard HEAD~1
text
HEAD is now at 0959eac Pack snacks for the train

--hard throws away uncommitted work

Any uncommitted change to a tracked file, staged or not, is gone and cannot be recovered. Commits you stepped back over can still be found with the reflog. BranchPilot makes you type the branch name before a Hard reset; in a terminal there is no such check. Run git status first.

In BranchPilot, right-click the target commit and choose Reset branch to here…, then Soft, Mixed or Hard. See Reset a branch.

The reflog: your safety net ​

Every time HEAD moves (a commit, a switch, a merge, a rebase, a reset), Git writes a line in the reflog, a private diary of where your branches have been. It is how you find commits that no branch points at any more.

You have run git reset --hard HEAD~1 and realise the commit you threw away was the merge you wanted. The log no longer shows it, and days.md has disappeared:

bash
git reflog
text
0959eac HEAD@{0}: reset: moving to HEAD~1
37dcd6b HEAD@{1}: commit (merge): Merge branch 'weekend'
0959eac HEAD@{2}: commit: Pack snacks for the train
eb4dd84 HEAD@{3}: checkout: moving from weekend to main
…

HEAD@{0} is now, HEAD@{1} is one move ago, and so on. The merge commit is 37dcd6b, one move ago. Put the branch back:

bash
git reset --hard "HEAD@{1}"
text
HEAD is now at 37dcd6b Merge branch 'weekend'

The merge and days.md are back. (The quotes stop some shells from treating the braces specially. Using the hash, git reset --hard 37dcd6b, works too.)

The same trick rescues a branch you deleted by mistake. Git even prints the hash when it deletes:

text
Deleted branch books (was ac16c1f).
bash
git branch books ac16c1f

What the reflog cannot do:

  • It only records commits. Edits you never committed are not in it.
  • It is local to your repository. It is not pushed, and a fresh clone has an empty one.
  • Old entries expire. By default Git keeps them for 90 days, or 30 days for commits no branch can reach, and then may clean them up.

Stash: put work aside ​

You are halfway through an edit when you need to switch branches, and Git refuses because the switch would overwrite your changes. Stash puts your uncommitted changes on a shelf and leaves a clean working tree:

bash
git stash push -m "packing ideas"
text
Saved working directory and index state On main: packing ideas
bash
git stash list
text
stash@{0}: On main: packing ideas

Switch, do the other job, come back, and take the changes off the shelf:

bash
git stash pop
text
…
Dropped refs/stash@{0} (846c77e285aa135bfbd1a8d92f06de3e8b8265c1)
CommandWhat it does
git stash push -m "note"Shelve tracked changes, with a note
git stash push -uAlso shelve untracked files
git stash listList the shelf
git stash popPut the newest stash back and remove it from the list
git stash applyPut it back and keep it in the list
git stash dropDelete the newest stash without applying it

Stashes are for minutes or hours, not weeks: they are often forgotten. For anything longer, commit to a branch. In BranchPilot, use the plus button on Stashes (Stash changes); see Stash work in progress.

Mark safe points: tags and backup branches ​

A tag is a name for one commit that never moves, typically a release:

bash
git tag -a v1.0 -m "First trip plan"
git tag
text
v1.0

-a makes an annotated tag, which records who tagged and when. Tags are not pushed by a normal git push; push one with git push origin v1.0. In BranchPilot, right-click a commit and choose Create tag here….

Before any risky operation on your history, such as a big interactive rebase or a reset, make a backup branch. It costs nothing and makes the way back obvious:

bash
git branch backup-before-rebase

If things go wrong, git reset --hard backup-before-rebase returns you to where you started. Delete the backup with git branch -D backup-before-rebase when you are happy.

Stay safe ​

Never commit secrets ​

Passwords, API keys, tokens, private keys and .env files do not belong in a repository, even a private one. Repositories get cloned, forked, shared with new colleagues, backed up and sometimes made public.

  • Keep secrets in files listed in .gitignore (such as .env), and commit an example file with fake values instead (.env.example).
  • Read git diff --staged before every commit.
  • Scan the project before you commit or push. DL Sentinel checks a folder for secrets and other risks, offline: sentinel scan . in the project folder. It scans the files in your folder, not old commits, so run it before the secret reaches the history.

If you committed a secret: rotate first ​

Deleting the line and committing again does not help. The secret is still in the history, and anyone with a copy can read it:

bash
git show HEAD~1:.env
text
DB_PASSWORD=example

Do this, in this order:

  1. Revoke or rotate the secret with the service that issued it, straight away. Once it has been pushed anywhere others can read, assume it has been seen. This is the only step that actually protects you.
  2. Remove it from the code and move it somewhere safe, such as an ignored .env file or a secret store.
  3. Optionally, clean the history. Separate tools such as git filter-repo can rewrite every commit to remove a file. This rewrites shared history, needs every collaborator to re-clone, and cannot reach copies already made, so it is a tidy-up, never a replacement for step 1.

See Reading findings in the DL Sentinel guide for the same advice.

Never force-push shared branches ​

A force push replaces the branch on the server. On a branch others use, such as main, it can delete their commits and leave everyone with a broken history. Rewrite only your own branches, use --force-with-lease even then, and if a shared branch needs undoing, use git revert. Many hosting sites let you protect main so force pushes are refused.

Back up before history surgery ​

Before an interactive rebase, a hard reset, a history clean-up or anything you have not done before:

  • make a backup branch (git branch backup-…), or
  • push your branch somewhere first, or
  • copy the whole folder, .git included.

Look before you destroy ​

  • git restore file, git reset --hard, git stash drop and git branch -D can lose work. Run git status or git stash list first.
  • git clean deletes untracked files for good. Always run git clean -n first: it only lists what it would remove.

Check yourself ​

QuizUndo and stay safe

Question 1 of 6

A commit you pushed yesterday broke something, and others have pulled it. What is the safe way to undo it?

Score: 0

What you learned ​

  • git restore fixes files and the staging area; unstaged edits it discards are gone for good.
  • git revert undoes a pushed commit safely by adding a new one.
  • git reset moves the branch: soft keeps changes staged, mixed keeps them unstaged, hard discards them.
  • The reflog records every move of HEAD, so committed work can almost always be recovered.
  • git stash shelves unfinished work; tags and backup branches mark safe points.
  • If a secret is committed, rotate it first; deleting it from the code is not enough.

Next steps ​