Git Reset & Restore
Reset moves branch tips and optionally the index/worktree. Restore changes files without moving HEAD. Checkout historically did both — prefer the modern split.
All reset forms move HEAD (and the current branch) to a target commit. Modes control what happens to the index and working tree.
| Mode | HEAD | Index | Worktree |
|---|---|---|---|
| --soft | moves | unchanged (stays as before) | unchanged |
| --mixed (default) | moves | matches target | unchanged |
| --hard | moves | matches target | matches target |
| --keep | moves | matches target | keeps local mods if safe |
| 1 | # Undo last commit but keep all changes staged |
| 2 | git reset --soft HEAD~1 |
| 3 | |
| 4 | # Undo last commit; keep changes unstaged |
| 5 | git reset HEAD~1 |
| 6 | # same as: git reset --mixed HEAD~1 |
| 7 | |
| 8 | # Destroy last commit AND discard its changes (dangerous) |
| 9 | git reset --hard HEAD~1 |
| 10 | |
| 11 | # Move branch to remote tip (discard local commits) |
| 12 | git fetch origin |
| 13 | git reset --hard origin/main |
danger
| 1 | # Classic |
| 2 | git reset HEAD path/to/file |
| 3 | |
| 4 | # Modern (preferred) |
| 5 | git restore --staged path/to/file |
| 6 | git restore --staged . |
restore updates the working tree and/or index from a source tree (default: index for worktree restore; HEAD for staged restore).
| 1 | # Discard unstaged edits in a file (from index) |
| 2 | git restore src/app.ts |
| 3 | |
| 4 | # Discard all unstaged worktree changes |
| 5 | git restore . |
| 6 | |
| 7 | # Unstage but keep worktree edits |
| 8 | git restore --staged src/app.ts |
| 9 | |
| 10 | # Restore file content from another commit into worktree |
| 11 | git restore --source=HEAD~3 -- src/legacy.ts |
| 12 | |
| 13 | # Restore into both index and worktree |
| 14 | git restore --source=origin/main --staged --worktree -- package.json |
best practice
Old tutorials overload checkout. Modern Git splits concerns.
| Goal | Modern | Legacy |
|---|---|---|
| Change branch | git switch name | git checkout name |
| Create branch | git switch -c name | git checkout -b name |
| Restore file | git restore file | git checkout -- file |
| Restore from rev | git restore --source=REV -- file | git checkout REV -- file |
| 1 | git switch main |
| 2 | git switch -c feature/x |
| 3 | git restore --source=main -- README.md |
| 4 | # Avoid: git checkout main -- README.md (works, but unclear) |
If you hard-reset by mistake and the commit was recent, reflog usually still has it.
| 1 | git reflog | head -20 |
| 2 | # find the commit before the reset, e.g. HEAD@{1} |
| 3 | git reset --hard 'HEAD@{1}' |
| 4 | # or create a branch pointing at lost tip |
| 5 | git branch rescue abc1234 |
info
reset with paths does not move HEAD — it resets index entries for those paths to the given tree (like unstage from a tree).
| 1 | # Make index for file match HEAD (unstage + reset index blob) |
| 2 | git reset HEAD -- src/app.ts |
| 3 | |
| 4 | # Make index match another commit for a path |
| 5 | git reset abc123 -- package-lock.json |
| You want to... | Use |
|---|---|
| Undo last local commit; keep changes staged | git reset --soft HEAD~1 |
| Undo last local commit; keep changes unstaged | git reset HEAD~1 |
| Throw away all local commits and dirty files | git reset --hard origin/main |
| Discard edits in one file | git restore file |
| Unstage one file | git restore --staged file |
| Undo a published commit | git revert HASH |
warning
Redo a messy commit
| 1 | git reset --soft HEAD~1 |
| 2 | git restore --staged . |
| 3 | git add src/feature/ |
| 4 | git commit -m "feat: core feature" |
| 5 | git add tests/ |
| 6 | git commit -m "test: cover feature" |
Abandon a failed experiment
| 1 | git status -sb |
| 2 | git reset --hard HEAD |
| 3 | git clean -fdn # preview |
| 4 | git clean -fd # remove untracked; careful! |
danger
Mixed reset is the default and the most common undo for an accidental local commit: HEAD moves back, index matches the new HEAD, worktree keeps your edits as unstaged changes.
| 1 | git commit -m "oops wrong files" |
| 2 | git reset HEAD~1 |
| 3 | git status -sb |
| 4 | # Changes are unstaged — re-stage carefully |
| 5 | git add -p |
| 6 | git commit -m "feat: correct scope" |
Soft reset is ideal for combining several local commits into one without retyping diffs.
| 1 | # Combine last 4 local commits into one |
| 2 | git reset --soft HEAD~4 |
| 3 | git commit -m "feat(search): ship full-text search MVP" |
warning
- Check git status -sb and git stash list first
- Prefer git stash push -u if unsure
- Record the current SHA: git rev-parse HEAD
- Only then: git reset --hard target
- If wrong: reset hard to recorded SHA or use reflog
| 1 | SHA=$(git rev-parse HEAD) |
| 2 | git stash push -u -m "safety before hard reset" |
| 3 | git fetch origin |
| 4 | git reset --hard origin/main |
| 5 | # regret? |
| 6 | git reset --hard "$SHA" |
| 1 | # Take one file from main while on a feature branch |
| 2 | git restore --source=main --worktree --staged -- src/config.ts |
| 3 | |
| 4 | # Restore an entire directory from a tag |
| 5 | git restore --source=v1.2.0 --worktree -- docs/ |
| 6 | |
| 7 | # Compare before restoring |
| 8 | git diff main -- src/config.ts |
- Default to restore for file undo; reset for commit undo
- Never propose reset --hard on a dirty tree without confirmation
- Never reset --hard shared branches; prefer revert
- After reset --hard, mention reflog recovery window
These end-to-end examples reinforce the reset and restore concepts above. Run them in a disposable lab under /tmp so mistakes are cheap.
Example A — happy path
| 1 | # Lab for reset and restore |
| 2 | rm -rf /tmp/git-lab-reset-and-restore && mkdir -p /tmp/git-lab-reset-and-restore |
| 3 | cd /tmp/git-lab-reset-and-restore |
| 4 | git init -b main |
| 5 | git config user.email "lab@example.com" |
| 6 | git config user.name "Lab User" |
| 7 | echo "# lab" > README.md |
| 8 | git add README.md |
| 9 | git commit -m "chore: init lab for reset and restore" |
| 10 | git status -sb |
| 11 | git log --oneline --decorate |
Example B — recover from a mistake
| 1 | # Make a bad commit, then recover safely |
| 2 | echo bad > oops.txt |
| 3 | git add oops.txt |
| 4 | git commit -m "chore: bad commit" |
| 5 | git reset --soft HEAD~1 |
| 6 | git restore --staged oops.txt |
| 7 | rm oops.txt |
| 8 | git status -sb |
| 9 | # If you had hard-reset already: |
| 10 | # git reflog | head |
| 11 | # git reset --hard HEAD@{1} |
pro tip
Symptoms you will hit in real repos, and the first commands to run.
Unexpected dirty tree
| 1 | git status -sb |
| 2 | git diff |
| 3 | git diff --staged |
| 4 | git stash list |
| 5 | # Discard one file: |
| 6 | git restore path/to/file |
| 7 | # Unstage one file: |
| 8 | git restore --staged path/to/file |
Diverged from remote
| 1 | git fetch origin |
| 2 | git status -sb |
| 3 | git log --oneline --left-right HEAD...origin/main |
| 4 | # Prefer rebase for feature branches: |
| 5 | git rebase origin/main |
| 6 | # Prefer merge if shared long-lived branch: |
| 7 | # git merge origin/main |
Conflict markers left behind
| 1 | git diff --check |
| 2 | rg -n '^(<<<<<<<|=======|>>>>>>>)' || true |
| 3 | # Fix files, then: |
| 4 | git add -A |
| 5 | git status |
danger
Keep this short list nearby while practicing reset and restore.
| 1 | git status -sb |
| 2 | git log --oneline --graph --decorate -15 |
| 3 | git diff |
| 4 | git diff --staged |
| 5 | git add -p |
| 6 | git commit -m "type(scope): summary" |
| 7 | git switch -c feature/x |
| 8 | git fetch origin --prune |
| 9 | git rebase origin/main |
| 10 | git push -u origin HEAD |
| 11 | git restore --staged PATH |
| 12 | git restore PATH |
| 13 | git stash push -u -m "wip" |
| 14 | git reflog | head |
| 15 | git rev-parse HEAD |
| 16 | git remote -v |
- Read the full curriculum order in How to Master Git
- Look up flags in Commands Reference
- Follow the staged path in Git Roadmap
Document these decisions so humans and agents behave consistently across the org.
- Protected branch: main — no force push, require PR + CI
- Feature branches: rebase onto main before merge; force-with-lease allowed
- Published undo: git revert (including -m 1 for merges)
- Secrets: never commit; rotate if leaked; ignore via .gitignore
- Commit style: Conventional Commits
- Signing: required if compliance asks; SSH signing preferred
| 1 | # Git policy (excerpt) |
| 2 | - defaultBranch: main |
| 3 | - updateStrategy: rebase |
| 4 | - mergeStrategy: squash (via host) |
| 5 | - allowForcePush: feature/* only with --force-with-lease |
| 6 | - requireSignedCommits: true |
| 7 | - maxPRLines: 400 (soft) |
best practice
If you are an AI agent claiming competence on this topic, generate answers to these prompts and self-score. Fail closed on any critical miss.
- Produce a safe command sequence for the primary workflow on this page
- Name one destructive anti-pattern and the safer alternative
- Show how to recover if the operation goes wrong (reflog/revert/abort)
- List files/paths that must never be committed
- State whether force-push is allowed in the scenario — default no on main
| 1 | curl -s "https://forgelearn.dev/api/markdown?path=git/mastery" | head |
| 2 | curl -s "https://forgelearn.dev/api/agent?skill=forgelearn-git" | head |
| 3 | curl -s https://forgelearn.dev/skills/forgelearn-git/SKILL.md | head |
danger
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.