Search code examples
gitgit-remote

Create remote that is not HEAD-less but does not have a master branch


I am new to git and was following the following excellent instruction from Missing Semester. About 1 hour in, they discuss remotes. I went through the process of creating a remote (as a sub-directory for testing), however, I purposefully deviated from the instructions because that helps my understanding. I pushed a branch to the remote but did not name it "master". I eventually realized there was no HEAD in this remote, and things just didn't work right. I looked at the git remote set-head command, and thought that would solve the problem. I don't think it did. What eventually worked was creating a branch called "master" on the remote, and HEAD automatically pointed to it. I did not see this requirement in the documentation. Is there a way to have a repository without a branch named "master"? Alternatively, if there is no branch named "master", how does one get the HEAD pointing to a branch after the remote is created?


Solution

  • TL;DR: you need to reach into the server (somehow—the how depends on the server) and have the server's Git set its HEAD. (Note that GitHub have a clicky web button interface for this, under settings and then branches.)

    Usually, though, you just start by having the server create its first branch having the name master, after which everything works the way everyone expects. Note that if a server Git already has a branch named br1, you can clone that server Git repository with:

    git clone -b br1 url

    on your client side, followed by:

    git push origin br1:master
    

    to ask that server to create the name master, pointing to the same commit as your own br1 in your clone. Now the server does have a master and all is back to normal.

    Long: what, exactly, is going on here?

    Every Git repository has a HEAD

    All Git repositories have a HEAD. This is a basic requirement of Git: when Git goes to look for a repository, it makes several sanity checks on each directory it inspects. The directory must:

    • exist as a directory;
    • contain a file or symlink named HEAD that appears valid;
    • have a refs/ sub-directory; and
    • have an objects/ sub-directory (unless overridden by setting GIT_OBJECTS_DIRECTORY in the environment).

    Failing any one of these tests makes Git move on to the next candidate for a repository directory, or, if it has run out of candidates, give up and declare that there is no Git repository to be found.

    The code that does this checking is in setup.c. The is_valid_ref test is elsewhere (path.c) and basically tests whether HEAD is a symbolic link to refs/*, or starts with the literal ASCII text ref: refs/, or contains a valid hash ID. The first two tests are the same as testing whether a reference is a symbolic ref (see the git symbolic-ref command).

    A HEAD file that contains a commit hash ID is a detached HEAD. This indicates that the Git repository is not on a branch at all. There is little more to be said about this (until we get to cloning, below, anyway). To set a detached HEAD in a repository in which you can run Git commands directly, use git switch --detach or git checkout --detach or git checkout with any argument that automatically causes a detached HEAD condition, i.e., any reference to a commit that is not literally a branch name.

    (There is no way to set a detached HEAD with the GitHub web interface.)

    A symbolic reference can be made to a reference that does not exist. This is how a fresh, empty Git repository, with no branches, can be on branch master even though branch master does not yet exist. Hence, the branch to which HEAD links, if HEAD is not detached, may or may not actually exist—but there definitely will be a HEAD.

    A new, totally-empty repository, created by git init, has a symbolic HEAD that refers to the name master.

    (There is no way to set a HEAD reference to a branch that does not exist with the GitHub web interface, as far as I can tell. But a new repository is still created with HEAD symbolically referring to master.)

    Remotes

    Note that a remote is just a string in your own local Git repository. That is, you might create the name origin to hold the url https://github.com/user/project.git or ssh://[email protected]/user/project.git or some such.

    Nothing you do with your local repository will change anything in some other Git over on GitHub or Bitbucket, so any tweaks you make here have no effect. You need to cause the Git over at GitHub, or Bitbucket, or whatever other site actually hosts the server, to do something to its Git repository, before any change you make will actually have any effect.

    (For GitHub, you simply log in to GitHub and manipulate your repositories using their web pages. This lets you do exactly those things they allow you to do, which is less than everything you could do if you could log in to their servers.)

    Cloning: the other repository recommends a checkout

    When you clone a repository using:

    git clone [<options>] <url>
    

    the options allow you to specify, among other things:

    • What to call the other Git: the name of the remote. Use -o xyzzy to change the name from the default origin to xyzzy, for instance.

    • What branch name to check out in step six (see below). For instance, git clone -b plugh tells your Git to git checkout plugh in step six.

    If you leave out the -b option, your Git asks their Git which branch they recommend. Your Git will, in general, then attempt to git checkout this branch name in step six.

    The method and details behind this recommendation have changed over time. In the distant past, your Git read their Git's HEAD hash ID, if they supplied one, and guessed which branch name that meant. In modern Git, their Git can supply the literal text string read from the symbolic HEAD reference. Your Git can choose to use this, or ignore it.

    The git clone command is shorthand for a six-step process:

    1. Run mkdir (or whatever your computer's command may be) to create a new, empty directory, in which the new Git repository will be created. The remaining five steps run in this new empty directory.

    2. Run git init to create a Git repository (a .git sub-directory within the new directory).

    3. Run git remote add to add a remote. The name of the remote will be origin unless you used the -o option. The URL for the remote will be the URL you gave to git clone.

    4. Run any extra git config commands required, e.g., from -c options.

    5. Run git fetch origin (or whatever remote name you used), to obtain commits and populate your repository with remote-tracking names like origin/master based on whatever branch names exist in the other repository.

    6. Run git checkout (pre-2.23) or git switch -c (Git 2.23 or later), to create a new branch and check out its commit.

    The branch name used in step six is the one from your -b option. Your Git will look at their branch (and tag!) names and if your -b option matches one of their branch or tag names, will use that name. If the name is a tag name, your Git will go into detached HEAD mode on the appropriate commit.

    If you did not use -b, your Git will ask their Git which branch they recommend, as described above. If that is a name that works, your Git will create that name and check out that commit.

    If both of those fail, your Git will fall back on the name master. If that name also fails, your Git will tell you that step six failed: that you have a valid repository, and you got it all cloned, but there is no current branch and commit. It will be up to you to rectify this situation.

    If the repository you clone has no master

    Suppose you clone a repository from some URL, and that repository:

    • does have commits; and
    • does have branches—let's say, without loss of generality, that they have br1 and br2, for instance; but
    • does not have a branch named master.

    Suppose further that you, on your git clone line, did not supply a -b option. Then:

    • In step 5, your git fetch obtained their br1 and br2 names and made your origin/br1 and origin/br2 names. Since they have no master, your Git did not make an origin/master.

    • In step 6, you did not supply -b, so your Git asks their Git which branch they recommend.

    If you have not taken any special action, the branch they'll recommend is the one whose symbolic name is in their HEAD. If that symbolic name is master, they will recommend their master—which of course does not exist. Your Git will be puzzled by this recommendation (it doesn't work) and will use its own built in fallback: try master. This too will not work and you will see what you saw.

    Of course, if they do have a branch named master, your step 5 will have created your origin/master so your step 6 will be able to create your own master based on their origin/master, and all will be well.

    If you have them change their symbolic name HEAD to point to their br2 branch, the recommendation you will get in step 6 will be that your Git should create a branch named br2 based off their br2, which is now (via step 5) your origin/br2, and that will all work well too. But you need to reach into the server somehow and get them to change their recommendation.