How to squash commits which have merge-commit in between?

69,000

Solution 1

You can rebase -i starting with commit 2's parent (that is, the commit on master that you branched from. You'll likely have to re-resolve conflicts when you get to the merge commit.

So if your history looks like

  * D commit 3 (HEAD)
  * M merge
 /|
| * C commit 2
* | B commit on master
|/
* A (master)

Start with git rebase -i A. You'll see a list of commits including both master and your_branch, but not the merge commit. pick the first one (B or C, depending on timing) and squash the rest.

Solution 2

Assuming the feature branch is called feature and the main branch main:

Create a temporary branch from main:

git checkout -b temp main

Squash the feature branch in:

git merge --squash feature

Commit the changes (the commit message contains all squashed commit messages):

git commit

Go back to the feature branch and point it to the temp branch:

git checkout feature
git reset --hard temp

Delete the temporary branch:

git branch -d temp

Solution 3

You can use the tool I've created specifically for this task:

https://github.com/sheerun/git-squash

It's only necessary to merge master branch, and then run squashing command:

git merge master
git squash master

Solution 4

The only way I have found to not have to re-resolve conflicts is this:

Given branch main and branch work, perform the following steps:

git checkout -b work-squashed `git merge-base main work`

This creates a new branch from the last main commit you merged into the work branch.

git diff work-squashed...work | patch -p1

This grabs and applies to the working directory all the changes between the last commit on main that was merged into work and the tip of the work branch. In other words, all the work, including the resolved conflicts.

At this point you need to take care of files added/removed on the work branch, because patch is not git. It doesn't know what files are being tracked by git. So, you need to git add/git rm until all the files are accounted for. Then, you simply commit the changes as a single commit.

Solution 5

In my case, I started working with a branch that had several commits, then a merge with the main/source branch, then more commits and I wanted to squash all commits, but kept running into an error because of the merge commit:

error: commit is a merge but no -m option was given.

->C1->C2->M(merge with source branch)->C3->C4

There's probably a better way (and I look forward to learning), but what I ended up doing after much reading and trial and error was creating a copy branch for reference, then reverting the current branch to C1,

reset --hard (C1 hash)

then cherry-picking C2, C3, C4, then squashing, then rebasing ... resulting in:

M->C

(just one commit that has been rebased with source!)

I hope this helps someone else with the same problem.

Share:
69,000
Tyr1on
Author by

Tyr1on

Updated on November 04, 2021

Comments

  • Tyr1on
    Tyr1on over 2 years

    I am working on a feature branch.

    1. Made several commits. Squashed commits.
    2. Pushed changes to remote branch. Got conflicts.
    3. Merged changes from master, resolved conflicts on feature branch.
      • git fetch origin master
      • git merge FETCH_HEAD
      • Resolved conflicts manually.
      • git commit
      • git push
    4. I made one more commit.

    So, current commit history looks like this. From current to old:

    1. commit 3
    2. commit M yyy (Merged)
    3. commit 2

    How do I squash above 3 commits into 1 before I merge my feature branch to master?

  • Tyr1on
    Tyr1on about 9 years
    It worked. But Can you point me to the "theory" why it worked?
  • Kristján
    Kristján about 9 years
    Absolutely, check out this article and comment again if you've got some specific questions.
  • Kristján
    Kristján about 9 years
    Looking again (and playing with a toy repo), it looks like git rebase -i master should also have worked just fine, and it wouldn't have included the master commit in your squash. Is that what you were trying before? What was going wrong?
  • Kristján
    Kristján about 9 years
    One more thing - mixing merges and rebases is generally a good way to get confused in your Git tree. I'd recommend sticking to just one or the other, and since you want to squash, that means avoiding merge in favor of rebase. In this case, you would have git rebase masterd instead of git merge master, which would hoist each branch commit up as if you'd started from commit 2. Then when you finally squash, there's no merge to tangle you up. However, if you've pushed the branch to other people, rebase requires a force push and breaks their history, so tradeoffs.
  • Kristján
    Kristján about 9 years
    Happened upon yet another way that might interest you: git rebase --preserve-merges origin/master from stackoverflow.com/questions/4783599/rebasing-a-git-merge-com‌​mit
  • fgblomqvist
    fgblomqvist over 3 years
    This is a winner in my books. However, it would be neat if you could also include the commands/procedure you do here and explain what it does. It's really quite simple when you look at the code, but would be nice to have it right here :)
  • OwnageIsMagic
    OwnageIsMagic over 2 years
    you can use git apply instead of patch
  • PinguinoSod
    PinguinoSod over 2 years
    this is genius, thanks a lot!
  • pmishev
    pmishev almost 2 years
    ...and git push --force in the end