83
votes

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?

6

6 Answers

46
votes

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.

12
votes

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
4
votes

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.

3
votes

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.

0
votes

I came across this issue when I committed a code change using Github UI, and then committed a change locally(without updating the local clone), and then tried to push the local commit to origin. A merge-commit was created as a result.

master
       \ 
         remote-branch --- commit #1 ---------------MergeCommit
                                                    ^
             |                                      |    
           local-branch -------------- commit #2 --- 

Checkout a new branch from the same origin branch, cherry-pick all commits except merge-commit, and then push the branch

master
       \ 
         new-branch --------------------------------------- Squash-commits
                                   ^                     ^
             |                     |   < Cherry-pick >   |                      
         previous-branch -----commit #1 --------- commit #2 

Note that the origin-branch from which we are forking is the same i.e. master.

The process of cherry-picking is straightforward for IntelliJ users. The new-branch will not have the merge-commit as a result.

0
votes

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