Reputation: 145
I cloned a repo and VScode shows that it's master
. I want to create a branch, but I get this:
GIT: fatal: ‘HEAD’ is not a commit and a branch branch name cannot be created from it
I checked git log and its says:
fatal: your current branch 'master' does not have any commits yet
this is my first time using get, please advise
Upvotes: 1
Views: 5717
Reputation: 488013
You have already answered this yourself, as have several others, but there is a deeper point here that's worth mentioning. The reason git branch
fails here is simple enough: A branch name, in Git, contains the raw hash ID of the last commit that is to be considered a member of the branch. This might not seem weird or startling, at least to someone new to Git and version control, but it in fact is weird and startling, to someone who is not new to version control, but is new to Git. Most version control systems that aren't Git don't work this way: in most version control systems, commits made on branch B are now part of branch B forever. But in Git, the set of branches that contain any one given commit—which may be many or all branches—changes over time!
The way to understand this visually, at least, is to draw a series of commits and some branch names:
A <-B <-C <--main
Here, we have a small starting repository with just three commits in it. Real commits have big, ugly, unique, random-looking hash IDs, expressed in hexadecimal, such as 75ae10bc75336db031ee58d13c5037b929235912, that are too difficult for humans to bother with, so I've used uppercase letters to stand in for them. The branch name main
here contains the raw hash ID of the last of these commits. We say that the name points to the commit.
The commit itself contains two things:
It has a complete snapshot of all of the files, as of the time you (or whoever) made it, stored in a special internal Git-only format in which the files are compressed and de-duplicated. The de-duplication takes care of the fact that every commit stores a full copy of every file: since most commits mostly just re-use older commits' files, they wind up taking no space.1
It has some metadata, or information about the commit itself. This includes the name and email address of whoever made the commit, and some date-and-time-stamps.
In the metadata for commit C
, Git has stored the raw hash ID of earlier commit B
. So just as main
points to C
, we say that C
points to B
. This means that Git can use the name main
to find C
, then use C
to find B
.
Commit B
, of course, is also a commit, so it has a full snapshot and metadata. The metadata inside commit B
includes the hash ID of earlier commit A
, so B
points to A
.
The process of adding a new commit to a branch consists of:
The new commit's backwards-looking pointer, of course, must point to whatever commit was the last commit before. Now new commit D
is the last commit, and the chain goes:
A--B--C--D <-- main
No part of any commit—neither its data, nor its metadata—can be changed once you make the commit. (This is why it's safe for commits to share one copy of a file, for instance.) So this means the links between commits are necessarily backwards: we never know, in advance, what the raw hash ID of some future commit will be, because it depends in part on the exact second we will (in the future) make that commit. It's impossible to predict, so a commit never points forwards, to its children: it only points backwards, to its parents. (This means we can be lazy and not draw the arrows between commits using arrows, if we don't have good drawing characters for instance. We just remember that they must necessarily point backwards.)
This is how and why Git commits are on more than one branch. Let's start with a repository with eight commits in it:
A--B--C--D--E--F--G--H <-- main
Now let's create a new branch, such as feature
, that also selects commit H
, like this:
A--B--C--D--E--F--G--H <-- main, feature
Now—without worrying about how we do it—we create two new commits on main
, then two new commits on feature
:
I--J <-- main
/
...--G--H
\
K--L <-- feature
Note that commits up through and including H
are on both branches; commits I-J
are, at the moment, only on main
, and commits K-L
are only on feature
.
If we now run:
git checkout main
git merge feature
and the merge is successful, we end up with a series of commits that look like this:
I--J
/ \
...--G--H M <-- main
\ /
K--L <-- feature
The new merge commit M
is special in exactly one way: it has two parents, pointing back to both J
and L
. Suddenly, commits K-L
are now on branch main
, as well as still being on branch feature
. Commits I-J
are still only on main
, and commits through H
are still on both branches, but main
has acquired three commits, even though we only created one commit.
Note that merge commit M
still has one snapshot of all files, and the usual author and committer and log message in its metadata. The only thing special about it is that it has these two parents, instead of the usual one. That's what makes it a merge commit.2
1These de-duplicated files do take a tiny bit of space, to hold the information that the file is present, but not enough to worry about. The file contents themselves are stored separately as blob objects, which get their own hash IDs. Git also has tricks that it employs later to make nearly-duplicate files smaller, via something it calls pack files, but you don't have to worry about that: it's almost completely invisible.
2Note that git merge
does not always make a merge commit, and other commands—such as git cherry-pick
—use Git's merge code internally, but make a non-merge commit. The upshot of this is that to merge, as a verb, means something very different from a merge, as a noun. We use git merge
to make merges, but the whole picture around merging is, um, let's just say nontrivial. 😀 This is why a good Git book will spend at least a whole chapter on merging.
Once you've digested the above—at least to the point of being able to draw some simple branch examples—it's time to try to draw a branch that has no commits on it.
We can do this, sort of, on paper or on a whiteboard:
<-- main
but if we do that, Git is supposed to be able to answer the question: what is the hash ID that goes with this branch name? So, what is the hash ID?
I don't know, and neither do you: it doesn't exist yet. There will be one in the future, probably, but we don't know what it is. To find out what hash ID the next commit we make has, we have to make it.
That's why your solution—of making a new but empty commit—works: once we make commit A
, it gets assigned a hash ID, and the name main
(or master
) can point to it:
A <-- main
We can make as many names as we like now. They all just have to point to A
, since that's the only commit:
A <-- main, master, develop, topic, feature/short, feature/tall
But, when we first create an empty repository—one that has no commits in it—we're always in this weird state, where we can't have any branch names either. We get out of the weird state by making the first commit.
There's a trick here. When Git creates a new commit, what Git does is:
git write-tree
) and the commit itself (git commit-tree
), which generates the hash ID; andWhen there isn't any current commit, as in this special situation in a new repository, there's still a current branch name.3 It's just that this branch doesn't exist! Git has two names for this, calling it an unborn branch sometimes, and an orphan branch at other times.
What Git does, when you're on an unborn branch, is that it notices, in that first step. It tries to read the current hash ID from the current branch name. This fails because the branch does not exist, but Git does not panic: it just says Aha, it's time to create the branch! Git goes ahead and creates a commit with no parent. Then Git writes the hash ID of the new commit into the branch name, which creates the branch.
The end result is commit A
: the one that does not point backwards, because it can't. Git calls this kind of commit—one with no parent—a root commit, and modern Git prints [root commit]
when you make one.
You can make more than one root commit if you want, but there's nothing special about extra root commits and they only have a few specialized uses. So there's rarely any reason to bother. If you do want to make a new root commit, the user-oriented commands for this are git checkout --orphan
and git switch --orphan
. These two commands are actually different, but we'll stop here for this answer.
3The underlying mechanism that Git uses for this is that there is a very special name,4 HEAD
, that stores the name of the current branch. This is implemented today as a file in .git/HEAD
, and you can inspect it directly with any file viewer, but in general you should not alter it directly: use git update-ref --symbolic
from programs, or other more user-oriented Git commands from the command-line.
4This name is so special that if the .git/HEAD
file goes missing, Git stops believing that the repository is a repository. Because the HEAD
file is relatively active, sometimes a computer crash causes the OS to remove the HEAD
file. You can recover from this by creating an appropriate .git/HEAD
file. You should never have to do this, because your computer never crashes or bluescreens or has the power fail, right? 🙄
Upvotes: 1
Reputation: 145
I did this and it let me make a new branch:
git commit --allow-empty -m 'Empty initial commit'
EDIT: this creates an empty commit as 1 first commit is needed before creating a branch from the current branch.
Upvotes: 1
Reputation: 61
Make sure you cloned your own branch and follow step :
git branch mynewBranch
Upvotes: 0
Reputation: 30174
If you haven't yet created a single revision on the repository, you can switch to a different branch from master/main by running:
git branch -m a-new-name
Upvotes: 1