问题
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?
回答1:
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
回答2:
In recent versions of git, you can use git rebase --root -i
.
For each commit except the first, change pick
to squash
.
回答3:
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"
Caveat: remember to provide a comment, otherwise the default commit message "A new start" would be used.
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")
Note: here "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
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 toHEAD
, namely the tip of your current branch. see Tree-Objects and Commit-Objects.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.
回答4:
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:
- git-checkout(1) Manual Page.
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:
- git-reset(1) Manual Page.
回答5:
echo "message" | git commit-tree HEAD^{tree}
This will create an orphaned commit with the tree of HEAD, and output it's name (SHA-1) on stdout. Then just reset your branch there.
git reset SHA-1
回答6:
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.
回答7:
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"
回答8:
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
回答9:
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
- How do I combine the first two commits of a Git repository?
- git: how to squash the first two commits?
回答10:
In one line of 6 words
git checkout --orphan new_root_branch && git commit
回答11:
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
回答12:
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
回答13:
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
')
回答14:
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 rungit commit --amend --no-edit
Run
git push -f
to force push your changes
来源:https://stackoverflow.com/questions/1657017/how-to-squash-all-git-commits-into-one