Skip to content

Commit 1527a6a

Browse files
committed
builtin/refs: add list subcommand
Currently, Git's reference management is distributed across multiple commands and as part of an ongoing effort to streamline and modernize reference handling, we are beginning to consolidate these operations into a cohesive `git refs` command. Add a `list` subcommand to `git refs` as a modern replacement for `git show-ref`, consolidating ref listing functionality under the unified `git refs` interface. Test the implemented functionality of `git refs list` and verify backward compatibility with `git show-ref` for the supported flags and patterns. Mentored-by: Patrick Steinhardt <ps@pks.im> Mentored-by: shejialuo <shejialuo@gmail.com> Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
1 parent 0bd2d79 commit 1527a6a

4 files changed

Lines changed: 208 additions & 0 deletions

File tree

Documentation/git-refs.adoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ SYNOPSIS
1111
[synopsis]
1212
git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
1313
git refs verify [--strict] [--verbose]
14+
git refs list [--head] [--branches] [--tag] [<pattern>...]
1415

1516
DESCRIPTION
1617
-----------
@@ -25,6 +26,10 @@ migrate::
2526

2627
verify::
2728
Verify reference database consistency.
29+
list::
30+
Displays references available in a local repository along with the associated
31+
commit IDs. This subcommand serves as a functional replacement for
32+
`git show-ref`, offering a more consistent interface within `git refs`.
2833

2934
OPTIONS
3035
-------
@@ -57,6 +62,24 @@ The following options are specific to 'git refs verify':
5762
--verbose::
5863
When verifying the reference database consistency, be chatty.
5964

65+
The following options are specific to 'git refs list':
66+
67+
--head::
68+
Show the HEAD reference, even if it would normally be filtered out.
69+
70+
--branches::
71+
--tags::
72+
Limit to local branches and local tags, respectively. These options
73+
are not mutually exclusive; when given both, references stored in
74+
"refs/heads" and "refs/tags" are displayed.
75+
76+
<pattern>...::
77+
Show references matching one or more patterns. Patterns are matched from
78+
the end of the full name, and only complete parts are matched, e.g.
79+
'master' matches 'refs/heads/master', 'refs/remotes/origin/master',
80+
'refs/tags/jedi/master' but not 'refs/heads/mymaster' or
81+
'refs/remotes/master/jedi'.
82+
6083
KNOWN LIMITATIONS
6184
-----------------
6285

builtin/refs.c

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#include "builtin.h"
33
#include "config.h"
44
#include "fsck.h"
5+
#include "hex.h"
6+
#include "object-name.h"
7+
#include "object-store.h"
58
#include "parse-options.h"
69
#include "refs.h"
710
#include "strbuf.h"
@@ -13,6 +16,9 @@
1316
#define REFS_VERIFY_USAGE \
1417
N_("git refs verify [--strict] [--verbose]")
1518

19+
#define REFS_LIST_USAGE \
20+
N_("git refs list [--head] [--branches] [--tag] [<pattern>...]")
21+
1622
static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
1723
struct repository *repo UNUSED)
1824
{
@@ -101,6 +107,100 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
101107
return ret;
102108
}
103109

110+
struct list_options {
111+
int show_head;
112+
int filter_branches;
113+
int filter_tags;
114+
int found_match;
115+
const char **patterns;
116+
};
117+
118+
static int show_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
119+
int flag UNUSED, void *cbdata)
120+
{
121+
const char *hex;
122+
struct list_options *data = cbdata;
123+
124+
if (!has_object(the_repository, oid,
125+
HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))
126+
die("git refs list: bad ref %s (%s)", refname,
127+
oid_to_hex(oid));
128+
hex = repo_find_unique_abbrev(the_repository, oid, 0);
129+
130+
131+
if (data->show_head && !strcmp(refname, "HEAD"))
132+
goto match;
133+
134+
if (data->patterns) {
135+
int reflen = strlen(refname);
136+
const char **p = data->patterns, *m;
137+
while ((m = *p++) != NULL) {
138+
int len = strlen(m);
139+
if (len > reflen)
140+
continue;
141+
if (memcmp(m, refname + reflen - len, len))
142+
continue;
143+
if (len == reflen)
144+
goto match;
145+
if (refname[reflen - len - 1] == '/')
146+
goto match;
147+
}
148+
return 0;
149+
}
150+
151+
match:
152+
data->found_match++;
153+
154+
printf("%s %s\n", hex, refname);
155+
156+
return 0;
157+
}
158+
159+
static int cmd_refs_list(int argc, const char **argv, const char *prefix,
160+
struct repository *repo UNUSED)
161+
{
162+
struct list_options list_opts = {0};
163+
const char * const list_usage[] = {
164+
REFS_LIST_USAGE,
165+
NULL,
166+
};
167+
struct option options[] = {
168+
OPT_BOOL(0, "head", &list_opts.show_head,
169+
N_("show the HEAD reference, even if it would be filtered out")),
170+
OPT_BOOL(0, "tags", &list_opts.filter_tags,
171+
N_("show tags (can be combined with --branches)")),
172+
OPT_BOOL(0, "branches", &list_opts.filter_branches,
173+
N_("show branches (can be combined with --tags)")),
174+
OPT_END(),
175+
};
176+
177+
argc = parse_options(argc, argv, prefix, options, list_usage, 0);
178+
179+
if (argv && *argv)
180+
list_opts.patterns = argv;
181+
if (list_opts.show_head)
182+
refs_head_ref(get_main_ref_store(the_repository), show_ref,
183+
&list_opts);
184+
if (list_opts.filter_tags || list_opts.filter_branches) {
185+
if (list_opts.filter_branches)
186+
refs_for_each_fullref_in(get_main_ref_store(the_repository),
187+
"refs/heads/", NULL,
188+
show_ref, &list_opts);
189+
190+
if (list_opts.filter_tags)
191+
refs_for_each_fullref_in(get_main_ref_store(the_repository),
192+
"refs/tags/", NULL,
193+
show_ref, &list_opts);
194+
} else {
195+
refs_for_each_ref(get_main_ref_store(the_repository),
196+
show_ref, &list_opts);
197+
}
198+
if (!list_opts.found_match)
199+
return 1;
200+
201+
return 0;
202+
}
203+
104204
int cmd_refs(int argc,
105205
const char **argv,
106206
const char *prefix,
@@ -109,12 +209,14 @@ int cmd_refs(int argc,
109209
const char * const refs_usage[] = {
110210
REFS_MIGRATE_USAGE,
111211
REFS_VERIFY_USAGE,
212+
REFS_LIST_USAGE,
112213
NULL,
113214
};
114215
parse_opt_subcommand_fn *fn = NULL;
115216
struct option opts[] = {
116217
OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
117218
OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
219+
OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
118220
OPT_END(),
119221
};
120222

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ integration_tests = [
224224
't1450-fsck.sh',
225225
't1451-fsck-buffer.sh',
226226
't1460-refs-migrate.sh',
227+
't1461-refs-list.sh',
227228
't1500-rev-parse.sh',
228229
't1501-work-tree.sh',
229230
't1502-rev-parse-parseopt.sh',

t/t1461-refs-list.sh

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/bin/sh
2+
3+
test_description='Verify git refs list functionality and compatibility with git show-ref'
4+
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
5+
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
6+
7+
. ./test-lib.sh
8+
9+
test_expect_success setup '
10+
test_commit --annotate A &&
11+
git checkout -b side &&
12+
test_commit --annotate B &&
13+
git checkout main &&
14+
test_commit C &&
15+
git branch B A^0
16+
'
17+
test_expect_success 'refs list --branches, --tags, --head' '
18+
for branch in B main side
19+
do
20+
echo $(git rev-parse refs/heads/$branch) refs/heads/$branch || return 1
21+
done >expect.branches &&
22+
git refs list --branches >actual &&
23+
test_cmp expect.branches actual &&
24+
25+
for tag in A B C
26+
do
27+
echo $(git rev-parse refs/tags/$tag) refs/tags/$tag || return 1
28+
done >expect.tags &&
29+
git refs list --tags >actual &&
30+
test_cmp expect.tags actual &&
31+
32+
cat expect.branches expect.tags >expect &&
33+
git refs list --branches --tags >actual &&
34+
test_cmp expect actual &&
35+
36+
{
37+
echo $(git rev-parse HEAD) HEAD &&
38+
cat expect.branches expect.tags
39+
} >expect &&
40+
git refs list --branches --tags --head >actual &&
41+
test_cmp expect actual &&
42+
43+
{
44+
echo $(git rev-parse HEAD) HEAD &&
45+
echo $(git rev-parse refs/heads/B) refs/heads/B &&
46+
echo $(git rev-parse refs/tags/B) refs/tags/B
47+
} >expect &&
48+
git refs list --head B >actual &&
49+
test_cmp expect actual
50+
'
51+
52+
test_expect_success 'Backward compatibility with show-ref' '
53+
git show-ref >expect.full &&
54+
git refs list >actual.full &&
55+
test_cmp expect.full actual.full &&
56+
57+
git show-ref --branches >expect.heads &&
58+
git refs list --branches >actual.heads &&
59+
test_cmp expect.heads actual.heads &&
60+
61+
git show-ref --tags >expect.tags &&
62+
git refs list --tags >actual.tags &&
63+
test_cmp expect.tags actual.tags &&
64+
65+
git show-ref --head >expect.head &&
66+
git refs list --head >actual.head &&
67+
test_cmp expect.head actual.head &&
68+
69+
git show-ref --branches --tags --head >expect.combined &&
70+
git refs list --branches --tags --head >actual.combined &&
71+
test_cmp expect.combined actual.combined &&
72+
73+
git show-ref B >expect.pattern &&
74+
git refs list B >actual.pattern &&
75+
test_cmp expect.pattern actual.pattern &&
76+
77+
git show-ref --head B >expect.pattern.head &&
78+
git refs list --head B >actual.pattern.head &&
79+
test_cmp expect.pattern.head actual.pattern.head
80+
'
81+
82+
test_done

0 commit comments

Comments
 (0)