828

This gives a good explanation of squashing multiple commits:

http://git-scm.com/book/en/Git-Branching-Rebasing

but it does not work for commits that have already been pushed. How do I squash the most recent few commits both in my local and remote repos?

When I do git rebase -i origin/master~4 master, keep the first one as pick, set the other three as squash, and then exit (via c-x c-c in emacs), I get:

$ git rebase -i origin/master~4 master
# Not currently on any branch.
nothing to commit (working directory clean)

Could not apply 2f40e2c... Revert "issue 4427: bpf device permission change option added"
$ git rebase -i origin/master~4 master
Interactive rebase already started

where 2f40 is the pick commit. And now none of the 4 commits appear in git log. I expected my editor to be restarted so that I could enter a commit message. What am I doing wrong?

Neuron
  • 5,141
  • 5
  • 38
  • 59
Loren
  • 13,903
  • 8
  • 48
  • 79

12 Answers12

1125

Squash commits locally with:

git rebase -i origin/master~4 master

where ~4 means the last 4 commits.

This will open your default editor. Here, replace pick in the second, third, and fourth lines (since you are interested in the last 4 commits) with squash. The first line (which corresponds to the newest commit) should be left with pick. Save this file.

Afterwards, your editor will open again, showing the messages of each commit. Comment the ones you are not interested in (in other words, leave the commit message that will correspond to this squashing uncommented). Save the file and close it.

You will than need to push again with the -f flag.

and then force push with :

git push origin +master

Difference between --force and +

From the documentation of git push:

Note that --force applies to all the refs that are pushed, hence using it with push.default set to matching or with multiple push destinations configured with remote.*.push may overwrite refs other than the current branch (including local refs that are strictly behind their remote counterpart). To force a push to only one branch, use a + in front of the refspec to push (e.g git push origin +master to force a push to the master branch).

Arturo Moncada-Torres
  • 1,236
  • 14
  • 23
Alan Haggai Alavi
  • 72,802
  • 19
  • 102
  • 127
  • 44
    you can also `git push --force origin master` – Daenyth Apr 14 '11 at 19:05
  • 9
    _[Daenyth](http://stackoverflow.com/users/350351/daenyth)_: Yes, but I always prefer this syntax as this is shorter. – Alan Haggai Alavi Apr 14 '11 at 19:07
  • 101
    And of course, realize that if anyone else might've pulled from the remote repository, you probably don't want to do this - the answer in that case is "you don't." – Cascabel Apr 14 '11 at 19:15
  • 10
    Also, I think the OP is exactly copying the command `git rebase -i origin/master`, and actually wants to know how to rebase commits farther back than that, e.g. `git rebase -i origin/master~20 master`. – Cascabel Apr 14 '11 at 19:16
  • 1
    @Jefromi: Why? What will happen in that case? Please explain for someone like me who haven't tried doing that. – HelloGoodbye Aug 06 '13 at 20:43
  • @HelloGoodbye Because then the history they have will conflict with the rewritten history you've just pushed, and it can be a huge pain to sort everyone out. See for example https://www.kernel.org/pub/software/scm/git/docs/git-rebase.html#_recovering_from_upstream_rebase, or one of the zillions of questions here about it. – Cascabel Aug 06 '13 at 20:57
  • @Jefromi: But as long as you're sure no one has pulled from the remote repository you're fine? For example if you are the only person who has read access to the remote repository and you haven't pulled from it in any other local repository you might have. – HelloGoodbye Aug 06 '13 at 21:03
  • @HelloGoodbye Yes, my comment did say *if anyone else might've pulled from the remote repository*. – Cascabel Aug 06 '13 at 21:23
  • 2
    For public git service, it is quite likely that you encountered `denying non-fast-forward`, please refer http://stackoverflow.com/questions/22026489/git-puzzler-working-around-denying-non-fast-forward for solution – Ding-Yi Chen Aug 25 '14 at 06:43
  • Does "+" equal "--force"? – gstackoverflow Nov 22 '14 at 17:13
  • 10
    *[gstackoverflow](http://stackoverflow.com/users/2674303/gstackoverflow)*: `+` forces only the refspec which is prefixed by it. `--force` will force all the refspecs being pushed. Please see the updated answer. – Alan Haggai Alavi Dec 09 '14 at 23:47
  • How does one locally squash commits after having pushed them? Whenever I try to do that, I simply get "noop" in the editor. – jonS90 Jan 21 '15 at 21:57
  • I think I just figured it out....I needed to set the upstream a few commits back. – jonS90 Jan 21 '15 at 22:05
  • 5
    @Jefromi You are correct about being careful with rewriting history on remote branches. But there are also cases when it's a good idea. For instance, I maintain several private repositories for open source projects. I test them on different machines so I push partially completed branches back to my remote all the time. When the time comes to make a pull request, I squash all commits which creates this very situation. And this is the preferred method of contributing to open source projects. But as you mention, the key is knowing that no one else is forking my private branch. – Nilpo Mar 20 '16 at 20:13
  • Does this actually work as intended? I could see the `git log` indicating commits are squashed successfully on my local, but on another clone, say, on another developer's PC (and in Atlassian BitBucket Server, of course), it lists the original unsquashed commits and then the squashed commit. What's the point of squashing then?! Does the version of the `git` matter? (I hope not.) – mystarrocks Jun 24 '16 at 18:14
  • 2
    Nice method :) You can also force push (if really needed) with `git push --force-with-lease` to do a "smooth force". Found it here : https://developer.atlassian.com/blog/2015/04/force-with-lease/ – Benj Jun 28 '16 at 14:02
  • 1
    It fails for me. I do the first command - "git rebase -i origin/master~20 master". This gives me a vi window and I change all the 'pick' lines to 'squash' and get the error message "cannot 'squash' without a previous commit. – Steve Cohen Sep 07 '16 at 16:38
  • 3
    @SteveCohen You have to leave one of the commits as 'pick': the rebase is squashing the 'squash' ones into the commit of the 'pick' one (presumably the first 'pick' one before if you leave more than one as 'pick'). – Stuart Rossiter Nov 02 '16 at 16:45
  • 1
    If you are squashing all the way back to the root commit you need to use `git rebase -i --root` otherwise it will fail with `fatal: Needed a single revision` – AndrewHarvey Sep 16 '18 at 12:44
  • Notice: you should change to `squash` all the entries for the commits that you want to squash, and `pick` for the ones you want to pick. And the commits appear in reversed order (newer on top). (See https://stackoverflow.com/questions/39595034/git-cannot-squash-without-a-previous-commit-error-while-rebase) – Michele Piccolini Jun 01 '20 at 17:18
  • local squash and then force push works flawlessly! – Gaurav Jul 30 '21 at 04:57
  • and also you most likely need to a force pull in another local machine using `git reset --hard origin/` – Benyamin Jafari Jan 22 '22 at 13:16
  • To quit the console, press ```ESC``` and type ```:wq!``` – Rafaël Moser Aug 18 '22 at 13:56
  • And then, when you are in the VIM editor, press ```ESC``` and type ```ZZ``` (uppercase) to close it. – Rafaël Moser Aug 18 '22 at 13:57
292

On a branch I was able to do it like this (for the last 4 commits)

git checkout my_branch
git reset --soft HEAD~4
git commit
git push --force origin my_branch
jakob-r
  • 6,824
  • 3
  • 29
  • 47
  • 4
    Doing this with the soft command on an already pushed branch ended up pushing a ton of other peoples commits for me. – cchamberlain Feb 04 '16 at 04:06
  • 6
    A ton? How can it be more than 4? Can you elaborate? – jakob-r Feb 05 '16 at 10:08
  • That I am unsure of but it had something to do with trying to squash an already pushed commit. Looks like others experienced similar here - http://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git#comment52575762_5201642 – cchamberlain Feb 05 '16 at 18:37
  • 15
    I would have accepted this as expected answer. More cleaner that accepted answer. – vikramvi Jul 14 '16 at 13:17
  • 10
    This is the most clear and accepted answer in just 4 steps – Ameya Salagre Oct 10 '18 at 13:55
  • 1
    very short and simple, instead of going through resolving conflicts and getting yourself into creating a branch with detached HEAD and evrything. – amarnath harish May 07 '19 at 13:44
  • this is what i use because i get stuck with interactive rebasing, because after rewinding and amending the commit, git will say its current position is at [ | REBASE-i ] and i dont know how to continue from there because none of the internet guides ive looked at include this part – heug Jun 28 '19 at 15:47
  • How does this squash commits, this will remove the last 4 commits completely? This will annihilate your branch. – Josip Domazet Dec 30 '21 at 20:40
  • 1
    This is a very wrong answer. This will cause a lot of complications if you work with others. – tuemar29 Oct 07 '22 at 20:20
  • 1
    @tuemar29 Changing anything in the log (ie using a force push) will cause complications when working with others. – jakob-r Oct 10 '22 at 10:53
  • This answer is clear and working well for me. – Arsham Arya Jan 02 '23 at 07:10
  • 1
    @tuemar29 when you work on your own feature branch, you are the one who edit it, so there would not be any complications. – Arsham Arya Jan 02 '23 at 07:13
124

Minor difference to accepted answer, but I was having a lot of difficulty squashing and finally got it.

$ git rebase -i HEAD~4
  • At the interactive screen that opens up, replace pick with squash at the top for all the commits that you want to squash.
  • Save and close the editor

Push to the remote using:

$ git push origin branch-name --force
KetZoomer
  • 2,701
  • 3
  • 15
  • 43
Sachin Rammoorthy
  • 1,355
  • 1
  • 7
  • 10
37

git rebase -i master

you will get the editor vm open and msgs something like this

Pick 2994283490 commit msg1
f 7994283490 commit msg2
f 4654283490 commit msg3
f 5694283490 commit msg4
#Some message 
#
#some more

Here I have changed pick for all the other commits to "f" (Stands for fixup).

git push -f origin feature/feature-branch-name-xyz

this will fixup all the commits to one commit and will remove all the other commits . I did this and it helped me.

Nupur
  • 571
  • 5
  • 3
  • 1
    This needs to be the top answer and thank you. I find this answer a better way of squashing than blindly doing it based on number of commits . Especially with the VM editor you can do lot of things at once like changing the commit, removing commit and of course squashing. – MG Developer Jul 17 '20 at 17:22
34

A lot of problems can be avoided by only creating a branch to work on & not working on master:

git checkout -b mybranch

The following works for remote commits already pushed & a mixture of remote pushed commits / local only commits:

# example merging 4 commits

git checkout mybranch
git rebase -i mybranch~4 mybranch

# at the interactive screen
# choose fixup for commit: 2 / 3 / 4

git push -u origin +mybranch

I also have some pull request notes which may be helpful.

Stuart Cardall
  • 2,099
  • 24
  • 18
15

1) git rebase -i HEAD~4

To elaborate: It works on the current branch; the HEAD~4 means squashing the latest four commits; interactive mode (-i)

2) At this point, the editor opened, with the list of commits, to change the second and following commits, replacing pick with squash then save it.

output: Successfully rebased and updated refs/heads/branch-name.

3) git push origin refs/heads/branch-name --force

output:

remote:
remote: To create a merge request for branch-name, visit:
remote: http://xxx/sc/server/merge_requests/new?merge_request%5Bsource_branch%5D=sss
remote:To ip:sc/server.git
 + 84b4b60...5045693 branch-name -> branch-name (forced update)
Gavin
  • 1,070
  • 18
  • 24
terwxqian
  • 591
  • 6
  • 7
13

Sqush Changes in remote

  • First ensure your local master is at par with remote
  • Then reset your local feature branch to be at par with master: git reset --soft master
  • then add all your modifications and changes (that you made on your local feature branch you just reset to master) to staging area: git add .
  • then commit these changes: git commit -m "this is the final commit message"
  • then force push to remote branch: git push RemoteBranch --force
cb4
  • 6,689
  • 7
  • 45
  • 57
Zul
  • 131
  • 1
  • 4
7

For squashing two commits, one of which was already pushed, on a single branch the following worked:

git rebase -i HEAD~2
    [ pick     older-commit  ]
    [ squash   newest-commit ]
git push --force

By default, this will include the commit message of the newest commit as a comment on the older commit.

vonaka
  • 913
  • 1
  • 11
  • 23
lobsterhands
  • 88
  • 2
  • 4
7

When you are working with a Gitlab or Github you can run in trouble in this way. You squash your commits with one of the above method. My preferite one is:

git rebase -i HEAD~4
or
git rebase -i origin/master

select squash or fixup for yours commit. At this point you would check with git status. And the message could be:

    On branch ABC-1916-remote
    Your branch and 'origin/ABC-1916' have diverged,
    and have 1 and 7 different commits each, respectively.
      (use "git pull" to merge the remote branch into yours)

And you can be tempted to pull it. DO NOT DO THAT or you will be in the same situation as before.

Instead push to your origin with:

git push origin +ABC-1916-remote:ABC-1916

The + allow to force push only to one branch.

Zioalex
  • 3,441
  • 2
  • 33
  • 30
  • 1
    there's nothing wrong on pulling, specially if you have conflicts they can be resolved easier than if you force push – Ray_Poly Mar 03 '20 at 11:00
  • @Ray_Poly - won't that just put those remote commits back as local commits? That's what I've found. – Steve Dunn Dec 11 '20 at 12:13
  • yes, it'll create a new commit, but you can see in that commit the differences, better than re-writing the git history everytime you rebase – Ray_Poly Dec 11 '20 at 14:39
2

For whom might be interested, this is how it looks like when you squash commits in GitHub PR.

Before: enter image description here

After:

enter image description here

Nuryagdy Mustapayev
  • 667
  • 1
  • 7
  • 27
1

In my case requirement was to squash all the feature branch commits into one, to have a clean commit history. Utilized the GitHub UI to do so.

Problem:

  • Feature branch (eg:featureBranch) has been created from master (a month ago).
  • Committed all my changes to the featureBranch (~12 commits, a month of work). And has been pushed to remote regularly.

Requirement:

  • To Get the feature branch updated with the master and to have a single commit in a featureBranch

Steps followed:

  • Create a new branch (eg: featureBranchLatest) from master in GitHub UI.
  • Create a PR from featureBranch to featureBranchLatest.
  • Resolve conflicts if any. Merge the PR with the squash commit option in GitHub UI. (Alter the commit message to have a cleaner message).

Now the featureBranchLatest will have a single commit of all the changes needed in a single commit, along with the latest changes from the master. Delete the old branch featureBranch if not required for reference.

Sasi Kumar M
  • 2,440
  • 1
  • 23
  • 23
0

Merge (rebase) commits in a specifc branch

List all specifc branch commits

MODEL

git cherry -v <ORIGINAL_BRANCH> <YOUR_BRANCH>

EXAMPLE

git cherry -v main some_branch_name

[Ref(s).: https://stackoverflow.com/a/24668421/3223785 ]

If there is more than one commit in the created branch, perform a rebase to centralize everything in just one commit.

TIP: This process is important for organizing and ensuring that all changes made in one branch can be brought over to another branch in a cherry pick process if this is the scenario.

Merge (rebase) commits

The rebase is basically a "merge" of commits.

MODEL

git rebase -i origin/<YOUR_BRANCH>~<NUMBER_COMMITS_TO_REBASE> <YOUR_BRANCH>

EXAMPLE

git rebase -i origin/some_branch_name~2 some_branch_name

NOTE: The <NUMBER_COMMITS_TO_REBASE> means the number of commits you are going to merge (rebase) - that is, the number of commits displayed by the "List all specifc branch commits" command - and -i means interactive mode.

A text editor will open. Keep only the commits (hash) shown by the "List all branch commit..." command. Keep the oldest commit (it will appear first) as "pick" and the others in sequence change to "squash". This configuration file after being saved will be executed as a batch by git.

MODEL

git push origin +<YOUR_BRANCH>

EXAMPLE

git push origin +some_branch_name

IMPORTANT: To force a push TO ONLY ONE BRANCH (<YOUR_BRANCH>), use a + in front of it to push.

[Ref(s).: https://stackoverflow.com/a/5668050/3223785 ]

Eduardo Lucio
  • 1,771
  • 2
  • 25
  • 43