From f6936e62a50e8bc9e268f3396618d33e88f4df7e Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 May 2024 08:50:42 +0200 Subject: refs: rename `is_pseudoref()` to `is_root_ref()` Rename `is_pseudoref()` to `is_root_ref()` to adapt to the newly defined terminology in our gitglossary(7). Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- ref-filter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ref-filter.c') diff --git a/ref-filter.c b/ref-filter.c index 59ad6f54dd..361beb6619 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2756,7 +2756,7 @@ static int ref_kind_from_refname(const char *refname) return ref_kind[i].kind; } - if (is_pseudoref(get_main_ref_store(the_repository), refname)) + if (is_root_ref(get_main_ref_store(the_repository), refname)) return FILTER_REFS_PSEUDOREFS; return FILTER_REFS_OTHERS; -- cgit v1.2.3 From afcd067dad27fa7eddde01ebde90daa6cc541cc5 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 May 2024 08:50:51 +0200 Subject: refs: do not check ref existence in `is_root_ref()` Before this patch series, root refs except for "HEAD" and our special refs were classified as pseudorefs. Furthermore, our terminology clarified that pseudorefs must not be symbolic refs. This restriction is enforced in `is_root_ref()`, which explicitly checks that a supposed root ref resolves to an object ID without recursing. This has been extremely confusing right from the start because (in old terminology) a ref name may sometimes be a pseudoref and sometimes not depending on whether it is a symbolic or regular ref. This behaviour does not seem reasonable at all and I very much doubt that it results in anything sane. Last but not least, the current behaviour can actually lead to a segfault when calling `is_root_ref()` with a reference that either does not exist or that is a symbolic ref because we never initialized `oid`, but then read it via `is_null_oid()`. We have now changed terminology to clarify that pseudorefs are really only "MERGE_HEAD" and "FETCH_HEAD", whereas all the other refs that live in the root of the ref hierarchy are just plain refs. Thus, we do not need to check whether the ref is symbolic or not. In fact, we can now avoid looking up the ref completely as the name is sufficient for us to figure out whether something would be a root ref or not. This change of course changes semantics for our callers. As there are only three of them we can assess each of them individually: - "ref-filter.c:ref_kind_from_refname()" uses it to classify refs. It's clear that the intent is to classify based on the ref name, only. - "refs/reftable_backend.c:reftable_ref_iterator_advance()" uses it to filter root refs. Again, using existence checks is pointless here as the iterator has just surfaced the ref, so we know it does exist. - "refs/files_backend.c:add_pseudoref_and_head_entries()" uses it to determine whether it should add a ref to the root directory of its iterator. This had the effect that we skipped over any files that are either a symbolic ref, or which are not a ref at all. The new behaviour is to include symbolic refs know, which aligns us with the adapted terminology. Furthermore, files which look like root refs but aren't are now mark those as "broken". As broken refs are not surfaced by our tooling, this should not lead to a change in user-visible behaviour, but may cause us to emit warnings. This feels like the right thing to do as we would otherwise just silently ignore corrupted root refs completely. So in all cases the existence check was either superfluous, not in line with the adapted terminology or masked potential issues. This commit thus changes the behaviour as proposed and drops the existence check altogether. Add a test that verifies that this does not change user-visible behaviour. Namely, we still don't want to show broken refs to the user by default in git-for-each-ref(1). What this does allow though is for internal callers to surface dangling root refs when they pass in the `DO_FOR_EACH_INCLUDE_BROKEN` flag. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- ref-filter.c | 2 +- refs.c | 19 +++++-------------- refs.h | 5 +++-- refs/files-backend.c | 4 ++-- refs/reftable-backend.c | 2 +- t/t6302-for-each-ref-filter.sh | 17 +++++++++++++++++ 6 files changed, 29 insertions(+), 20 deletions(-) (limited to 'ref-filter.c') diff --git a/ref-filter.c b/ref-filter.c index 361beb6619..23e81e3e04 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2756,7 +2756,7 @@ static int ref_kind_from_refname(const char *refname) return ref_kind[i].kind; } - if (is_root_ref(get_main_ref_store(the_repository), refname)) + if (is_root_ref(refname)) return FILTER_REFS_PSEUDOREFS; return FILTER_REFS_OTHERS; diff --git a/refs.c b/refs.c index c1c406fc5f..4fec29e660 100644 --- a/refs.c +++ b/refs.c @@ -856,7 +856,7 @@ static int is_root_ref_syntax(const char *refname) return 1; } -int is_root_ref(struct ref_store *refs, const char *refname) +int is_root_ref(const char *refname) { static const char *const irregular_root_refs[] = { "AUTO_MERGE", @@ -865,26 +865,17 @@ int is_root_ref(struct ref_store *refs, const char *refname) "NOTES_MERGE_REF", "MERGE_AUTOSTASH", }; - struct object_id oid; size_t i; if (!is_root_ref_syntax(refname)) return 0; - if (ends_with(refname, "_HEAD")) { - refs_resolve_ref_unsafe(refs, refname, - RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - &oid, NULL); - return !is_null_oid(&oid); - } + if (ends_with(refname, "_HEAD")) + return 1; for (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++) - if (!strcmp(refname, irregular_root_refs[i])) { - refs_resolve_ref_unsafe(refs, refname, - RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - &oid, NULL); - return !is_null_oid(&oid); - } + if (!strcmp(refname, irregular_root_refs[i])) + return 1; return 0; } diff --git a/refs.h b/refs.h index d0374c3275..8a574a22c7 100644 --- a/refs.h +++ b/refs.h @@ -1052,7 +1052,8 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT]; void update_ref_namespace(enum ref_namespace namespace, char *ref); /* - * Check whether the reference is an existing root reference. + * Check whether the provided name names a root reference. This function only + * performs a syntactic check. * * A root ref is a reference that lives in the root of the reference hierarchy. * These references must conform to special syntax: @@ -1076,7 +1077,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref); * * - MERGE_AUTOSTASH */ -int is_root_ref(struct ref_store *refs, const char *refname); +int is_root_ref(const char *refname); int is_headref(struct ref_store *refs, const char *refname); diff --git a/refs/files-backend.c b/refs/files-backend.c index 0fcb601444..06240ce327 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -351,8 +351,8 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store, strbuf_addstr(&refname, de->d_name); dtype = get_dtype(de, &path, 1); - if (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) || - is_headref(ref_store, de->d_name))) + if (dtype == DT_REG && (is_root_ref(de->d_name) || + is_headref(ref_store, de->d_name))) loose_fill_ref_dir_regular_file(refs, refname.buf, dir); strbuf_setlen(&refname, dirnamelen); diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 5a5e64fe69..c11ba95736 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -356,7 +356,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator) */ if (!starts_with(iter->ref.refname, "refs/") && !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS && - (is_root_ref(&iter->refs->base, iter->ref.refname) || + (is_root_ref(iter->ref.refname) || is_headref(&iter->refs->base, iter->ref.refname)))) { continue; } diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index 948f1bb5f4..92ed8957c8 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -62,6 +62,23 @@ test_expect_success '--include-root-refs with other patterns' ' test_cmp expect actual ' +test_expect_success '--include-root-refs omits dangling symrefs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + git symbolic-ref DANGLING_HEAD refs/heads/missing && + cat >expect <<-EOF && + HEAD + $(git symbolic-ref HEAD) + refs/tags/initial + EOF + git for-each-ref --format="%(refname)" --include-root-refs >actual && + test_cmp expect actual + ) +' + test_expect_success 'filtering with --points-at' ' cat >expect <<-\EOF && refs/heads/main -- cgit v1.2.3 From f1701f279a3678e95daa5c093b8d30e815c1701b Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 15 May 2024 08:51:05 +0200 Subject: ref-filter: properly distinuish pseudo and root refs The ref-filter interfaces currently define root refs as either a detached HEAD or a pseudo ref. Pseudo refs aren't root refs though, so let's properly distinguish those ref types. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/for-each-ref.c | 2 +- ref-filter.c | 16 +++++++++------- ref-filter.h | 4 ++-- refs.c | 18 +----------------- refs.h | 18 ++++++++++++++++++ 5 files changed, 31 insertions(+), 27 deletions(-) (limited to 'ref-filter.c') diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 919282e12a..5517a4a1c0 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) } if (include_root_refs) - flags |= FILTER_REFS_ROOT_REFS; + flags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD; filter.match_as_path = 1; filter_and_format_refs(&filter, flags, sorting, &format); diff --git a/ref-filter.c b/ref-filter.c index 23e81e3e04..41f639bc2f 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2628,7 +2628,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, each_ref_fn cb, void *cb_data) { - if (filter->kind == FILTER_REFS_KIND_MASK) { + if (filter->kind & FILTER_REFS_ROOT_REFS) { /* In this case, we want to print all refs including root refs. */ return refs_for_each_include_root_refs(get_main_ref_store(the_repository), cb, cb_data); @@ -2756,8 +2756,10 @@ static int ref_kind_from_refname(const char *refname) return ref_kind[i].kind; } - if (is_root_ref(refname)) + if (is_pseudo_ref(refname)) return FILTER_REFS_PSEUDOREFS; + if (is_root_ref(refname)) + return FILTER_REFS_ROOT_REFS; return FILTER_REFS_OTHERS; } @@ -2794,11 +2796,11 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const struct /* * Generally HEAD refs are printed with special description denoting a rebase, * detached state and so forth. This is useful when only printing the HEAD ref - * But when it is being printed along with other pseudorefs, it makes sense to - * keep the formatting consistent. So we mask the type to act like a pseudoref. + * But when it is being printed along with other root refs, it makes sense to + * keep the formatting consistent. So we mask the type to act like a root ref. */ - if (filter->kind == FILTER_REFS_KIND_MASK && kind == FILTER_REFS_DETACHED_HEAD) - kind = FILTER_REFS_PSEUDOREFS; + if (filter->kind & FILTER_REFS_ROOT_REFS && kind == FILTER_REFS_DETACHED_HEAD) + kind = FILTER_REFS_ROOT_REFS; else if (!(kind & filter->kind)) return NULL; @@ -3072,7 +3074,7 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref * When printing all ref types, HEAD is already included, * so we don't want to print HEAD again. */ - if (!ret && (filter->kind != FILTER_REFS_KIND_MASK) && + if (!ret && !(filter->kind & FILTER_REFS_ROOT_REFS) && (filter->kind & FILTER_REFS_DETACHED_HEAD)) head_ref(fn, cb_data); } diff --git a/ref-filter.h b/ref-filter.h index 0ca28d2bba..27ae1aa0d1 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -23,9 +23,9 @@ FILTER_REFS_REMOTES | FILTER_REFS_OTHERS) #define FILTER_REFS_DETACHED_HEAD 0x0020 #define FILTER_REFS_PSEUDOREFS 0x0040 -#define FILTER_REFS_ROOT_REFS (FILTER_REFS_DETACHED_HEAD | FILTER_REFS_PSEUDOREFS) +#define FILTER_REFS_ROOT_REFS 0x0080 #define FILTER_REFS_KIND_MASK (FILTER_REFS_REGULAR | FILTER_REFS_DETACHED_HEAD | \ - FILTER_REFS_PSEUDOREFS) + FILTER_REFS_PSEUDOREFS | FILTER_REFS_ROOT_REFS) struct atom_value; struct ref_sorting; diff --git a/refs.c b/refs.c index 2074281a0e..c13b8ff6d8 100644 --- a/refs.c +++ b/refs.c @@ -844,24 +844,8 @@ int is_per_worktree_ref(const char *refname) starts_with(refname, "refs/rewritten/"); } -static int is_pseudo_ref(const char *refname) +int is_pseudo_ref(const char *refname) { - /* - * Pseudorefs are refs that have different semantics compared to - * "normal" refs. These refs can thus not be stored in the ref backend, - * but must always be accessed via the filesystem. The following refs - * are pseudorefs: - * - * - 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. - * - * Reading, writing or deleting references must consistently go either - * through the filesystem (pseudorefs) or through the reference - * backend (normal ones). - */ static const char * const pseudo_refs[] = { "FETCH_HEAD", "MERGE_HEAD", diff --git a/refs.h b/refs.h index 8489b45265..dc4358727f 100644 --- a/refs.h +++ b/refs.h @@ -1080,4 +1080,22 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref); */ int is_root_ref(const char *refname); +/* + * Pseudorefs are refs that have different semantics compared to + * "normal" refs. These refs can thus not be stored in the ref backend, + * but must always be accessed via the filesystem. The following refs + * are pseudorefs: + * + * - 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. + * + * Reading, writing or deleting references must consistently go either + * through the filesystem (pseudorefs) or through the reference + * backend (normal ones). + */ +int is_pseudo_ref(const char *refname); + #endif /* REFS_H */ -- cgit v1.2.3