ls-refs: introduce ls-refs server command

Introduce the ls-refs server command. In protocol v2, the ls-refs command is used to request the ref advertisement from the server. Since it is a command which can be requested (as opposed to mandatory in v1), a client can sent a number of parameters in its request to limit the ref advertisement based on provided ref-prefixes. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Mar 15, 2018 at 10:31 UTC 72d0ea0056b8656765748a8bdeb42ee26ee1c8dc
6 files changed +261
Documentation/technical/protocol-v2.txt
+31
@@ -168,3 +168,34 @@ printable ASCII characters except space (i.e., the byte range 32 < x <
168 "git/1.8.3.1"). The agent strings are purely informative for statistics
169 and debugging purposes, and MUST NOT be used to programmatically assume
170 the presence or absence of particular features.
171 +
172 + ls-refs
173 +~~~~~~~~~
174 +
175 +`ls-refs` is the command used to request a reference advertisement in v2.
176 +Unlike the current reference advertisement, ls-refs takes in arguments
177 +which can be used to limit the refs sent from the server.
178 +
179 +Additional features not supported in the base command will be advertised
180 +as the value of the command in the capability advertisement in the form
181 +of a space separated list of features: "<command>=<feature 1> <feature 2>"
182 +
183 +ls-refs takes in the following arguments:
184 +
185 + symrefs
186 + In addition to the object pointed by it, show the underlying ref
187 + pointed by it when showing a symbolic ref.
188 + peel
189 + Show peeled tags.
190 + ref-prefix <prefix>
191 + When specified, only references having a prefix matching one of
192 + the provided prefixes are displayed.
193 +
194 +The output of ls-refs is as follows:
195 +
196 + output = *ref
197 + flush-pkt
198 + ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)
199 + ref-attribute = (symref | peeled)
200 + symref = "symref-target:" symref-target
201 + peeled = "peeled:" obj-id
Makefile
+1
@@ -820,6 +820,7 @@ LIB_OBJS += list-objects-filter-options.o
820 LIB_OBJS += ll-merge.o
821 LIB_OBJS += lockfile.o
822 LIB_OBJS += log-tree.o
823 +LIB_OBJS += ls-refs.o
824 LIB_OBJS += mailinfo.o
825 LIB_OBJS += mailmap.o
826 LIB_OBJS += match-trees.o
ls-refs.c new
+96
@@ -0,0 +1,96 @@
1 +#include "cache.h"
2 +#include "repository.h"
3 +#include "refs.h"
4 +#include "remote.h"
5 +#include "argv-array.h"
6 +#include "ls-refs.h"
7 +#include "pkt-line.h"
8 +
9 +/*
10 + * Check if one of the prefixes is a prefix of the ref.
11 + * If no prefixes were provided, all refs match.
12 + */
13 +static int ref_match(const struct argv_array *prefixes, const char *refname)
14 +{
15 + int i;
16 +
17 + if (!prefixes->argc)
18 + return 1; /* no restriction */
19 +
20 + for (i = 0; i < prefixes->argc; i++) {
21 + const char *prefix = prefixes->argv[i];
22 +
23 + if (starts_with(refname, prefix))
24 + return 1;
25 + }
26 +
27 + return 0;
28 +}
29 +
30 +struct ls_refs_data {
31 + unsigned peel;
32 + unsigned symrefs;
33 + struct argv_array prefixes;
34 +};
35 +
36 +static int send_ref(const char *refname, const struct object_id *oid,
37 + int flag, void *cb_data)
38 +{
39 + struct ls_refs_data *data = cb_data;
40 + const char *refname_nons = strip_namespace(refname);
41 + struct strbuf refline = STRBUF_INIT;
42 +
43 + if (!ref_match(&data->prefixes, refname))
44 + return 0;
45 +
46 + strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons);
47 + if (data->symrefs && flag & REF_ISSYMREF) {
48 + struct object_id unused;
49 + const char *symref_target = resolve_ref_unsafe(refname, 0,
50 + &unused,
51 + &flag);
52 +
53 + if (!symref_target)
54 + die("'%s' is a symref but it is not?", refname);
55 +
56 + strbuf_addf(&refline, " symref-target:%s", symref_target);
57 + }
58 +
59 + if (data->peel) {
60 + struct object_id peeled;
61 + if (!peel_ref(refname, &peeled))
62 + strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled));
63 + }
64 +
65 + strbuf_addch(&refline, '\n');
66 + packet_write(1, refline.buf, refline.len);
67 +
68 + strbuf_release(&refline);
69 + return 0;
70 +}
71 +
72 +int ls_refs(struct repository *r, struct argv_array *keys,
73 + struct packet_reader *request)
74 +{
75 + struct ls_refs_data data;
76 +
77 + memset(&data, 0, sizeof(data));
78 +
79 + while (packet_reader_read(request) != PACKET_READ_FLUSH) {
80 + const char *arg = request->line;
81 + const char *out;
82 +
83 + if (!strcmp("peel", arg))
84 + data.peel = 1;
85 + else if (!strcmp("symrefs", arg))
86 + data.symrefs = 1;
87 + else if (skip_prefix(arg, "ref-prefix ", &out))
88 + argv_array_push(&data.prefixes, out);
89 + }
90 +
91 + head_ref_namespaced(send_ref, &data);
92 + for_each_namespaced_ref(send_ref, &data);
93 + packet_flush(1);
94 + argv_array_clear(&data.prefixes);
95 + return 0;
96 +}
ls-refs.h new
+10
@@ -0,0 +1,10 @@
1 +#ifndef LS_REFS_H
2 +#define LS_REFS_H
3 +
4 +struct repository;
5 +struct argv_array;
6 +struct packet_reader;
7 +extern int ls_refs(struct repository *r, struct argv_array *keys,
8 + struct packet_reader *request);
9 +
10 +#endif /* LS_REFS_H */
serve.c
+8
@@ -4,8 +4,15 @@
4 #include "pkt-line.h"
5 #include "version.h"
6 #include "argv-array.h"
7 +#include "ls-refs.h"
8 #include "serve.h"
9
10 +static int always_advertise(struct repository *r,
11 + struct strbuf *value)
12 +{
13 + return 1;
14 +}
15 +
16 static int agent_advertise(struct repository *r,
17 struct strbuf *value)
18 {
@@ -46,6 +53,7 @@ struct protocol_capability {
53
54 static struct protocol_capability capabilities[] = {
55 { "agent", agent_advertise, NULL },
56 + { "ls-refs", always_advertise, ls_refs },
57 };
58
59 static void advertise_capabilities(void)
t/t5701-git-serve.sh
+115
@@ -8,6 +8,7 @@ test_expect_success 'test capability advertisement' '
8 cat >expect <<-EOF &&
9 version 2
10 agent=git/$(git version | cut -d" " -f3)
11 + ls-refs
12 0000
13 EOF
14
@@ -57,4 +58,118 @@ test_expect_success 'request invalid command' '
58 test_i18ngrep "invalid command" err
59 '
60
61 +# Test the basics of ls-refs
62 +#
63 +test_expect_success 'setup some refs and tags' '
64 + test_commit one &&
65 + git branch dev master &&
66 + test_commit two &&
67 + git symbolic-ref refs/heads/release refs/heads/master &&
68 + git tag -a -m "annotated tag" annotated-tag
69 +'
70 +
71 +test_expect_success 'basics of ls-refs' '
72 + test-pkt-line pack >in <<-EOF &&
73 + command=ls-refs
74 + 0000
75 + EOF
76 +
77 + cat >expect <<-EOF &&
78 + $(git rev-parse HEAD) HEAD
79 + $(git rev-parse refs/heads/dev) refs/heads/dev
80 + $(git rev-parse refs/heads/master) refs/heads/master
81 + $(git rev-parse refs/heads/release) refs/heads/release
82 + $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag
83 + $(git rev-parse refs/tags/one) refs/tags/one
84 + $(git rev-parse refs/tags/two) refs/tags/two
85 + 0000
86 + EOF
87 +
88 + git serve --stateless-rpc <in >out &&
89 + test-pkt-line unpack <out >actual &&
90 + test_cmp actual expect
91 +'
92 +
93 +test_expect_success 'basic ref-prefixes' '
94 + test-pkt-line pack >in <<-EOF &&
95 + command=ls-refs
96 + 0001
97 + ref-prefix refs/heads/master
98 + ref-prefix refs/tags/one
99 + 0000
100 + EOF
101 +
102 + cat >expect <<-EOF &&
103 + $(git rev-parse refs/heads/master) refs/heads/master
104 + $(git rev-parse refs/tags/one) refs/tags/one
105 + 0000
106 + EOF
107 +
108 + git serve --stateless-rpc <in >out &&
109 + test-pkt-line unpack <out >actual &&
110 + test_cmp actual expect
111 +'
112 +
113 +test_expect_success 'refs/heads prefix' '
114 + test-pkt-line pack >in <<-EOF &&
115 + command=ls-refs
116 + 0001
117 + ref-prefix refs/heads/
118 + 0000
119 + EOF
120 +
121 + cat >expect <<-EOF &&
122 + $(git rev-parse refs/heads/dev) refs/heads/dev
123 + $(git rev-parse refs/heads/master) refs/heads/master
124 + $(git rev-parse refs/heads/release) refs/heads/release
125 + 0000
126 + EOF
127 +
128 + git serve --stateless-rpc <in >out &&
129 + test-pkt-line unpack <out >actual &&
130 + test_cmp actual expect
131 +'
132 +
133 +test_expect_success 'peel parameter' '
134 + test-pkt-line pack >in <<-EOF &&
135 + command=ls-refs
136 + 0001
137 + peel
138 + ref-prefix refs/tags/
139 + 0000
140 + EOF
141 +
142 + cat >expect <<-EOF &&
143 + $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag peeled:$(git rev-parse refs/tags/annotated-tag^{})
144 + $(git rev-parse refs/tags/one) refs/tags/one
145 + $(git rev-parse refs/tags/two) refs/tags/two
146 + 0000
147 + EOF
148 +
149 + git serve --stateless-rpc <in >out &&
150 + test-pkt-line unpack <out >actual &&
151 + test_cmp actual expect
152 +'
153 +
154 +test_expect_success 'symrefs parameter' '
155 + test-pkt-line pack >in <<-EOF &&
156 + command=ls-refs
157 + 0001
158 + symrefs
159 + ref-prefix refs/heads/
160 + 0000
161 + EOF
162 +
163 + cat >expect <<-EOF &&
164 + $(git rev-parse refs/heads/dev) refs/heads/dev
165 + $(git rev-parse refs/heads/master) refs/heads/master
166 + $(git rev-parse refs/heads/release) refs/heads/release symref-target:refs/heads/master
167 + 0000
168 + EOF
169 +
170 + git serve --stateless-rpc <in >out &&
171 + test-pkt-line unpack <out >actual &&
172 + test_cmp actual expect
173 +'
174 +
175 test_done