147

I'm trying to squash a range of commits - HEAD to HEAD~3. Is there a quick way to do this, or do I need to use rebase --interactive?

1

8 Answers 8

182
+50

Make sure your working tree is clean, then

git reset --soft HEAD~3
git commit -m 'new commit message'
10
  • @wilhelmtell: Great! Now would I be able to construct a git alias, e.g. "mysquash 3 'some message'", to cut this down to one line?
    – Phillip
    Commented Sep 1, 2011 at 19:53
  • 7
    If its just the number of lines: git reset --soft HEAD~3 && git commit -m "my message"
    – KingCrunch
    Commented Sep 1, 2011 at 20:16
  • 9
    @Phillip: You can embed a shell function in the git alias. git config alias.mysquash '!f(){ git reset --soft HEAD~$1 && git commit ${2:+-m "$2"}; };f'. git mysquash 3 'some message' will work, but I also tweaked it so git musquash 3 will omit the -m flag entirely so you'll get the interactive git commit UI in that case. Commented Sep 1, 2011 at 20:16
  • 6
    Just to make it clear: It is not the same as a squash. A squash will also merge the commit messages. If you do a soft reset you will lose all messages of the commits. If you want to squash try stackoverflow.com/a/27697274/974186
    – René Link
    Commented Aug 3, 2015 at 13:21
  • 1
    @sebnukem - That's when we try to push the branch and the remote is configured to reject force pushes.
    – avmohan
    Commented Feb 17, 2016 at 10:47
38

I personally like wilhelmtell's solution:

git reset --soft HEAD~3
git commit -m 'new commit message'

However, I made an alias with some error checking so that you can do this:

g.squash 3 'my commit message'

I recommend setting up aliases that actually run scripts so that it is easier to (a) code up your scripts and (b) do more complex work with error checking. Below is a script that does the work of squashing. I put that in a scripts folder in my HOME path.

Script for squashing (squash.sh)

#!/bin/bash
#

#get number of commits to squash
squashCount=$1

#get the commit message
shift
commitMsg=$@

#regular expression to verify that squash number is an integer
regex='^[0-9]+$'

echo "---------------------------------"
echo "Will squash $squashCount commits"
echo "Commit message will be '$commitMsg'"

echo "...validating input"
if ! [[ $squashCount =~ $regex ]]
then
    echo "Squash count must be an integer."
elif [ -z "$commitMsg" ]
then
    echo "Invalid commit message.  Make sure string is not empty"
else
    echo "...input looks good"
    echo "...proceeding to squash"
    git reset --soft HEAD~$squashCount
    git commit -m "$commitMsg"
    echo "...done"
fi

echo
exit 0

Then to hook up that squash.sh script to an alias, I add the following to my .zprofile:

export PATH="$PATH:$HOME/scripts" # Add scripts folder to PATH
...
alias g.squash='function _gSquash(){ sh squash.sh $1 $2; };_gSquash'
...

Note: You can make your alias anything you want. I have my a lot of my git shortcuts as g.<myCommand>

10
  • 4
    You can also put it in $PATH named git-squash.sh and it will be automatically aliased as git squash. I didn't change your answer, just in case there's a reason to use the create-aiases.sh script that I'm not aware of.
    – user167661
    Commented Jun 12, 2014 at 13:19
  • I basically use the create_aliases.command script so that even our PMs and designers can easily get set up. Simply a double-click on the script and they are all set (especially since I have the setup script in our repo and the relative path is known). Then they don't even need to restart terminal.
    – n8tr
    Commented Jun 12, 2014 at 20:28
  • 1
    I tried this and it reads my commit message as squash count and fails because it's not an integer.
    – Minthos
    Commented Aug 12, 2014 at 9:46
  • 1
    The solution was to append - after /squash.sh \$1 \$2'
    – Minthos
    Commented Aug 12, 2014 at 9:52
  • 1
    I like the idea of the solution, but the comment above is not yet taken into account in the solution. There needs to be a minus sign between the single and the double quote. Commented Oct 27, 2015 at 15:48
29

To add to the answer by wilhelmtell I find it convenient to soft reset to HEAD~2 and then amending the commit of HEAD~3:

git reset --soft HEAD~2
git commit --all --amend --no-edit    

This will merge all commits to the HEAD~3 commit and use its commit message. Be sure to start from a clean working tree.

2
  • 12
    This is the correct answer because it squashes a series of commits leading up to head, and it uses the 1st commit's message. It is non-interactive. Commented Sep 29, 2017 at 20:02
  • 1
    In this scenario, I think the --all parameter in git commit is unnecessary because the modified files are already staged after soft resetting. Doc for git commit --all
    – li ki
    Commented Jun 17, 2021 at 14:45
11

I used:

EDITOR="sed -i '2,/^$/s/^pick\b/s/'" git rebase -i <ref>

Worked quite fine. Just don't try to have a commit log with a line that starts with "pick" :)

2
  • Sadly this does not work for me on Windows. Still get the interactive editor.
    – abergmeier
    Commented Nov 12, 2014 at 10:42
  • neither worked for me on Mac
    – netimen
    Commented Oct 18, 2021 at 10:07
4

Use the following command to squash the last 4 commits within the last commit:

git squash 4

With the alias:

squash = !"f() { NL=$1; GIT_EDITOR=\"sed -i '2,$NL s/pick/squash/;/# This is the 2nd commit message:/,$ {d}'\"; git rebase -i HEAD~$NL; }; f"
sq = !git squash $1
sqpsf = !git squash $1 && git psf 

From https://github.com/brauliobo/gitconfig/blob/master/configs/.gitconfig

2
  • You didn't specify what OS/shell is being used here. This solution will probably not work for all the other desktops people use.
    – Rick-777
    Commented Mar 11, 2016 at 10:12
  • yep, you should use linux/bash|zsh for this
    – brauliobo
    Commented Mar 12, 2016 at 10:51
2

Here is a one liner to squash the last 2 commits. In this example, the message of second last commit will be retained. You may change the message as you wish.

git commit -am "$(git log -1 --skip=1 --pretty=%B | xargs && git reset --soft HEAD~2)"

This command will be very useful if you create an alias for this command and use the alias instead.

1

To squash everything since the branch was forked from master:

git reset --soft $(git merge-base --fork-point master) \
  && git commit --verbose --reedit-message=HEAD --reset-author
1
  • --reedit-message=HEAD will use the message of the last commit which is not part of the squashing. This is likely not the one you want. To rather get the message of the first commit to be included, either (1) replace HEAD with the hash of the commit you want the message of, or (2) jump to the first commit to be included and git commit --amend --reedit-message=HEAD. This is what harmonious’ answer does.
    – Maëlan
    Commented Nov 9, 2018 at 12:13
-1

You can get pretty close with

git rebase --onto HEAD~4 HEAD~ master

This assumes you're on master with a linear history. It's not quite a squash because it discards the intermediate commits. You'd need to amend the new HEAD to modify the commit message.

2
  • Thanks Greg; by 'discards' do you mean that those intermediate commits are subject to cleanup by the git gc?
    – Phillip
    Commented Sep 1, 2011 at 20:39
  • @Phillip Yes, the intermediate commits become garbage as well as the old HEAD because it is rewritten to have HEAD~4 as its parent.
    – Greg Bacon
    Commented Sep 6, 2011 at 1:53

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