-2

My Goal : Make a successful checkout without detached HEAD error.

What I have done so far : Currently I have two remotes setup :

origin : It points to a repo of a course I am currently learning.

fsprojects: It points to my personal repo.

$ git remote
fsprojects
origin


$ git remote show fsprojects
* remote fsprojects
  Fetch URL: https://github.com/fs-projects/core-concepts-nextjs.git
  Push  URL: https://github.com/fs-projects/core-concepts-nextjs.git
  HEAD branch: events-projects-finsih
  Remote branches:
    client-and-serverside-fetching-useswr                            tracked
    events-project-data-fetching-pre-rendering                       tracked
    events-projects-finsih                                           tracked
    nextjs-optimisations                                             tracked
    project-api-routes                                               tracked
    ssg-functions                                                    tracked
    writing-api-in-same-server-and-leveraging-from-server-and-client tracked
  Local branch configured for 'git pull':
    project-api-routes merges with remote project-api-routes
  Local ref configured for 'git push':
    project-api-routes pushes to project-api-routes (up to date)

I checked out a commit in remote "origin" so I entered into a detached HEAD state(which is as expected). I did some new changes in the detached HEAD state and created a new branch project-api-routes from that point.

I now setup this branch to track remote branch with same name located in fsprojects/projects-api-routes using this command -

git branch -u fsprojects/project-api-routes

git status output :

$ git status
On branch project-api-routes
Your branch is up to date with 'fsprojects/project-api-routes'.

nothing to commit, working tree clean

Now when I checkout to any branch belonging to either remotes origin or fsprojects with below command

git checkout remotes/fsprojects/events-projects-finsih

I get below error :

$ git checkout remotes/fsprojects/events-projects-finsih
Note: switching to 'remotes/fsprojects/events-projects-finsih'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 539cfbe All events projects - finish up all pages

I am not able to understand why I am getting this error despite my source is a branch and not any commit id.

I want to checkout to the destination branch without any detached HEAD error. Any help/guidance to achieve it will be helpful. Thanks in advance!!

Just FYI -

git branch -a (to see list of all branch that can be checked out) gives below output :

MINGW64 /d/Next.Js/nextjs-course-code ((539cfbe...))
$ git branch -a
* (HEAD detached at fsprojects/events-projects-finsih)
  main
  project-api-routes
  remotes/fsprojects/client-and-serverside-fetching-useswr
  remotes/fsprojects/events-project-data-fetching-pre-rendering
  remotes/fsprojects/events-projects-finsih
  remotes/fsprojects/nextjs-optimisations
  remotes/fsprojects/project-api-routes
  remotes/fsprojects/ssg-functions
  remotes/fsprojects/writing-api-in-same-server-and-leveraging-from-server-and-client
  remotes/origin/01-getting-started
  remotes/origin/01-getting-started-extra-files
  remotes/origin/02-file-based-routing
  remotes/origin/02-file-based-routing-extra-files
  remotes/origin/03-prj-routing
  remotes/origin/03-prj-routing-extra-files
  remotes/origin/04-data-fetching
  remotes/origin/04-data-fetching-extra-files
  remotes/origin/05-prj-data-fetching
  remotes/origin/06-optimizations
  remotes/origin/07-api-routes
  remotes/origin/07-api-routes-extra-files
  remotes/origin/08-prj-api-routes
  remotes/origin/08-prj-api-routes-extra-files
  remotes/origin/09-context
  remotes/origin/09-context-extra-files
  remotes/origin/10-prj-blog
  remotes/origin/10-prj-blog-extra-files
  remotes/origin/11-deployment
  remotes/origin/11-deployment-extra-files
  remotes/origin/11-deployment-static
  remotes/origin/12-auth
  remotes/origin/12-auth-extra-files
  remotes/origin/HEAD -> origin/main
  remotes/origin/empty
  remotes/origin/main
  remotes/origin/starting-prj
  remotes/origin/zz-nextjs-summary
  remotes/origin/zz-nextjs-summary-extra-files
  remotes/origin/zz-prj-nextjs-summary
  remotes/origin/zz-prj-react-summary
  remotes/origin/zz-react-summary
  remotes/origin/zz-react-summary-extra-files
utkarsh-k
  • 836
  • 8
  • 17
  • 1
    https://meta.stackoverflow.com/a/285557/5586359 Text, please! http://idownvotedbecau.se/imageofcode. [edit] your question, copy/paste from the terminal and properly [format](https://stackoverflow.com/help/formatting) it as code. Images are not helpful, they're hard to read and hard to search. Badly formatted code is hard to read. Images are only good to show something non-textual (like colors). – phd Jul 03 '22 at 11:07
  • @phd thanks for sharing the link. I have edited my question now and removed images. – utkarsh-k Jul 03 '22 at 14:09
  • 1
    Unclear what you expect or believe, but the simple fact is that checking out a remote tracking branch like `git checkout remotes/fsprojects/events-projects-finsih` (?) is indeed a detached head. The only checkout that is not detached is the name of a local branch. – matt Jul 03 '22 at 14:23
  • By the way I recommend that you stop saying `checkout`. Just abandon it entirely. Switch to `switch`. – matt Jul 03 '22 at 14:24
  • @matt the git verb is literally `checkout` though... So I don't understand the advice to stop saying "checkout". You even used that language twice in your own answer. *confused emoji*. – Wyck Jul 03 '22 at 14:32
  • 1
    @joanis thank you for pointing out to that post. It was indeed helpful in understanding what I was doing wrong. – utkarsh-k Jul 03 '22 at 15:01
  • 1
    @utkarsh-k about "checkout" versus "switch", there was a change some years back, with the command `git switch ` added for switching to a different branch. In its most basic use, it does the same thing as `git checkout `, except it's clearer, and it won't go into detached HEAD mode by default, it'll give you an error instead. – joanis Jul 03 '22 at 15:55
  • 1
    E.g., `git switch origin/main` tells me `fatal: a branch is expected, got remote branch 'origin/main'`, reminding me that I'm trying to switch to something that's not actually a local branch. – joanis Jul 03 '22 at 15:56

2 Answers2

2

Let me add a few notes. (This should be a comment, but I want to use formatting—and I'm going to overrun the comment length limit, as usual. )

  1. You say that with git checkout remote/name:

    I get below error :

    You are in 'detached HEAD' state. ...
    

    This is, technically, not an error. It's just a hint / warning message that you have done something you might not have intended. The git switch command will (as joanis noted in a comment) produce an error, and not put you into detached-HEAD state.

  2. Both git checkout and git switch have a --guess option (which is turned on by default). The explicit option—which comes with a git config setting as well—was new in Git 2.30.0 so if your Git version is older than this, it lacks the --guess and --no-guess options and guessing is always turned on.

The guess mode, which used to be call DWIM mode (DWIM = Do What I Mean, vs "do what I say"), has always been a feature of git checkout and now git switch that allows you to create a (local) branch name from a remote-tracking name without having to tell Git explicitly to create a branch name from a remote-tracking name. But before we can describe this properly, we have to define the difference between a branch name and a remote-tracking name in the first place. It's actually very simple:

  • A branch name is, internally, a name whose internal spelling starts with refs/heads/.

  • A remote-tracking name is, internally, a name whose internal spelling starts with refs/remotes/.

Unfortunately, these internal spellings don't show up in git branch output—not directly anyway. Here's a snippet of your git branch -a output, which shows you what does show up:

  main
  project-api-routes
  remotes/fsprojects/client-and-serverside-fetching-useswr
  remotes/fsprojects/events-project-data-fetching-pre-rendering
  remotes/origin/main
  remotes/origin/starting-prj

The name main is a branch name: it's shown by stripping refs/heads/ off the front, so that refs/heads/main gets displayed as main.

The name project-api-routes is similarly a branch name.

The name remotes/fsprojects/client-and-serverside-fetching-useswr is a remote-tracking name: it's shown in this case by stripping refs/ off the front of refs/remotes/fsprojects/client-and-serverside-fetching-useswr.

For some reason—I have no idea what this reason is; it's lost to history somewhere in the early 00's (between 2001 and 2004 perhaps)—git branch -r will strip refs/remotes/ from the remote-tracking names, but git branch -a strips only refs/ from them; yet at the same time, git branch -a doesn't stop stripping heads/ from the (local) branch names. It might be more consistent if git branch -a stripped only refs/ from everything, for instance. But it is what it is, and it's mostly usable:

  • Note that if you accidentally (or on purpose) create a local branch whose full name is refs/heads/remotes/origin/main, git branch -a will show you remotes/origin/main twice.
  • One of these will be the (local) branch remotes/origin/main, full name refs/heads/remotes/origin/main.
  • The other will be the remote-tracking remotes/origin/name, full name refs/remotes/origin/main.
  • Git won't confuse the two, but you probably will! (Fortunately, git branch -a will color mode turned on will show the branch name in green and the remote-tracking name in red, by default.)

The remote-tracking names spring into existence when you run git fetch. You use git fetch to call up a remote—another Git repository, whose URL you've stored under the name origin or fsprojects for instance—and that Git repository has branch names. Your Git software copies those branch names to your Git repository, but to avoid overwriting your branch names, renames those branch names, turning them into remote-tracking names.

Anyway, now that you understand the difference between a remote-tracking name and a branch name—that the latter is your Git's memory of some other Git's branch name—and that git checkout will go into detached-HEAD mode when given a remote-tracking name, you're also ready to understand the --guess or DWIM option.

Suppose that, as is the case right now, you do not have a branch name xyzzy (and no file with that name either). If you run:

git checkout xyzzy

or:

git switch xyzzy

then these commands will try to use xyzzy as a branch name. This will fail because there is no such branch name. (The old and not-so-great git checkout command will, next, try it as a file name too, which is one reason you might want to start using git switch exclusively here. But let's assume that this also fails.)

Now, if xyzzy isn't a branch name, you'd expect an attempt to switch to that branch to just fail. And it almost has failed right now, but now, both commands check to see if --guess is enabled—and it is! So both commands will look through all of your remote-tracking names now.

If there's some remote-tracking name that ends with /xyzzy, that remote-tracking name matches the branch name you asked for (sort of). That is, if there's a refs/remotes/fsprojects/xyzzy, that's a candidate match. If there's a refs/remotes/origin/xyzzy, that, too, is a candidate match.

Having found some number of candidate matches—zero, one, two, or maybe even more if you have more remotes—the guess code now checks the number of matches. If and only if there is exactly one match, the --guess code changes your command from:

git switch xyzzy

to:

git switch --track <remote-tracking-name>

which creates a new (local) branch (xyzzy) using the specified remote-tracking name.

The guessing code will fail if there are no matches, or if there are two or more matches. So if you had a remotes/origin/xyzzy and a remotes/fsprojects/xyzzy, the --guess mode would fail. If you have neither (which is the case now), the --guess mode would also fail.

Why I'm going on so long about this

Most Git clones have exactly one remote, the one named origin. So in most clones, you can run:

git switch develop

even if you don't have a develop yet as long as you have an origin/develop. The --guess mode—which is on by default—will find origin/develop and create your own develop.

Once you have two remotes, though, the --guess mode often stops working because both remotes have a develop. Git has yet another configuration setting to make this work again:

checkout.defaultRemote
      When you run git checkout <something> or git switch <something> and only have one remote, it may implicitly fall back on checking out and tracking e.g. origin/<something>. This stops working as soon as you have more than one remote with a <something> reference. This setting allows for setting the name of a preferred remote that should always win when it comes to disambiguation. The typical use-case is to set this to origin.

That is, checkout.defaultRemote says that if there are two or more matches in the --guess code, and one of them comes from the specified remote, make that one "win", as if it were the only match.

If this feels like a Rube Goldberg machine, that's because it is. I almost wish Git never had the --guess mode in the first place. Note that you can run:

git switch -t origin/somebranch

to create somebranch from origin/somebranch, so it's not like --guess mode buys a lot (it lets you avoid typing -t and the remote name and a slash).

torek
  • 448,244
  • 59
  • 642
  • 775
  • thank you so much for sharing such a detailed information. I am quite curious to know - When I did git checkout remotes/fsprojects/events-projects-finsih and ran into detached HEAD state then at that point of time guess code had returned no match or 1 match? – utkarsh-k Jul 09 '22 at 18:57
  • The string `remotes/fsprojects/events-projects-finsih` (with or without any typos corrected) starts with `remotes/`, so it *doesn't* invoke guess mode. However, `fsprojects/events-projects-finsih` *would* invoke it and would find `refs/remotes/fsprojects/events-projects-finsih` if that exists. – torek Jul 09 '22 at 19:28
  • very interesting. could you please add the point that if the branch name begins with `/remotes` then it doesn't invoke guess mode, in your answer. Also one important followup question I want to discuss - Suppose I don't have a local branch `events-projects-finsih` and if I do a git checkout on `fsprojects/events-projects-finsih` and say `refs/remotes/fsprojects/events-projects-finsih` exist then a local branch `events-projects-finsih` will be created and will be setup to track the remote branch `refs/remotes/fsprojects/events-projects-finsih`? Is my understanding correct? – utkarsh-k Jul 09 '22 at 21:12
  • It's not so much that it specifically begins with `remotes`. There is not enough space in comments to describe the algorithm properly *and descriptively*. The branch name must (a) not exist as a branch name and (b) a `git for-each-ref refs/remotes` must produce as the names, strings that match the exact string you typed in once the `refs/remotes/` and remote name and slash are stripped off. If you had a `refs/remotes/origin/remotes/foo` and asked for `remotes/foo`, that would be a guess candidate. – torek Jul 09 '22 at 21:23
  • As for the "one last question": if `fsprojects/events-projects-finsih` does not exist, Git will iterate over the names listed by `git for-each-ref refs/remotes`. If that produces `refs/remotes/fsprojects/fsprojects/events-projects-finsih`, *that* name matches. But `refs/remotes/fsprojects/events-projects-finsih` has `fsproejcts/` stripped to do the matching; the resulting name `events-projects-finsih` *doesn't* match. – torek Jul 09 '22 at 21:26
  • As for general advice: use or don't use the `--guess` mode as you like when you're typing in commands, just always *watch the result* to make sure that the guess Git made is the one you wanted, if you're letting it do guessing. From *programs*, try to avoid the guessing mode entirely (use Git "plumbing" commands if at all possible as they don't have user-interaction in mind and just do what you ask them to). – torek Jul 09 '22 at 21:29
1

Nearly all the details in your question (multiple remotes, what you did previously) are irrelevant. The facts are simple: Checking out a remote tracking branch like

git checkout remotes/fsprojects/events-projects-finsih 

(?) is indeed a detached head. The only checkout that is not detached is the name of a local branch. That's all there is to know, and all you need to know, about detached heads.

Thus, given your branch list

git branch -a
  main
  project-api-routes
  remotes/fsprojects/...

...the only nondetached checkouts you can do are main and project-api-routes. Those are the only local branch names you have. If you want to check out another commit in a nondetached way, you need to make another local branch name that points to it.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Great thank you very much. I now know that you can checkout to a remote branch without a detached head error when you have a local tracking branch for the same. Without a local tracking branch I can checkout to the remote branch but in a detached HEAD state. I agree that after understanding the problem I know some of the details are irrelevant but that wasn't intentional although I will put an edit on my question for future readers to ignore the irrelevant stuff. – utkarsh-k Jul 03 '22 at 15:03
  • @utkarsh-k You're still not listening. I'll say it again. It doesn't matter what you _have_. It matters what you _say_. Even if local `project-api-routes` points to the same commit as `fsprojects/project-api-routes`, saying `git checkout project-api-routes` is not detached but saying `git checkout fsprojects/project-api-routes` is detached. – matt Jul 03 '22 at 15:08
  • Agreed. I got the point. I was checking to a commit so I will get the detached HEAD error which is as expected. I have added an EDIT section in my question for future readers to ignore the irrelevant stuff. – utkarsh-k Jul 03 '22 at 15:33
  • Don't change the question to include the answer. My answer is the answer. – matt Jul 03 '22 at 16:08