How do I squash my last N commits together into one commit?
-
8Related: Git - combining multiple commits before pushing.– user456814Commented Jun 6, 2014 at 8:07
-
5For squashing upto THE first commit see this - stackoverflow.com/questions/1657017/…– goelakashCommented Mar 6, 2016 at 13:26
-
3post squash one need to do force push stackoverflow.com/questions/10298291/…– vikramviCommented Jul 14, 2016 at 13:04
-
3In addition to the posted answers, GUI clients can do this easily. I can squash dozens of commits in GitKraken with only four clicks.– Aaron FrankeCommented Jan 14, 2019 at 12:56
-
All answers i tried messed up submodules. This answer didn't.– ridilculousCommented Apr 3, 2023 at 21:32
46 Answers
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.
-
With
GitLab Enterprise Edition 12.8.6-ee
it just randomly took a commit message for the squashed commit...– WolfsonCommented Jun 9, 2020 at 12:23
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!
What about an answer for the question related to a workflow like this?
- many local commits, mixed with multiple merges FROM master,
- finally a push to remote,
- 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.
git pull master
git checkout -b new-branch
git checkout -b new-branch-temp
- edit and commit a lot locally, merge master regularly
git checkout new-branch
git merge --squash new-branch-temp
// puts all changes in stagegit commit 'one message to rule them all'
git push
- Reviewer does PR and merges to master.
-
From many opinions I like your approach. It's very convenient and fast Commented Sep 5, 2018 at 9:57
git rebase -i HEAD^^
where the number of ^'s is X
(in this case, squash the two last commits)
-
Better yet, you can replace the duplicate
^
s with~X
(whereX
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
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.
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:
git rebase -i HEAD~[N]
, where N is the number of commits I want to join, starting from the most recent one. Sogit rebase -i HEAD~5
would mean "squash the last 5 commits into a new one";- 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;
- the editor pops up again with a default message for the new commit: change it to your needs, save and close. Squash completed!
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.)
-
I tried the alias but I'm not sure if the sed replaces are having any effect. What should they do?– raineCommented 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.– EthanCommented 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?– CoderCommented 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.)– EthanCommented Jun 28, 2016 at 1:51
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.
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.
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
-
Couldn't this be avoided by merging the parent branch before rebasing?– JabariCommented 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.– dvvrtCommented Aug 6, 2021 at 13:27
-
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
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.
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"
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.
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
-
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
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>