15

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.

Henning
  • 1,289
  • 2
  • 11
  • 25

2 Answers2

12

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.

Enrico Campidoglio
  • 56,676
  • 12
  • 126
  • 154
  • 1
    Thanks! Ok, i understand that there are some special cases like HEAD or ORIG_HEAD. But apart from that: Shouldn't there be just 'standard' refs/? Why would i want to have a reference directly in my git root dir, or to make it concret, why would i want to have .git/someref instead of .git/refs/someref? – Henning Mar 15 '16 at 10:35
  • 3
    Well, `update-ref` doesn't have an opinion about what you should and shouldn't do with it. Being a low-level command, it simply allows you to manipulate Git's internal file system. Normally, you wouldn't create a branch reference in the _.git_ directory but other kinds or references like [`HEAD`](https://git-scm.com/book/en/v2/Git-Internals-Git-References#The-HEAD) do exist there and you should be able to update them as well. – Enrico Campidoglio Mar 15 '16 at 11:30
  • True, but (as of git 2.41) plumbing is inconsistently _sometimes_ opinionated: `update-ref main` happily creates .git/main, `rev-parse main` then complains `warning: refname 'main' is ambiguous.`, yet `show-ref main` won't mention it and same command in delete mode `update-ref -d main` fails `error: refusing to update ref with bad name 'main'` cause it [isn't all-caps](https://git-scm.com/docs/gitglossary.html%23def_pathspec#Documentation/gitglossary.txt-aiddefpseudorefapseudoref). – Beni Cherniavsky-Paskin Jun 27 '23 at 08:34
  • oh, under a worktree there is also difference in where it puts it :-\ `git update-ref MAIN` puts it in the git-dir `.git/worktrees/mytree2/MAIN`, while `git update-ref main` puts it in git-common-dir `.git/main`. – Beni Cherniavsky-Paskin Jun 27 '23 at 09:48
2

Git 2.18 (Q2 2018) improves git update-ref robustness, because "git update-ref A B" is supposed to ensure that ref A does not yet exist when B is a NULL OID, but this check was not done correctly for pseudo-refs outside refs/ hierarchy, e.g. MERGE_HEAD.

See commit db0210d, commit 65eb8fc, commit c0bdd65 (10 May 2018) by Martin Ågren (``).
Helped-by: Rafael Ascensão rafa.almas@gmail.com.
(Merged by Junio C Hamano -- gitster -- in commit 26597cb, 30 May 2018)

refs: handle zero oid for pseudorefs

According to the documentation, it is possible to "specify 40 '0' or an empty string as <oldvalue> to make sure that the ref you are creating does not exist."
But in the code for pseudorefs, we do not implement this, as demonstrated by the failing tests added in the previous commit.
If we fail to read the old ref, we immediately die. But a failure to read would actually be a good thing if we have been given the zero oid.

With the zero oid, allow -- and even require -- the ref-reading to fail. This implements the "make sure that the ref ... does not exist" part of the documentation and fixes both failing tests from the previous commit.

Since we have a strbuf err for collecting errors, let's use it and signal an error to the caller instead of dying hard.


Note: as explained in "Does git lock a remote for writing when a user pushes?"

some references are considered "pseudorefs".
These are never packed.
The pseudorefs are things like ORIG_HEAD, MERGE_HEAD, and so on.


With Git 2.29 (Q4 2020), starts preliminary changes to refs API, which does influence those non refs references.

See commit 55dd8b9, commit 0974341 (27 Jul 2020), and commit 0b7de6c (16 Jul 2020) by Han-Wen Nienhuys (hanwen).
(Merged by Junio C Hamano -- gitster -- in commit 95c687b, 17 Aug 2020)

pseudorefs:Modify pseudo refs through ref backend storage

Signed-off-by: Han-Wen Nienhuys

The previous behavior was introduced in commit 74ec19d4be ("pseudorefs: create and use pseudoref update and delete functions", Jul 31, 2015, Git v2.6.0-rc0), with the justification "**alternate ref backends still need to store pseudorefs in GIT_DIR**".

Refs such as REBASE_HEAD are read through the ref backend.
This can only work consistently if they are written through the ref backend as well. Tooling that works directly on files under .git should be updated to use commands to read refs instead.

The following behaviors change:

  • Updates to pseudorefs (eg. ORIG_HEAD) with core.logAllRefUpdates=always will create reflogs for the pseudoref.

  • non-HEAD pseudoref symrefs are also dereferenced on deletion.

git update-ref now includes in its man page:

If config parameter "core.logAllRefUpdates" is true and the ref is:

  • one under "refs/heads/", "refs/remotes/", "refs/notes/", or
  • a pseudoref like HEAD or ORIG_HEAD; or
  • the file "$GIT_DIR/logs/<ref>" exists

then git update-ref will append a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all symbolic refs before creating the log name) describing the change in ref value.


With Git 2.29 (Q4 2020), adjust sample hooks for hash algorithm other than SHA-1.

See commit d8d3d63, commit 8c7e505, commit 6a117da (23 Sep 2020) by Denton Liu (Denton-L).
(Merged by Junio C Hamano -- gitster -- in commit 9f489ac, 29 Sep 2020)

hooks--update.sample: use hash-agnostic zero OID

Signed-off-by: Denton Liu

The update sample hook has the zero OID hardcoded as 40 zeros.

However, with the introduction of SHA-256 support, this assumption no longer holds true.
Replace the hardcoded $z40 with a call to

git hash-object --stdin </dev/null | tr '[0-9a-f]' '0'  

so the sample hook becomes hash-agnostic.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250