TL;DR
Add an alias
start = !git init && git commit --allow-empty -m \"Initial commit\"
and use it to create an empty repository.To fix the issue, run the following commands
git checkout -b my-feature-1 master
git branch -D master
git checkout --orphan master
git reset --hard
git commit --allow-empty -m "Initial commit"
git push origin master:master --force-with-lease
git checkout my-feature-1
git rebase origin/master
git push origin my-feature-1:my-feature-1 --force-with-lease
Continue reading if you want to know why.
If you want to understand little bit more about git internals, there is also a method using plumbing commands.
How do I fix this mess?
What you need to do is make master empty again without loosing your precious code.
First of all check that you have everything commited and your working copy is clean. You can stash everything (also untracked) using git stash -u
.
Create a backup branch, that will hold your code so that it does not get lost, when you reset master.
git checkout -b my-feature-1 master
Now you delete master (!). Don't worry, we'll recreate it in a minute.
git branch -D master
You will create new master
out of thin air. The --orphan
flag will do that for you. Orphan branch is not based off of anything.
git checkout --orphan master
But now your working copy contains changes from my-feature-1
branch. So we need to reset it.
git reset --hard
You can't just commit empty commit as git will refuse to do so. So you need to add --allow-empty
flag.
git commit --allow-empty -m "Initial commit"
Now your master contains only one empty commit and you can push it to the server. But because the branch changed completely you need to force push.
git push origin master:master --force-with-lease
Notice we did not use a simple --force
. When you force with lease, git makes sure that the remote branch is where it's supposed to be. In case someone pushed after you last fetched the origin/master
the force push will fail.
Now you're done when it comes to the master branch. But there are still your changes in the my-feature-1
branch. If you push it and try to create a PR from it, you will see an error. Something along the lines:
master
andmy-feature-1
are completely different trees
To fix this you need to rebase the feature branch on master
.
git checkout my-feature-1
git rebase origin/master
git push origin my-feature-1:my-feature-1 --force-with-lease
Why should I always have empty commit on master?
There are multiple reasons why starting master
with an empty commit is a good idea. Apart from being nice and clean, it's more practical when you need to rebase any branch based off of it. The commit you rebase onto is not part of the interactive rebase list. So if your first commit was titled Dummy test 999
, you're not getting rid of that that easily as you can't rebase a commit that has no parent.
To prevent the situation altogether there is this handy alias.
start = !git init && git commit --allow-empty -m \"Initial commit\"
When you call git start
in a directory, it will init the repository as well as add an empty commit on master
.
Under-the-hood method
The above method of fixing master is made for mere mortals. It's using mostly the commands you know and love. But with git there is always more then one way to skin the cat. If you feel adventurous, you can try the "under the hood" method.
First we need to know what an empty tree (e.g. empty directory) looks like. For that we'll employ git's hash-object
, that returns a hash that git would internally use to represent whatever we pass to it.
git hash-object -t tree /dev/null
Here we're asking git to give us a hash of a tree that is created from /dev/null
(-t tree
means object of type tree
).
You'll probably get 4b825dc642cb6eb9a060e54bf8d69288fbee4904
, unless you're reading this in the future when git changed hashes from SHA1 to SHA256 to avoid collisions.
Now that you know the hash of the tree, you can easily create a new commit at that tree.
git commit-tree -m "inital commit" 4b825dc642cb6eb9a060e54bf8d69288fbee4904
That will give you a hash of the empty commit. The commit is not part of any branch. You just created it using the plumbing command. But as you may know, branches are just labels assigned to commits. So all you need to do is to make the master
label point to this commit. In my case the commit hash was a5c0737b
, so I used it in the reset command.
git reset --hard a5c0737b
And voila! Done.
You still need to rebase your feature branch on top of it, though.