Let's address this rather long comment (which I should do in another comment, but this won't fit at all so I'm answering a different question, namely, "how does detaching and reattaching HEAD work"):
Tnx torek I managed to force away that error with the link you provided. I also had some detached HEAD issues which I got rid by following Razan Paul's post here Fix a Git detached head? But all my changes while in detached HEAD mode was lost after the fixes. I have copies so I can just recreate them manually I guess. So in the future when I want to go back to the latest working version on my local working directory. What procedure will suite my situation best? I see so many different suggestions I don't know what's best for me
(incidentally, when addressing a comment to someone in particular you may want to use the @<name>
syntax so they get alerted).
What you need is a proper Git book. The best free one is, in my opinion, the Pro Git 2 book. (There are not many, if any, other free ones so "best" could be vacuously true here. :-) ) A shorter, non-book reference that I find helpful is Think Like (a) Git, because Git is built atop some very simple bits of graph theory—but unless you know this, and until you realize this, you're stuck in this xkcd cartoon.
(I even have my own start on a book, but I have very little time to work on it these days.)
There are many work-flows you can use in Git. The first thing to remember, though, is that Git's "unit of storage" as it were—the thing it desperately tries to protect, so that you can get it back—is the commit. That's why you were having trouble pushing: you were telling some other Git: throw away some commits. Git does not want to do that.
Commits are uniquely identified by their hash IDs. These hash IDs are not very useful to humans, so Git gives us things like branch names to remember particular IDs—but in some cases you may have to resort to raw IDs, which you can cut and paste with your mouse, for instance.
Git's desire to keep commits around means that when you make commits using a detached HEAD, Git tries to keep those too. It will do so for, by default, at least 30 days. You can find the hash IDs using git reflog
. If you use git reset --hard
to make Git "forget" commits that were on a branch (rather than a detached HEAD), Git keeps those IDs around for at least 30 days as well, on the branch name's reflog. There's one reflog for each branch name, and one for HEAD
itself.
Last, an attached HEAD—attached being the opposite of detached—is when you are, as git status
will say, "on" some particular branch:
$ git status
On branch master
...
In this case, the branch name master
is how Git actually identifies which commit you have checked out right now; and the name HEAD
is simply attached to the name master
.
To make this all work right, Git works backwards.
A brief introduction to graphs
To really comprehend what all this means, you need to draw the commit graph. Remember that each commit has its own unique hash ID—those big ugly 40-character-long strings of a hexadecimal number, like 5be1f00a9a701532232f57958efab4be8c959a29
—but that's kind of unweildy, so you might want to just use single letters for small drawings:
A <-B <-C <--master
This is a pretty-new repository with just three commits in it. We made A
first, then we made B
, then we made C
. (We'll run out of names for commits when we get to our 27th commit, so you can see why Git uses longer hash IDs.)
Since Git works backwards, the name master
identifies, not commit A
, but rather commit C
. We say that the name master
points to C
.
Commit C
, being our third commit, has inside it the name—the hash ID—of our second commit, commit B
. We say that C
's parent commit is B
. So commit C
points to B
. Likewise, B
has inside it the hash ID of commit A
, so B
points back to A
.
Commit A
was the first commit. It can't point back to a previous commit, so it just doesn't. This makes commit A
special: it's a root commit. Every non-empty repository has at least one of these, the first commit ever made.
When you run git log
, Git will start with your current commit—commit C
, here—and work backwards: it shows you C
, and then since C
points back to B
, Git shows B
too. Since B
points back to A
, Git shows A
as well; but A
is a root commit, so Git can stop.
How branches grow
Since we're on master
, let's make a new commit D
. We'll do whatever we want with the source, git add
files, and run git commit
to create D
.
Where will D
point back to? Well, obviously it has to point back to C
. So Git makes D
have a parent commit of C
:
A <-B <-C <-D
Git's final step here is to change the name master
so that it holds the hash ID of commit D
, giving us this picture:
A <-B <-C <-D <--master
Note that all the arrows necessarily point backwards. Moreover, all the arrows that are inside commits are frozen for all time: D
points back to C
, never anywhere else. The only arrow that changes is the one coming out of the branch name! So I tend to draw them without the internal arrows, to save space and make it fit better:
A--B--C--D <-- master
This is another key to understanding Git: branch names move, over time. Branches acquire new commits, and the names point to the last commit on the branch—what Git calls the tip commit. Git uses this tip commit to find the next-older commit for that branch: the tip's parent. Git uses that commit to find its previous commit, and so on, all the way back to the root commit.
The name HEAD
normally just identifies a branch name
Let's complicate-up the above repository by adding a branch coming out of C
:
A--B--C--D <-- master
\
E <-- develop
Here, we now have two branches. The name develop
identifies commit E
, whose parent is C
. If we now run:
git checkout master
we'll be on branch master
, as git status
will say; if we create a new commit F
now, its parent will be D
. If we instead git checkout develop
and create a new commit F
, its parent will be E
instead. So Git needs to know: which branch are we on? This is where we need to draw in the name HEAD
:
A--B--C--D <-- master (HEAD)
\
E <-- develop
or:
A--B--C--D <-- master
\
E <-- develop (HEAD)
When your HEAD
is attached like this, it's attached to a branch name. When you make new commits, Git will change the branch name so that it points to the new commit you just made.
A "detached HEAD" simply holds the commit hash ID directly
If you find yourself in detached HEAD
mode, it's usually because you checked out an older commit directly. For instance, let's check out commit B
, while keeping everything we have:
A--B <-- HEAD
\
C--D <-- master
\
E <-- develop
I had to bend the graph lines around to fit HEAD
in, but now HEAD
points directly to commit B
.
If we now make a new commit F
, its parent will be B
, and Git will make HEAD
point directly to F
:
A--B--F <-- HEAD
\
C--D <-- master
\
E <-- develop
Note that whenever Git creates a new commit, the commit to which HEAD
points—directly, if detached, or indirectly, if attached to a branch name—also changes! The new commit's parent is always whatever was HEAD
just a moment ago, and now HEAD
is the new commit, which is once again the current commit.
(This is also why parents don't know their children's hash IDs, but a child does know its own parent's ID. Git has to work backwards, because children know their parents: the parent commits exist when the child is created; but parent commits—which are read-only and frozen in time—don't know what children they will acquire in the future.)
As soon as we re-attach HEAD
, though, we "lose" the ID of commit F:
A--B--F <-- ???
\
C--D <-- master (HEAD)
\
E <-- develop
Who remembers F
's ID? The answer is: only the reflogs.
If you want to hang on to commit F
, you should find its ID in your reflogs and attach a name to it—a branch or tag name, usually:
A--B--F <-- newbranch
\
C--D <-- master (HEAD)
\
E <-- develop
Now newbranch
remembers the ID of F
. If you'd made two commits, F
and G
, while you had a detached HEAD, you'd have to find the later one somehow and make sure your name points to G
, so that you have:
G <-- newbranch
/
A--B--F
\
C--D <-- master (HEAD)
\
E <-- develop
and not:
G <-- ???
/
A--B--F <-- newbranch
\
C--D <-- master (HEAD)
\
E <-- develop
This is why making a lot of commits on a detached HEAD is not a great idea: it can be really hard to tell, from hash IDs, which commit is the tip-most.