736

How do you squash your entire repository down to the first commit?

I can rebase to the first commit, but that would leave me with 2 commits. Is there a way to reference the commit before the first one?

Jacek Laskowski
  • 72,696
  • 27
  • 242
  • 420
Verhogen
  • 27,221
  • 34
  • 90
  • 109
  • 42
    @innaM - It's the *Primordial commit that begat the git*. (Hopes humor passes well enough through the interweb). – ripper234 Dec 31 '11 at 09:23
  • 14
    For those coming later to this question, be sure to use [the more modern answer](http://stackoverflow.com/a/9254257/881224). – yurisich Jul 19 '13 at 18:33
  • 1
    Related, but not a duplicate (`--root` is actually not the best solution to squashing ***all*** commits if there are a lot of them to squash): [Combine the first two commits of a Git repository?](http://stackoverflow.com/q/435646/456814). –  Jun 11 '14 at 03:01
  • 1
    ^ that modern answer is not the best, better solution w/o squash overhead is http://stackoverflow.com/questions/1657017#23486788 – ryenus Jun 24 '14 at 14:23
  • 2
    Imo: this is the best from @MrTux: http://stackoverflow.com/questions/30236694/how-do-i-squash-all-commits-without-losing-submodules. – J0hnG4lt May 14 '15 at 12:17
  • Possible duplicate of [Combine the first two commits of a Git repository?](https://stackoverflow.com/questions/435646/combine-the-first-two-commits-of-a-git-repository) – Evan Carroll Aug 26 '18 at 05:54
  • 1
    I know the question asks for a squash approach, but one could also: ´rm -rf .git´ and ´git commit -m"new message"´ – Andres Zapata Sep 09 '21 at 01:19

24 Answers24

954

As of git 1.6.2, you can use git rebase --root -i

For each commit except the first, change pick to squash.

jasonleonhard
  • 12,047
  • 89
  • 66
Jordan Lewis
  • 16,900
  • 4
  • 29
  • 46
  • 3
    Added in 1.7.12: https://github.com/git/git/blob/master/Documentation/RelNotes/1.7.12.txt – serbaut Mar 05 '13 at 20:45
  • 81
    This answer is *ok*, but if you're interactively rebasing more than, say, 20 commits, the interactive rebase will probably be way too slow and unwieldy. You're probably going to have a hard time trying to squash hundreds or thousands of commits. I would go with a soft or mixed reset to the root commit, then recommit, in that case. –  Jun 11 '14 at 02:55
  • This was the only way I could introduce a master branch to my newly created github repo after I had pushed a feature branch that I wanted to create a pull request for. – jishi Apr 25 '16 at 15:30
  • Does this take a long time to run? – catastrophic-failure Jun 15 '16 at 11:24
  • It actually takes some time. About 1 second per commit here. – mcont Aug 16 '16 at 21:41
  • 41
    If you have many commits it's hard to change 'pick' to 'squash' manually. Use _:%s/pick/squash/g_ in VIM command line to do this faster. – eilas Aug 04 '17 at 14:17
  • 5
    this works find, but I had to do a forced push. be careful! `git push -f` – philshem Oct 25 '19 at 06:42
  • 1
    For nano users, use `ctrl+\` to trigger search/replace. – Mentor Feb 19 '21 at 10:30
  • 2
    @eilas No need for the `g`. You only want to replace the first instance of the word `pick` on each line. Even better is `:%s/^pick/squash/`. – user137369 Apr 13 '23 at 19:05
  • Tools like GitKraken provide a GUI that lets you bulk select hundreds of commits and squash/reword pretty easily – kennsorr Jun 22 '23 at 17:30
515

One Liner (for Bash/Zsh)

You can create a squash-all commit right from HEAD, without rebase at all, just run:

git reset $(git commit-tree HEAD^{tree} -m "A new start")

Note: this requires a POSIX compatible shell like bash/zsh, or Git Bash on Windows.

Making an Alias in ~/.gitconfig

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} \"$@\");};f"

Then just run: git squash-all -m "a brand new start"

Note: Either provide the commit message from standard input, or via the -m/-F options, just like the git commit command. See git-commit-tree manual.

Alternatively, you can create the alias with the following command:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} "$@");};f'

Explain

  1. create a single commit via git commit-tree

    What git commit-tree HEAD^{tree} -m "A new start" does is:

    Creates a new commit object based on the provided tree object and emits the new commit object id on stdout. The log message is read from the standard input, unless -m or -F options are given.

    The expression HEAD^{tree} represents the tree object corresponding to HEAD, namely the tip of your current branch. see Tree-Objects and Commit-Objects.

  2. reset the current branch to the new commit

    Then git reset simply reset the current branch to the newly created commit object.

    This way, nothing in the workspace is touched, nor there's need for rebase/squash, which makes it really fast. And the time needed is irrelevant to the repository size or history depth.


Variation: New Repo from a Project Template

This is useful to create the "initial commit" in a new project using another repository as the template/archetype/seed/skeleton. For example:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

This avoids adding the template repo as a remote (origin or otherwise) and collapses the template repo's history into your initial commit.

ryenus
  • 15,711
  • 5
  • 56
  • 63
  • 8
    The git revision syntax (HEAD^{tree}) syntax is explained here in case anyone else was wondering: http://jk.gs/gitrevisions.html – Colin Bowern Jun 23 '14 at 23:45
  • 2
    Does this reset both the local and remote repository, or just one of them? – aleclarson Oct 28 '14 at 07:41
  • 6
    @aleclarson, this only reset the current branch in the local repository, use `git push -f` for propagation. – ryenus Oct 28 '14 at 12:20
  • 2
    I found this answer while looking for a way to start a new project from a project template repository that didn't involve `git clone`. If you add `--hard` to `git reset` and switch `HEAD` with `FETCH_HEAD` in the `git commit-tree` you can create an initial commit after fetching the template repo. I've edited the answer with a section at the end demonstrating this. –  Feb 07 '15 at 19:57
  • 1
    Note about `git checkout --orphan`, this command can also be used to create a root commit, but it only does the preparation (things are only staged, not committed), and it must be followed by `git commit -m "commit message"`, which is slower, but can be used interactively so one has a chance to make some change before committing. – ryenus Jun 29 '15 at 02:06
  • thanks for this awesome one liner!!! i like to add that if you already pushed your project into your remote, this will only work on unprotected branches. to push this into your repo you will need to *force push* with `push -f` – denns Aug 18 '16 at 11:45
  • 6
    You could get rid of that "Caveat" but just using `${1?Please enter a message}` – Elliot Cameron Oct 13 '17 at 20:57
  • will this work for getting multiple template repos? History like "initial commit", "template 1", "template 2", etc. – Bernardo Dal Corno Apr 05 '22 at 17:36
  • Getting error `error: unknown switch `m'` with git version 2.36.0.windows.1 – Benny Code Feb 17 '23 at 23:44
  • @benny, on Windows this requires Git Bash, it doesn't work in cmd though. – ryenus Feb 21 '23 at 03:30
281

If all you want to do is squash all of your commits down to the root commit, then while

git rebase --interactive --root

can work, it's impractical for a large number of commits (for example, hundreds of commits), because the rebase operation will probably run very slowly to generate the interactive rebase editor commit list, as well as run the rebase itself.

Here are two quicker and more efficient solutions when you're squashing a large number of commits:

Alternative solution #1: orphan branches

You can simply create a new orphan branch at the tip (i.e. the most recent commit) of your current branch. This orphan branch forms the initial root commit of an entirely new and separate commit history tree, which is effectively equivalent to squashing all of your commits:

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

Documentation:

Alternative solution #2: soft reset

Another efficient solution is to simply use a mixed or soft reset to the root commit <root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

Documentation:

  • 49
    Alternative solution #1: orphan branches - rocks! – Thomas May 09 '16 at 15:51
  • 13
    Alternative solution #1 FTW. Just to add, if you want to push your changes to the remote, do `git push origin master --force`. – Eddy Verbruggen Sep 09 '16 at 11:01
  • 3
    should not forget `git push --force` – NecipAllef Mar 28 '18 at 16:38
  • 2
    Don't do an orphan branch on code (i.e. don't do alternative solution #1 above) if you're about to push to an open Github pull request!!! Github will close your PR because [the current head isn't a descendant of the stored head sha](https://github.com/isaacs/github/issues/361). – Andrew Mackie Mar 29 '19 at 00:54
  • 1
    Alternative solution #1 also avoids the [merge conflicts](https://stackoverflow.com/questions/3133449/why-does-git-rebase-give-me-merge-conflicts-when-all-im-doing-is-squashing-comm) that can occur when squashing commits – FernAndr May 13 '20 at 15:54
  • The soft reset solution is perfect for me. Easy and comprehensible! – phatmann Feb 08 '22 at 19:36
  • Don't know how, or why. But followed Solution #1 and all my commits were erased. Luckily i made a backup branch. – Staghouse Mar 23 '22 at 20:04
  • @AndrewMackie that's pretty much what "squash all git commits into one" means by definition though. Applies to every other answer as well if the original branch is eventually reset to this commit. – SOFe Feb 09 '23 at 08:25
  • Can't believe I had to scroll down this far to find this, the orphaned branches solution worked perfectly for me and I haven't found any downsides of this approach. This should've been the accepted answer. – Raleigh L. Feb 28 '23 at 07:51
  • the orphan branch solution does not remove old reflog messages reflecting actions (and user names and email addresses) associated with the old removed branch's commits. You can [delete these reflog entries locally](https://stackoverflow.com/a/72337019/5359531) but if the commits have already been pushed to a remote repo on GitHub they will remain in the reflog when someone clones it again. So, using an orphan branch does not completely erase the history of the repo. – user5359531 Apr 04 '23 at 15:38
169

Perhaps the easiest way is to just create a new repository with current state of the working copy. If you want to keep all the commit messages you could first do git log > original.log and then edit that for your initial commit message in the new repository:

rm -rf .git
git init
git add .
git commit

or

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log
Pat Notz
  • 208,672
  • 30
  • 90
  • 92
  • 77
    but you are loosing branches with this method – Olivier Refalo Jan 24 '12 at 20:40
  • @OlivierRefalo – There is no way to preserve branches while reducing whole history to a single commit (make new initial commit). If someone wants to preserve branches, he got to preserve the old initial commit. To do that, rebase against initial commit (like written in question) and live with as many as two commits. – skalee Nov 23 '12 at 16:04
  • Actually, there is a way to preserve branches, see answer by @FrefichRaabe However concept of old branches with history squashed to single commit sounds odd. – skalee Nov 23 '12 at 16:11
  • 213
    Git has evolved since this answer was given. No, there is a simpler and better way: `git rebase -i --root`. See: http://stackoverflow.com/a/9254257/109618 – David J. Jul 12 '13 at 05:24
  • 11
    This might work for some cases, but it is essentially not the answer to the question. With this recipe, you loose all your config and all other branches too. – iwein Nov 16 '13 at 19:58
  • 1
    If you have any files forcedly added over `.gitignore`, this recipe wouldn't add them again. – sanmai Jun 11 '14 at 04:44
  • 2
    you are loosing all the repos information by doing this. – thebugfinder Aug 17 '15 at 08:38
  • 18
    This is a horrible solution that's needlessly destructive. Please don't use it. – Daniel Kamil Kozar Nov 24 '15 at 15:05
  • git remote -v // take note of the URL git remote add origin – E Ciotti Mar 07 '16 at 12:52
  • 11
    also will break submodules. -1 – Krum Apr 27 '16 at 12:31
  • This is definitely not the best way. @ryenus has the best solutions. – Elliot Cameron Oct 13 '17 at 20:55
  • one should mention that it could be wise to either place the log file outside the repo before `git add .` or adding all files called `**/log`, `**/*.*log*` to .gitignore (choose wildcards wisely) - otherwise that file or backup copies you created from it might end up in public – dfsg76 Aug 22 '19 at 17:27
  • @DavidJ. You said "git rebase -i --root", but the answer you cited swaps the order of the arguments: "git rebase --root -i". – Raleigh L. Jan 04 '23 at 21:35
  • This answer is dangerous, don't follow it. See the comments above for reasons why. – Evgenii Mar 08 '23 at 16:02
95
echo "message" | git commit-tree HEAD^{tree}

This will create an orphaned commit with the tree of HEAD, and output its name (SHA-1) on stdout. Then just reset your branch there.

git reset SHA-1

To do the above in a single step:

git reset $(git commit-tree HEAD^{tree} -m "Initial commit.")
sashoalm
  • 75,001
  • 122
  • 434
  • 781
kusma
  • 6,516
  • 2
  • 22
  • 26
  • 41
    `git reset $(git commit-tree HEAD^{tree} -m "commit message")` would make it easier. – ryenus Feb 04 '13 at 07:19
  • 7
    ^ THIS! - should be an answer. Not entirely sure if it was the author's intention, but was mine (needed a pristine repo with a single commit, and that gets the job done). – chesterbr Jan 23 '14 at 14:04
  • @ryenus, your solution did exactly what I was looking for. If you add your comment as an answer, I'll accept it. – tldr May 04 '14 at 00:38
  • 2
    The reason I didn't suggest the subshell-variant myself, is that it won't work on cmd.exe in Windows. – kusma May 05 '14 at 16:36
  • 1
    In Windows prompts, you might have to quote the last parameter: `echo "message" | git commit-tree "HEAD^{tree}"` – Bernard Feb 18 '16 at 23:48
  • @kusma in my opinion git bash with mintty should be used anyway in windows. – denns Aug 18 '16 at 09:51
47

I read something about using grafts but never investigated it much.

Anyway, you can squash those last 2 commits manually with something like this:

git reset HEAD~1
git add -A
git commit --amend
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
47

Here's how I ended up doing this, just in case it works for someone else:

Remember that there's always risk in doing things like this, and its never a bad idea to create a save branch before starting.

Start by logging

git log --oneline

Scroll to first commit, copy SHA

git reset --soft <#sha#>

Replace <#sha#> w/ the SHA copied from the log

git status

Make sure everything's green, otherwise run git add -A

git commit --amend

Amend all current changes to current first commit

Now force push this branch and it will overwrite what's there.

Logan
  • 52,262
  • 20
  • 99
  • 128
  • 1
    Note that this actually seems to leave the history around. You orphan it, but it's still there. – Brad Jan 07 '17 at 03:22
  • Very useful answer ... but you should be aware that after the amend command you will find yourself in vim editor with its special syntax. ESC, ENTER, :x is your friend. – Erich Kuester Feb 22 '19 at 21:22
37

The easiest way is to use the 'plumbing' command update-ref to delete the current branch.

You can't use git branch -D as it has a safety valve to stop you deleting the current branch.

This puts you back into the 'initial commit' state where you can start with a fresh initial commit.

git update-ref -d refs/heads/master
git commit -m "New initial commit"
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
27

In one line of 6 words:

git checkout --orphan new_root_branch  &&  git commit
Asclepius
  • 57,944
  • 17
  • 167
  • 143
kyb
  • 7,233
  • 5
  • 52
  • 105
  • @AlexanderMills, you should read `git help checkout` about `--orphan` – kyb May 10 '19 at 10:33
  • can you link to the docs for that so everyone who reads this doesn't have to manually search for it? – Alexander Mills May 10 '19 at 17:47
  • 2
    easy. here it is [`git help checkout --orphan`](https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt---orphanltnewbranchgt) – kyb May 10 '19 at 18:30
17

create a backup

git branch backup

reset to specified commit

git reset --soft <#root>

then add all files to staging

git add .

commit without updating the message

git commit --amend --no-edit

push new branch with squashed commits to repo

git push -f
David
  • 1,034
  • 11
  • 20
  • Will this preserve the previous commit messages? – not2qubit Nov 29 '18 at 21:14
  • 1
    @not2qubit no this will not preserve the previous commit messages, instead of commit #1, commit #2, commit #3, you'll get all of the changes in those commits packed into a single commit #1. Commit #1 will be the `` commit you reset back to. `git commit --amend --no-edit` will commit all changes to the current commit which is `` without needing to edit the commit message. – David Nov 30 '18 at 10:26
15

First, squash all your commits into a single commit using git rebase --interactive. Now you're left with two commits to squash. To do so, read any of

Community
  • 1
  • 1
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
14

To do this, you can reset you local git repository to the first commit hashtag, so all your changes after that commit will be unstaged, then you can commit with --amend option.

git reset your-first-commit-hashtag
git add .
git commit --amend

And then edit the first commit nam if needed and save file.

T.EL MOUSSAOUI
  • 141
  • 1
  • 3
9

Let's say you have 3 commits in your branch and it is already pushed to remote branch.

Example:

git log -4

Will display results like:

<your_third_commit_sha>
<your_second_commit_sha>
<your_first_commit_sha>
<master_branch_commit_sha - your branch created from master>

You want to squash your last 3 commits to one commit and push to remote branch. Here are the steps.

git reset --soft <master_branch_commit_sha>

Now all commits changes are integrated but uncommitted. Verify by:

git status

Commit all changes with a message:

git commit -m 'specify details'

Forcefully push the single commit to remote branch:

git push -f
Prabhu P
  • 199
  • 1
  • 3
7

To squash using grafts

Add a file .git/info/grafts, put there the commit hash you want to become your root

git log will now start from that commit

To make it 'real' run git filter-branch

dimus
  • 8,712
  • 10
  • 45
  • 56
5

I usually do it like this:

  • Make sure everything is committed, and write down the latest commit id in case something goes wrong, or create a separate branch as the backup

  • Run git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD` to reset your head to the first commit, but leave your index unchanged. All changes since the first commit will now appear ready to be committed.

  • Run git commit --amend -m "initial commit" to amend your commit to the first commit and change the commit message, or if you want to keep the existing commit message, you can run git commit --amend --no-edit

  • Run git push -f to force push your changes

Kent Munthe Caspersen
  • 5,918
  • 1
  • 35
  • 34
5

For me it worked like this: I had 4 commits in total, and used interactive rebase:

git rebase -i HEAD~3

The very first commit remains and i took 3 latest commits.

In case you're stuck in editor which appears next, you see smth like:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

You have to take first commit and squash others onto it. What you should have is:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

For that use INSERT key to change 'insert' and 'edit' mode.

To save and exit the editor use :wq. If your cursor is between those commit lines or somewhere else push ESC and try again.

As a result i had two commits: the very first which remained and the second with message "This is a combination of 3 commits.".

Check for details here: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

4

Let's say, you have these commits from 06 to 10.

You want to combine the commits post 06 to a single commit Id.

commit 10
commit 09  
commit 08  
commit 07  
commit 06

You can do a git reset soft.

git reset --soft "06"

Then, run the below command to push these changes to remote branch.

git push origin HEAD --force

Now, all the commits you have made before should be available as your local changes and you can combine all these commits to a single commit.

Now, the new commit structure should like below:

commit <new_commit_containing_all_7_8_9_10>
commit 06
2

I tried different commands with HEAD^{tree} but got this error from zsh:

zsh: no matches found: HEAD^{tree}

It was how ohmyzsh handles expansion, see this issue for more details: https://github.com/ohmyzsh/ohmyzsh/issues/449

Easiest fix is to run this command:

unsetopt nomatch
harre
  • 59
  • 7
1

This answer improves on a couple above (please vote them up), assuming that in addition to creating the one commit (no-parents no-history), you also want to retain all of the commit-data of that commit:

  • Author (name and email)
  • Authored date
  • Commiter (name and email)
  • Committed date
  • Commmit log message

Of course the commit-SHA of the new/single commit will change, because it represents a new (non-)history, becoming a parentless/root-commit.

This can be done by reading git log and setting some variables for git commit-tree. Assuming that you want to create a single commit from master in a new branch one-commit, retaining the commit-data above:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')
javabrett
  • 7,020
  • 4
  • 51
  • 73
1

This worked best for me.

git rebase -X ours -i master

This will git will prefer your feature branch to master; avoiding the arduous merge edits. Your branch needs to be up to date with master.

ours
           This resolves any number of heads, but the resulting tree of the merge is always that of the current
           branch head, effectively ignoring all changes from all other branches. It is meant to be used to
           supersede old development history of side branches. Note that this is different from the -Xours
           option to the recursive merge strategy.
Derrick Petzold
  • 1,118
  • 13
  • 13
1

Since I had some trouble with the solutions proposed here, I want to share a really simple solution (to squash all commits on a feature branch into one):

git merge origin/master && git reset --soft origin/master

The preceding merge cmd ensures, that no recent changes from master will go on your head when committing! After that, just commit the changes and do git push -f

seebi
  • 488
  • 5
  • 9
1

I usually squash the entire tree when I restore a template from a git repository, to get a cleaner history and to ensure legal compliance. My workflow looks like this:

git clone https://git.invalid/my-awesome-template.git my-awesome-foo
cd !$
git branch -M master my-awesome-template/master
git checkout --orphan master
git rm -rf /
git commit --allow-empty --allow-empty-message -m 'Initial commit'
git merge --squash --allow-unrelated-histories my-awesome-template/master
git commit
git branch -D my-awesome-template/master
# you can now `drop' that "Initial commit":
git rebase -i --root

This squashes your entire history into a single large commit message.

In this particular example:

  • master is the working branch
  • my-awesome-template/master is an intermediate branch
rem
  • 11
  • 1
0

In order to squash all the commits into one by doing following commands.

All the answers are awesome but I would like to put another approach with simple commands

git clone --depth 1 <remote-url> .
git commit --amend -m <commit message you want>
git push --force
SAMUEL
  • 8,098
  • 3
  • 42
  • 42
0

In my opinion the easiest way:

git checkout --orphan <your new branch>

A response containing this idea

A more technical though still generic way:

Generic way to squash commits

Mircea Sirghi
  • 310
  • 1
  • 6