mirror of
https://github.com/git/git.git
synced 2024-11-23 09:56:28 +08:00
Merge branch 'ps/pseudo-ref-terminology'
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
This commit is contained in:
commit
16a592f132
@ -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
|
||||
@ -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:
|
||||
+
|
||||
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 <<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
|
||||
|
@ -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);
|
||||
|
16
ref-filter.c
16
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);
|
||||
|
@ -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;
|
||||
|
98
refs.c
98
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);
|
||||
|
||||
|
48
refs.h
48
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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -518,7 +518,7 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '
|
||||
test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.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.<name>.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.<name>.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.<name>.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)"
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user