bundle-uri: parse bundle list in config format

When a bundle provider wants to operate independently from a Git remote, they want to provide a single, consistent URI that users can use in their 'git clone --bundle-uri' commands. At this point, the Git client expects that URI to be a single bundle that can be unbundled and used to bootstrap the rest of the clone from the Git server. This single bundle cannot be re-used to assist with future incremental fetches. To allow for the incremental fetch case, teach Git to understand a bundle list that could be advertised at an independent bundle URI. Such a bundle list is likely to be inspected by human readers, even if only by the bundle provider creating the list. For this reason, we can take our expected "key=value" pairs and instead format them using Git config format. Create bundle_uri_parse_config_format() to parse a file in config format and convert that into a 'struct bundle_list' filled with its understanding of the contents. Be careful to use error_action CONFIG_ERROR_ERROR when calling git_config_from_file_with_options() because the default action for git_config_from_file() is to die() on a parsing error. The current warning isn't particularly helpful if it arises to a user, but it will be made more verbose at a higher layer later. Update 'test-tool bundle-uri' to take this config file format as input. It uses a filename instead of stdin because there is no existing way to parse a FILE pointer in the config machinery. Using git_config_from_mem() is overly complicated and more likely to introduce bugs than this simpler version. Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Oct 12, 2022 at 12:52 UTC 738e5245fa423fc43495e2e17e053365dc6b2fc0
4 files changed +123 -12
bundle-uri.c
+27
@@ -173,6 +173,33 @@ static int bundle_list_update(const char *key, const char *value,
173 return 0;
174 }
175
176 +static int config_to_bundle_list(const char *key, const char *value, void *data)
177 +{
178 + struct bundle_list *list = data;
179 + return bundle_list_update(key, value, list);
180 +}
181 +
182 +int bundle_uri_parse_config_format(const char *uri,
183 + const char *filename,
184 + struct bundle_list *list)
185 +{
186 + int result;
187 + struct config_options opts = {
188 + .error_action = CONFIG_ERROR_ERROR,
189 + };
190 +
191 + result = git_config_from_file_with_options(config_to_bundle_list,
192 + filename, list,
193 + &opts);
194 +
195 + if (!result && list->mode == BUNDLE_MODE_NONE) {
196 + warning(_("bundle list at '%s' has no mode"), uri);
197 + result = 1;
198 + }
199 +
200 + return result;
201 +}
202 +
203 static char *find_temp_filename(void)
204 {
205 int fd;
bundle-uri.h
+9
@@ -62,6 +62,15 @@ int for_all_bundles_in_list(struct bundle_list *list,
62 struct FILE;
63 void print_bundle_list(FILE *fp, struct bundle_list *list);
64
65 +/**
66 + * A bundle URI may point to a bundle list where the key=value
67 + * pairs are provided in config file format. This method is
68 + * exposed publicly for testing purposes.
69 + */
70 +int bundle_uri_parse_config_format(const char *uri,
71 + const char *filename,
72 + struct bundle_list *list);
73 +
74 /**
75 * Fetch data from the given 'uri' and unbundle the bundle data found
76 * based on that information.
t/helper/test-bundle-uri.c
+37 -12
@@ -4,12 +4,21 @@
4 #include "strbuf.h"
5 #include "string-list.h"
6
7 -static int cmd__bundle_uri_parse(int argc, const char **argv)
7 +enum input_mode {
8 + KEY_VALUE_PAIRS,
9 + CONFIG_FILE,
10 +};
11 +
12 +static int cmd__bundle_uri_parse(int argc, const char **argv, enum input_mode mode)
13 {
14 const char *key_value_usage[] = {
15 "test-tool bundle-uri parse-key-values <input>",
16 NULL
17 };
18 + const char *config_usage[] = {
19 + "test-tool bundle-uri parse-config <input>",
20 + NULL
21 + };
22 const char **usage = key_value_usage;
23 struct option options[] = {
24 OPT_END(),
@@ -19,21 +28,35 @@ static int cmd__bundle_uri_parse(int argc, const char **argv)
28 int err = 0;
29 FILE *fp;
30
22 - argc = parse_options(argc, argv, NULL, options, usage, 0);
23 - if (argc != 1)
24 - goto usage;
31 + if (mode == CONFIG_FILE)
32 + usage = config_usage;
33 +
34 + argc = parse_options(argc, argv, NULL, options, usage,
35 + PARSE_OPT_STOP_AT_NON_OPTION);
36
37 init_bundle_list(&list);
27 - fp = fopen(argv[0], "r");
28 - if (!fp)
29 - die("failed to open '%s'", argv[0]);
38
31 - while (strbuf_getline(&sb, fp) != EOF) {
32 - if (bundle_uri_parse_line(&list, sb.buf))
33 - err = error("bad line: '%s'", sb.buf);
39 + switch (mode) {
40 + case KEY_VALUE_PAIRS:
41 + if (argc != 1)
42 + goto usage;
43 + fp = fopen(argv[0], "r");
44 + if (!fp)
45 + die("failed to open '%s'", argv[0]);
46 + while (strbuf_getline(&sb, fp) != EOF) {
47 + if (bundle_uri_parse_line(&list, sb.buf))
48 + err = error("bad line: '%s'", sb.buf);
49 + }
50 + fclose(fp);
51 + break;
52 +
53 + case CONFIG_FILE:
54 + if (argc != 1)
55 + goto usage;
56 + err = bundle_uri_parse_config_format("<uri>", argv[0], &list);
57 + break;
58 }
59 strbuf_release(&sb);
36 - fclose(fp);
60
61 print_bundle_list(stdout, &list);
62
@@ -62,7 +85,9 @@ int cmd__bundle_uri(int argc, const char **argv)
85 goto usage;
86
87 if (!strcmp(argv[1], "parse-key-values"))
65 - return cmd__bundle_uri_parse(argc - 1, argv + 1);
88 + return cmd__bundle_uri_parse(argc - 1, argv + 1, KEY_VALUE_PAIRS);
89 + if (!strcmp(argv[1], "parse-config"))
90 + return cmd__bundle_uri_parse(argc - 1, argv + 1, CONFIG_FILE);
91 error("there is no test-tool bundle-uri tool '%s'", argv[1]);
92
93 usage:
t/t5750-bundle-uri-parse.sh
+50
@@ -118,4 +118,54 @@ test_expect_success 'bundle_uri_parse_line() parsing edge cases: duplicate lines
118 test_cmp_config_output expect actual
119 '
120
121 +test_expect_success 'parse config format: just URIs' '
122 + cat >expect <<-\EOF &&
123 + [bundle]
124 + version = 1
125 + mode = all
126 + [bundle "one"]
127 + uri = http://example.com/bundle.bdl
128 + [bundle "two"]
129 + uri = https://example.com/bundle.bdl
130 + [bundle "three"]
131 + uri = file:///usr/share/git/bundle.bdl
132 + EOF
133 +
134 + test-tool bundle-uri parse-config expect >actual 2>err &&
135 + test_must_be_empty err &&
136 + test_cmp_config_output expect actual
137 +'
138 +
139 +test_expect_success 'parse config format edge cases: empty key or value' '
140 + cat >in1 <<-\EOF &&
141 + = bogus-value
142 + EOF
143 +
144 + cat >err1 <<-EOF &&
145 + error: bad config line 1 in file in1
146 + EOF
147 +
148 + cat >expect <<-\EOF &&
149 + [bundle]
150 + version = 1
151 + mode = all
152 + EOF
153 +
154 + test_must_fail test-tool bundle-uri parse-config in1 >actual 2>err &&
155 + test_cmp err1 err &&
156 + test_cmp_config_output expect actual &&
157 +
158 + cat >in2 <<-\EOF &&
159 + bogus-key =
160 + EOF
161 +
162 + cat >err2 <<-EOF &&
163 + error: bad config line 1 in file in2
164 + EOF
165 +
166 + test_must_fail test-tool bundle-uri parse-config in2 >actual 2>err &&
167 + test_cmp err2 err &&
168 + test_cmp_config_output expect actual
169 +'
170 +
171 test_done