3

I am newbie and poorly able to use git.
I somehow detached head from my git project repository, wrote many code and I tried to commit changes.
I used:

git add *
git commit -m "comment"

I tried to use git push, but I got

fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)

I am used git checkout master and changes was returned to master's branch commit, detached HEAD branch was deleted and I don't know how to return code which was lost.

Vine
  • 41
  • 2

3 Answers3

9

(summing up the information in other answers)

Your commit is not lost, you can find it using git reflog :

# Here is a sample output :
# your commit should appear as 'HEAD@{xx}: commit: comment'
#    ('comment' is the commit message you used)
$ git reflog
f9a32813c (HEAD -> master) HEAD@{0}: checkout: moving from 92e0cd0c6... to master
92e0cd0c6 HEAD@{1}: commit: comment
046a74ef8 (tag: foo) HEAD@{2}: checkout: moving from master to 046a74ef87ea...

Copy the hash you see at the beginning of the line (in the example above : 92e0cd0c6), and, from your current master branch, run :

git merge --ff-only 92e0cd0c6
LeGEC
  • 46,477
  • 5
  • 57
  • 104
3

For the command git checkout x where x is a commit, a tag, or anything that is not a short branch name, it leads to the detached HEAD. The detached HEAD is like a nameless branch. Once you switch to another branch or another detached HEAD, the previous one is gone and it makes people confused and think it's lost.

git checkout c8919ac48bc24080fa9d5c477bae8bcfb47991d4  # detached HEAD
git checkout master # switch to master, not detached
git checkout refs/heads/master # detached HEAD, though "master" and "refs/heads/master" are equivalent in most cases
git tag v1.0 c8919ac48bc24080fa9d5c477bae8bcfb47991d4
git checkout v1.0 # detached HEAD
git checkout origin/master # detached HEAD

Run git reflog and you can find the commit on which the detached HEAD was.

Then use git checkout <commit> to go back to the detached HEAD. A better practice is to run git checkout -b foo <commit> instead, to create a local branch foo from the commit so that the commit can be traced. This command is equal to git branch foo <commit> && git checkout foo.

If you actually intended to make the new commit on master, after you find the commit, you can cherry-pick it to master directly, git checkout master && git cherry-pick <commit>.

If you really want to push a detached HEAD to a branch, you need to specify the remote and the refspec instead of a bare git push.

git pull origin -r foo   # before push, pull first to avoid non-fast-forward error
                         # -r is recommended to do a rebase pull
git push origin HEAD:foo # push the detached HEAD in the local repository to "foo" in "origin" 
ElpieKay
  • 27,194
  • 6
  • 32
  • 53
0

The good news is that your commits are still available. You will need to use git reflog to find them.

The first thing to know is that Git is not about files, and not really about branches either. It's about commits. Each commit holds files, and a collection of commits forms a branch or set of branches, but the central thing in Git is the commit itself.

Every commit has a unique number. This number takes the form of a big ugly random-looking hash ID, such as 51ebf55b9309824346a6589c9f3b130c6f371b8f. Every commit you make stores a full and complete snapshot of all of your files—well, all of your tracked files anyway—in a read-only, compressed, frozen-for-all-time, Git-only format, that only Git can use. So when you make a commit, Git produces a new random-looking hash ID: a new number, different from every other commit ever. That's why the number has to be so big.

If we did not have a computer to remember them for us, we would have to write down every one of these random-looking hash IDs. But we have a computer, so we have it remember the IDs for us.

Every commit we make remembers the hash ID of some earlier commit. That is, we pick a commit, e.g., by some hash ID, with git checkout. That's what you did when you got into your detached HEAD state. That commit—the one we have checked out—is the current commit. Then, when you make a new commit, Git makes our new commit and puts into it, along with the snapshot, some metadata: your name and email address, the date-and-time of when you made the commit, your log message, and—very important for Git itself—the hash ID of the commit you had checked out, that was the current commit.

This process of creating the new commit results in a new unique hash ID, and now that commit becomes the current commit. That is, Git writes the new commit's hash ID into HEAD. Since the current commit contains the ID of the previous commit, Git can find its way, backwards, from here to the previous commit:

... <--previous  <--current   <--HEAD

A detached HEAD is not the normal way to work, though. Normally, we git checkout branch-name, e.g., git checkout master. In this case, the name HEAD is attached to a branch name. If we use single uppercase letters to stand in for actual hash IDs, and the current end of the master branch is commit H, we can draw that like this:

... <-F <-G <-H   <--master(HEAD)

That is, Git has saved the hash ID of the last commit in our branch named master under our name master. Then, it attached the special name HEAD to the name master. From HEAD, Git can find the name master, and from there, Git can find the hash ID of commit H.

Commit H, meanwhile, contains the hash ID of earlier commit G, so from H Git can work backwards to G. Of course, G contains the hash ID of earlier commit F, so Git can work backwards there too, to F ... and F contains the hash ID of another earlier commit, and so on.

If you git checkout master, do some work, and then create a new commit, Git creates that new commit with some random-looking hash ID, but we'll just call it I. Commit I will point back to commit H:

...--F--G--H   <-- ???
            \
             I   <-- ???

The name master pointed to H a moment ago, but because HEAD is attached to the name master, Git now writes I's commit hash ID into the name master, making master point to I:

...--F--G--H
            \
             I   <-- master (HEAD)

How will we find commit H? Well, the name master has I's hash ID written down in it, so we can easily find I. And commit I has commit H's hash ID written inside it—so that's how we'll find H, by starting with HEAD to get master to get I to find H.

We don't need the kink in the drawing any more as we don't need a pointer directly pointing to H. But in fact, we do have several. One is in the reflog for HEAD, and one is in the reflog for master. Git keeps a history of every commit that master has ever named, and every commit that HEAD has ever named, including through branch names like master, so at this point we have:

...--F--G--H   <-- master@{1}, HEAD@{1}
            \
             I   <-- master (HEAD)

If the name master pointed to G earlier, you'll have master@{2} as well.

When you detach HEAD—e.g., by git checkout <hash-of-F>—you get HEAD pointing directly to the commit, because instead of containing a branch name, HEAD now just contains a raw commit hash ID. So you have this—I'll draw in HEAD@{1} but not any of the others:

...--F   <-- HEAD
      \
       G--H--I   <-- master, HEAD@{1}

If you now create a new commit, it gets a new unique hash ID which we'll call J:

       J   <-- HEAD
      /
...--F   <-- HEAD@{1}
      \
       G--H--I   <-- master, HEAD@{2}

Note how what was HEAD@{1} is now HEAD@{2}; what was HEAD is now HEAD@{1}; and HEAD refers to new commit J.

Every time you move HEAD, or attach it to a different branch, all the numbers bump up one to make room for the previous value. Git stores the previous hash ID from HEAD into the reflog for HEAD, and writes the appropriate branch name or hash ID into the name HEAD.

So: run git reflog HEAD. Note that it spills out both abbreviated hash IDs, and HEAD@{number}. The hash IDs are the true names of the commits. The commits are what Git is all about; that's what it is keeping. The names with @{1}, @{2}, etc., all get renumbered frequently. Note that Git only keeps these old values around for a while—30 to 90 days by default.1 After that, Git figures you probably don't care any more, and starts erasing old reflog entries.

Once you've seen which reflog entry or entries are the commits you want back, give them some name. For instance, you can create a new branch name to refer to the last commit in a chain. If you have done this:

       J--K--L   <-- HEAD
      /
...--F
      \
       G--H--I   <-- master

by making three detached-HEAD commits, and then this:

       J--K--L   <-- HEAD@{1}
      /
...--F
      \
       G--H--I   <-- master (HEAD)

by re-attaching HEAD to the name master, you could do this:

git branch lazarus HEAD@{1}

and then you would have:

       J--K--L   <-- lazarus, HEAD@{1}
      /
...--F
      \
       G--H--I   <-- master (HEAD)

The new name lazarus now points to the resurrected commit. (Perhaps I should have made four of them?)

See more at GIT restore last detached HEAD.


1The 30 day limit is for unreachable commits and the 90 day limit is for reachable commits. The term reachable here is from graph theory, and reachability implies a starting point. The starting point in this case is the current commit hash ID in the ref. If the reflog's saved hash ID is reachable from the ref-name value, the entry has a longer lease on life.

Once the reflog entry is gone, the commit itself has one fewer ways to reach it. Commits must be reachable from some name—some branch name, or tag name, or any other kind of name—or otherwise Git's garbage collector, git gc, will eventually delete them. So these reflog entries serve to keep the commits themselves alive. While all commits are frozen for all time, commits themselves will be reaped (discarded) if they are unreachable, so once the reflog entry is gone, the commit needs to be reachable from some other reachable commit, or named directly by some other name.

torek
  • 448,244
  • 59
  • 642
  • 775