441

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
Soviut
  • 79,529
  • 41
  • 166
  • 227
user3803850
  • 4,559
  • 3
  • 9
  • 6
  • 12
    You can do `git rebase -i 58333012713fc168bd70ad00d191b3bdc601fa2d` wich will do an interactive rebase where the commitnumber is the last commit that stays **unchanged** – denns Apr 03 '17 at 16:49
  • 2
    @denns Using this method with the last commit in the branch you are rebasing _from_ worked fantastic. Thanks so much! – Joshua Pinter Apr 16 '20 at 17:44

15 Answers15

585

Another way to squash all your commits is to reset the index to master:

 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).


EDIT: you will need to use git push --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.

VonC
  • 1,042,979
  • 435
  • 3,649
  • 4,283
  • 2
    Better to checkout to commit where `YourBranch` currently is. This will keep `YourBranch` intact when you do `reset` – Eugen Konkov Apr 03 '18 at 10:40
  • On Windows just run "git merge-base master yourBranch" take the output and paste the output after "git reset" – Abdurrahim Jun 26 '18 at 11:34
  • 1
    @Abdurrahim Or open a git bash, and yo can copy-paste thos commands! – VonC Jun 26 '18 at 12:01
  • 7
    For the reset, you can do `git reset $(git merge-base master $(git rev-parse --abbrev-ref HEAD))` to automatically use 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. – Karlotcha Hoa Aug 03 '18 at 20:53
  • @KarlotchaHoa Thank you, very good idea. I have included your comment in the answer for more visibility. – VonC Aug 03 '18 at 20:55
  • 1
    WOWWWW +1 @VonC – ShellZero Aug 13 '18 at 16:52
  • I've always used `git reset $(git merge-base origin/master @)` without issues. Is this error-prone? – Druska Sep 21 '18 at 14:30
  • 1
    @Druska For simlpe branching cases, no, it should work fine. – VonC Sep 21 '18 at 14:57
  • 1
    @Shimmy yes, provided you force push after the reset: `git push --force` (and warn your colleagues if you are several people working on that branch) – VonC Apr 28 '20 at 07:41
  • 1
    It works perfectly when you work with merged branches where you can't rebase your commits. Thanks! – yigidix Jan 26 '21 at 09:48
245

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

Step 1:

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

Step 2:

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.

git add -A && git commit -m "commit message goes here"
Community
  • 1
  • 1
shanky
  • 2,643
  • 1
  • 8
  • 14
  • 14
    This was the most reliable solution for me - not causing any rebase errors nor merge conflicts. – ANTARA Jul 27 '18 at 12:45
  • 5
    Warning: the git add -A add EVERYTHING you have in the local folder - to the branch. – David H Aug 24 '18 at 15:27
  • love this solution! this is exactly what I wanted! – jacoballenwood Oct 05 '18 at 16:40
  • 2
    Best solution for a noob - non destructive and only oops moment may be checking in too much such as app secrets etc, which shouldn't matter if you have a proper gitignore file – kkarakk May 31 '19 at 06:41
  • @NSduToit Short answer: No, you don't have to. After doing the above mentioned steps in my answer, you will end up with one commit with some code changes. You can think of it as just like any other commit with some code changes. You can push that to your remote branch without the `-f` flag. – shanky Jul 18 '19 at 16:38
  • this works only when the "feature_branch" is new branch – Bhimashankar Mantur Mar 17 '20 at 13:02
  • 2
    @BhimashankarMantur you can make it work if you push with "--force" to existing branch - because this "git reset --soft" is rewriting history. But of course you should push with "--force" only to branches which are used only by you - not shared ones - unless you notify all people which are using such branch about your force push (so they can retrieve those changes corerctly for example using: "git pull --rebase origin my-branch-name" ) – domis86 Apr 23 '20 at 11:49
  • git reset --soft master . master has to be main I guess – Shreyas Shetty Feb 18 '21 at 10:46
140

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.

Eevee
  • 43,129
  • 10
  • 82
  • 119
  • 14
    Agreed this is your best solution. but follow this [link](https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit) as it better explains what you need to do. – Christo Aug 14 '16 at 18:22
  • 3
    Instead of squashing commits, you could merge the branch to master and do a git reset to origin/master to unstage all commits. That would let you commit your existing unstaged code with `commit -am "the whole thing!"` – nurettin Sep 12 '17 at 10:37
  • 3
    @nurettin I think the `reset origin/master` method is really bad as it's just the same as making commits directly on master - there is not 'merge branch' history, no pull request option. The answer by @WaZaA is much more in keeping with normal git workflow I think – Drenai Dec 13 '17 at 10:04
  • Actually i think the `git merge --squash feature-branch` does the same thing!. There is a GitHub merge pull request squash option, think that's the route I'll go – Drenai Dec 13 '17 at 10:30
  • @Brian that's what I started doing afterwards. – nurettin Dec 13 '17 at 11:43
  • 1
    @nurettin Did you notice that after doing a Squash and Merge on your own repositories, that the branch still says `7 commits ahead of master`, even after the merge – Drenai Dec 13 '17 at 11:44
  • @Brian I haven't noticed that, but I've had `not fully merged` errors while deleting branches which I merge/squashed. Perhaps your message is talking about origin/master before a push ? – nurettin Dec 13 '17 at 12:35
  • @nurettin I've added it as a question, because I'm stuck on it https://stackoverflow.com/questions/47791622/github-merge-pull-request-with-squash-commits-branch-still-says-7-commits – Drenai Dec 13 '17 at 13:13
  • [Here's a really good overview of the merging and rebasing workflows](https://randyfay.com/content/rebase-workflow-git), and [in this comment on the same page](https://randyfay.com/comment/1107#comment-1107) you'll find some really helpful visualisation of the different ways of doing roughly the same thing. – pfabri Jun 01 '19 at 19:21
102

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: ...
Appulus
  • 17,551
  • 10
  • 33
  • 43
WaZaA
  • 1,190
  • 1
  • 7
  • 9
46

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 add -A
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. Gitlab Squashing option

mles
  • 3,851
  • 8
  • 41
  • 83
20

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.

Travis Reeder
  • 31,147
  • 12
  • 77
  • 80
  • 2
    Thanks a lot, company has a lot of restrictions in place and could not rebase the usual way with an editor as was not aloud to save. Also could not use squash and merge feature in git as this branch goes to Lead dev for merging and he does not like it. This 1 liner worked and saved headaches. Awesome job. – L1ghtk3ira Oct 24 '17 at 15:25
18

Solution for people who prefer clicking:

  1. Install sourcetree (it is free)

  2. Check how your commits look like. Most likely you have something similar to this enter image description here

  3. Right click on parent commit. In our case it is master branch.

enter image description here

  1. You can squash commit with previous one by clicking a button. In our case we have to click 2 times. You can also change commit message enter image description here

  2. Results are awesome and we are ready to push! enter image description here

Side note: If you were pushing your partial commits to remote you have to use force push after squash

Marcin Szymczak
  • 9,665
  • 4
  • 48
  • 60
15

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

seebi
  • 169
  • 2
  • 5
  • Interesting alternative. Upvoted. – VonC Nov 27 '20 at 12:01
  • This approach makes a lot of sense! `Merge + reset = diff` then `commit + push -f` to change history. Love it – Caveman Jan 25 '21 at 12:55
  • This is really simple and clear. Great job on finding this! This helped me without creating extra commits or steps like the other answers. – jojo Feb 15 '21 at 20:02
  • which branch am I supposed to make this call from? the main branch or the feature branch? – salyela Apr 26 '21 at 18:47
  • depending on where you want to squash your commits (usually on the feature branch) – seebi Apr 29 '21 at 07:41
6

If you use JetBrains based IDE like IntelliJ Idea and prefare using GUI over command line:

  1. Go to Version control window (Alt + 9/Command + 9) - "Log" tab.
  2. Choose a point in the tree from which you created your branch
  3. Right click on it -> Reset current branch to here -> Pick Soft (!!!) (it's important for not to lose your changes)
  4. Push the Reset button in the bottom of the dialog window.

That's it. You uncommited all your changes. Now if you'll make a new commit it will be squashed

Eduard Streltsov
  • 585
  • 7
  • 11
3

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

sheerun
  • 1,476
  • 12
  • 27
1

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
Marcus
  • 429
  • 2
  • 7
1

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? :)

ItaiRoded
  • 122
  • 6
0

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

pranav
  • 129
  • 1
  • 4
0

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.

user1889992
  • 193
  • 2
  • 7
0

Although this answer is a bit verbose it does allow you to create a single commit branch by doing a single merge with another branch. (No rebasing/squashing/merging individual commits).

Since we don't care about the in-between commits we can use a simple solution:

# Merge and resolve conflicts
git merge origin/master

# Soft reset and create a HEAD with the version you want
git reset --soft origin/master
git commit -m "Commit message"
git push origin your-branch -f

... then to remove the history...

# Get the most up-to-date master
git checkout master
git reset --hard

# Create a temporary branch
git checkout -b temp-branch

# Retrieve the diff between master and your-branch and commit with a single commit
git checkout origin/your-branch
git commit -m "Feature commit"

# Force push to the feature branch
git push origin temp-branch:your-branch -f

# Clean up
git checkout your-branch
git branch -D temp-branch

This can all be put into a bash function with 3 params like so squashall --their master --mine your-branch --msg "Feature commit" and it will work as long as you have the correct version of files locally.

Caveman
  • 1,190
  • 9
  • 12