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
+}