From: Meet Soni <meetsoni3017@gmail•com>
To: git@vger•kernel.org
Cc: ps@pks•im, shejialuo@gmail•com, Meet Soni <meetsoni3017@gmail•com>
Subject: [GSoC][PATCH] builtin/refs: add 'get' subcommand
Date: Tue, 23 Sep 2025 16:15:33 +0530 [thread overview]
Message-ID: <20250923104533.21165-1-meetsoni3017@gmail.com> (raw)
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
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
next reply other threads:[~2025-09-23 10:45 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-23 10:45 Meet Soni [this message]
2025-09-23 16:57 ` [GSoC][PATCH] builtin/refs: add 'get' subcommand Ben Knoble
2025-09-24 6:32 ` Patrick Steinhardt
2025-09-23 21:50 ` Junio C Hamano
2025-09-24 6:32 ` Patrick Steinhardt
2025-09-24 15:29 ` Ben Knoble
2025-09-24 17:11 ` Junio C Hamano
2025-09-25 6:25 ` Patrick Steinhardt
2025-09-25 18:08 ` D. Ben Knoble
2025-09-25 18:43 ` Junio C Hamano
2025-09-24 6:32 ` Patrick Steinhardt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250923104533.21165-1-meetsoni3017@gmail.com \
--to=meetsoni3017@gmail$(echo .)com \
--cc=git@vger$(echo .)kernel.org \
--cc=ps@pks$(echo .)im \
--cc=shejialuo@gmail$(echo .)com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox