You can modify the most recent commit in the same branch by running git commit --amend. This command is convenient for adding new or updated files to the previous commit. It is also a simple way to edit or add comments to the previous commit. Use git commit --amend to modify the most recent commit.
As you're working, you change and save a file, or multiple files. Then, before you commit, you must git add . This step allows you to choose what you are going to commit. Commits should be logical, atomic units of change - but not everyone works that way.
There are 2 steps to achieving this:
We’ll put the new empty commit on a temporary branch newroot
for convenience.
There is a number of ways you can do this.
The cleanest approach is to use Git’s plumbing to just create a commit directly, which avoids touching the working copy or the index or which branch is checked out, etc.
Create a tree object for an empty directory:
tree=`git hash-object -wt tree --stdin < /dev/null`
Wrap a commit around it:
commit=`git commit-tree -m 'root commit' $tree`
Create a reference to it:
git branch newroot $commit
You can of course rearrange the whole procedure into a one-liner if you know your shell well enough.
With regular porcelain commands, you cannot create an empty commit without checking out the newroot
branch and updating the index and working copy repeatedly, for no good reason. But some may find this easier to understand:
git checkout --orphan newroot
git rm -rf .
git clean -fd
git commit --allow-empty -m 'root commit'
Note that on very old versions of Git that lack the --orphan
switch to checkout
, you have to replace the first line with this:
git symbolic-ref HEAD refs/heads/newroot
You have two options here: rebasing, or a clean history rewrite.
git rebase --onto newroot --root master
This has the virtue of simplicity. However, it will also update the committer name and date on every last commit on the branch.
Also, with some edge case histories, it may even fail due to merge conflicts – despite the fact that you are rebasing onto a commit that contains nothing.
The cleaner approach is to rewrite the branch. Unlike with git rebase
, you will need to look up which commit your branch starts from:
git replace <currentroot> --graft newroot
git filter-branch master
The rewriting happens in the second step, obviously; it’s the first step that needs explanation. What git replace
does is it tells Git that whenever it sees a reference to an object you want replaced, Git should instead look at the replacement of that object.
With the --graft
switch, you are telling it something slightly different than normally. You are saying don’t have a replacement object yet, but you want to replace the <currentroot>
commit object with an exact copy of itself except the parent commit(s) of the replacement should be the one(s) that you listed (i.e. the newroot
commit). Then git replace
goes ahead and creates this commit for you, and then declares that commit as the replacement for your original commit.
Now if you do a git log
, you will see that things already look as you want them to: the branch starts from newroot
.
However, note that git replace
does not actually modify history – nor does it propagate out of your repository. It merely adds a local redirect to your repository from one object to another. What this means is that nobody else sees the effect of this replacement – only you.
That’s why the filter-branch
step is necessary. With git replace
you create an exact copy with adjusted parent commits for the root commit; git filter-branch
then repeats this process for all the following commits as well. That is where history actually gets rewritten so that you can share it.
Merge of Aristotle Pagaltzis's and Uwe Kleine-König's answers and Richard Bronosky's comment.
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
# touch .gitignore && git add .gitignore # if necessary
git commit --allow-empty -m 'initial'
git rebase --onto newroot --root master
git branch -d newroot
(just to put everything in one place)
I like Aristotle's answer. But found that for a large repository (>5000 commits) filter-branch works better than rebase for several reasons 1) it's faster 2) it doesn't require human intervention when there's a merge conflict. 3) it can rewrite the tags -- preserving them. Note that filter-branch works because there is no question about the contents of each commit -- it is exactly the same as before this 'rebase'.
My steps are:
# first you need a new empty branch; let's call it `newroot`
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
# then you apply the same steps
git commit --allow-empty -m 'root commit'
# then use filter-branch to rebase everything on newroot
git filter-branch --parent-filter 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat master
Note that the '--tag-name-filter cat' options means that tags will be rewritten to point to the newly created commits.
I think that using git replace
and git filter-branch
is a better solution than using a git rebase
:
The idea behind it is to:
git filter-branch
Here is a script for the 2 first steps:
#!/bin/bash
root_commit_sha=$(git rev-list --max-parents=0 HEAD)
git checkout --force --orphan new-root
find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null
git add -A
GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit"
new_root_commit_sha=$(git rev-parse HEAD)
echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..."
parent="parent $new_root_commit_sha"
replacement_commit=$(
git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" |
git hash-object -t commit -w --stdin
) || return 3
git replace "$root_commit_sha" "$replacement_commit"
You could run this script without risk (even if doing a backup before doing action you never did before is a good idea ;) ), and if the result is not the one expected, just delete the files created in the folder .git/refs/replace
and try again ;)
Once you have verified that the state of the repository is what you expect, run the following command to update the history of all branches:
git filter-branch -- --all
Now, you must see 2 histories, the old one and the new one (see help on filter-branch
for more information). You could compare the 2 and check again if all is OK. If you are satisfied, delete the no more needed files:
rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace
You could return to your master
branch and delete the temporary branch:
git checkout master
git branch -D new-root
Now, all should be done ;)
To add an empty commit at the start of a repository, if you forgot to create an empty commit immediately after "git init":
git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)
I used pieces of Aristotle's and Kent's answer successfully:
# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .
git commit --allow-empty -m 'root commit'
git filter-branch --parent-filter \
'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat -- --all
# clean up
git checkout master
git branch -D newroot
# make sure your branches are OK first before this...
git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d
This will also rewrite all branches (not just master
) in addition to tags.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With