While `git-rev-parse(1)` and `git-show-ref(1)` can be used to read reference values, they have drawbacks for scripting and discoverability. `rev-parse` performs DWIM expansion which is unpredictable for scripts, and `show-ref --verify` is difficult to discover and cannot read the direct target of a symbolic reference.
To address this, introduce a new plumbing command, `git refs get <ref>`. This new command provides three key advantages:
- It requires an exact refname and does not perform expansion, making
it safer and more predictable for scripting. - Its name clearly states its purpose and it lives in the logical `git
refs` namespace, unlike the `--verify` flag which lives in
`git-show-ref`. - It provides a clean, dedicated way to read the direct target of a
symbolic reference (e.g., `HEAD`) without recursively dereferencing
it to an object ID.Add documentation for the new subcommand to the `git-refs(1)` man page and a comprehensive test suite to verify its behavior.
Mentored-by: Patrick Steinhardt <ps@pks.im> Mentored-by: shejialuo <shejialuo@gmail.com> Signed-off-by: Meet Soni <meetsoni3017@gmail.com> --- Documentation/git-refs.adoc | 7 ++++ builtin/refs.c | 43 ++++++++++++++++++++++++ t/meson.build | 1 + t/t1464-refs-get.sh | 66 +++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100755 t/t1464-refs-get.sh
Show changes to 4 files +117 −0
Documentation/git-refs.adoc, builtin/refs.c, t/meson.build, t/t1464-refs-get.sh
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc index bfa9b3ea2d..f07fe8c864 100644 --- a/Documentation/git-refs.adoc +++ b/Documentation/git-refs.adoc @@ -19,6 +19,7 @@ git refs list [--count=<count>] [--shell|--perl|--python|--tcl] [(--exclude=<pattern>)...] [--start-after=<marker>] [ --stdin | (<pattern>...)] git refs exists <ref> +git refs get <ref> DESCRIPTION ----------- @@ -45,6 +46,12 @@ exists:: failed with an error other than the reference being missing. This does not verify whether the reference resolves to an actual object. +get:: + Reads the raw value of a single, exact reference. Instead of + recursively dereferencing symbolic references, this command prints the + direct target of the symref (e.g., ref: refs/heads/main). For regular + references, it prints the object ID (SHA-1) they point to. + OPTIONS ------- diff --git a/builtin/refs.c b/builtin/refs.c index 91548783b7..b473a78e18 100644 --- a/builtin/refs.c +++ b/builtin/refs.c @@ -2,6 +2,7 @@ #include "builtin.h" #include "config.h" #include "fsck.h" +#include "hex.h" #include "parse-options.h" #include "refs.h" #include "strbuf.h" @@ -18,6 +19,9 @@ #define REFS_EXISTS_USAGE \ N_("git refs exists <ref>") +#define REFS_GET_USAGE \ + N_("git refs get <ref>") + static int cmd_refs_migrate(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { @@ -159,6 +163,43 @@ static int cmd_refs_exists(int argc, const char **argv, const char *prefix, return ret; } +static int cmd_refs_get(int argc, const char **argv, const char *prefix, + struct repository *repo UNUSED) +{ + const char *refname; + struct object_id oid; + unsigned int type; + int failure_errno = 0; + struct strbuf referent = STRBUF_INIT; + + const char * const exists_usage[] = { + REFS_EXISTS_USAGE, + NULL, + }; + struct option options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, exists_usage, 0); + if (argc != 1) + die("refs get requires exactly one reference"); + + refname = *argv++; + if (refs_read_raw_ref(get_main_ref_store(the_repository), refname, + &oid, &referent, &type, &failure_errno)) { + die("'%s' - not a valid ref", refname); + } + + if (type & REF_ISSYMREF) { + printf("ref: %s\n", referent.buf); + } else { + printf("%s\n", oid_to_hex(&oid)); + } + + strbuf_release(&referent); + return 0; +} + int cmd_refs(int argc, const char **argv, const char *prefix, @@ -169,6 +210,7 @@ int cmd_refs(int argc, REFS_VERIFY_USAGE, "git refs list " COMMON_USAGE_FOR_EACH_REF, REFS_EXISTS_USAGE, + REFS_GET_USAGE, NULL, }; parse_opt_subcommand_fn *fn = NULL; @@ -177,6 +219,7 @@ int cmd_refs(int argc, OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify), OPT_SUBCOMMAND("list", &fn, cmd_refs_list), OPT_SUBCOMMAND("exists", &fn, cmd_refs_exists), + OPT_SUBCOMMAND("get", &fn, cmd_refs_get), OPT_END(), }; diff --git a/t/meson.build b/t/meson.build index 7974795fe4..0c8067c69d 100644 --- a/t/meson.build +++ b/t/meson.build @@ -213,6 +213,7 @@ integration_tests = [ 't1460-refs-migrate.sh', 't1461-refs-list.sh', 't1462-refs-exists.sh', + 't1464-refs-get.sh', 't1500-rev-parse.sh', 't1501-work-tree.sh', 't1502-rev-parse-parseopt.sh', diff --git a/t/t1464-refs-get.sh b/t/t1464-refs-get.sh new file mode 100755 index 0000000000..166176c881 --- /dev/null +++ b/t/t1464-refs-get.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +test_description='git refs get' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +test_expect_success 'setup repository' ' + test_commit one && + git tag -a -m "tagging one" my-tag one && + git symbolic-ref refs/my-symref refs/heads/main && + git symbolic-ref refs/dangling-symref refs/heads/no-such-branch +' + +test_expect_success 'fails with no arguments' ' + test_must_fail git refs get >out 2>err && + test_grep "refs get requires exactly one reference" err +' + +test_expect_success 'fails with too many arguments' ' + test_must_fail git refs get HEAD HEAD >out 2>err && + test_grep "refs get requires exactly one reference" err +' + +test_expect_success 'get a branch head' ' + git rev-parse main >expect && + git refs get refs/heads/main >actual && + test_cmp expect actual +' + +test_expect_success 'get an annotated tag' ' + git rev-parse my-tag >expect && + git refs get refs/tags/my-tag >actual && + test_cmp expect actual +' + +test_expect_success 'get HEAD (a symbolic ref)' ' + echo "ref: refs/heads/main" >expect && + git refs get HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'get a custom symbolic ref' ' + echo "ref: refs/heads/main" >expect && + git refs get refs/my-symref >actual && + test_cmp expect actual +' + +test_expect_success 'get a dangling symbolic ref' ' + echo "ref: refs/heads/no-such-branch" >expect && + git refs get refs/dangling-symref >actual && + test_cmp expect actual +' + +test_expect_success 'get a non-existent ref' ' + test_must_fail git refs get refs/heads/no-such-branch 2>err && + test_grep "not a valid ref" err +' + +test_expect_success 'get does not perform DWIM' ' + test_must_fail git refs get main 2>err && + test_grep "not a valid ref" err +' + +test_done base-commit: ca2559c1d630eb4f04cdee2328aaf1c768907a9e
-- 2.34.1