Home > Mobile >  Why are not all references listed in the .git/FETCH_HEAD file listed in my .git/ref folder?
Why are not all references listed in the .git/FETCH_HEAD file listed in my .git/ref folder?

Time:09-16

My git config file specifies the following refspec for origin:

fetch =  refs/heads/rschmidtner/*:refs/remotes/origin/rschmidtner/*

git ls-remote --heads | grep rschmidtner shows me six branches. These branches are also listed in the .git/FETCH_HEAD file.

Why does .git/refs/remotes/origin/rschmidtner only contain three references, even after I fetched via git fetch origin?

CodePudding user response:

The short answer to your question is that refs are no longer always stored individually in files.

Git's original design did exactly that: a regular reference, such as a branch name or remote-tracking name, was just a file in the refs/ directory. If refs/heads/master existed, it was a file containing one hash ID, and hence that hash ID was the tip commit of the master branch. When remotes and remote-tracking names were invented, refs/remotes/origin would contain directories and/or files as needed to hold names like refs/remotes/origin/rschmidtner/foo.

This stuff still exists, but it has several drawbacks:

  • Repositories with tens of thousands of tags (Chromium, for instance) cause bad behavior (such as "use up all the inodes") if the tags are stored this way.

  • On typical Windows and macOS systems, a branch named fred and a branch named Fred become conflated, even though Git itself assumes these are two different branch names—and they are on Linux servers.

  • Last—although there's an intent to preserve this for the indefinite future, to avoid creating problems for older Git versions—this means that it is impossible, for instance, to have branches named hello and hello/world at the same time: this would require the file system to store both a file named refs/heads/hello and a directory named refs/heads/hello containing a file named world.

    This particular issue combines with the case-folding issue (second bullet point) when someone creates refs/heads/fred, then decides to add refs/heads/Fred/one, refs/heads/Fred/two, and so on, on their Linux system. It works great there, and causes misery in case-folding land.

Current versions of Git therefore store ref names in a hybrid fashion: there is a file in .git named packed-refs that contains newline-terminated lines, each of which lists a ref or "peeled ref" name and hash ID. The peeled value is useful with tags, which often point to tag objects that in turn point to commits: this way Git can find the commit instantly, without having to indirect through one or more tag objects. (The peeled variant is indicated by a suffix, ^{}.) If a packed ref exists for ref R, and an unpacked file exists for the same ref R, the unpacked file overrides the packed ref: in this way, Git can leave the packed-refs file alone when it updates a branch name, for instance.

Future versions of Git are likely to have a real refs database, rather than this rather cheesy flat-file "database" named packed-refs. When that happens, even the act of updating a ref won't create a file-system-level file. This will neatly solve all the listed drawbacks, while creating one new drawback: namely, you won't be able to just create files any more.

So how do I find my refs?

Use the prescribed API: git for-each-ref for listing while reading the values programmatically, git branch and git tag for viewing them as a human. Use git symbolic-ref to read or write individual symbolic refs—currently this means HEAD and refs/remotes/remote/HEAD only1—and git update-ref to change the value stored in one or more refs. Use git rev-parse to read a specific ref's value; pay close attention to the documentation here as there is a lot of helpful stuff you can have Git do for you during the rev-parse operation.

Don't depend on the files existing, as they already don't in some cases.


1A symbolic ref is a reference containing the name of another reference. Git originally did this just for HEAD, by making it a symbolic link. Since symlinks don't exist on some systems, Git had to adapt by changing this to a file containing the name of another ref. The Git source code for dealing with these has long been slightly squirrelly as a result, and if you made a normal-looking ref name that was a symbolic ref, it would occasionally misbehave. For example, deleting a branch named indir that was really a symbolic ref to a branch named b would delete branch b rather than indir.

Some of these oddities have been fixed, but unless you plan to find and fix the rest of them, it's wisest to avoid symbolic refs other than those directly supported by Git, i.e., the special HEAD names.

  •  Tags:  
  • git
  • Related