Rewriting History
Git lets you rewrite the past — squash commits, undo mistakes, move changes between branches. This page covers doing it without losing work or breaking teammates.
Interactive Rebase — Clean Up Before Merging
Before opening a Merge Request, your branch might have messy commits like "fix typo", "actually fix typo", "oops wrong file". Interactive rebase lets you tidy these into one clean commit.
# Rewrite the last 4 commits interactively
git rebase -i HEAD~4
This opens an editor with a list of your commits and these options:
pick— keep this commit as-isreword— keep the commit but change its messagesquash— combine this commit into the one above itfixup— same as squash but discard this commit's messagedrop— delete this commit entirely
Change the words at the start of each line, save, and close the editor. Git will replay the commits in the new order.
After rebasing, push with:
git push --force-with-lease origin feature/your-branch
Moving a branch to a different base — if you branched off the wrong place, or need to replay just your commits onto a new parent, use --onto:
# Replay commits after 'old-base' onto 'new-base'
git rebase --onto new-base old-base feature/your-branch
When a rebase hits a conflict, Git pauses on the commit that failed to replay. You have three ways out:
# fix the conflict in your editor, stage it, then continue the rebase
git add .
git rebase --continue
# or: drop this commit entirely and move on to the next one
git rebase --skip
# or: bail out completely — back to exactly where you started
git rebase --abort
Undoing Things
Here is how to undo common mistakes. The right command depends on whether you have already pushed your changes:
| Situation | Command |
|---|---|
| Unstage a file (keep changes) | git restore --staged README.md |
| Discard unsaved changes to a file | git restore app.js |
| Undo last commit, keep changes staged | git reset --soft HEAD~1 |
| Undo last commit, keep changes unstaged (default) | git reset HEAD~1 |
| Undo last commit, discard changes entirely | git reset --hard HEAD~1 |
| Undo a commit that was already pushed | git revert <hash> |
| Remove untracked files/folders (build output, stray files) | git clean -fdn (dry run) → git clean -fd |
git reset --hard and git clean -fd permanently delete uncommitted changes with no undo. Only use them when you are certain you want to throw away your work. For commits already pushed to a shared branch, always use git revert instead — it creates a new commit that undoes the change, rather than rewriting history.git reflog to see every position HEAD has been at, find the commit hash from before the reset, and recover it with git checkout -b recovered <hash>. Entries expire after ~90 days.Changing the author of your last commit — wrong name or email (common when switching between a work and personal machine)? Amend it before pushing:
git commit --amend --author="Correct Name <correct@email.com>" --no-edit
For a commit further back in history, target it during an interactive rebase: mark it edit instead of pick, then when the rebase pauses there, run the same --amend --author= command followed by git rebase --continue. (Changing only the message of your last commit? See Wrong commit message.)
See the side-by-side comparison →
Safe Force Push
After an interactive rebase, your local branch history no longer matches what is on the remote. A normal git push will be rejected. You need a force push.
Always use --force-with-lease instead of --force:
git push --force-with-lease origin feature/your-branch
--force-with-lease checks that nobody else has pushed to the branch since you last fetched. If someone did, Git will stop and warn you instead of overwriting their work. --force does not do this check.
main, develop, or any shared branch.See the side-by-side comparison →
Cherry-pick — Copy One Commit to Another Branch
Cherry-pick copies a single commit from one branch and applies it to another. Useful when you made a fix on the wrong branch, or need a specific change without merging everything else.
# Find the commit hash you want
git log --oneline
# Switch to the branch you want to copy it to
git switch main
# Apply that one commit
git cherry-pick a1b2c3d
Clean History — Remove a Secret or Large File
If you accidentally committed a password or API key and pushed it, it lives in every clone of the repo. git filter-repo rewrites the entire history to remove it permanently.
# Install once: pip install git-filter-repo
# Remove a file from all history
git filter-repo --path secrets.json --invert-paths
# Force-push all branches
git push origin --force --all
git rerere — Reuse Recorded Conflict Resolutions
If you rebase the same long-lived branch repeatedly, you often resolve the exact same conflict over and over. rerere ("reuse recorded resolution") remembers how you resolved a conflict and replays it automatically the next time the identical conflict appears.
# Enable once, globally
git config --global rerere.enabled true
Resolve a conflict normally the first time — Git records it silently. Next time the same conflict shows up (e.g. re-rebasing after more commits), Git auto-applies the remembered fix; you still need to review and git add it before continuing.
git rerere diff # preview what rerere would apply
git rerere forget <path> # discard a bad recorded resolution