How to Reorder Commits in Git
To reorder commits, move their lines in an interactive rebase’s todo list. Git replays them in the new order, producing new commits with new IDs.
The mechanics take one minute. The part worth understanding is dependency: two commits can only be swapped if neither relies on the other, and Git will tell you — via a conflict — when they do.
Why reorder
Section titled “Why reorder”Land part of the work sooner. A branch containing a bug fix and a feature can be reordered so the fix comes first, then split into two branches — one of which merges immediately.
Make the branch readable. Work is rarely done in the order that explains it best. Introducing a helper after the code that uses it is confusing to read forwards.
Group related commits. So that squashing them later is a matter of adjacent lines.
Move a fix next to what it fixes, before folding it in. This is what
--autosquash automates.
Isolate a risky change at the end, where it is easy to drop or revert.
Doing it
Section titled “Doing it”git rebase -i mainpick de4df70 Add parserpick c413def Add validationpick f195a48 Fix parser crash on empty inputTo land the fix first, move its line to the top:
pick f195a48 Fix parser crash on empty inputpick de4df70 Add parserpick c413def Add validationSave and close. Git replays in the order written — top line first, which is the reverse of git log.
Successfully rebased and updated refs/heads/feature.Dependencies
Section titled “Dependencies”Commits are not independent units. Each is a change relative to the state before it. Swapping two commits means applying the second one’s change to a state it was never written against.
That works fine when they touch unrelated things. It breaks when they do not.
| Relationship | Reorder outcome |
|---|---|
| Different files | Clean |
| Same file, different regions | Usually clean |
| Same lines | Conflict |
| Second commit modifies code the first introduced | Conflict, or an empty commit |
| Second commit deletes a file the first created | Conflict — the file does not exist yet |
The last two are the ones that catch people. If commit B edits a function that commit A added, putting B first means applying an edit to something that is not there.
error: could not apply c413def... Add validationhint: Resolve all conflicts manually, mark them as resolved withhint: "git add/rm <conflicted_files>", then run "git rebase --continue".When that happens, the honest answer is usually that the reorder is not valid. Abort and reconsider:
git rebase --abortEvery commit ID changes
Section titled “Every commit ID changes”Reordering rewrites commits. Even a commit whose position did not change gets a new ID if anything before it moved, because its parent changed.
git log --format='%h %s' -3Before:
f195a48 Fix parser crash on empty inputc413def Add validationde4df70 Add parserAfter moving the fix to the front, all three IDs differ. The first one changed because its parent changed; the others because their parents changed.
The consequences are the usual ones for any rewrite:
- The branch has diverged from its remote copy; pushing requires
--force-with-lease. - Anyone who pulled the branch now has orphaned commits.
- Review comments anchored to specific commits lose their anchors.
- Branches based on these commits need re-parenting.
Verifying the result
Section titled “Verifying the result”Reordering must not change the code. Two checks confirm it did not.
git diff backup-feature HEADWhat it doesCompares the final tree of your reordered branch against the backup you took before starting.
Why we run itReordering rearranges how you arrived at a state; it must not change the state itself. Empty output proves that.
Expected resultNo output at all. Any output means the reorder altered the result — usually because a conflict was resolved incorrectly.
The second check is that each commit still builds, which reordering can break even when the final state is correct:
git rebase --exec "make test" mainGit replays every commit, running the command after each, and stops at the first failure. If a commit now sits before something it depends on, this finds it.
Recovering
Section titled “Recovering”Take a backup before you start:
git branch backup-featuregit rebase -i mainThen recovery is one command:
git reset --hard backup-featureWithout a backup, the reflog has it:
git reflogeee8f01 HEAD@{0}: rebase (finish): returning to refs/heads/feature68cbf80 HEAD@{2}: rebase (start): checkout maina1d07c2 HEAD@{3}: commit: Fix parser crash on empty inputThe entry immediately before rebase (start) is the pre-rebase tip:
git reset --hard HEAD@{3}And while the rebase is still running, git rebase --abort restores everything without needing either.
Splitting a branch after reordering
Section titled “Splitting a branch after reordering”Reordering is often a means to an end: getting the independently-landable work to the front so it can be split off into its own branch.
Once the fix is the first commit on the branch:
f195a48 Fix parser crash on empty input ← now firstde4df70 Add parserc413def Add validationCreate a branch containing only it:
git switch -c fix/parser-crash maingit cherry-pick f195a48Or, equivalently, branch at that commit:
git branch fix/parser-crash <id-of-the-reordered-fix>That branch can go for review and merge immediately. Afterwards, rebase the original branch onto the
updated main — the fix commit will be recognised as already applied and dropped, or will replay as empty
and can be skipped:
git switch featuregit rebase mainThe previous cherry-pick is now empty, possibly due to conflict resolution.git rebase --skipThis is the one legitimate use of --skip: the commit’s change is genuinely already present upstream, so
skipping loses nothing.
Reordering without reordering
Section titled “Reordering without reordering”Sometimes the goal is achievable more simply than by rearranging a branch.
Just cherry-pick it. If you only want one commit somewhere else, git cherry-pick copies it to another
branch without touching the original. No rewrite, no force push.
git switch maingit cherry-pick f195a48Just squash them. If the commits are going to be combined anyway, their relative order stops mattering.
Just reword. If the problem is that the messages make the order look wrong, reword fixes the
description without moving anything.
Do nothing. If the branch will be squash-merged, main receives one commit regardless of internal
order. Curating a history that is about to be discarded is wasted effort.
That last point is worth stating plainly: check how your team integrates before spending time reshaping a branch. Careful curation pays off with merge or rebase integration, and is thrown away by squash merging.
When not to reorder
Section titled “When not to reorder”The branch is shared. Any rewrite orphans other people’s copies.
Review is under way. Comments lose their anchors.
The commits are genuinely dependent. If the reorder conflicts, the order you have is probably the order the work requires.
The history is already on main. Reordering commits on a shared mainline is a substantially larger
operation and almost never justified.
You only want to squash them. If the commits are being combined anyway, their order stops mattering. Squashing Commits is the simpler operation.
Common mistakes
Section titled “Common mistakes”Moving a line down to make a commit earlier. The list is oldest-first; up is earlier.
Reordering commits that depend on each other, then resolving the resulting conflicts by guessing. Abort instead.
Forgetting to verify. git diff backup HEAD should be empty. Run it.
Using --skip when a reordered commit conflicts. That deletes the commit. Abort and rethink the order.
Reordering after pushing without warning anyone. Even with --force-with-lease, colleagues who have
the branch need to know.
Reordering to fix a commit message. reword does that without moving anything.
Mental Model
Section titled “Mental Model”A commit is a change relative to what came before it, not an independent object.
Reordering asks Git to apply each change against a different starting point than it was written for. Where the changes are unrelated that works perfectly. Where the second depends on the first, you are asking Git to apply an edit to something that does not exist yet — and it will say so.
What You Learned
Section titled “What You Learned”- Reordering is moving lines in an interactive rebase todo list; the list is oldest-first.
- Every commit from the earliest moved position onwards gets a new ID.
- Commits reorder cleanly only when they do not depend on each other.
- A conflict during reordering usually means the new order is invalid — abort rather than resolve.
git diff backup HEADmust be empty; reordering changes history, not code.git rebase --execverifies that each commit still builds in its new position.- A backup branch, the reflog and
--abortare three independent ways back.
Try It Yourself
Section titled “Try It Yourself”- Create a repository. Make three commits touching different files:
a.txt,b.txt,c.txt. - Back it up:
git branch backup. - Run
git rebase -i HEAD~3and reverse the three lines. Predict whether it conflicts. - Check
git log --oneline— the order should be reversed and every ID different. - Run
git diff backup HEAD. Predict the output before pressing Enter. - Reset:
git reset --hard backup. - Now create a dependent pair: one commit adds a function to
d.py, the next edits that function. - Try to swap them. Predict the result, then observe it.
- Abort with
git rebase --abortand confirm you are back where you started.
Step 5 should print nothing. Step 8 should conflict — and the conflict is the correct answer, telling you the order is not arbitrary.
Next Lesson
Section titled “Next Lesson”Squashing combines commits rather than rearranging them, and is the operation most people reach for when cleaning up a branch.