ewok
ewok

Reputation: 21443

git clone did not give me all branches

I just cloned a repo from bitbucket. For some reason, My local repo does not have all the branches in it. It only has master. I tried git checkout origin/develop and ended up with a detached HEAD. git fetch gives nothing.

I was able to solve it by creating a new branch manually with git checkout -b develop, then executing git pull origin develop, but I can't remember ever having to do that before. Usually I can just sync branches from my remotes.

What's going on?

Upvotes: 1

Views: 2211

Answers (1)

torek
torek

Reputation: 487983

You did get all the branches, just not in the form you realized.

What you wanted, as Arkadiusz Drabczyk said in a comment, was to run git checkout develop, which invokes a special shortcut option inside Git—but by running git checkout -b develop earlier, you broke the shortcut option. :-)

Here's how the whole thing works. It's based on remote-tracking names, which are names like origin/master and origin/develop. They have a longer spelling, a sort of full name—instead of just origin/master, you can type in refs/remotes/origin/master, for instance—but why type the longer name when the shorter one will do?1


1Maybe the full name is useful for when you're angry or upset, like when the Stella android starts up: HARCOURT FENTON MUDD!


Cloning is shorthand for some more-basic commands

Running:

git clone url

(or the same with a target directory option) is essentially the same as running a bunch of separate commands, most (but not all) being Git commands:

  1. mkdir some-directory && cd some-directory && git init

    The directory name here is based on the url argument: e.g., if you git clone ssh://web.site/path/to/foo.git you get a directory named foo. The result at this point is a completely-empty repository, with no branches and no commits; but it does have a configuration.

  2. git remote add origin url

    This adds a "remote" to the configuration. The remote's associatd URL is url. The remote's associated fetch line—the configured value for remote.origin.fetch—is set to +refs/heads/*:refs/remotes/origin/*.

  3. There may be some extra git config commands here (if you add -c options to your git clone, or use some of the other clone options like --bare). In your case there aren't any here, but I include this on general principles.

  4. git fetch origin

    This has your Git call up the Git at the now-configured url and obtain from that Git, all of its branches and tags. Note that this is the same as any other git fetch origin command you might run.

  5. git checkout master

    It's possible to get Git to use other branch names than master here, but master is the most common and most likely. You can control what git checkout command git clone runs yourself, but if you don't, you get controlled by the repository you are cloning: they say which branch to check out. If they haven't done anything special, they will say "check out master", so this last step will check out master.

Git's fetch renames their branches

Note that during step 4, git fetch origin obtains from the other Git a list of all its branch names, and their corresponding commit hash IDs. (To see these directly, without using git fetch, run git ls-remote origin.) But git fetch doesn't save those branch names as your branch names, because your branch names are yours. Saving theirs could mess with your work! So what git fetch does, by default, is to save their branch names as your remote-tracking names.

If they have a branch named master, your Git changes this name—the full name is refs/heads/master; master is just the short version—to read instead refs/remotes/origin/master, the full name of the shorter origin/master. If they have a develop, you get an origin/develop, and so on.

In other words, for every branch name branch that they (origin) have, you get a remote-tracking name, spelled origin/branch. The hash IDs for each commit carry across unchanged, but the names change. If you look back at step 2 above, when git clone added the remote named origin, you'll see exactly how it is that Git knows to make this particular substitution. The important thing, though, is that it does make the substitution.

Hence, when your clone process finishes, you still have no branches at all. And yet, your git clone runs git checkout master, and somehow, that works.

The git checkout command is fancy

If you ask your Git to check out a branch by name, and you don't have a branch by that name, your Git won't just say: Sorry, I don't know that name. Instead, your Git will look at all of your remote-tracking names.

Since you have run git clone (and maybe some git fetch-es since then for good measure), you have the remote-tracking name origin/master, and all the other remote-tracking names that your git fetch step created or updated. Your Git scans through all of them. If it finds any that look a lot like master, or whatever branch you asked to check out, your Git will say to itself: Aha, I found exactly one matching name: origin/master. You must want me to create master, using the same commit that I see under origin/master.

This is a "do what I mean" (or DWIM) trick that Git uses when you run git checkout name but don't have a branch named name. It is shorthand for git checkout -b name origin/name.

This won't work if you have both an origin/develop and a remote2/develop, for instance, because your Git will find two matching names. But to get a remote2/develop, you'd have to run git remote add remote2 another-url and then git fetch remote2. If you haven't added another remote, you can't get two suitable remote-tracking .../develops.

But it also won't work for develop if you've already created a local branch named develop, because now git checkout develop will just check out the local develop. You already have it! There's no need—and no ability—to create a new one with the fancy DWIM code.

One last caveat

The git checkout command will only let you get "on" a local branch. Once you have a local branch named develop, or create it with git checkout, the git checkout command will set things up so that your current branch is the one named develop. After that succeeds, running git status will say:

On branch develop

and then print the rest of the status.

A remote-tracking name, such as origin/develop, won't work for this. Instead, Git will give you that "detached HEAD" mode you saw. Git will check out the commit, but you will be on no branch at all.

To exit this "detached HEAD" mode, you simply have to git checkout some branch that does exist, or that can and will be created by the DWIM code.

Upvotes: 3

Related Questions