gitreferencegit-plumbing

Why does git update-ref accepts non-refs/ references?


While commands like "git log" will happily accept different expressions for the same ref, e.g.

refs/heads/master
heads/master
master

this is not true for "git update-ref". For example

git update-ref master HEAD^

is not the same as

git update-ref refs/heads/master HEAD^

The first command creates a new ref .git/master (and in turn introduces an ambiguity regarding refs/heads/master). Only the second command really updates master's head. (.git/refs/heads/master)

Why does git update-ref accepts references without "refs/" prefix? Shouldn't there be at least a warning or a command line option to force creation of such references?

It took me quite a long time to figure out why

git update-ref master HEAD^

did not work as expected.


Solution

  • tl;dr

    The main reason why git log and git update-ref behave differently is because git-log is a high-level command – and therefore designed to be user-friendly – while git-update-ref is a low-level command meant to be used in scripts.

    Porcelain vs. Plumbing

    In Git parlance, high-level commands are referred to as porcelain while low-level ones are collectively called plumbing.

    Porcelain commands are meant to be used interactively by humans and therefore expose familiar high-level concepts such as symbolic references. Plumbing commands, on the other hand, are meant to be used programmatically – usually in scripts – and allow to directly manipulate Git's internal file system structures.

    git-update-ref

    While git-log is able to resolve references, git-update-ref – it being a plumbing command – interprets the first argument as either a symlink or a regular file name depending on how it's specified.

    From the documentation:

    It follows real symlinks only if they start with "refs/": otherwise it will just try to read them and update them as a regular file.

    So that's why if you say git update-ref master <value> it will treat master as a file name and create it in the .git directory. By the same token, when you say git update-ref HEAD <value> it will write <value> to the .git/HEAD file.