Reputation: 625
I had a problem with a detached head in my Git. I did checkout on the previous commit. After that, we had made commits. So after I created branch for this commits.
git checkout -b detached-head-after-gitlab-crush
So after that, I made changes and committed changes.
But now, when I write git branch
:
* (detached from a71c5ea)
detached-head-after-gitlab-crush
master
So I want to understand what is the current branch, and how it was created.
How I can make changes to the last branch, and also I can't push this current branch to the origin.
git log --all --decorate --oneline --graph
shows following result:
* 548af67 (HEAD) Images were changed, and issue with Printing Gate entry records
* be89a73 (origin/detached-head-after-gitlab-crush, detached-head-after-gitlab-c
* 6979cba Files before solving HEAD detached problem
* fb89a62 Rules added, made some changes which I don't remember
| * d4183f3 (origin/master, origin/HEAD, master) Merged files
| |\
|/ /
| * 3c3cadc Merge branch 'master' of http://gitlab.sdu.edu.kz/sdu/portal
Upvotes: 1
Views: 727
Reputation: 490118
Let's start with a quick review of things you already know:
The basic unit of storage in any Git repository is the commit. There are smaller units inside commits—for instance, commits contain files, sort of vaguely similar to how atoms hold protons, neutrons, and electrons—but the commit itself is the container you're supposed to use. (In this analogy, we want to do chemistry, not nuclear physics. 😀)
Each commit has its own unique hash ID. These hash IDs are big and ugly and hard for humans to work with, so Git sometimes shortens them for display: you have, for instance, 548af67
(which is short for something that is much longer), be89a73
(again short for something 40 characters long), and so on. I got these from your git log --all --decorate --oneline --graph
output. Every Git repository everywhere agrees that these hash IDs are reserved for these particular commits, even if that repository doesn't have these commits.
You can always use the raw hash ID to refer to any commit you have in your own repository.
A commit itself contains:
data: a snapshot of all of your files. This is not a difference from a previous commit. It is a full copy of every file. (These files are stored in a compressed, frozen, read-only, Git-only format. Because they are read-only, they can be shared. For instance, if most of your commits have a README
file, and there are only three versions of README
in 90 commits, with one change every 30 commits, then one internal Git-format frozen copy of README serves the first 30 commits, another servers the next 30, and so on.)
metadata: information about the commit, such as who made it (name and email address), when (date-and-time stamps), and why (a commit log message). In this metadata, each commit can list the raw hash ID of some previous commits.
Most commits list exactly one previous commit hash ID. The listed commit is this commit's parent, i.e., the commit that comes just before this commit. Some commits list more than one previous commit, i.e., have more than one parent. One commit in every non-empty repository was the first commit ever in that repository, and therefore lists no parent.
Whenever Git has access to one commit, Git can look at that one commit's parent (or parents), and therefore work backwards to the previous commit. That gives Git access to the parent commit. So now Git can find another parent—the parent of this parent, i.e., the grandparent of the commit we had a moment ago—and of course that commit has a parent. So Git can find the entire history just by starting from the last commit and working backwards.
But commit hash IDs look random, and are unpredictable. How are you—and Git—to know, quickly and easily, which commit is the last one? This is where branch names come in. A branch name like master
or detached-head-after-gitlab-crush
stores one commit hash ID. That hash ID is, by definition, the last commit in that branch.
Let's use uppercase letters to stand in for actual commit hash IDs. We'll run out pretty fast, which is one reason Git doesn't use simple uppercase letters, but it will be OK for our drawing. Let's suppose our repository is really new and has only three commits in it. The very first one is commit A
, and because it is the first one, it has no parent:
A
We will call the second commit B
. It remembers the hash ID of the first commit as its parent. So we will say that commit B
points to commit A
, and draw that like this:
A <-B
And of course, commit C
contains the hash ID of commit B
, so C
points backwards to B
:
A <-B <-C
To find C
quickly, Git stores its hash ID in the name master
:
A--B--C <-- master
(At this point we're a little tired and get lazy and draw the connections from commit to commit as lines, instead of arrows. Just remember that they're still arrows, and they come out of the child and point to the parent, never from the parent to the child. All parts of every commit are frozen for all time, including the arrows coming out of it, so we can't go back and add a forward-pointing arrow later: we make a commit, it has one or two backwards-pointing arrows for its parents, and from then on we're stuck with that. The children know who their parents are, but the parents never know who their children are.)
Now that we have this, let's add another branch name to this picture. Rather than spell crash
as crush
I'll just call this one develop
:
A--B--C <-- master, develop
Now let's add a new commit to our collection. We do that using the usual process in Git. We'll call the new commit D
, regardless of what hash ID Git comes up with. New commit D
will point back to existing commit C
because we'll start working to make D
by checking out commit C
. So once D
is made, it will look like this:
A--B--C
\
D
with D
pointing up-and-left to C
, C
pointing back to B
, and so on.
HEAD
comes inWe have a problem now. We have two branch names. Which one should remember the new commit D
?
To tell Git which one, we will attach the special name HEAD
, in all capital letters, to one of the two existing branch names. Let's say we have this as the arrangement before we make new commit D
:
A--B--C <-- master (HEAD), develop
Then we'll get this afterward:
A--B--C <-- develop
\
D <-- master (HEAD)
But if that's not what we want, we should git checkout develop
first. Then we'll have:
A--B--C <-- master, develop (HEAD)
and when we make new commit D
, we'll get:
A--B--C <-- master
\
D <-- develop (HEAD)
We get the same set of commits either way. The difference is that when Git makes a new commit, it writes the hash ID of the new commit to whichever branch-name the name HEAD
is attached-to. That branch name then automatically points to the new commit.
In fact, the parent of the new commit is whichever commit HEAD
's branch name pointed-to before. That, by definition, is the commit we had out. We used git checkout master
or git checkout develop
, but either way, we selected existing commit C
.
HEAD
is not attached to a branch nameNow that we have:
A--B--C <-- master
\
D <-- develop (HEAD)
we can go on and make more new commits:
A--B--C <-- master
\
D--E--F <-- develop (HEAD)
for instance. But we could, if we like, take our HEAD off. Git has a mode where we can make HEAD
point directly to any existing commit. Let's say, for instance, that for some reason, we want to make our HEAD
point directly to commit E
:
A--B--C <-- master
\
D--E <-- HEAD
\
F <-- develop
We can now make a new commit—we'll call it G
—that will point back to existing commit E
. Git will write the new commit's hash ID, whatever it may be, into the detached HEAD, giving us:
A--B--C <-- master
\
D--E--G <-- HEAD
\
F <-- develop
There is nothing inherently wrong with this mode, but it makes things harder later. Let's say we want to go look at commit C
again. We might run git checkout master
. This would attach the name HEAD
to the name master
again:
A--B--C <-- master (HEAD)
\
D--E--G
\
F <-- develop
How will you find commit G
? We can find C
easily: it's our current commit and the names HEAD
and master
both find it. We can find B
from C
by going back one. We can't find D
from C
, but we can find F
from the name develop
. From F
, we can step back to E
, and from there to D
. But we can't step forward. All of Git's arrows point backwards. There is no longer an easy way to find commit G
.
The solution is to add a new branch name before we switch away from G
. That's what you did earlier, when you created the name detached-head-after-gitlab-crush
. We can do the same thing another way, if we know the hash ID of G
(if it's still on the screen, for instance):
git branch save-it <hash-of-G>
will do the trick:
A--B--C <-- master (HEAD)
\
D--E--G <-- save-it
\
F <-- develop
and now we can work with commit C
for a while, and maybe even make a new commit H
that makes master
change to point to H
:
A--B--C--H <-- master (HEAD)
\
D--E--G <-- save-it
\
F <-- develop
All we have to do to get back to G
is git checkout save-it
, which attaches HEAD
to the name save-it
(which still points to G
):
A--B--C--H <-- master
\
D--E--G <-- save-it (HEAD)
\
F <-- develop
While there is nothing fundamentally wrong with the detached HEAD mode in Git, it's hard to work with. You have to manually create and/or update branch names to remember your commits.
Git will enter this detached HEAD mode whenever you tell it to:
git checkout --detach master
for instance says "I want to use the commit identified by master
, but I want to do that in detached HEAD mode".
Git will also detach HEAD whenever you ask it to check out (or switch to, with the new Git 2.23 and later git switch
) a commit by a raw hash ID, or by any name that is not a branch name. That includes remote-tracking names like origin/master
, and tag names like v1.2
, if you've created tags.
Some commands, including specifically git rebase
, will temporarily detach HEAD while they run. If they can't finish, so that you are in the middle of a rebase, they will stop and leave you in this detached HEAD mode. You must then choose whether to finish the rebase, or terminate it entirely with git rebase --abort
. (If you don't want to do either of these you're a bit stuck: you really do have to do one of them.)
So: find out why you keep getting into this detached HEAD mode. What are you doing that is causing it? You can create new branch names for commits with git branch
, or with git checkout -b
(or in Git 2.23 and later, git switch -c
, with c
standing for create) to fix things when you're in detached HEAD mode, or if you don't need to remember where you are now—if you're deliberately looking at a historical commit that you can, and probably did, find using git log
for instance—just use git checkout
or git switch
to re-attach your HEAD to an existing branch name. But except for those special cases where you do want a detached HEAD (to use a tagged commit or look at a historical commit), or cases like working-on-rebase where you are in detached HEAD mode until you finish, you probably don't want to work in detached HEAD mode. So, don't do that!
Upvotes: 6
Reputation: 3535
From the result of your git branch command, you are in detached head of a commit, and you only create a new branch without the detached commit.
To create a branch from a previous commit you have 3 methods:
git branch branchname sha1-of-commit
git branch branchname HEAD~5
git checkout -b branchname sha1-of-commit or HEAD~3
Upvotes: 1