5498

How do I squash my last N commits together into one commit?

6

46 Answers 46

1
2
10

If you're working with GitLab, you can just click the Squash option in the Merge Request as shown below. The commit message will be the title of the Merge Request.

enter image description here

1
  • With GitLab Enterprise Edition 12.8.6-ee it just randomly took a commit message for the squashed commit...
    – Wolfson
    Commented Jun 9, 2020 at 12:23
10

We were really happy with this answer, it means you don't need to count through commits.

If you have loads of commits on master branch and want to combine them all into one:

  • Ensure you're on the master branch and do git status to ensure it's all clean
  • Copy the commit SHA for the commit BEFORE you started your work
  • git reset ‒soft commitIdSHA

[ You'll notice that all your changes at this point will be shown in the source control section of your IDE - good to review them to check it's all there]

  • git commit -am "the message you want that will replace all the other commit messages"
  • git push (will complain as you are behind on the commits) so do git push --force

That's it!

9

What about an answer for the question related to a workflow like this?

  1. many local commits, mixed with multiple merges FROM master,
  2. finally a push to remote,
  3. PR and merge TO master by reviewer. (Yes, it would be easier for the developer to merge --squash after the PR, but the team thought that would slow down the process.)

I haven't seen a workflow like that on this page. (That may be my eyes.) If I understand rebase correctly, multiple merges would require multiple conflict resolutions. I do NOT want even to think about that!

So, this seems to work for us.

  1. git pull master
  2. git checkout -b new-branch
  3. git checkout -b new-branch-temp
  4. edit and commit a lot locally, merge master regularly
  5. git checkout new-branch
  6. git merge --squash new-branch-temp // puts all changes in stage
  7. git commit 'one message to rule them all'
  8. git push
  9. Reviewer does PR and merges to master.
1
  • From many opinions I like your approach. It's very convenient and fast Commented Sep 5, 2018 at 9:57
8
git rebase -i HEAD^^

where the number of ^'s is X

(in this case, squash the two last commits)

1
  • Better yet, you can replace the duplicate ^s with ~X (where X is the number of previous commits, as before), like so: git rebase -i HEAD~2 for the previous two commits. Commented Aug 24, 2022 at 6:54
8

I was inspired by Chris Johnsen's answer and find a better solution.

If we wanna squash the last 3 commits, and write the new commit message from scratch, this suffices:

git reset --soft HEAD~3 && git commit

If we wanna squash the last many commits, lets say 162 commits, it's very hard to count the rank of the 162th commit, but we can use the commit ID, this suffices:

git reset --soft ceb5ab28ca && git commit

ceb5ab28ca is the commit ID of the 162th commit.

7

In addition to other excellent answers, I'd like to add how git rebase -i always confuses me with the commit order - older to newer one or vice versa? So this is my workflow:

  1. git rebase -i HEAD~[N] , where N is the number of commits I want to join, starting from the most recent one. So git rebase -i HEAD~5 would mean "squash the last 5 commits into a new one";
  2. the editor pops up, showing the list of commits I want to merge. Now they are displayed in reverse order: the older commit is on top. Mark as "squash" or "s" all the commits in there except the first/older one: it will be used as a starting point. Save and close the editor;
  3. the editor pops up again with a default message for the new commit: change it to your needs, save and close. Squash completed!

Sources & additional reads: #1, #2.

7

I find a more generic solution is not to specify 'N' commits, but rather the branch/commit-id you want to squash on top of. This is less error-prone than counting the commits up to a specific commit—just specify the tag directly, or if you really want to count you can specify HEAD~N.

In my workflow, I start a branch, and my first commit on that branch summarizes the goal (i.e. it's usually what I will push as the 'final' message for the feature to the public repository.) So when I'm done, all I want to do is git squash master back to the first message and then I'm ready to push.

I use the alias:

squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i

This will dump the history being squashed before it does so—this gives you a chance to recover by grabbing an old commit ID off the console if you want to revert. (Solaris users note it uses the GNU sed -i option, Mac and Linux users should be fine with this.)

7
  • I tried the alias but I'm not sure if the sed replaces are having any effect. What should they do?
    – raine
    Commented Mar 21, 2015 at 18:04
  • The first sed just dumps the history to the console. The second sed replaces all the 'pick' with 'f' (fixup) and rewrites the editor file in-place (the -i option). So the second one does all the work.
    – Ethan
    Commented Apr 22, 2015 at 20:28
  • You are right, counting N-number of specific commits is very error prone. It has screwed me up several times and wasted hours trying to undo the rebase. Commented Oct 2, 2015 at 0:37
  • Hi Ethan, I would like to know if this workflow will hide possible conflicts on the merge. So please consider if you have two branches master and slave. If the slave has a conflict with the master and we use git squash master when we are checked out on the slave. what will it happen? will we hide the conflict?
    – Coder
    Commented Feb 24, 2016 at 7:55
  • @Sergio This is a case of rewriting history, so you probably will have conflicts if you squash commits that have already been pushed, and then try to merge/rebase the squashed version back. (Some trivial cases might get away with it.)
    – Ethan
    Commented Jun 28, 2016 at 1:51
6

In question it could be ambiguous what is meant by "last".

for example git log --graph outputs the following (simplified):

* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| | 
* | commit H1
| |
* | commit H2
|/
|

Then last commits by time are H0, merge, B0. To squash them you will have to rebase your merged branch on commit H1.

The problem is that H0 contains H1 and H2 (and generally more commits before merge and after branching) while B0 don't. So you have to manage changes from H0, merge, H1, H2, B0 at least.

It's possible to use rebase but in different manner then in others mentioned answers:

rebase -i HEAD~2

This will show you choice options (as mentioned in other answers):

pick B1
pick B0
pick H0

Put squash instead of pick to H0:

pick B1
pick B0
s H0

After save and exit rebase will apply commits in turn after H1. That means that it will ask you to resolve conflicts again (where HEAD will be H1 at first and then accumulating commits as they are applied).

After rebase will finish you can choose message for squashed H0 and B0:

* commit squashed H0 and B0
|
* commit B1
| 
* commit H1
|
* commit H2
|

P.S. If you just do some reset to BO: (for example, using reset --mixed that is explained in more detail here https://stackoverflow.com/a/18690845/2405850):

git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'

then you squash into B0 changes of H0, H1, H2 (losing completely commits for changes after branching and before merge.

3

First I find out the number of commits between my feature branch and current master branch by

git checkout master
git rev-list master.. --count

Then, I create another branch based out my-feature branch, keep my-feature branch untouched.

Lastly, I run

git checkout my-feature
git checkout -b my-rebased-feature
git checkout master
git checkout my-rebased-feature
git rebase master
git rebase head^x -i
// fixup/pick/rewrite
git push origin my-rebased-feature -f // force, if my-rebased-feature was ever pushed, otherwise no need for -f flag
// make a PR with clean history, delete both my-feature and my-rebased-feature after merge

Hope it helps, thanks.

3

To avoid having to resolve any merge conflicts when rebasing onto a commit in the same branch, you can use the following command

git rebase -i <last commit id before your changes start> -s recursive -X ours

To squash all commits into one, when you are prompted to edit commits to be merged (-i flag), update all but first action from pick to squash as recommended in other answers too.

Here, we use the merge strategy (-s flag) recursive and strategy option (-X) ours to make sure that the later commits in the history win any merge conflicts.

NOTE: Do not confuse this with git rebase -s ours which does something else.

Reference: git rebase recursive merge strategy

3
  • Couldn't this be avoided by merging the parent branch before rebasing?
    – Jabari
    Commented Aug 3, 2021 at 7:19
  • 2
    @Jabari - My answer was for situations when rebasing against a previous commit in the same branch. For example: If you are working on a change and made 10-15 commits locally, but want to clean-up the commit history before pushing to a remote branch. You can use this command to squash all your commits into one commit and push that to the remote.
    – dvvrt
    Commented Aug 6, 2021 at 13:27
  • Gotcha! I should have read the first line better!
    – Jabari
    Commented Aug 6, 2021 at 18:45
3

Tried all approaches mention here. But finally my issue resolved by following this link. https://gist.github.com/longtimeago/f7055aa4c3bba8a62197

$ git fetch upstream
$ git checkout omgpull 
$ git rebase -i upstream/master

 < choose squash for all of your commits, except the first one >
 < Edit the commit message to make sense, and describe all your changes >

$ git push origin omgpull -f
3

Just add this bash function to your bash of .zshrc file.

# Squash last X commits with a Commit message.
# Usage: squash X 'COMMIT_MSG'
# where X= Number of last commits.
# where COMMIT_MSG= New commit msg.
function squash() {
    if [ -z "${1}" -o -z "${2}" ]; then
        echo "Usage: \`squash X COMMIT_MSG\`"
        echo "X= Number of last commits."
        echo "COMMIT_MSG= New commit msg."
        return 1
    fi

    git reset --soft HEAD~"$1"
    git add . && git ci -m "$2" # With 100 emoji
    git push --force
}

Then just run

squash X 'New Commit Message'

And you're done.

0
3

Let's say n is really large. You do not want to deal with each commit. You just want a new branch called new_feature_branch that has 1 commit and all your changes of old_feature_branch. You can do the following

git checkout main 

# will contain changes all changes from old_feature_branch 
git checkout -b new_feature_branch 

# get all changes from old_feature_branch and stage them
for f in $(git --no-pager diff --name-only old_feature_branch ) ; do git checkout old_feature_branch -- $f ; done

git commit -m "one commit message"
2

If you're using GitUp, select the commit you want to merge with its parent and press S. You have to do it once for each commit, but it's much more straightforward than coming up with the correct command line incantation. Especially if it's something you only do once in a while.

0

If you are on windows, you might want to use this powershell code.

function SquashCommits([int]$count) {
$commitHashes = git log --pretty=format:%h -n $count    

$commands= ( 0..$($count-2) ) |  %{   "sed -i 's/^pick $($commitHashes[$_])/squash $($commitHashes[$_])/' `$file"    }
$st= $commands -join "`n"

$st="func() { 
local file=`$1
$st 
}; func"
$env:GIT_SEQUENCE_EDITOR=$st
try{
        git rebase -i HEAD~$count
    }finally
    {
        Remove-Item Env:\GIT_SEQUENCE_EDITOR
    }
}

Usage Example:

SquashCommits 3
1
  • The advantage here is that it can be easily manipulated to do other things such as drop certain commit. Commented Jul 16, 2023 at 23:46
0

Variation of existing answers for the specific case when the remote is configured to disallow --force; assuming the commits are in a branch that you have permissions to delete remotely:

# 'rewind' to last commit before your commit (use `git log` to identify)
git reset <hash-BEFORE-first-commit-in-feature>

# re-add files (new and modified)
git add <modified-and-new-files> 

# make new single commit with all changes
git commit -m <new-single-commit-message> 

# delete remote branch if you already pushed a PR
git push origin --delete <feature-branch-name>

git push -u origin <feature-branch-name>
1
2

Not the answer you're looking for? Browse other questions tagged or ask your own question.