Squash my last X commits together using Git

2,657,108

Solution 1

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.

Solution 2

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

Solution 3

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.

Solution 4

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.

Solution 5

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

Share:
2,657,108
markdorison
Author by

markdorison

Updated on March 30, 2022

Comments

  • markdorison
    markdorison about 2 years

    How can I squash my last X commits together into one commit using Git?

  • Mark Longair
    Mark Longair over 13 years
    +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.
  • Mark Longair
    Mark Longair almost 11 years
    @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.
  • Adrian Ratnapala
    Adrian Ratnapala over 10 years
    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.
  • cregox
    cregox over 10 years
    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.
  • Cheezmeister
    Cheezmeister over 10 years
    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!)
  • Asclepius
    Asclepius about 10 years
    When doing the soft reset, what's the best way to auto-determine the target commit id, i.e. of the last pushed commit? I don't want to have to lookup and count 3, for example.
  • Asclepius
    Asclepius about 10 years
    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.
  • Chris Johnsen
    Chris Johnsen about 10 years
    @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.
  • EthanB
    EthanB about 10 years
    @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.)
  • n8tr
    n8tr about 10 years
    I like this solution in general but like providing my own commit message. Here is a solution that sets up an alias so that you can do git squash 3 'my commit message'
  • funroll
    funroll almost 10 years
    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.
  • Tobias Kienzler
    Tobias Kienzler almost 10 years
    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.
  • Axel M. Garcia
    Axel M. Garcia over 9 years
    @funroll Agreed. Just dropping the last commit msg is a super common need for me. We should be able to devise that...
  • EthanB
    EthanB over 9 years
    @SteveClay Somewhere around here: git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\" :)
  • 2rs2ts
    2rs2ts over 9 years
    This kinda-sorta required me to push -f but otherwise it was lovely, thanks.
  • 2540625
    2540625 over 9 years
    What is meant by <after-this-commit>?
  • joozek
    joozek over 9 years
    <after-this-commit> is commit X+1 i.e. parent of the oldest commit you want to squash.
  • sschuberth
    sschuberth over 9 years
    Here's an alias in case you'd like to fixup instead of squash: fixup = "!f() { base=${1:-2}; git reset --soft HEAD~$base && git commit -C ORIG_HEAD~$(expr $base - 1); }; f".
  • interfect
    interfect over 9 years
    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.
  • Mark K Cowan
    Mark K Cowan over 9 years
    git log --one-line or something like that will display a list of commit IDs and messages, one per line. Very useful with this solution.
  • Andy Hayden
    Andy Hayden over 9 years
    you might like to count to the branch or commit with something like git rev-list @{1}..head --count
  • Andy Hayden
    Andy Hayden over 9 years
    I was hoping the following would work, it doesn't quite: f(){c=$(git rev-list ${1}..head --count); git reset --soft HEAD~$c && git commit --edit -m\"$(git log --format=%B --reverse ${1}..head)\"; }
  • Tobias Kienzler
    Tobias Kienzler over 9 years
    @PaulDraper well let's say due to a power outage or other interruption you cannot finish this, and next time you're in that machine out of habit you do a git pull, obtaining a few commits but not the ones you wanted to squash. I don't know if there also is a HEAD@{2} etc but with a temporary tag you don't have to remember... of course so long as you don't git gc --prune you can still retrieve your HEAD, but it's just more tedious
  • Paul Draper
    Paul Draper over 9 years
    @TobiasKienzler, you may be interested in git reflog. Git already "tags" recent commits for this purpose. git gc --prune shouldn't change that, unless you ask to expire the reflog entries. It may all be a matter of preference.
  • raine
    raine over 9 years
    I tried the alias but I'm not sure if the sed replaces are having any effect. What should they do?
  • Tobias Kienzler
    Tobias Kienzler over 9 years
    @PaulDraper Thanks for the info, yeah it's probably preference - and Python's "Explicit is better than implicit"
  • joedragons
    joedragons about 9 years
    I personally found this article (git-scm.com/book/en/v2/Git-Tools-Rewriting-History) a lot easier to follow than the one linked (same site).
  • Ethan
    Ethan about 9 years
    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.
  • David Victor
    David Victor almost 9 years
    Just what I needed. Squash down commits from my feature branch, and then I git cherry pick that commit into my master.
  • Mohamed El Mahallawy
    Mohamed El Mahallawy almost 9 years
    is there a way to squash all the commits for that branch instead of last X commits?
  • EthanB
    EthanB almost 9 years
    @MohamedElMahallawy What would that even mean? All commits to branch X, since it diverged from branch Y? You'd still need to enter the name of branch Y. For me, it's simpler to just count the number of commits.
  • zionyx
    zionyx almost 9 years
    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.
  • Matthias M
    Matthias M almost 9 years
    @2rs2ts git push -f sound dangerous. Take care to only squash local commits. Never touch pushed commits!
  • IgorGanapolsky
    IgorGanapolsky over 8 years
    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.
  • IgorGanapolsky
    IgorGanapolsky over 8 years
    This does not squash previous commits!
  • sage
    sage over 8 years
    Since I was just squashing ~100 commits (for syncing svn branches via git-svn), this is far faster than interactively rebasing!
  • sage
    sage over 8 years
    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...
  • Sandesh Kumar
    Sandesh Kumar over 8 years
    agree with you @sage, lets hope they might do it sometime in the future
  • trudolf
    trudolf over 8 years
    could you elaborate that a bit further @igorGanapolsky ?
  • IgorGanapolsky
    IgorGanapolsky over 8 years
    @trudolf This isn't really squashing (picking individual commits to squash). This is more of committing all of your changes at once.
  • trudolf
    trudolf over 8 years
    yes, hence it squashes all of your commits into one. congratulations!
  • yoyodunno
    yoyodunno over 8 years
    I always end up screwing up the rebase for some reason, reset --soft seems more intuitive for most purposes.
  • Coder
    Coder over 8 years
    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?
  • B T
    B T about 8 years
    Wonderful.. this destroyed my commit. -1
  • Mark Longair
    Mark Longair about 8 years
    @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)".
  • Ethan
    Ethan almost 8 years
    @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.)
  • Ethan
    Ethan almost 8 years
    @Sergio squashing can hide some conflicts if you do rebases instead of merges. The merge only happens on the final commit, so it's equivalent to merging the squashed version. But a rebase has to process each commit, so you might have transitory conflicts that are unrolled by a later commit and you'll get a conflict on each as the rebase is processed. Rebasing the squashed version hides this intermediate history.
  • vikramvi
    vikramvi almost 8 years
    after doing this while trying to push getting error error: failed to push some refs to '[email protected]:....' hint: Updates were rejected because the tip of your current branch is behind. I found how to solve this here: stackoverflow.com/questions/5667884/…
  • dashesy
    dashesy almost 8 years
    @EthanB yes, merging with the branch ancestor (at the time of initial branching) is quite useful. The problem is we cannot rely on the ancestor's name because it could be changed. Right now the best way is to look at git-log, count and use the marvelous squash alias of yours.
  • dashesy
    dashesy almost 8 years
    @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.
  • Zach Saucier
    Zach Saucier almost 8 years
    I also need to use git push --force afterwards so that it takes the commit
  • Cheeso
    Cheeso almost 8 years
    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?
  • Victor Sergienko
    Victor Sergienko almost 8 years
    Make sure your working copy has no other changes (git stash them or something). Otherwise, you have the chance to commit them into the squashed commit too, which might be not something you intend to do.
  • EthanB
    EthanB almost 8 years
    @dashesy Thanks for the (unintentional) reminder that folks might not be familiar with --amend. I've updated the answer to provide the hint.
  • Coder
    Coder over 7 years
    yes I solved with git push --force-with-lease when the branch is mine. I don't have to take care about the history :)
  • stevenhaddox
    stevenhaddox over 7 years
    I want to upvote this, but I can't bring myself to be the one who changes the upvoted count from 1337...
  • TeChn4K
    TeChn4K over 7 years
    This is the safest method : no reset soft / hard (!!), or reflog used !
  • Alexander Mills
    Alexander Mills over 7 years
    is there a way to squash using git rebase without using interactive mode?
  • GreensterRox
    GreensterRox over 7 years
    This helped me when git rebase --interactive was giving me error: could not apply. Possibly because one of the commits I was trying to squash was a merge with a resolved conflict.
  • Adam
    Adam about 7 years
    It would be great if you expanded on (1).
  • Brent Bradburn
    Brent Bradburn about 7 years
    @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.
  • Kyrstellaine
    Kyrstellaine about 7 years
    Note that this method doesn't mark the working branch as being fully merged, so removing it requires forcing deletion. :(
  • Thorkil Holm-Jacobsen
    Thorkil Holm-Jacobsen about 7 years
    As far as I am aware, this will not work for merge commits.
  • eis
    eis about 7 years
    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.
  • soMuchToLearnAndShare
    soMuchToLearnAndShare about 7 years
    the git reset --soft HEAD~3 definitely is better in terms of number of conflicts to solve. our test case was HEAD~6, but one of the commit was a merge commit, so we had 45 commits and the rebase -i HEAD~6 gave us so many conflicts which end up failing the build. And when we used reset --soft it gave us 0 conflicts to solve.
  • Tom Russell
    Tom Russell almost 7 years
    git push -f doesn't appear to alter the commit log of the upsteam repository.
  • Brent Bradburn
    Brent Bradburn almost 7 years
    @Kyrstellaine: Yes, the merge does not affect the working branch -- so you will have to remove it manually, if you want it removed (an optional step 5).
  • Eric Schnipke
    Eric Schnipke over 6 years
    @Cheeso, this article helps explain the squash and fixup designations used during an interactive rebase. Specifically, the squash and fixup designations tell Git how to combine the commit messages.
  • Tyrone Wilson
    Tyrone Wilson over 6 years
    I put this into ~/bin/git-funge as a script because we have to have single commit messages on pull requests. Now I just write git funge HEAD~3 and edit my messages to suite a single PR message and save and it is all sorted. :) Thanks for a great answer. (funge is my way of meaning fungible but reads better as a verb :P)
  • polynomial_donut
    polynomial_donut over 6 years
    The explanation for <after-this-commit> is needlessly complicated. With a simpler explanation (the chosen words are almost self-explanatory, already, but the given explanation overcomplicates), this answer would be perfect.
  • CIRCLE
    CIRCLE over 6 years
    It should be <after-and-including-this-commit>
  • Spencer Williams
    Spencer Williams over 6 years
    I feel like you really mean <before-this-commit>, but you describe such an argument as a commit in the past.
  • rsmith54
    rsmith54 about 6 years
    I think better than those saying to force push is to just make a new feature branch. So something like git checkout -b feature-name-squashed, follow these directions, then push the new squashed version of the branch. If you do this, you will have access to the full commit logs of the feature (on feature-name) if you ever need them.
  • moodboom
    moodboom about 6 years
    Having to git push -f is always a requirement after rewriting history. It CAN be bad to rewrite history, certainly if you are doing it to a shared repository. But that's a separate subject not related to this answer (which is great).
  • Charles Roberto Canato
    Charles Roberto Canato about 6 years
    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.
  • Geynen
    Geynen about 6 years
  • mageos
    mageos about 6 years
    If you have already pushed the commits that you are squashing and you want to avoid having to do a force push, create a new branch at the commit you are at and push it: git checkout -b new-branch-name && git push -u
  • Malcolm Johnson-Brown
    Malcolm Johnson-Brown almost 6 years
    It only updates the last two commits even I reset to a commit Id to the 6th last commit, do not know why
  • rashok
    rashok almost 6 years
    Even you can rearrange the commit order. It works fine.
  • Kas Elvirov
    Kas Elvirov almost 6 years
    From many opinions I like your approach. It's very convenient and fast
  • Kellen Stuart
    Kellen Stuart almost 6 years
    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
  • br3nt
    br3nt almost 6 years
    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'.".
  • jpganz18
    jpganz18 over 5 years
    maybe is outdated? I did the reset --hard and when doing the merge --squash I get this error "fatal: You cannot combine --squash with --no-ff."
  • jackrabbithanna
    jackrabbithanna over 5 years
    What you supposed to do after the squashing to get it pushed?
  • Jared
    Jared over 5 years
    For some reason this overwrites existing changes on master that have been pushed to it after I branched off master to create feature branch. Not sure why though. I will try doing it the other way, as mentioned in the linked article.
  • Jared
    Jared over 5 years
    As continuation to my comment. When I create a merge branch, then merge feature branch into it, squashing works fine. It requires a little more work, but still less than cherry-picking everything by hand.
  • not2qubit
    not2qubit over 5 years
    @Geynen Thanks, that link provide the only description that made full sense. Now I can even tell people that <after-this-commit>, means exactly that. It is non-inclusive and will let you pick and squash commits after that one, as long as none of them are an initial commit. If you want to include initial commit, you need to use the git reset method instead.
  • not2qubit
    not2qubit over 5 years
    Please also explain what the --continue and vim :x does.
  • aabiro
    aabiro over 5 years
    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
  • Timothy L.J. Stewart
    Timothy L.J. Stewart over 5 years
    would be nice to see the final result, e.g., git log
  • Cyker
    Cyker over 5 years
    I thik this is currently the best method. But watch our for its caveat: If you have unstaged or uncommitted changes, Git won't stop you from performing these operations, and your uncommitted changes will get merged into the squashed commit. This probably is or is not what you want.
  • Raphael
    Raphael over 5 years
    A noteworthy difference between this approach and "real" rebase is that the authors of the original commits are lost here.
  • Stan
    Stan over 5 years
    This is 100% the easiest way to do this. If your current HEAD is the correct state you want, then you can skip #1.
  • ColinM
    ColinM over 5 years
    Not sure what version this was added in but you can use this one liner to reliably reuse the latest commit message and author: git reset --soft {base-commit} && git commit --reuse=HEAD@{1}
  • csey
    csey over 5 years
    Note that you can re-order commits when rebasing interactively - e.g. if you made commits A, B, then C, you can squash commit C onto A by swapping the order of the lines of B and C when prompted (and changing C to "squash"). Useful if you have committed more work since the commit you are trying to squash.
  • rockdaboot
    rockdaboot over 5 years
    Try to get used to git push --force-with-lease instead of git push -f !
  • Kyle Krzeski
    Kyle Krzeski over 5 years
    Don't forget + if your other commits were already pushed to remote - i.e. git push origin +name-of-branch
  • Axalix
    Axalix over 5 years
    This is the right way. Rebase approach is good, but should only be used for squash as a last resort solution.
  • Chiramisu
    Chiramisu over 5 years
    @AdrianRatnapala I've not tried it yet, but EthanB added an answer below, based on this one, to do something like that using .gitconfig.
  • Zach Saucier
    Zach Saucier about 5 years
    I accidentally did git reset --soft HEAD~3 twice. Be aware that they stack.
  • LosManos
    LosManos about 5 years
    FYI: If you do git merge --squash HEAD@{1} and get a error: unknown switch 'c back you are probably running in powershell console. Probably easiest way to continue is to temporarily move to ordinary command shell through cmd then do your git merge --squash HEAD@{1} and then go back to powershell through leaving the command shell by exit. (I haven't bothered to figure out how to run the git merge --squash HEAD@{1} through powershell.)
  • Locane
    Locane about 5 years
    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.
  • ColinM
    ColinM about 5 years
    git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
  • cowlinator
    cowlinator about 5 years
    For those curious about HEAD..HEAD@{1}, r1..r2 specifies a range of commits that are reachable from r2 excluding those that are reachable from r1, and <refname>@{<n>} specifies the n-th prior value of that ref. So here, HEAD..HEAD@{1} means all the commits between the current HEAD and where HEAD was right before the reset.
  • a3y3
    a3y3 about 5 years
    This. Why don't more people use this? It's way faster than rebasing and squashing individual commits.
  • a3y3
    a3y3 about 5 years
    @Axalix Did you remove all your lines? That's how you lose your commits.
  • Albert Ruelan
    Albert Ruelan almost 5 years
    Just be careful, Since if you use force then there is no way to retrieve the previous commits since you removed it
  • dyslexicanaboko
    dyslexicanaboko almost 5 years
    If you want to target a commit specifically you can do so by providing the hash, but it wasn't as simple as replacing the index with the hash. I got this by using Source Tree: git -c diff.mnemonicprefix=false -c core.quotepath=false --no-optional-locks reset -q --soft <Your SHA here> git commit The hash that should be provided though should be the commit just before your target.
  • joel
    joel over 4 years
    If my current branch has branched off 'otherbranch', and I want to squash everything I did on the current branch before I merge it into 'otherbranch', I can do git reset --soft otherbranch rather than having to work out how many commits ago that was
  • Iulian Onofrei
    Iulian Onofrei over 4 years
    But shouldn't it be For example, if the user wishes to view 5 commits from the current HEAD in the past the command is git rebase -i HEAD~6. instead?
  • RTHarston
    RTHarston over 4 years
    This worked for me where the git rebase -i did not. I was trying to squash some work that had a couple of merge commits thrown in the middle and I kept running in to conflicts (I didn't think that was possible, but I'm guessing it was because of the merge commits that were removed). Tried this and voila! No complaints, worked first time. Highly recommended. :)
  • Bogatyr
    Bogatyr over 4 years
    @Adam: There is an assumption in this answer that you already have a working branch that has diverged from your target branch, and that you want to squash all the commits on the working branch from the divergence point from target, and apply them to the target branch in a single squash (non-merge) commit. If your changes are already committed on the target branch, this answer doesn't help, since if you follow it, it will just add a new commit to your target on top of the ones you want to squash (in that case, you'd need to rebase to rewrite history on the target branch to remove commits).
  • Varun Singh
    Varun Singh over 4 years
    Put this in your bashrc for an alias to squash the last two commits together, using the message of the first commit --> alias gsquash="git reset --soft HEAD~2 && git commit --edit -m\"$(git log --format=%B HEAD@{0}..HEAD@{2})\"" # Squash last two commits together, using message of first commit.
  • Vadorequest
    Vadorequest over 4 years
    This is the only way I know that allows to rewrite the first commit history.
  • Brian Pursley
    Brian Pursley over 4 years
    Leaving this here in case it helps someone. Using bash, I just define a function squash() { git reset --soft HEAD~$1; git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; } and then I can just say squash 2 or squash 3.
  • bharath
    bharath over 4 years
    --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.
  • Brent Bradburn
    Brent Bradburn about 4 years
    Futher elaboration regarding (1): If you are adept with gitk (a GUI tool), then you can (with care) move and rename branches. For example, you can place a new branch called tobesquashed on the same commit with master, then delete master and recreate it at the base of the to-be-squashed commits. ... Then proceed with step 2.
  • Wolfson
    Wolfson about 4 years
    With GitLab Enterprise Edition 12.8.6-ee it just randomly took a commit message for the squashed commit...
  • B. Bohdan
    B. Bohdan almost 4 years
    Nice and quick solution, as for me.
  • V Cezar
    V Cezar almost 4 years
    Easy. This is the best answer.
  • typesanitizer
    typesanitizer almost 4 years
    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.
  • Osama Shabrez
    Osama Shabrez almost 4 years
    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.
  • Menai Ala Eddine - Aladdin
    Menai Ala Eddine - Aladdin almost 4 years
    @BrentBradburn, would you provide an real-world example, I did not understand your answer.
  • Brent Bradburn
    Brent Bradburn almost 4 years
    @MenaiAlaEddine-Aladdin, gitk is a GUI for viewing your Git repository. I highly recommend using gitk or something similar. Exploring your repository with a graphical tool should help bring this into focus. Using gitk, you can attach the head of branches (such as 'master' and 'my_new_branch') to specific commits in the history. Once this is done, the steps 2, 3, and 4 are trivial. See my previous "Further elaboration" comment for more...
  • lasec0203
    lasec0203 almost 4 years
    I agree that this is the safest way, and also as a note to Git-novices, if it's your first time using this command you can also create duplicate branches of the target and working branch to get a feel for the command first before actually applying it to the real branches.
  • lasec0203
    lasec0203 almost 4 years
    Important to note that method assumes the working branch being squash will be deleted afterwards, because if you add more changes to it and then do a regular git merge it will bring merge all of the previous commits that were squashed
  • Felipe Romero
    Felipe Romero over 3 years
    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
  • Flov
    Flov over 3 years
    I would also argue that "elegant" is not the right word to use here. I would say it's slightly quicker rather than slightly more elegant since you don't spawn an editor. It's a great advantage but a hard reset also seems dangerous since you could easily lose your data.
  • Flov
    Flov over 3 years
    I am constantly using this command. I recommend to add an alias for that called gr3: alias gr3='git rebase -i HEAD~3'
  • MING WU
    MING WU over 3 years
    @LosManos I just found that we can use the command in powershell. git merge --squash 'HEAD@{1}' just put HEAD@{1} inside ' ' to make it work.
  • Timo
    Timo over 3 years
    I get "Your branch is behind 'origin/master' by .. commits, and can be fast-forwarded". I then do commit or pull or merge (--ff-only) and get the same commit history as before..
  • Timo
    Timo over 3 years
    The idea is to create a helper branch that is at head of master(latest commit) and goes down until the end of the squashed commit(1).Checkout master(2). Git merge --squash (3) How does git know to merge the helper branch and master? git commit(4) git branch -D helper (5)
  • Timo
    Timo over 3 years
    More on the git rebase -interactive here
  • Timo
    Timo over 3 years
    More details on SO on the git rebase -interactive here
  • Timo
    Timo over 3 years
    Git rebase -i .. turns the git commits around: the newest is the last in the list and the oldest the first.Am I looking wrong?
  • br3nt
    br3nt over 3 years
    @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.
  • FXQuantTrader
    FXQuantTrader over 3 years
    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
  • Timo
    Timo over 3 years
    How can I create a zsh function out of it with this line git merge --squash HEAD@{1} which results in a parse error.
  • Jake Sylvestre
    Jake Sylvestre over 3 years
    I'm getting everything up to date
  • ivan.ukr
    ivan.ukr over 3 years
    works good for the personal feature branches, should not be used on the branches where multiple people work, but I'd say latter may be in some cases just bad organization.
  • Staghouse
    Staghouse over 3 years
    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 .
  • br3nt
    br3nt over 3 years
    @Staghouse yeah, it gives you a lot of control and it’s super flexible. You might want to squash a commit which fixes a type in a variable name but also reword a commit message at the same time
  • Karl Pokus
    Karl Pokus over 3 years
    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.
  • blisher
    blisher over 3 years
    @FXQuantTrader when you git rebase -i you have to force-push too, don't you? Unless you're not using --force-with-lease I don't think there's any harm in that solution.
  • Brian Schermerhorn
    Brian Schermerhorn over 3 years
    I was trying to do other solutions, but for w/e reason they weren't working. This one did.
  • sanpat
    sanpat over 3 years
    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.
  • hakre
    hakre about 3 years
    @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.
  • Matheus Felipe
    Matheus Felipe about 3 years
    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.
  • Jake
    Jake about 3 years
    @MatthiasM re "git push -f sound dangerous. Take care to only squash local commits. Never touch pushed commits!" Depends on your workflow and context. Using GitHub it is normal (actually required) to have to git push -f after a rebase of a branch. This may be no different. If you're working on your own branch for a PR, there's no problem. Also, you cannot force-push to a protected branch (like main on GitHub). So there's no danger if your project is set up right.
  • Radek
    Radek about 3 years
    nice try, but there is a better way mentioned here with rebase
  • Srini Karthikeyan
    Srini Karthikeyan almost 3 years
    What if code push has already been done and I want to remove previous commits from history. Will it work?
  • Jabari
    Jabari almost 3 years
    Couldn't this be avoided by merging the parent branch before rebasing?
  • dvvrt
    dvvrt almost 3 years
    @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.
  • Jabari
    Jabari almost 3 years
    Gotcha! I should have read the first line better!
  • Eduardo Pignatelli
    Eduardo Pignatelli almost 3 years
    What if there are too many pickto replace manually?
  • bebbo
    bebbo over 2 years
    you'll end up losing information and history if files were moved around. You should use the correct approach mentioned from @user1376350
  • Prabhu
    Prabhu over 2 years
    After rebase, if we want to push to remote, we need to use --force flag to push. If we don't use, push will fail. ` git push --force <remote> <remote_branch> `
  • Correcter
    Correcter over 2 years
    Thank you man! It helped me. I'm already desperate to fix anything...
  • GLP
    GLP over 2 years
    This works like charm. But I think there is one assumption, you need make sure the feature_branch has 0 behind master branch.
  • colm.anseo
    colm.anseo over 2 years
    Nice technique to split the operations into a staging branch! One can then diff clean branches more easily.
  • Rush
    Rush over 2 years
    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.
  • martian
    martian over 2 years
    I prefer this command with GIT_SEQUENCE_EDITOR, for I need autostash
  • Greg7000
    Greg7000 over 2 years
    For those who use SourceTree, I normally do all the steps described here in cmd line until 'git merge --squash HEAD@{1}' (inclusive). Then I refresh SourceTree with F5 and perform my commit via SourceTree to benefit from the ui when editing the commit message.
  • muditrustagii
    muditrustagii over 2 years
    Thanks for your answer. I tried rebase a lot of times, but only your's worked.
  • Dercsár
    Dercsár over 2 years
    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.
  • Dercsár
    Dercsár over 2 years
    Also crucial is that you will need --force when you want to push your squashed commit
  • FXQuantTrader
    FXQuantTrader over 2 years
    @blisher Use rebase in your local repo to your hearts content. But once you push to a "shared" public branch (main, master, develop) it's just bad to be rewriting shared branch commit histories.