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 namedFred
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
andhello/world
at the same time: this would require the file system to store both a file namedrefs/heads/hello
and a directory namedrefs/heads/hello
containing a file namedworld
.This particular issue combines with the case-folding issue (second bullet point) when someone creates
refs/heads/fred
, then decides to addrefs/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.