]> git.ipfire.org Git - thirdparty/git.git/blob - t/helper/test-rot13-filter.c
Merge branch 'ds/cmd-main-reorder'
[thirdparty/git.git] / t / helper / test-rot13-filter.c
1 /*
2 * Example implementation for the Git filter protocol version 2
3 * See Documentation/gitattributes.txt, section "Filter Protocol"
4 *
5 * Usage: test-tool rot13-filter [--always-delay] --log=<path> <capabilities>
6 *
7 * Log path defines a debug log file that the script writes to. The
8 * subsequent arguments define a list of supported protocol capabilities
9 * ("clean", "smudge", etc).
10 *
11 * When --always-delay is given all pathnames with the "can-delay" flag
12 * that don't appear on the list bellow are delayed with a count of 1
13 * (see more below).
14 *
15 * This implementation supports special test cases:
16 * (1) If data with the pathname "clean-write-fail.r" is processed with
17 * a "clean" operation then the write operation will die.
18 * (2) If data with the pathname "smudge-write-fail.r" is processed with
19 * a "smudge" operation then the write operation will die.
20 * (3) If data with the pathname "error.r" is processed with any
21 * operation then the filter signals that it cannot or does not want
22 * to process the file.
23 * (4) If data with the pathname "abort.r" is processed with any
24 * operation then the filter signals that it cannot or does not want
25 * to process the file and any file after that is processed with the
26 * same command.
27 * (5) If data with a pathname that is a key in the delay hash is
28 * requested (e.g. "test-delay10.a") then the filter responds with
29 * a "delay" status and sets the "requested" field in the delay hash.
30 * The filter will signal the availability of this object after
31 * "count" (field in delay hash) "list_available_blobs" commands.
32 * (6) If data with the pathname "missing-delay.a" is processed that the
33 * filter will drop the path from the "list_available_blobs" response.
34 * (7) If data with the pathname "invalid-delay.a" is processed that the
35 * filter will add the path "unfiltered" which was not delayed before
36 * to the "list_available_blobs" response.
37 */
38
39 #include "test-tool.h"
40 #include "pkt-line.h"
41 #include "string-list.h"
42 #include "strmap.h"
43 #include "parse-options.h"
44
45 static FILE *logfile;
46 static int always_delay, has_clean_cap, has_smudge_cap;
47 static struct strmap delay = STRMAP_INIT;
48
49 static inline const char *str_or_null(const char *str)
50 {
51 return str ? str : "(null)";
52 }
53
54 static char *rot13(char *str)
55 {
56 char *c;
57 for (c = str; *c; c++)
58 if (isalpha(*c))
59 *c += tolower(*c) < 'n' ? 13 : -13;
60 return str;
61 }
62
63 static char *get_value(char *buf, const char *key)
64 {
65 const char *orig_buf = buf;
66 if (!buf ||
67 !skip_prefix((const char *)buf, key, (const char **)&buf) ||
68 !skip_prefix((const char *)buf, "=", (const char **)&buf) ||
69 !*buf)
70 die("expected key '%s', got '%s'", key, str_or_null(orig_buf));
71 return buf;
72 }
73
74 /*
75 * Read a text packet, expecting that it is in the form "key=value" for
76 * the given key. An EOF does not trigger any error and is reported
77 * back to the caller with NULL. Die if the "key" part of "key=value" does
78 * not match the given key, or the value part is empty.
79 */
80 static char *packet_key_val_read(const char *key)
81 {
82 char *buf;
83 if (packet_read_line_gently(0, NULL, &buf) < 0)
84 return NULL;
85 return xstrdup(get_value(buf, key));
86 }
87
88 static inline void assert_remote_capability(struct strset *caps, const char *cap)
89 {
90 if (!strset_contains(caps, cap))
91 die("required '%s' capability not available from remote", cap);
92 }
93
94 static void read_capabilities(struct strset *remote_caps)
95 {
96 for (;;) {
97 char *buf = packet_read_line(0, NULL);
98 if (!buf)
99 break;
100 strset_add(remote_caps, get_value(buf, "capability"));
101 }
102
103 assert_remote_capability(remote_caps, "clean");
104 assert_remote_capability(remote_caps, "smudge");
105 assert_remote_capability(remote_caps, "delay");
106 }
107
108 static void check_and_write_capabilities(struct strset *remote_caps,
109 const char **caps, int nr_caps)
110 {
111 int i;
112 for (i = 0; i < nr_caps; i++) {
113 if (!strset_contains(remote_caps, caps[i]))
114 die("our capability '%s' is not available from remote",
115 caps[i]);
116 packet_write_fmt(1, "capability=%s\n", caps[i]);
117 }
118 packet_flush(1);
119 }
120
121 struct delay_entry {
122 int requested, count;
123 char *output;
124 };
125
126 static void free_delay_entries(void)
127 {
128 struct hashmap_iter iter;
129 struct strmap_entry *ent;
130
131 strmap_for_each_entry(&delay, &iter, ent) {
132 struct delay_entry *delay_entry = ent->value;
133 free(delay_entry->output);
134 free(delay_entry);
135 }
136 strmap_clear(&delay, 0);
137 }
138
139 static void add_delay_entry(char *pathname, int count, int requested)
140 {
141 struct delay_entry *entry = xcalloc(1, sizeof(*entry));
142 entry->count = count;
143 entry->requested = requested;
144 if (strmap_put(&delay, pathname, entry))
145 BUG("adding the same path twice to delay hash?");
146 }
147
148 static void reply_list_available_blobs_cmd(void)
149 {
150 struct hashmap_iter iter;
151 struct strmap_entry *ent;
152 struct string_list_item *str_item;
153 struct string_list paths = STRING_LIST_INIT_NODUP;
154
155 /* flush */
156 if (packet_read_line(0, NULL))
157 die("bad list_available_blobs end");
158
159 strmap_for_each_entry(&delay, &iter, ent) {
160 struct delay_entry *delay_entry = ent->value;
161 if (!delay_entry->requested)
162 continue;
163 delay_entry->count--;
164 if (!strcmp(ent->key, "invalid-delay.a")) {
165 /* Send Git a pathname that was not delayed earlier */
166 packet_write_fmt(1, "pathname=unfiltered");
167 }
168 if (!strcmp(ent->key, "missing-delay.a")) {
169 /* Do not signal Git that this file is available */
170 } else if (!delay_entry->count) {
171 string_list_append(&paths, ent->key);
172 packet_write_fmt(1, "pathname=%s", ent->key);
173 }
174 }
175
176 /* Print paths in sorted order. */
177 string_list_sort(&paths);
178 for_each_string_list_item(str_item, &paths)
179 fprintf(logfile, " %s", str_item->string);
180 string_list_clear(&paths, 0);
181
182 packet_flush(1);
183
184 fprintf(logfile, " [OK]\n");
185 packet_write_fmt(1, "status=success");
186 packet_flush(1);
187 }
188
189 static void command_loop(void)
190 {
191 for (;;) {
192 char *buf, *output;
193 char *pathname;
194 struct delay_entry *entry;
195 struct strbuf input = STRBUF_INIT;
196 char *command = packet_key_val_read("command");
197
198 if (!command) {
199 fprintf(logfile, "STOP\n");
200 break;
201 }
202 fprintf(logfile, "IN: %s", command);
203
204 if (!strcmp(command, "list_available_blobs")) {
205 reply_list_available_blobs_cmd();
206 free(command);
207 continue;
208 }
209
210 pathname = packet_key_val_read("pathname");
211 if (!pathname)
212 die("unexpected EOF while expecting pathname");
213 fprintf(logfile, " %s", pathname);
214
215 /* Read until flush */
216 while ((buf = packet_read_line(0, NULL))) {
217 if (!strcmp(buf, "can-delay=1")) {
218 entry = strmap_get(&delay, pathname);
219 if (entry && !entry->requested)
220 entry->requested = 1;
221 else if (!entry && always_delay)
222 add_delay_entry(pathname, 1, 1);
223 } else if (starts_with(buf, "ref=") ||
224 starts_with(buf, "treeish=") ||
225 starts_with(buf, "blob=")) {
226 fprintf(logfile, " %s", buf);
227 } else {
228 /*
229 * In general, filters need to be graceful about
230 * new metadata, since it's documented that we
231 * can pass any key-value pairs, but for tests,
232 * let's be a little stricter.
233 */
234 die("Unknown message '%s'", buf);
235 }
236 }
237
238 read_packetized_to_strbuf(0, &input, 0);
239 fprintf(logfile, " %"PRIuMAX" [OK] -- ", (uintmax_t)input.len);
240
241 entry = strmap_get(&delay, pathname);
242 if (entry && entry->output) {
243 output = entry->output;
244 } else if (!strcmp(pathname, "error.r") || !strcmp(pathname, "abort.r")) {
245 output = "";
246 } else if (!strcmp(command, "clean") && has_clean_cap) {
247 output = rot13(input.buf);
248 } else if (!strcmp(command, "smudge") && has_smudge_cap) {
249 output = rot13(input.buf);
250 } else {
251 die("bad command '%s'", command);
252 }
253
254 if (!strcmp(pathname, "error.r")) {
255 fprintf(logfile, "[ERROR]\n");
256 packet_write_fmt(1, "status=error");
257 packet_flush(1);
258 } else if (!strcmp(pathname, "abort.r")) {
259 fprintf(logfile, "[ABORT]\n");
260 packet_write_fmt(1, "status=abort");
261 packet_flush(1);
262 } else if (!strcmp(command, "smudge") &&
263 (entry = strmap_get(&delay, pathname)) &&
264 entry->requested == 1) {
265 fprintf(logfile, "[DELAYED]\n");
266 packet_write_fmt(1, "status=delayed");
267 packet_flush(1);
268 entry->requested = 2;
269 if (entry->output != output) {
270 free(entry->output);
271 entry->output = xstrdup(output);
272 }
273 } else {
274 int i, nr_packets = 0;
275 size_t output_len;
276 const char *p;
277 packet_write_fmt(1, "status=success");
278 packet_flush(1);
279
280 if (skip_prefix(pathname, command, &p) &&
281 !strcmp(p, "-write-fail.r")) {
282 fprintf(logfile, "[WRITE FAIL]\n");
283 die("%s write error", command);
284 }
285
286 output_len = strlen(output);
287 fprintf(logfile, "OUT: %"PRIuMAX" ", (uintmax_t)output_len);
288
289 if (write_packetized_from_buf_no_flush_count(output,
290 output_len, 1, &nr_packets))
291 die("failed to write buffer to stdout");
292 packet_flush(1);
293
294 for (i = 0; i < nr_packets; i++)
295 fprintf(logfile, ".");
296 fprintf(logfile, " [OK]\n");
297
298 packet_flush(1);
299 }
300 free(pathname);
301 strbuf_release(&input);
302 free(command);
303 }
304 }
305
306 static void packet_initialize(void)
307 {
308 char *pkt_buf = packet_read_line(0, NULL);
309
310 if (!pkt_buf || strcmp(pkt_buf, "git-filter-client"))
311 die("bad initialize: '%s'", str_or_null(pkt_buf));
312
313 pkt_buf = packet_read_line(0, NULL);
314 if (!pkt_buf || strcmp(pkt_buf, "version=2"))
315 die("bad version: '%s'", str_or_null(pkt_buf));
316
317 pkt_buf = packet_read_line(0, NULL);
318 if (pkt_buf)
319 die("bad version end: '%s'", pkt_buf);
320
321 packet_write_fmt(1, "git-filter-server");
322 packet_write_fmt(1, "version=2");
323 packet_flush(1);
324 }
325
326 static const char *rot13_usage[] = {
327 "test-tool rot13-filter [--always-delay] --log=<path> <capabilities>",
328 NULL
329 };
330
331 int cmd__rot13_filter(int argc, const char **argv)
332 {
333 int i, nr_caps;
334 struct strset remote_caps = STRSET_INIT;
335 const char *log_path = NULL;
336
337 struct option options[] = {
338 OPT_BOOL(0, "always-delay", &always_delay,
339 "delay all paths with the can-delay flag"),
340 OPT_STRING(0, "log", &log_path, "path",
341 "path to the debug log file"),
342 OPT_END()
343 };
344 nr_caps = parse_options(argc, argv, NULL, options, rot13_usage,
345 PARSE_OPT_STOP_AT_NON_OPTION);
346
347 if (!log_path || !nr_caps)
348 usage_with_options(rot13_usage, options);
349
350 logfile = fopen(log_path, "a");
351 if (!logfile)
352 die_errno("failed to open log file");
353
354 for (i = 0; i < nr_caps; i++) {
355 if (!strcmp(argv[i], "smudge"))
356 has_smudge_cap = 1;
357 if (!strcmp(argv[i], "clean"))
358 has_clean_cap = 1;
359 }
360
361 add_delay_entry("test-delay10.a", 1, 0);
362 add_delay_entry("test-delay11.a", 1, 0);
363 add_delay_entry("test-delay20.a", 2, 0);
364 add_delay_entry("test-delay10.b", 1, 0);
365 add_delay_entry("missing-delay.a", 1, 0);
366 add_delay_entry("invalid-delay.a", 1, 0);
367
368 fprintf(logfile, "START\n");
369 packet_initialize();
370
371 read_capabilities(&remote_caps);
372 check_and_write_capabilities(&remote_caps, argv, nr_caps);
373 fprintf(logfile, "init handshake complete\n");
374 strset_clear(&remote_caps);
375
376 command_loop();
377
378 if (fclose(logfile))
379 die_errno("error closing logfile");
380 free_delay_entries();
381 return 0;
382 }