diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 1272809e13..30b394ab47 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -497,20 +497,18 @@ exclude;; unusual refs. [[def_pseudoref]]pseudoref:: - Pseudorefs are a class of files under `$GIT_DIR` which behave - like refs for the purposes of rev-parse, but which are treated - specially by git. Pseudorefs both have names that are all-caps, - and always start with a line consisting of a - <> followed by whitespace. So, HEAD is not a - pseudoref, because it is sometimes a symbolic ref. They might - optionally contain some additional data. `MERGE_HEAD` and - `CHERRY_PICK_HEAD` are examples. Unlike - <>, these files cannot - be symbolic refs, and never have reflogs. They also cannot be - updated through the normal ref update machinery. Instead, - they are updated by directly writing to the files. However, - they can be read as if they were refs, so `git rev-parse - MERGE_HEAD` will work. + A ref that has different semantics than normal refs. These refs can be + read via normal Git commands, but cannot be written to by commands like + linkgit:git-update-ref[1]. ++ +The following pseudorefs are known to Git: + + - `FETCH_HEAD` is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It + may refer to multiple object IDs. Each object ID is annotated with metadata + indicating where it was fetched from and its fetch status. + + - `MERGE_HEAD` is written by linkgit:git-merge[1] when resolving merge + conflicts. It contains all commit IDs which are being merged. [[def_pull]]pull:: Pulling a <> means to <> it and @@ -552,20 +550,38 @@ exclude;; to the result. [[def_ref]]ref:: - A name that begins with `refs/` (e.g. `refs/heads/master`) - that points to an <> or another - ref (the latter is called a <>). + A name that that points to an <> or + another ref (the latter is called a <>). For convenience, a ref can sometimes be abbreviated when used as an argument to a Git command; see linkgit:gitrevisions[7] for details. Refs are stored in the <>. + The ref namespace is hierarchical. -Different subhierarchies are used for different purposes (e.g. the -`refs/heads/` hierarchy is used to represent local branches). +Ref names must either start with `refs/` or be located in the root of +the hierarchy. For the latter, their name must follow these rules: + -There are a few special-purpose refs that do not begin with `refs/`. -The most notable example is `HEAD`. + - The name consists of only upper-case characters or underscores. + + - The name ends with "`_HEAD`" or is equal to "`HEAD`". ++ +There are some irregular refs in the root of the hierarchy that do not +match these rules. The following list is exhaustive and shall not be +extended in the future: ++ + - `AUTO_MERGE` + + - `BISECT_EXPECTED_REV` + + - `NOTES_MERGE_PARTIAL` + + - `NOTES_MERGE_REF` + + - `MERGE_AUTOSTASH` ++ +Different subhierarchies are used for different purposes. For example, +the `refs/heads/` hierarchy is used to represent local branches whereas +the `refs/tags/` hierarchy is used to represent local tags.. [[def_reflog]]reflog:: A reflog shows the local "history" of a ref. In other words, @@ -639,20 +655,6 @@ The most notable example is `HEAD`. An <> used to temporarily store the contents of a <> working directory and the index for future reuse. -[[def_special_ref]]special ref:: - A ref that has different semantics than normal refs. These refs can be - accessed via normal Git commands but may not behave the same as a - normal ref in some cases. -+ -The following special refs are known to Git: - - - "`FETCH_HEAD`" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It - may refer to multiple object IDs. Each object ID is annotated with metadata - indicating where it was fetched from and its fetch status. - - - "`MERGE_HEAD`" is written by linkgit:git-merge[1] when resolving merge - conflicts. It contains all commit IDs which are being merged. - [[def_submodule]]submodule:: A <> that holds the history of a separate project inside another repository (the latter of 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 31cc096644..cf6525195a 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2634,7 +2634,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); @@ -2764,8 +2764,10 @@ 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_pseudo_ref(refname)) return FILTER_REFS_PSEUDOREFS; + if (is_root_ref(refname)) + return FILTER_REFS_ROOT_REFS; return FILTER_REFS_OTHERS; } @@ -2802,11 +2804,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; @@ -3086,7 +3088,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)) refs_head_ref(get_main_ref_store(the_repository), 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 32e91ff740..8260c27cde 100644 --- a/refs.c +++ b/refs.c @@ -819,7 +819,22 @@ int is_per_worktree_ref(const char *refname) starts_with(refname, "refs/rewritten/"); } -static int is_pseudoref_syntax(const char *refname) +int is_pseudo_ref(const char *refname) +{ + static const char * const pseudo_refs[] = { + "FETCH_HEAD", + "MERGE_HEAD", + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(pseudo_refs); i++) + if (!strcmp(refname, pseudo_refs[i])) + return 1; + + return 0; +} + +static int is_root_ref_syntax(const char *refname) { const char *c; @@ -828,56 +843,37 @@ static int is_pseudoref_syntax(const char *refname) return 0; } - /* - * HEAD is not a pseudoref, but it certainly uses the - * pseudoref syntax. - */ return 1; } -int is_pseudoref(struct ref_store *refs, const char *refname) +int is_root_ref(const char *refname) { - static const char *const irregular_pseudorefs[] = { + static const char *const irregular_root_refs[] = { + "HEAD", "AUTO_MERGE", "BISECT_EXPECTED_REV", "NOTES_MERGE_PARTIAL", "NOTES_MERGE_REF", "MERGE_AUTOSTASH", }; - struct object_id oid; size_t i; - if (!is_pseudoref_syntax(refname)) + if (!is_root_ref_syntax(refname) || + is_pseudo_ref(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_pseudorefs); i++) - if (!strcmp(refname, irregular_pseudorefs[i])) { - refs_resolve_ref_unsafe(refs, refname, - RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - &oid, NULL); - return !is_null_oid(&oid); - } - - return 0; -} - -int is_headref(struct ref_store *refs, const char *refname) -{ - if (!strcmp(refname, "HEAD")) - return refs_ref_exists(refs, refname); + for (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++) + if (!strcmp(refname, irregular_root_refs[i])) + return 1; return 0; } static int is_current_worktree_ref(const char *ref) { - return is_pseudoref_syntax(ref) || is_per_worktree_ref(ref); + return is_root_ref_syntax(ref) || is_per_worktree_ref(ref); } enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref, @@ -1243,6 +1239,13 @@ int ref_transaction_update(struct ref_transaction *transaction, return -1; } + if (!(flags & REF_SKIP_REFNAME_VERIFICATION) && + is_pseudo_ref(refname)) { + strbuf_addf(err, _("refusing to update pseudoref '%s'"), + refname); + return -1; + } + if (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS) BUG("illegal flags 0x%x passed to ref_transaction_update()", flags); @@ -1816,43 +1819,12 @@ done: return result; } -static int is_special_ref(const char *refname) -{ - /* - * Special references 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 special: - * - * - 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 (special refs) or through the reference - * backend (normal ones). - */ - static const char * const special_refs[] = { - "FETCH_HEAD", - "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 (is_special_ref(refname)) + if (is_pseudo_ref(refname)) return refs_read_special_head(ref_store, refname, oid, referent, type, failure_errno); diff --git a/refs.h b/refs.h index fb419ab2ed..34568ee1fb 100644 --- a/refs.h +++ b/refs.h @@ -1013,8 +1013,52 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT]; */ void update_ref_namespace(enum ref_namespace namespace, char *ref); -int is_pseudoref(struct ref_store *refs, const char *refname); -int is_headref(struct ref_store *refs, const char *refname); +/* + * 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: + * + * - Their name must be all-uppercase or underscores ("_"). + * + * - Their name must end with "_HEAD". As a special rule, "HEAD" is a root + * ref, as well. + * + * - Their name may not contain a slash. + * + * There is a special set of irregular root refs that exist due to historic + * reasons, only. This list shall not be expanded in the future: + * + * - AUTO_MERGE + * + * - BISECT_EXPECTED_REV + * + * - NOTES_MERGE_PARTIAL + * + * - NOTES_MERGE_REF + * + * - MERGE_AUTOSTASH + */ +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); /* * The following functions have been removed in Git v2.45 in favor of functions diff --git a/refs/files-backend.c b/refs/files-backend.c index 3dce0c2a34..5f3089d947 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -351,8 +351,7 @@ 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_pseudoref(ref_store, de->d_name) || - is_headref(ref_store, de->d_name))) + if (dtype == DT_REG && is_root_ref(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 98cebbcf39..1af86bbdec 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -354,8 +354,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_pseudoref(&iter->refs->base, iter->ref.refname) || - is_headref(&iter->refs->base, iter->ref.refname)))) { + is_root_ref(iter->ref.refname))) { continue; } diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 6c20fe1871..3b3991ab86 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -518,7 +518,7 @@ test_expect_success 'fetch with a non-applying branch..merge' ' test_expect_success 'fetch from GIT URL with a non-applying branch..merge [1]' ' one_head=$(cd one && git rev-parse HEAD) && this_head=$(git rev-parse HEAD) && - git update-ref -d FETCH_HEAD && + rm .git/FETCH_HEAD && git fetch one && test $one_head = "$(git rev-parse --verify FETCH_HEAD)" && test $this_head = "$(git rev-parse --verify HEAD)" @@ -530,7 +530,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch..merge one_ref=$(cd one && git symbolic-ref HEAD) && git config branch.main.remote blub && git config branch.main.merge "$one_ref" && - git update-ref -d FETCH_HEAD && + rm .git/FETCH_HEAD && git fetch one && test $one_head = "$(git rev-parse --verify FETCH_HEAD)" && test $this_head = "$(git rev-parse --verify HEAD)" @@ -540,7 +540,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch..merge # the merge spec does not match the branch the remote HEAD points to test_expect_success 'fetch from GIT URL with a non-applying branch..merge [3]' ' git config branch.main.merge "${one_ref}_not" && - git update-ref -d FETCH_HEAD && + rm .git/FETCH_HEAD && git fetch one && test $one_head = "$(git rev-parse --verify FETCH_HEAD)" && test $this_head = "$(git rev-parse --verify HEAD)" diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index 948f1bb5f4..163c378cfd 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' ' test_cmp expect actual ' +test_expect_success '--include-root-refs pattern does not print special refs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + git rev-parse HEAD >.git/MERGE_HEAD && + git for-each-ref --format="%(refname)" --include-root-refs >actual && + cat >expect <<-EOF && + HEAD + $(git symbolic-ref HEAD) + refs/tags/initial + EOF + test_cmp expect actual + ) +' + test_expect_success '--include-root-refs with other patterns' ' cat >expect <<-\EOF && HEAD @@ -62,6 +79,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