How do I squash my last N commits together?

Asked 2023-09-20 20:01:24 View 395,658

How do I squash my last N commits together into one commit?

Answers

You can do this fairly easily without git rebase or git merge --squash. In this example, we'll squash the last 3 commits.

If you want to write the new commit message from scratch, this suffices:

git reset --soft HEAD~3 &&
git commit

If you want to start editing the new commit message with a concatenation of the existing commit messages (i.e. similar to what a pick/squash/squash/…/squash git rebase -i instruction list would start you with), then you need to extract those messages and pass them to git commit:

git reset --soft HEAD~3 && 
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"

Both of those methods squash the last three commits into a single new commit in the same way. The soft reset just re-points HEAD to the last commit that you do not want to squash. Neither the index nor the working tree are touched by the soft reset, leaving the index in the desired state for your new commit (i.e. it already has all the changes from the commits that you are about to “throw away”).

Answered   2023-09-20 20:01:24

  • Ha! I like this method. It is the one closes to the spirit of the problem. It's a pity that it requires so much voodoo. Something like this should be added to one of the basic commands. Possibly git rebase --squash-recent, or even git commit --amend-many. - anyone
  • @A-B-B: If your branch has an “upstream” set, then you may be able to use branch@{upstream} (or just @{upstream} for the current branch; in both cases, the last part can be abbreviated to @{u}; see gitrevisions). This may differ from your “last pushed commit” (e.g. if someone else pushed something that built atop your most recent push and then you fetched that), but seems like it might be close to what you want. - anyone
  • This kinda-sorta required me to push -f but otherwise it was lovely, thanks. - anyone
  • @2rs2ts git push -f sound dangerous. Take care to only squash local commits. Never touch pushed commits! - anyone
  • I also need to use git push --force afterwards so that it takes the commit - anyone

Use git rebase -i <after-this-commit> and replace "pick" on the second and subsequent commits with "squash" or "fixup", as described in the manual.

In this example, <after-this-commit> is either the SHA1 hash or the relative location from the HEAD of the current branch from which commits are analyzed for the rebase command. For example, if the user wishes to view 5 commits from the current HEAD in the past the command is git rebase -i HEAD~5.

Answered   2023-09-20 20:01:24

  • What is meant by <after-this-commit>? - anyone
  • <after-this-commit> is commit X+1 i.e. parent of the oldest commit you want to squash. - anyone
  • If you've already pushed the commits, you will need to push -f to forcibly move the remote branch to your new commit. This will upset anyone who was working on top of the old commits, though. - anyone
  • The difference between this rebase -i approach and reset --soft is, rebase -iallows me to retain the commit author, while reset --soft allows me to recommit. Sometimes i need to squash commits of pull requests yet maintaining the author information. Sometimes i need to reset soft on my own commits. Upvotes to both great answers anyways. - anyone
  • Use git rebase -i <after-this-commit> and replace "pick" on the second and subsequent commits with "squash" or "fixup", as described in the manual. uhhhh... wut? - anyone

You can use git merge --squash for this, which is slightly more elegant than git rebase -i. Suppose you're on master and you want to squash the last 12 commits into one.

WARNING: First make sure you commit your work—check that git status is clean (since git reset --hard will throw away staged and unstaged changes)

Then:

# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12

# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}

# Commit those squashed changes.  The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit

The documentation for git merge describes the --squash option in more detail.


Update: the only real advantage of this method over the simpler git reset --soft HEAD~12 && git commit suggested by Chris Johnsen in his answer is that you get the commit message prepopulated with every commit message that you're squashing.

Answered   2023-09-20 20:01:24

  • @Mark Amery: There are various reasons that I said that this is more elegant. For example, it doesn't involve unnecessarily spawning an editor and then searching and replacing for a string in the "to-do" file. Using git merge --squash is also easier to use in a script. Essentially, the reasoning was that you don't need the "interactivity" of git rebase -i at all for this. - anyone
  • Even though I appreciate the advantage of having a verbose commit message for big changes such as this, there's also a real disadvantage of this method over Chris's: doing a hard reset (git reset --hard) touches a lot more files. If you're using Unity3D, for instance, you'll appreciate less files being touched. - anyone
  • Another advantage is that git merge --squash is less likely to produce merge conflicts in the face of moves/deletes/renames compared to rebasing, especially if you're merging from a local branch. (disclaimer: based on only one experience, correct me if this isn't true in the general case!) - anyone
  • I'm always very reluctant when it comes to hard resets - I'd use a temporal tag instead of HEAD@{1} just to be on the safe side e.g. when your workflow is interrupted for an hour by a power outage etc. - anyone
  • @B T: Destroyed your commit? :( I'm not sure what you mean by that. Anything that you committed you'll easily be able to get back to from git's reflog. If you had uncommitted work, but the files were staged, you should still be able to get their contents back, although that will be more work. If your work wasn't even staged, however, I'm afraid there's little that can be done; that's why the answer says up-front: "First check that git status is clean (since git reset --hard will throw away staged and unstaged changes)". - anyone

2020 Simple solution without rebase :

git reset --soft HEAD~2 
git commit -m "new commit message"
git push -f

2 means the last two commits will be squashed. You can replace it by any number

Answered   2023-09-20 20:01:24

  • This solution shouldn't be encouraged. Using the-f (force) option in push is a dangerous practice, particularly if you're pushing to a shared repo (i.e public history) that'll make life dfficult for contributors - anyone
  • if the goal is to add this new commit to master as part of an ongoing pr, you could use git reset --soft $(git merge-base feature master) and then git commit. - anyone
  • @FXQuantTrader: If everybody force-pushes, the opposite the case. Almost always there is need to push with --force. Doing this aligns you well with the rest of the team and different remote locations as you know what is going on. Also you know which part of the history has stabilized already. No need to hide that. - anyone
  • I would argue that pushing with force should be actually encouraged with very well communicated impacts of this approach –– Force-pushing goes hand in hand with rebase and synchronization of work so it is better, in my humble opinion, that more people know about the effects of this action, rather than being an exoteric command that people are scared to use. - anyone
  • Agree with Matheus, force pushing to feature branches is fine and should be encouraged with rebasing. Pushing directly to main should always be restricted anyways. - anyone

I recommend avoiding git reset when possible -- especially for Git-novices. Unless you really need to automate a process based on a number of commits, there is a less exotic way...

  1. Put the to-be-squashed commits on a working branch (if they aren't already) -- use gitk for this
  2. Check out the target branch (e.g. 'master')
  3. git merge --squash (working branch name)
  4. git commit

The commit message will be prepopulated based on the squash.

Answered   2023-09-20 20:01:24

  • This is the safest method : no reset soft / hard (!!), or reflog used ! - anyone
  • It would be great if you expanded on (1). - anyone
  • @Adam: Basically, this means use the GUI interface of gitk to label the line of code that you are squashing and also label the base upon which to squash to. In the normal case, both of these labels will already exist, so step (1) can be skipped. - anyone
  • Note that this method doesn't mark the working branch as being fully merged, so removing it requires forcing deletion. :( - anyone
  • For (1), I've found git branch your-feature && git reset --hard HEAD~N the most convenient way. However, it does involve git reset again, which this answer tried to avoid. - anyone

Thanks to this handy blog post I found that you can use this command to squash the last 3 commits:

git rebase -i HEAD~3

This is handy as it works even when you are on a local branch with no tracking information/remote repo.

The command will open the interactive rebase editor which then allows you to reorder, squash, reword, etc as per normal.


Using the interactive rebase editor:

The interactive rebase editor shows the last three commits. This constraint was determined by HEAD~3 when running the command git rebase -i HEAD~3.

The most recent commit, HEAD, is displayed first on line 1. The lines starting with a # are comments/documentation.

The documentation displayed is pretty clear. On any given line you can change the command from pick to a command of your choice.

I prefer to use the command fixup as this "squashes" the commit's changes into the commit on the line above and discards the commit's message.

As the commit on line 1 is HEAD, in most cases you would leave this as pick. You cannot use squash or fixup as there is no other commit to squash the commit into.

You may also change the order of the commits. This allows you to squash or fixup commits that are not adjacent chronologically.

interactive rebase editor


A practical everyday example

I've recently committed a new feature. Since then, I have committed two bug fixes. But now I have discovered a bug (or maybe just a spelling error) in the new feature I committed. How annoying! I don't want a new commit polluting my commit history!

The first thing I do is fix the mistake and make a new commit with the comment squash this into my new feature!.

I then run git log or gitk and get the commit SHA of the new feature (in this case 1ff9460).

Next, I bring up the interactive rebase editor with git rebase -i 1ff9460~. The ~ after the commit SHA tells the editor to include that commit in the editor.

Next, I move the commit containing the fix (fe7f1e0) to underneath the feature commit, and change pick to fixup.

When closing the editor, the fix will get squashed into the feature commit and my commit history will look nice and clean!

This works well when all the commits are local, but if you try to change any commits already pushed to the remote you can really cause problems for other devs that have checked out the same branch!

enter image description here

Answered   2023-09-20 20:01:24

  • do you have to pick the top one and squash the rest? You should edit your answer to explain how to use the interactive rebase editor in more detail - anyone
  • Yes, leave pick in line 1. If you choose squash or fixup for the commit on line 1, git will show a message saying "error: cannot 'fixup' without a previous commit". Then it will give you the option to fix it: "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'." or you can just abort and start over: "Or you can abort the rebase with 'git rebase --abort'.". - anyone
  • I am constantly using this command. I recommend to add an alias for that called gr3: alias gr3='git rebase -i HEAD~3' - anyone
  • @Timo, correct. Oldest at the top, newest at the bottom. That's why you need to pick the first line. And when you choose squash or fixup on a line, it will put the changes into the commit on the line above. - anyone
  • This feels like the best answer when you know that you want to squash a certain amount of commits or at least see the commits you can squash by entering some arbitrary number. Generally, I use this . - anyone

Based on Chris Johnsen's answer,

Add a global "squash" alias from bash: (or Git Bash on Windows)

git config --global alias.squash '!f(){ git reset --soft HEAD~${1} && git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; };f'

... or using Windows' Command Prompt:

git config --global alias.squash "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"

Your `~/.gitconfig` should now contain this alias:
[alias]
    squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"

Usage:
git squash N

... Which automatically squashes together the last N commits, inclusive.

Note: The resultant commit message is a combination of all the squashed commits, in order. If you are unhappy with that, you can always git commit --amend to modify it manually. (Or, edit the alias to match your tastes.)

Answered   2023-09-20 20:01:24

  • Interesting, but I'd much rather type the squashed commit message myself, as a descriptive summary of my multiple commits, than have it auto-entered for me. So I'd rather specify git squash -m "New summary." and have N determined automatically as the number of unpushed commits. - anyone
  • @A-B-B, This sounds like a separate question. (I don't think it's exactly what the OP was asking; I've never felt a need for it in my git squash workflow.) - anyone
  • This is pretty sweet. Personally I'd like a version that uses the commit message from the first of the squashed-together commits. Would be good for things like whitespace tweaks. - anyone
  • @funroll Agreed. Just dropping the last commit msg is a super common need for me. We should be able to devise that... - anyone
  • @A-B-B you can use git commit --amend to further change the message, but this alias lets you have a good start on what should be in the commit message. - anyone

In the branch you would like to combine the commits on, run:

git rebase -i HEAD~(n number of commits back to review)

example:

git rebase -i HEAD~2

This will open the text editor and you must switch the 'pick' in front of each commit with 'squash' if you would like these commits to be merged together. From documentation:

p, pick = use commit

s, squash = use commit, but meld into previous commit

For example, if you are looking to merge all the commits into one, the 'pick' is the first commit you made and all future ones (placed below the first) should be set to 'squash'. If using vim, use :x in insert mode to save and exit the editor.

Then to continue the rebase:

git add .

git rebase --continue

For more on this and other ways to rewrite your commit history see this helpful post

Answered   2023-09-20 20:01:24

  • Please also explain what the --continue and vim :x does. - anyone
  • The rebase will happen in blocks as it goes through the commits on your branch, after you git add the correct configuration in your files you use git rebase --continue to move to the next commit and start to merge. :x is one command that will save the changes of the file when using vim see this - anyone

To do this you can use following git command.

 git rebase -i HEAD~n

n(=4 here) is the number of last commit. Then you got following options,

pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....

Update like below pick one commit and squash the others into the most recent,

p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....

For details click on the Link

Answered   2023-09-20 20:01:24

  • More details on SO on the git rebase -interactive here - anyone

Here is another visual example of what would follow after executing: git rebase -i HEAD~3

Here there is a visual example of what would follow after executing git rebase for the three last commits

Source: https://www.git-tower.com/learn/git/faq/git-squash/

Answered   2023-09-20 20:01:24

If you use TortoiseGit, you can the function Combine to one commit:

  1. Open TortoiseGit context menu
  2. Select Show Log
  3. Mark the relevant commits in the log view
  4. Select Combine to one commit from the context menu

Combine commits

This function automatically executes all necessary single git steps. Unfortunatly only available for Windows.

Answered   2023-09-20 20:01:24

  • As far as I am aware, this will not work for merge commits. - anyone
  • Although it's not commented by any others, this even works for commits which are not at the HEAD. For instance, my need was to squash some WIP commits I did with a more sane description before pushing. Worked beautifully. Of course, I still hope I can learn how to do it by commands. - anyone
  • This is superb! Everything will be done by just couple of mouse clicks and I could merge 200 commits of old repo before archiving! Thanks. Really useful to make branch tree clean and easily review code changes at once. - anyone
  • You can similarly select n commits and squash them via GUI of GitHub Desktop desktop.github.com - anyone

Many answers are based on git rebase command, but in my experience it is somewhat complex and advanced for git-beginners.

Let's say you want to squash last 3 commits. Then following are the steps:

  • Note down current commit id: Use git log -1 --oneline and note the commit-id of the present state (just in case you do something wrong with git reset)
  • Go back 3 commits: Using git reset --soft HEAD~3 you'll go back 3 commits (and sort of forget that you've had made these three commits earlier)
  • Do a new commit: Now simply do git commit -m <NEW_SINGLE_MESSAGE> which will automatically combine the three commits under your message

In case something goes wrong with git reset, you can again return to the original state by git reset --soft <ORIGINAL_COMMIT>

Answered   2023-09-20 20:01:24

  • It's IMPORTANT to use "--soft" in step #2, otherwise you will just revert to a state three commits ago. I think it should be emphasized. - anyone
  • Also crucial is that you will need --force when you want to push your squashed commit - anyone
  • if you're EXTRA nervous like me, you can always create a new branch right before you do git reset --soft HEAD~3 as a backup. This way you have an exact, easy to use replica of where you started. Even after you've force-pushed at the end - anyone

Based on this article I found this method easier for my usecase.

My 'dev' branch was ahead of 'origin/dev' by 96 commits (so these commits were not pushed to the remote yet).

I wanted to squash these commits into one before pushing the change. I prefere to reset the branch to the state of 'origin/dev' (this will leave all changes from the 96 commits unstaged) and then commit the changes at once:

git reset origin/dev
git add --all
git commit -m 'my commit message'

Answered   2023-09-20 20:01:24

  • Just what I needed. Squash down commits from my feature branch, and then I git cherry pick that commit into my master. - anyone
  • This does not squash previous commits! - anyone
  • could you elaborate that a bit further @igorGanapolsky ? - anyone
  • @trudolf This isn't really squashing (picking individual commits to squash). This is more of committing all of your changes at once. - anyone
  • yes, hence it squashes all of your commits into one. congratulations! - anyone

Anomies answer is good, but I felt insecure about this so I decided to add a couple of screenshots.

Step 0: git log

See where you are with git log. Most important, find the commit hash of the first commit you don't want to squash. So only the :

enter image description here

Step 1: git rebase

Execute git rebase -i [your hash], in my case:

$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d

Step 2: pick / squash what you want

In my case, I want to squash everything on the commit that was first in time. The ordering is from first to last, so exactly the other way as in git log. In my case, I want:

enter image description here

Step 3: Adjust message(s)

If you have picked only one commit and squashed the rest, you can adjust one commit message:

enter image description here

That's it. Once you save this (:wq), you're done. Have a look at it with git log.

Answered   2023-09-20 20:01:24

  • would be nice to see the final result, e.g., git log - anyone
  • @Axalix Did you remove all your lines? That's how you lose your commits. - anyone

I think the easiest way to do this is by making a new branch based on master and doing a merge --squash of the feature branch.

git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch

Then you have all of the changes ready to commit.

Answered   2023-09-20 20:01:24

  • This is a nice alternative to achieve a similar end result. I came looking on how to do it using rebase, but I chose this way better. I keep forgetting about the existence of git merge - anyone
  • I was trying to do other solutions, but for w/e reason they weren't working. This one did. - anyone
  • Thank you man! It helped me. I'm already desperate to fix anything... - anyone
  • This works like charm. But I think there is one assumption, you need make sure the feature_branch has 0 behind master branch. - anyone
  • Nice technique to split the operations into a staging branch! One can then diff clean branches more easily. - anyone

Procedure 1

1) Identify the commit short hash

# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....

Here even git log --oneline also can be used to get short hash.

2) If you want to squash (merge) last two commit

# git rebase -i deab3412 

3) This opens up a nano editor for merging. And it looks like below

....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....

4) Rename the word pick to squash which is present before abcd1234. After rename it should be like below.

....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....

5) Now save and close the nano editor. Press ctrl + o and press Enter to save. And then press ctrl + x to exit the editor.

6) Then nano editor again opens for updating comments, if necessary update it.

7) Now its squashed successfully, you can verify it by checking logs.

# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....

8) Now push to repo. Note to add + sign before the branch name. This means forced push.

# git push origin +master

Note : This is based on using git on ubuntu shell. If you are using different os (Windows or Mac) then above commands are same except editor. You might get different editor.

Procedure 2

  1. First add the required files for commit
git add <files>
  1. Then commit using --fixup option and the OLDCOMMIT should be on which we need to merge(squash) this commit.
git commit --fixup=OLDCOMMIT

Now this creates a new commit on top of HEAD with fixup1 <OLDCOMMIT_MSG>.

  1. Then execute below command to merge(squash) the new commit to the OLDCOMMIT.
git rebase --interactive --autosquash OLDCOMMIT^

Here ^ means the previous commit to OLDCOMMIT. This rebase command opens interactive window on a editor (vim or nano) on that we no need to do anything just save and exiting is sufficient. Because the option passed to this will automatically move the latest commit to next to old commit and change the operation to fixup (equivalent to squash). Then rebase continues and finishes.

Procedure 3

  1. If need to add new changes to the last commit means --amend can be used with git-commit.
    # git log --pretty=oneline --abbrev-commit
    cdababcd Fix issue B
    deab3412 Fix issue A
    ....
    # git add <files> # New changes
    # git commit --amend
    # git log --pretty=oneline --abbrev-commit
    1d4ab2e1 Fix issue B
    deab3412 Fix issue A
    ....  

Here --amend merges the new changes to last commit cdababcd and generates new commit ID 1d4ab2e1

Conclusion

  • Advantage of 1st procedure is to squash multiple commits and to reorder. But this procedure will be difficult if we need to merge a fix to very old commit.
  • So the 2nd procedure helps to merge the commit to very old commit easily.
  • And the 3rd procedure is useful in a case to squash a new changes to last commit.

Answered   2023-09-20 20:01:24

  • It only updates the last two commits even I reset to a commit Id to the 6th last commit, do not know why - anyone
  • Even you can rearrange the commit order. It works fine. - anyone

To squash the last 10 commits into 1 single commit:

git reset --soft HEAD~10 && git commit -m "squashed commit"

If you also want to update the remote branch with the squashed commit:

git push -f

Answered   2023-09-20 20:01:24

  • --force is dangerous when multiple people are working on a shared branch as it blindly updates remote with your local copy. --force-with-lease could have been better as it makes sure that remote has no commits from others since you last fetched it. - anyone

If for example, you want to squash the last 3 commits to a single commit in a branch (remote repository) in for example: https://bitbucket.org

What I did is

  1. git reset --soft HEAD~3
  2. git commit
  3. git push origin <branch_name> --force

Answered   2023-09-20 20:01:24

  • Just be careful, Since if you use force then there is no way to retrieve the previous commits since you removed it - anyone
  • Nice and quick solution, as for me. - anyone
  • Force is destructive. This is not squashing commits rather removing the last three commits and adding them back as a fourth (now new) commit, essentially rewriting the history which can break the repo for other users until they also force pull. This will also remove any other commits your team has pushed meanwhile. - anyone

If you are on a remote branch(called feature-branch) cloned from a Golden Repository(golden_repo_name), then here's the technique to squash your commits into one:

  1. Checkout the golden repo

    git checkout golden_repo_name
    
  2. Create a new branch from it(golden repo) as follows

    git checkout -b dev-branch
    
  3. Squash merge with your local branch that you have already

    git merge --squash feature-branch
    
  4. Commit your changes (this will be the only commit that goes in dev-branch)

    git commit -m "My feature complete"
    
  5. Push the branch to your local repository

    git push origin dev-branch
    

Answered   2023-09-20 20:01:24

  • Since I was just squashing ~100 commits (for syncing svn branches via git-svn), this is far faster than interactively rebasing! - anyone
  • Reading down, I see @Chris's comment, which is what I used to do (rebase --soft...) - too bad that stackoverflow is no longer putting the answer with hundreds of upvotes at the top... - anyone
  • agree with you @sage, lets hope they might do it sometime in the future - anyone
  • This is the right way. Rebase approach is good, but should only be used for squash as a last resort solution. - anyone
  • I'm getting everything up to date - anyone

Did anyone mention how easy it is to do on IntelliJ IDEA UI:

  • Go to git window
  • Manually select all the commits you want to merge into one.
  • Right-click > Squash Commits > Edit the squashed commit message
  • Click on branch name on left side > Right-click > Push > Force Push

enter image description here

Answered   2023-09-20 20:01:24

  • This doesn't work for merge commits - anyone
  • This works in Android Studio also. - anyone

What can be really convenient:
Find the commit hash you want to squash on top of, say d43e15.

Now use

git reset d43e15
git commit -am 'new commit name'

Answered   2023-09-20 20:01:24

  • This. Why don't more people use this? It's way faster than rebasing and squashing individual commits. - anyone
  • @a3y3 maybe the working directory is not clean. one might stash changes first, in that case. - anyone
  • Because people need to put all logs of each commit into the new one... manually - anyone

method 1 if you have many commits

git rebase -i master then press keyboard 'i' to edit

you will see like this:

pick etc1
pick etc2
pick etc2

replace the word pick with 'f' and press esc y :wq

pick etc1 //this commit will the one commit
f etc2
f etc2

and press this command

git push origin +head

method 2 if you have few commits you can do this to delete a commit, you need to do same for delete your second commit and so on

git reset --soft HEAD^1 // or git reset --soft head~1
git commit --amend //then press `:wq` 
git push -f

method 3 if you already have one commit and you dont want submit another commit more

git add files...
git commit --amend  //then press `:wq`
git push origin +head

Answered   2023-09-20 20:01:24

  • It should be noted, that this answer assumes you have vi set as your default editor in git. - anyone
  • Method 1 is nice and very easy, thanks @titleLogin!! - anyone

simple solution:

git reset --soft HEAD~5

git commit -m "commit message"

git push origin branch --force-with-lease

Answered   2023-09-20 20:01:24

  • nice try, but there is a better way mentioned here with rebase - anyone
  • Thanks for your answer. I tried rebase a lot of times, but only your's worked. - anyone

If you want to squish every commit into a single commit (e.g. when releasing a project publicly for the first time), try:

git checkout --orphan <new-branch>
git commit

Answered   2023-09-20 20:01:24

This is super-duper kludgy, but in a kind of cool way, so I'll just toss it into the ring:

GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo

Translation: provide a new "editor" for git which, if the filename to be edited is git-rebase-todo (the interactive rebase prompt) changes all but the first "pick" to "squash", and otherwise spawns vim - so that when you're prompted to edit the squashed commit message, you get vim. (And obviously I was squashing the last five commits on branch foo, but you could change that however you like.)

I'd probably do what Mark Longair suggested, though.

Answered   2023-09-20 20:01:24

  • +1: that's fun and instructive, in that it's wasn't at all obvious to me that you can put anything more complex than the name of a program in the GIT_EDITOR environment variable. - anyone
  • You could simplify this using gawk. git -c core.editor="gawk -i inplace '{if(NR>1 && \$1==\"pick\"){\$1=\"squash\"} print \$0}'" rebase -i --autosquash HEAD~5. - anyone
  • I prefer this command with GIT_SEQUENCE_EDITOR, for I need autostash - anyone

Simple one-liner that always works, given that you are currently on the branch you want to squash, master is the branch it originated from, and the latest commit contains the commit message and author you wish to use:

git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}

Answered   2023-09-20 20:01:24

  • I have been absolutely livid with frustration about squashing commits and how stupidly complicated it is - just effing use the last message and squash them all to one commit! Why is it that hard???? This one liner does that for me. Thank you from the bottom of my angry heart. - anyone

⚠️ WARNING: "My last X commits" might be ambiguous.

  (MASTER)  
Fleetwood Mac            Fritz
      ║                    ║
  Add Danny  Lindsey     Stevie       
    Kirwan  Buckingham    Nicks                                              
      ║         ╚═══╦══════╝     
Add Christine       ║          
   Perfect      Buckingham
      ║           Nicks            
    LA1974══════════╝                                    
      ║                  
      ║                  
    Bill <══════ YOU ARE EDITING HERE
  Clinton        (CHECKED OUT, CURRENT WORKING DIRECTORY)              

In this very abbreviated history of the https://github.com/fleetwood-mac/band-history repository you have opened a pull request to merge in the the Bill Clinton commit into the original (MASTER) Fleetwood Mac commit.

You opened a pull request and on GitHub you see this:

Four commits:

  • Add Danny Kirwan
  • Add Christine Perfect
  • LA1974
  • Bill Clinton

Thinking that nobody would ever care to read the full repository history. (There actually is a repository, click the link above!) You decide to squash these commits. So you go and run git reset --soft HEAD~4 && git commit. Then you git push --force it onto GitHub to clean up your PR.

And what happens? You just made single commit that get from Fritz to Bill Clinton. Because you forgot that yesterday you were working on the Buckingham Nicks version of this project. And git log doesn't match what you see on GitHub.

🐻 MORAL OF THE STORY

  1. Find the exact files you want to get to, and git checkout them
  2. Find the exact prior commit you want to keep in history, and git reset --soft that
  3. Make a git commit that warps directly from the from to the to

Answered   2023-09-20 20:01:24

  • This is 100% the easiest way to do this. If your current HEAD is the correct state you want, then you can skip #1. - anyone
  • This is the only way I know that allows to rewrite the first commit history. - anyone

Easiest way to do this is using GitHub Desktop. Just select all the commits in your History, right-click and select "Squash x commits":

GitHub Desktop squash

Answered   2023-09-20 20:01:24

  • yes, this solved the problem. however is not that-pro comparing to commandline answers - anyone
  • @Siwei doesn’t matter how “pro” it is if it works - anyone
  • I tried this because the other answers gave me a super long list of commits that were intimidating to deal with (at work we have a LOT of people touching code and feature branches abound). The message I got when attempting to squash was: "Unable to squash. Squashing replays all commits up to the last one required for the squash. A merge commit cannot exist among those commits." - anyone
  • This should have more votes so it can be more visible on the first page :) - anyone

If you don't care about the commit messages of the in-between commits, you can use

git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend

Answered   2023-09-20 20:01:24

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.

enter image description here

Answered   2023-09-20 20:01:24

  • With GitLab Enterprise Edition 12.8.6-ee it just randomly took a commit message for the squashed commit... - anyone