Skip to content

How to Reorder Commits in Git

Lesson 3 of 7Intermediate7 min readModern Git Workflows · RebasingVerified: Git 2.43.0 on Ubuntu 24.04

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.

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.

Terminal window
git rebase -i main
pick de4df70 Add parser
pick c413def Add validation
pick f195a48 Fix parser crash on empty input

To land the fix first, move its line to the top:

pick f195a48 Fix parser crash on empty input
pick de4df70 Add parser
pick c413def Add validation

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

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.

RelationshipReorder outcome
Different filesClean
Same file, different regionsUsually clean
Same linesConflict
Second commit modifies code the first introducedConflict, or an empty commit
Second commit deletes a file the first createdConflict — 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 validation
hint: Resolve all conflicts manually, mark them as resolved with
hint: "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:

Terminal window
git rebase --abort

Reordering rewrites commits. Even a commit whose position did not change gets a new ID if anything before it moved, because its parent changed.

Terminal window
git log --format='%h %s' -3

Before:

f195a48 Fix parser crash on empty input
c413def Add validation
de4df70 Add parser

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

Reordering must not change the code. Two checks confirm it did not.

Terminal window
git diff backup-feature HEAD

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

Terminal window
git rebase --exec "make test" main

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

Take a backup before you start:

Terminal window
git branch backup-feature
git rebase -i main

Then recovery is one command:

Terminal window
git reset --hard backup-feature

Without a backup, the reflog has it:

Terminal window
git reflog
eee8f01 HEAD@{0}: rebase (finish): returning to refs/heads/feature
68cbf80 HEAD@{2}: rebase (start): checkout main
a1d07c2 HEAD@{3}: commit: Fix parser crash on empty input

The entry immediately before rebase (start) is the pre-rebase tip:

Terminal window
git reset --hard HEAD@{3}

And while the rebase is still running, git rebase --abort restores everything without needing either.

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 first
de4df70 Add parser
c413def Add validation

Create a branch containing only it:

Terminal window
git switch -c fix/parser-crash main
git cherry-pick f195a48

Or, equivalently, branch at that commit:

Terminal window
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:

Terminal window
git switch feature
git rebase main
The previous cherry-pick is now empty, possibly due to conflict resolution.
Terminal window
git rebase --skip

This is the one legitimate use of --skip: the commit’s change is genuinely already present upstream, so skipping loses nothing.

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.

Terminal window
git switch main
git cherry-pick f195a48

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

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.

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.

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.

  • 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 HEAD must be empty; reordering changes history, not code.
  • git rebase --exec verifies that each commit still builds in its new position.
  • A backup branch, the reflog and --abort are three independent ways back.
  1. Create a repository. Make three commits touching different files: a.txt, b.txt, c.txt.
  2. Back it up: git branch backup.
  3. Run git rebase -i HEAD~3 and reverse the three lines. Predict whether it conflicts.
  4. Check git log --oneline — the order should be reversed and every ID different.
  5. Run git diff backup HEAD. Predict the output before pressing Enter.
  6. Reset: git reset --hard backup.
  7. Now create a dependent pair: one commit adds a function to d.py, the next edits that function.
  8. Try to swap them. Predict the result, then observe it.
  9. Abort with git rebase --abort and 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.

Squashing combines commits rather than rearranging them, and is the operation most people reach for when cleaning up a branch.