How to unsquash an old commit?

435 Views Asked by At

I squashed a branch with a very large amount of commits (+1000) to my main branch accidentally. After that, some commits were made to some of those commits files. Now, I'd like to "unsquash" those +1000 commits.

Any ideas about how can I unsquash those commits without losing the commits made over them?

I tried reset --hard my main branch to before the squash and merge again the branch with the +1000 commits, but it didn't work because I lost the commits made after the squash. Also, if I merge main again, the squash commit would still exists and will "cover" the other commits in Git Blame

2

There are 2 best solutions below

7
Schwern On

I squashed a branch with a very large amount of commits (+1000) to my main branch accidentally.

Before the squash, your repo looked like this:

A - B - C - D - E - F [main]
     \
      1 - 2 ... 1000 [branch]

After the squash, it looked like this...

A - B - C - D - E - F - SQUASH [main]
     \
      1 - 2 ... 1000 [branch]

...assuming you did not delete the branch.

After that, some commits were made to some of those commits files.

Assuming you mean that you added commits to main, your repo now looks like this:

A - B - C - D - E - F - SQUASH - G - H [main]
     \
      1 - 2 ... 1000 [branch]

Any ideas about how can I unsquash those commits without losing the commits made over them?

Yes, do an interactive rebase and remove the squash commit.

Pick a commit before the squash, and then start an interactive rebase: git rebase -i <commit>. An editor will appear with all the commits after your chosen one. Simply delete the line with the squash, save, and exit the editor. Git will rewrite the commits after the squash (G - H) on top of the previous commit (F). See Rewriting History for more detail on interactive commit.

A - B - C - D - E - F - G1 - H1 [main]
     \
      1 - 2 ... 1000 [branch]

Then, if you already pushed main, git push --force-with-lease to safely push the new branch.


UPDATE Comments from the OP have made it clear the situation is much more complex.

Let's say I have 3 merge commits after the 5 squashes [in main], and each of these 3 merge commits comes from a branch with over 1k commits. And after these 3 merge commits, main has around 50 commits with recent work.

A branch with over 1000 commits indicates something is wrong, or very exceptional, with your process, and I encourage you to ask a question about that process.

Your repo looks something like this. I've only shown two squashes and two merges for simplicity.

 1 - 2 - 3 ...... 1000
/                     \
A - B - S1 - S2 - M1 - M2 - 1 ... 50 [main]
     \           /
      Z ....... Y

You want this.

 1 - 2 - 3 ...... 1000
/                     \
A - B ---------- M1 - M2 - 1 ... 50 [main]
     \           /
      Z ....... Y

You can do this with a rebase, being careful to preserve the merges.

git rebase -r --onto B S2 main

Where "S2" is the last squash merge you wish to delete. -r tells Git to preserve merge commits. This should work, but be prepared to undo it with git reset --hard ORIG_HEAD. See Rebasing Merges for more.

0
LeGEC On

Copying back the diagram from @Schwern's answer :

A - B - C - D - E - F - SQUASH - G - H [main]
     \
      1 - 2 ... 1000 [branch]

If you run :

git rebase --onto 1000 SQUASH H

you should reach :

A - B - C - D - E - F - SQUASH - G - H [main]
     \
      1 - 2 ... 1000 - G1 - H1 < HEAD (detached HEAD state)
                  ^
                [branch]

You can then choose from several ways to update [branch], one of them is:

git switch branch
git merge --ff-only H1