Skip to content

Commit

Permalink
Merge branch 'ps/pseudo-ref-terminology'
Browse files Browse the repository at this point in the history
Terminology to call various ref-like things are getting
straightened out.

* ps/pseudo-ref-terminology:
  refs: refuse to write pseudorefs
  ref-filter: properly distinuish pseudo and root refs
  refs: pseudorefs are no refs
  refs: classify HEAD as a root ref
  refs: do not check ref existence in `is_root_ref()`
  refs: rename `is_special_ref()` to `is_pseudo_ref()`
  refs: rename `is_pseudoref()` to `is_root_ref()`
  Documentation/glossary: define root refs as refs
  Documentation/glossary: clarify limitations of pseudorefs
  Documentation/glossary: redefine pseudorefs as special refs
  • Loading branch information
gitster committed May 28, 2024
2 parents 3b1e3f0 + 8e4f5c2 commit 16a592f
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 117 deletions.
72 changes: 37 additions & 35 deletions Documentation/glossary-content.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
<<def_SHA1,SHA-1>> 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
<<def_per_worktree_ref,per-worktree refs>>, 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 <<def_branch,branch>> means to <<def_fetch,fetch>> it and
Expand Down Expand Up @@ -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 <<def_object_name,object name>> or another
ref (the latter is called a <<def_symref,symbolic ref>>).
A name that that points to an <<def_object_name,object name>> or
another ref (the latter is called a <<def_symref,symbolic ref>>).
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 <<def_repository,repository>>.
+
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:
+
- The name consists of only upper-case characters or underscores.

- The name ends with "`_HEAD`" or is equal to "`HEAD`".
+
There are a few special-purpose refs that do not begin with `refs/`.
The most notable example is `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,
Expand Down Expand Up @@ -639,20 +655,6 @@ The most notable example is `HEAD`.
An <<def_object,object>> used to temporarily store the contents of a
<<def_dirty,dirty>> 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 <<def_repository,repository>> that holds the history of a
separate project inside another repository (the latter of
Expand Down
2 changes: 1 addition & 1 deletion builtin/for-each-ref.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 9 additions & 7 deletions ref-filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions ref-filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
98 changes: 35 additions & 63 deletions refs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}

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;
}
if (ends_with(refname, "_HEAD"))
return 1;

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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -1816,43 +1819,12 @@ static int refs_read_special_head(struct ref_store *ref_store,
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);

Expand Down
48 changes: 46 additions & 2 deletions refs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions refs/files-backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions refs/reftable-backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading

0 comments on commit 16a592f

Please sign in to comment.