ardakshalkar
ardakshalkar

Reputation: 625

Strange branch name after creating branch from detached head

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

Answers (2)

torek
torek

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.

Branch names find a particular commit

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.

This is where HEAD comes in

We 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.

A detached HEAD occurs when HEAD is not attached to a branch name

Now 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

What you need to do is find out why you keep getting your HEAD detached

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

Zig Razor
Zig Razor

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:

  • You can create the branch via a hash:

git branch branchname sha1-of-commit

  • Or by using a symbolic reference:

git branch branchname HEAD~5

  • To checkout the branch when creating it, use

git checkout -b branchname sha1-of-commit or HEAD~3

Upvotes: 1

Related Questions