Reputation: 9997
I make new branch from master
with:
git checkout -b testbranch
I make 20 commits into it.
Now I want to squash those 20 commits. I do that with:
git rebase -i HEAD~20
What about if I don't know how many commits? Is there any way to do something like:
git rebase -i all on this branch
Upvotes: 984
Views: 1048736
Reputation: 1328712
Another way to squash all your commits is to reset the index to master
:
Note: Git's default branch name is still master
with Git version 2.41 (Q3 2023), as seen in git init
man page.
Git version 2.28 (Q3 2020) introduced configurable default branch names, which means your remote repository may optionally use another default branch name such as main
. In order to provide the most universally applicable examples, as well as avoid confusion, this answer shall assume Git's default configuration.
If you need the following commands to work for any default branch, replace master
with ${defaultBranch}
.
And define defaultBranch=$(git config --get init.defaultBranch || echo main)
.
Back to the solution: (to squash all your commit) reset the index to master
:
git switch yourBranch
git reset --soft $(git merge-base master HEAD)
git commit -m "one commit on yourBranch"
This incorporates improvements noted by Hiroki Osame in the comments:
git branch --show-current
since HEAD is already a reference to that branch.git add -A
, since git reset --soft
only moves HEAD, and leaves the index untouched (in other words, the files are already "added").Or, as I originally proposed:
git checkout yourBranch
git reset $(git merge-base master $(git branch --show-current))
git add -A
git commit -m "one commit on yourBranch"
This isn't perfect as it implies you know from which branch "yourBranch
" is coming from.
Note: finding that origin branch isn't easy/possible with Git (the visual way is often the easiest, as seen here).
Note: git branch --show-current
has been introduced with Git 2.22 (Q2 2019).
EDIT: you will need to use git push --force
(or git push --force-with-lease
)
See "git push --force-with-lease
vs. --force
"
Karlotcha Hoa adds in the comments:
For the reset, you can do
git reset $(git merge-base master $(git rev-parse --abbrev-ref HEAD))
[That] automatically uses the branch you are currently on.
And if you use that, you can also use an alias, as the command doesn't rely on the branch name.
Upvotes: 1187
Reputation: 6695
The shortest way to squash commits done since master
on the current branch is likely to git reset --soft
and git commit
.
Another solution that only requires one shell command (and one Vim command):
git rebase -i master
If you want to squash1 all commits into one and your Git editor is Vim2, then issue this Vim command:
:2,$s/pick/fixup/
Then save and quit (:wq
) Vim.
1 fixup
means to discard additional commit messages of squashed commits. If you want to process commit messages of squashed commits further, replace it with squash
.
2 You can switch your editor just for the single command by setting the EDITOR
environment variable:
EDITOR=vim git rebase -i master
Upvotes: 3
Reputation: 3476
Assuming you were branching from the master, you don't need to enter yourBranch
into the reset step all the time:
git checkout yourBranch
git reset --soft HEAD~$(git rev-list --count HEAD ^master)
git commit -m "one commit on yourBranch"
Explanation:
git rev-list --count HEAD ^master
counts the commits since you made your feature branch from the master, f.ex. 20.git reset --soft HEAD~20
will make a soft reset of the last 20 commits. This leaves your changes in the files, but removes the commits.Usage:
In my .bash_profile I have added an alias for gisquash
to do this with one command:
# squash all commits into one
alias gisquash='git reset --soft HEAD~$(git rev-list --count HEAD ^master)'
After reseting and committing you need to do a git push --force
.
Hint:
If you're using Gitlab >= 11.0 you don't need to do this anymore as it has a squashing option when merging branches.
Upvotes: 77
Reputation: 1134
Solution - 1
A. Pull master into your feature branch (Make sure to update your Master)
git pull origin master
B. Do soft reset to master
git reset --soft master
C. Commit your changes
git commit -m 'commit message'
D. Do git push
git push --force
Solution - 2
Squashing Commit using git rebase
A.
$git rebase -i HEAD~3 (HEAD~<no. of commits you want to squash>)
B. You will get one interactive prompt where you need to Pick the top commit and insert squash or s in front of those which you want to combine/squash.
Note: Make sure do changes in insert mode and save the file ; (wq in VI Editor)
C. Now you will get another interactive prompt where you need to put # in front of commits message that you don't want, and or add your own message. Again save the file and your commits will successfully rebase.
Cheers!
Upvotes: 48
Reputation: 616
The only generic solution I found so far.
git reset $(git reflog show --no-abbrev $(git branch --show-current) | grep "branch: Created from" | awk '{print $1;}')
git add .
git commit -m "Squashed commit"
Upvotes: 0
Reputation: 19989
git checkout -b temp
git checkout yourbranch
git fetch
git reset --hard origin/master
git merge --squash temp
git commit -m "new message"
most easiest way to do.
This creates a new branch , then reset your branch to base branch and then we squash the changes and creates a new commit before merging back temp branch to our branch
Upvotes: 5
Reputation: 24628
In previous answers, I have not seen any information on how to deal with "messy branches" and "self conflicts". E.g. I often end up having master
commits on my feature branch (call it feature
) that cause conflicts against themselves. This I found to be one of the most annoying issues to deal with.
I found Felix Rieseberg's solution to be the best. This is my slightly shorter transcription of his advice:
tmp
branch of off master
git checkout master && git pull && git checkout -b tmp
feature
changes into tmp
(without any commits, only staged file changes).
git merge --squash $feature
tmp
is now master
+ 1 commit (containing all changes).
git commit ...
feature
and git reset --hard tmp
(feature
's original contents are gone, and it is now basically tmp
, but renamed)
git checkout $feature && git reset --hard tmp
origin/feature
(then clean up)
git push -f && git branch -D tmp
Felix points out that this is going to produce the cleanest possible merge, without any weird self-conflicts coming from a messy/complicated relationship between master
and feature
:
you might be getting a smaller number of unavoidable merge conflicts. Have faith that this is the smallest possible number of conflicts as you're skipping the many in-between commits you've originally created.
Upvotes: 16
Reputation: 977
Assuming you are on feature branch:
git log <source_branch>..<feature_branch> --pretty=format:%h
git reset --soft <base_commit_hash>
git commit --amend --no-edit
Now at this stage, on your local, you have 1 commit which includes changes done in all the previous commits.
Review it and you need to force push it. After force push, all the changes will be combined in one commit and your branch will have only 1 commit.
git push --force
Upvotes: 8
Reputation: 1960
I know this question is already answered but I went and wrote a bash function around the accepted answer to allow you to do it in one command. It starts by creating a backup branch in case the squash fails for some reason. Then squashes and commits.
# Squashes every commit starting after the given head of the given branch.
# When the squash is done, it will prompt you to commit the squash.
# The head of the given parent branch must be a commit that actually exists
# in the current branch.
#
# This will create a backup of the current branch before it performs the squash.
# The name of the backup is the second argument to this function.
#
# Example: $ git-squash master my-current-branch-backup
git-squash() {
PARENT_BRANCH=$1
BACKUP_BRANCH=$2
CURRENT_BRANCH=$(git branch --show-current)
git branch $BACKUP_BRANCH
BACKUP_SUCCESS=$?
if [ $BACKUP_SUCCESS -eq 0 ]; then
git reset $(git merge-base $PARENT_BRANCH $CURRENT_BRANCH)
git add -A
git commit
echo "Squashed $CURRENT_BRANCH. Backup of original created at $BACKUP_BRANCH$"
else
echo "Could not create backup branch. Aborting squash"
fi
}
Upvotes: 1
Reputation: 29014
To refine Caveman's answer a bit, use git reset --soft <commit>
. From the documentation, this command:
Does not touch the index file or the working tree at all (but resets the head to <commit>, just like all modes do). This leaves all your changed files "Changes to be committed", as
git status
would put it.
In other words, it undoes all of your commits up to <commit>
. But it does not change the working directory. You end up with all of the changes, unstaged and uncommitted. It's as if those intervening commits never happened.
Example:
# on master
git checkout -b testbranch
# make many commits
git reset --soft master
git add .
git commit -m 'The only commit.'
At this point, you're still on testbranch
, which has a single commit. Merge into master as you would ordinarily do.
The first part of Caveman's answer (git rebase -i
) did not in my hands squash commits.
Upvotes: 15
Reputation: 121
You can do this with subcommands ie
$ git rebase -i HEAD~$(git rev-list --count HEAD ^master)
This will run first count the commits since you diverged from master and then rebase back to that exact length.
Upvotes: 9
Reputation: 2953
You need to get the merge base of your branch
git merge-base master your-branch
# 566f8438e0cd0e331ceb49a9cb0920143dfb065c
Then you can rebase to it
git rebase -i 566f8438e0cd0e331ceb49a9cb0920143dfb065c
# then squash/pick/do commit messages
or just do a soft-reset and commit everything
git reset --soft 566f8438e0cd0e331ceb49a9cb0920143dfb065c
git add .
git commit -m "The only commit"
If you do this often you can automate it by putting these in your .bashrc
using.
g-rebase-branch() {
git branch --show-current | xargs git merge-base master | xargs git rebase -i
}
g-one-commit() {
local last_commit_message=`git show -s --format=%s`
git branch --show-current | xargs git merge-base master | xargs git reset --soft
git add -A
git commit -m "$last_commit_message"
git commit --amend
}
and then do these directly in the terminal.
g-one-commit
but if you're merging against a different branch than master then you can replace master
with "$1"
to do this
g-one-commit staging
Upvotes: 19
Reputation: 7263
Checkout the branch for which you would like to squash all the commits into one commit. Let's say it's called feature_branch
.
git checkout feature_branch
Do a soft reset of your origin/feature_branch
with your local main
branch (depending on your needs, you can reset with origin/main as well). This will reset all the extra commits in your feature_branch
, but without changing any of your file changes locally.
git reset --soft main
Add all of the changes in your git repo directory, to the new commit that is going to be created. And commit the same with a message.
# Add files for the commit.
git add ...
git commit -m "commit message goes here"
Upvotes: 703
Reputation: 375
In case you are okay with an answer involving another branch, try git checkout --orphan <new_branch>
It allowed me to simply commit ALL files from previous branch as one commit.
This is something like a git merge squash but not quite the same.
Upvotes: 1
Reputation: 528
Since I had some trouble with the solutions proposed here, I want to share a really simple solution (which really works regardless):
git merge origin/master && git reset --soft origin/master
The preceding merge cmd ensures, that no recent changes from master will go on your head (inverted) when committing! After that, just commit the changes and do git push -f
Upvotes: 30
Reputation: 1956
If you use JetBrains based IDE like IntelliJ Idea and prefare using GUI over command line:
That's it. You uncommited all your changes. Now if you'll make a new commit it will be squashed
Upvotes: 10
Reputation: 2100
Another simple way to do this: go on the origin branch and do a merge --squash
. This command doesn't do the "squashed" commit. when you do it, all commit messages of yourBranch will be gathered.
$ git checkout master
$ git merge --squash yourBranch
$ git commit # all commit messages of yourBranch in one, really useful
> [status 5007e77] Squashed commit of the following: ...
Upvotes: 193
Reputation: 1794
You can use tool I've created specifically for this task:
https://github.com/sheerun/git-squash
Basically you need to call git squash master
and you're done
Upvotes: 2
Reputation: 142
All this git reset, hard, soft, and everything else mentioned here is probably working (it didn't for me) if you do the steps correctly and some sort of a genie.
If you are the average Joe smo, try this:
How to use git merge --squash?
Saved my life, and will be my go to squash, been using this 4 times since I found out about it. Simple, clean and basically 1 comamnd.
In short:
If you are on a branch lets call it "my_new_feature" off develop and your pull request has 35 commits (or however many) and you want it to be 1.
A. Make sure your branch is up to date, Go on develop, get latest and merge and resolve any conflicts with "my_new_feature"
(this step really you should take as soon as you can all the time anyway)
B. Get latest of develop and branch out to a new branch call it
"my_new_feature_squashed"
C. magic is here.
You want to take your work from "my_new_feature" to "my_new_feature_squashed"
So just do (while on your new branch we created off develop):
git merge --squash my_new_feature
All your changes will now be on your new branch, feel free to test it, then just do your 1 single commit, push, new PR of that branch - and wait for repeat the next day.
Don't you love coding? :)
Upvotes: 2
Reputation: 725
Git reset, as mentioned in many answers before, is by far the best and simplest way to achieve what you want. I use it in the following workflow:
(on development branch)
git fetch
git merge origin/master #so development branch has all current changes from master
git reset origin/master #will show all changes from development branch to master as unstaged
git gui # do a final review, stage all changes you really want
git commit # all changes in a single commit
git branch -f master #update local master branch
git push origin master #push it
Upvotes: 0
Reputation: 527
Another solution would be to save all commit logs to a file
git log > branch.log
Now branch.log will have all commit ids since beginning.. scroll down and take the first commit (this will be difficult in terminal) using the first commit
git reset --soft
all commits will be squashed
Upvotes: 1
Reputation: 41133
Based on reading several Stackoverflow questions and answers on squashing, I think this is a good one liner to squash all commits on a branch:
git reset --soft $(git merge-base master YOUR_BRANCH) && git commit -am "YOUR COMMIT MESSAGE" && git rebase -i master
This is assuming master is the base branch.
Upvotes: 25
Reputation: 48584
What you're doing is pretty error-prone. Just do:
git rebase -i master
which will automatically rebase only your branch's commits onto the current latest master.
Upvotes: 216