[git] How to squash all git commits into one?

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?

This question is related to git rebase git-rebase squash git-rewrite-history

The answer is


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"

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

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


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.


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:


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


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


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

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

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


In one line of 6 words:

git checkout --orphan new_root_branch  &&  git commit

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

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.

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 "commit message")

Update

I've made an alias git squash-all.
Example usage: git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Note: the optional message is for commit message, if omitted, it will default to "A new start".

Or you can create the alias with the following command:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

One Liner

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

Here, the commit message "A new start" is just an example, feel free to use your own language.

TL;DR

No need to squash, use git commit-tree to create an orphan commit and go with it.

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} means the tree object corresponding to HEAD, namely the tip of your current branch. see Tree-Objects and Commit-Objects.

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


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


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.


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


Examples related to git

Does the target directory for a git clone have to match the repo name? Git fatal: protocol 'https' is not supported Git is not working after macOS Update (xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools) git clone: Authentication failed for <URL> destination path already exists and is not an empty directory SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443 GitLab remote: HTTP Basic: Access denied and fatal Authentication How can I switch to another branch in git? VS 2017 Git Local Commit DB.lock error on every commit How to remove an unpushed outgoing commit in Visual Studio?

Examples related to rebase

Git refusing to merge unrelated histories on rebase Git push rejected "non-fast-forward" What's the difference between 'git merge' and 'git rebase'? How can I combine two commits into one commit? Remove folder and its contents from git/GitHub's history git cherry-pick says "...38c74d is a merge but no -m option was given" What does 'git remote add upstream' help achieve? How to get "their" changes in the middle of conflicting Git rebase? Git: How to rebase to a specific commit? How do you rebase the current branch's changes on top of changes being merged in?

Examples related to git-rebase

rebase in progress. Cannot commit. How to proceed or stop (abort)? git remove merge commit from history Choose Git merge strategy for specific files ("ours", "mine", "theirs") What's the difference between 'git merge' and 'git rebase'? Your branch is ahead of 'origin/master' by 3 commits Rebase feature branch onto another feature branch git rebase merge conflict Remove folder and its contents from git/GitHub's history How to get "their" changes in the middle of conflicting Git rebase? How to rebase local branch onto remote master

Examples related to squash

How to squash commits in git after they have been pushed? Squash my last X commits together using Git In git, what is the difference between merge --squash and rebase? How to squash all git commits into one? Squash the first two commits in Git?

Examples related to git-rewrite-history

How to amend older Git commit? Rebasing a Git merge commit How to remove/delete a large file from commit history in Git repository? How to squash all git commits into one? How to modify a specified commit? Remove sensitive files and their commits from Git history How to change the author and committer name and e-mail of multiple commits in Git? How can one change the timestamp of an old commit in Git? How do you fix a bad merge, and replay your good commits onto a fixed merge? How to modify existing, unpushed commit messages?