GitLearnHub
Practice Flow
15px

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.

bash
# Rewrite the last 4 commits interactively
git rebase -i HEAD~4

This opens an editor with a list of your commits and these options:

Change the words at the start of each line, save, and close the editor. Git will replay the commits in the new order.

⚠️
Only rebase commits that exist on your own branch and have not been pushed to a shared branch. Rebasing already-pushed commits rewrites history and causes problems for teammates.

After rebasing, push with:

bash
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:

bash
# 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:

bash
# 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

Try it in Practice →

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 filegit restore app.js
Undo last commit, keep changes stagedgit reset --soft HEAD~1
Undo last commit, keep changes unstaged (default)git reset HEAD~1
Undo last commit, discard changes entirelygit reset --hard HEAD~1
Undo a commit that was already pushedgit 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.
💡
Safety net: even a hard reset rarely deletes anything for real. Run 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:

bash
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.)

Try it in Practice →

Common mistake here →

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:

bash
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.

⚠️
Force push only on your own feature branches. Never force-push to main, develop, or any shared branch.

Try it in Practice →

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.

bash
# 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

Try it in Practice →

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.

⚠️
This rewrites every commit hash. All team members must delete their local clone and re-clone afterwards. Coordinate before running this.
bash
# 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
⚠️
Always rotate the secret after this. The file was public while it was in history — treat it as compromised regardless.

Common mistake here →

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.

bash
# 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.

bash
git rerere diff            # preview what rerere would apply
git rerere forget <path>   # discard a bad recorded resolution