Is there a way to squash a number of commits non-interactively?
Solution 1
Make sure your working tree is clean, then
git reset --soft HEAD~3
git commit -m 'new commit message'
Solution 2
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>
Solution 3
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.
Solution 4
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" :)
Solution 5
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
Phillip
Updated on July 28, 2022Comments
-
Phillip almost 2 years
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?
-
Phillip over 12 years@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?
-
KingCrunch over 12 yearsIf its just the number of lines:
git reset --soft HEAD~3 && git commit -m "my message"
-
Lily Ballard over 12 years@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 sogit musquash 3
will omit the -m flag entirely so you'll get the interactivegit commit
UI in that case. -
Phillip over 12 yearsThanks Greg; by 'discards' do you mean that those intermediate commits are subject to cleanup by the git gc?
-
Greg Bacon over 12 years@Phillip Yes, the intermediate commits become garbage as well as the old HEAD because it is rewritten to have
HEAD~4
as its parent. -
Asclepius about 10 years@KevinBallard, just to clarify, I think that by
mu
you meantmy
. -
Asclepius about 10 yearsHow do I automate
HEAD~3
to reflect the last pushed commit? I don't want to have to type3
, etc. -
JuJoDi about 10 yearsI found it more understandable to use the SHA hash of the commit I wanted to squash to instead of HEAD~3
-
Admin almost 10 yearsYou can also put it in
$PATH
namedgit-squash.sh
and it will be automatically aliased asgit squash
. I didn't change your answer, just in case there's a reason to use thecreate-aiases.sh
script that I'm not aware of. -
n8tr almost 10 yearsI 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.
-
Minthos almost 10 yearsI tried this and it reads my commit message as squash count and fails because it's not an integer.
-
Minthos almost 10 yearsThe solution was to append - after /squash.sh \$1 \$2'
-
n8tr almost 10 yearsMinthos Yes and that is already in the solution above--see this line: git config --global alias.squash "!sh -c 'sh <path to scripts directory>/squash.sh \$1 \$2'"
-
abergmeier over 9 yearsSadly this does not work for me on Windows. Still get the interactive editor.
-
sebnukem about 9 yearsthis answer leads to a "! [rejected] master -> master (non-fast-forward)" error...
-
René Link almost 9 yearsJust 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
-
physicalattraction over 8 yearsI 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.
-
physicalattraction over 8 yearsIf the previous commits were already pushed, should I forcefully push the created commit here?
-
n8tr over 8 yearsThanks physicalattraction updated the have the
-
. Also, yes you'll have to force push if you've already pushed the previously un-squashed commits. I recommend to only force push to your own branches, and not do that to a master branch. -
avmohan about 8 years@sebnukem - That's when we try to push the branch and the remote is configured to reject force pushes.
-
Rick-777 about 8 yearsYou didn't specify what OS/shell is being used here. This solution will probably not work for all the other desktops people use.
-
brauliobo about 8 yearsyep, you should use linux/bash|zsh for this
-
Landon Kuhn over 6 yearsThis 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.
-
Maëlan over 5 years
--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) replaceHEAD
with the hash of the commit you want the message of, or (2) jump to the first commit to be included andgit commit --amend --reedit-message=HEAD
. This is what harmonious’ answer does. -
Abhishek almost 5 yearsif these are merge commits it would loose all notion of parents would it not?
-
li ki almost 3 yearsIn this scenario, I think the
--all
parameter ingit commit
is unnecessary because the modified files are already staged after soft resetting. Doc forgit commit --all
-
netimen over 2 yearsneither worked for me on Mac
-
ruslaniv over 2 yearsToo bad this solution does not retain commit messages from squashed commits, otherwise it's pretty good. Would it be possible to revise it?
-
n8tr over 2 yearsI purposely do not retain all the commit messages and force typing in a new one. My rationale is that when you squash, you should have a clear explanation of what that commit is about. The previous commits are no longer important and creates noise. If you want to retain all of the commit messages, then you probably shouldn't be squashing, or you should be doing an interactive squash to decide what commits to keep and which to squash.