From 668cdc043fe6f6d1fa2bf2b3f3c2375a20819e77 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 14 Dec 2023 14:37:02 +0100 Subject: refs: propagate errno when reading special refs fails Some refs in Git are more special than others due to reasons explained in the next commit. These refs are read via `refs_read_special_head()`, but this function doesn't behave the same as when we try to read a normal ref. Most importantly, we do not propagate `failure_errno` in the case where the reference does not exist, which is behaviour that we rely on in many parts of Git. Fix this bug by propagating errno when `strbuf_read_file()` fails. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- refs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'refs.c') diff --git a/refs.c b/refs.c index fcae5dddc6..00e72a2abf 100644 --- a/refs.c +++ b/refs.c @@ -1806,8 +1806,10 @@ static int refs_read_special_head(struct ref_store *ref_store, int result = -1; strbuf_addf(&full_path, "%s/%s", ref_store->gitdir, refname); - if (strbuf_read_file(&content, full_path.buf, 0) < 0) + if (strbuf_read_file(&content, full_path.buf, 0) < 0) { + *failure_errno = errno; goto done; + } result = parse_loose_ref_contents(content.buf, oid, referent, type, failure_errno); -- cgit v1.2.3 From 70c70de616c306cd4bb7c70426e394d08f929dff Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 14 Dec 2023 14:37:06 +0100 Subject: refs: complete list of special refs We have some references that are more special than others. The reason for them being special is that they either do not follow the usual format of references, or that they are written to the filesystem directly by the respective owning subsystem and thus circumvent the reference backend. This works perfectly fine right now because the reffiles backend will know how to read those refs just fine. But with the prospect of gaining a new reference backend implementation we need to be a lot more careful here: - We need to make sure that we are consistent about how those refs are written. They must either always be written via the filesystem, or they must always be written via the reference backend. Any mixture will lead to inconsistent state. - We need to make sure that such special refs are always handled specially when reading them. We're already mostly good with regard to the first item, except for `BISECT_EXPECTED_REV` which will be addressed in a subsequent commit. But the current list of special refs is missing some refs that really should be treated specially. Right now, we only treat `FETCH_HEAD` and `MERGE_HEAD` specially here. Introduce a new function `is_special_ref()` that contains all current instances of special refs to fix the reading path. Note that this is only a temporary measure where we record and rectify the current state. Ideally, the list of special refs should in the end only contain `FETCH_HEAD` and `MERGE_HEAD` again because they both may reference multiple objects and can contain annotations, so they indeed are special. Based-on-patch-by: Han-Wen Nienhuys Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- refs.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) (limited to 'refs.c') diff --git a/refs.c b/refs.c index 00e72a2abf..8fe34d51e4 100644 --- a/refs.c +++ b/refs.c @@ -1820,15 +1820,65 @@ done: return result; } +static int is_special_ref(const char *refname) +{ + /* + * Special references get written and read directly via the filesystem + * by the subsystems that create them. Thus, they must not go through + * the reference backend but must instead be read directly. It is + * arguable whether this behaviour is sensible, or whether it's simply + * a leaky abstraction enabled by us only having a single reference + * backend implementation. But at least for a subset of references it + * indeed does make sense to treat them specially: + * + * - FETCH_HEAD may contain multiple object IDs, and each one of them + * carries additional metadata like where it came from. + * + * - MERGE_HEAD may contain multiple object IDs when merging multiple + * heads. + * + * There are some exceptions that you might expect to see on this list + * but which are handled exclusively via the reference backend: + * + * - CHERRY_PICK_HEAD + * + * - HEAD + * + * - ORIG_HEAD + * + * - "rebase-apply/" and "rebase-merge/" contain all of the state for + * rebases, including some reference-like files. These are + * exclusively read and written via the filesystem and never go + * through the refdb. + * + * Writing or deleting references must consistently go either through + * the filesystem (special refs) or through the reference backend + * (normal ones). + */ + static const char * const special_refs[] = { + "AUTO_MERGE", + "BISECT_EXPECTED_REV", + "FETCH_HEAD", + "MERGE_AUTOSTASH", + "MERGE_HEAD", + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(special_refs); i++) + if (!strcmp(refname, special_refs[i])) + return 1; + + return 0; +} + int refs_read_raw_ref(struct ref_store *ref_store, const char *refname, struct object_id *oid, struct strbuf *referent, unsigned int *type, int *failure_errno) { assert(failure_errno); - if (!strcmp(refname, "FETCH_HEAD") || !strcmp(refname, "MERGE_HEAD")) { + if (is_special_ref(refname)) return refs_read_special_head(ref_store, refname, oid, referent, type, failure_errno); - } return ref_store->be->read_raw_ref(ref_store, refname, oid, referent, type, failure_errno); -- cgit v1.2.3 From 0a06892ddde5bc3a82a4fe2963e3ea294252ffdd Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 14 Dec 2023 14:37:13 +0100 Subject: bisect: consistently write BISECT_EXPECTED_REV via the refdb We're inconsistently writing BISECT_EXPECTED_REV both via the filesystem and via the refdb, which violates the newly established rules for how special refs must be treated. This works alright in practice with the reffiles reference backend, but will cause bugs once we gain additional backends. Fix this issue and consistently write BISECT_EXPECTED_REV via the refdb so that it is no longer a special ref. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- refs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'refs.c') diff --git a/refs.c b/refs.c index 8fe34d51e4..c76ce86bef 100644 --- a/refs.c +++ b/refs.c @@ -1840,6 +1840,8 @@ static int is_special_ref(const char *refname) * There are some exceptions that you might expect to see on this list * but which are handled exclusively via the reference backend: * + * - BISECT_EXPECTED_REV + * * - CHERRY_PICK_HEAD * * - HEAD @@ -1857,7 +1859,6 @@ static int is_special_ref(const char *refname) */ static const char * const special_refs[] = { "AUTO_MERGE", - "BISECT_EXPECTED_REV", "FETCH_HEAD", "MERGE_AUTOSTASH", "MERGE_HEAD", -- cgit v1.2.3