]>
Commit | Line | Data |
---|---|---|
c0335005 | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
c0335005 | 3 | * |
bb742ede VM |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
c0335005 CMN |
6 | */ |
7 | ||
c0335005 | 8 | #include "config.h" |
ac3d33df | 9 | |
b0b527e0 | 10 | #include "git2/config.h" |
83041c71 | 11 | #include "git2/sys/config.h" |
22a2d3d5 | 12 | |
19be0692 | 13 | #include "array.h" |
e579e0f7 | 14 | #include "str.h" |
22a2d3d5 | 15 | #include "config_backend.h" |
ac3d33df | 16 | #include "config_entries.h" |
22a2d3d5 UG |
17 | #include "config_parse.h" |
18 | #include "filebuf.h" | |
19 | #include "regexp.h" | |
20 | #include "sysdir.h" | |
21 | #include "wildmatch.h" | |
e579e0f7 | 22 | #include "hash.h" |
c0335005 | 23 | |
d8d25acb CMN |
24 | /* Max depth for [include] directives */ |
25 | #define MAX_INCLUDE_DEPTH 10 | |
a603c191 | 26 | |
22a2d3d5 UG |
27 | typedef struct config_file { |
28 | git_futils_filestamp stamp; | |
e579e0f7 | 29 | unsigned char checksum[GIT_HASH_SHA1_SIZE]; |
22a2d3d5 UG |
30 | char *path; |
31 | git_array_t(struct config_file) includes; | |
32 | } config_file; | |
33 | ||
4b99b8f5 CMN |
34 | typedef struct { |
35 | git_config_backend parent; | |
4b99b8f5 | 36 | git_mutex values_mutex; |
ac3d33df | 37 | git_config_entries *entries; |
eae0bfdc PP |
38 | const git_repository *repo; |
39 | git_config_level_t level; | |
55ebd7d3 | 40 | |
eae0bfdc | 41 | git_array_t(git_config_parser) readers; |
c0335005 | 42 | |
b1667039 CMN |
43 | bool locked; |
44 | git_filebuf locked_buf; | |
e579e0f7 | 45 | git_str locked_content; |
b1667039 | 46 | |
22a2d3d5 UG |
47 | config_file file; |
48 | } config_file_backend; | |
55ebd7d3 | 49 | |
ac3d33df JK |
50 | typedef struct { |
51 | const git_repository *repo; | |
22a2d3d5 | 52 | config_file *file; |
ac3d33df JK |
53 | git_config_entries *entries; |
54 | git_config_level_t level; | |
55 | unsigned int depth; | |
22a2d3d5 | 56 | } config_file_parse_data; |
ac3d33df | 57 | |
22a2d3d5 UG |
58 | static int config_file_read(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth); |
59 | static int config_file_read_buffer(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen); | |
60 | static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value); | |
49938cad | 61 | static char *escape_value(const char *ptr); |
c0335005 | 62 | |
4b99b8f5 CMN |
63 | /** |
64 | * Take the current values map from the backend and increase its | |
65 | * refcount. This is its own function to make sure we use the mutex to | |
66 | * avoid the map pointer from changing under us. | |
67 | */ | |
22a2d3d5 | 68 | static int config_file_entries_take(git_config_entries **out, config_file_backend *b) |
4b99b8f5 | 69 | { |
22a2d3d5 | 70 | int error; |
4b99b8f5 | 71 | |
22a2d3d5 UG |
72 | if ((error = git_mutex_lock(&b->values_mutex)) < 0) { |
73 | git_error_set(GIT_ERROR_OS, "failed to lock config backend"); | |
74 | return error; | |
836447e5 | 75 | } |
4b99b8f5 | 76 | |
22a2d3d5 UG |
77 | git_config_entries_incref(b->entries); |
78 | *out = b->entries; | |
4b99b8f5 | 79 | |
22a2d3d5 | 80 | git_mutex_unlock(&b->values_mutex); |
4b99b8f5 | 81 | |
22a2d3d5 | 82 | return 0; |
4b99b8f5 CMN |
83 | } |
84 | ||
22a2d3d5 | 85 | static void config_file_clear(config_file *file) |
eae0bfdc | 86 | { |
22a2d3d5 | 87 | config_file *include; |
eae0bfdc PP |
88 | uint32_t i; |
89 | ||
90 | if (file == NULL) | |
91 | return; | |
92 | ||
93 | git_array_foreach(file->includes, i, include) { | |
94 | config_file_clear(include); | |
95 | } | |
96 | git_array_clear(file->includes); | |
97 | ||
98 | git__free(file->path); | |
99 | } | |
100 | ||
22a2d3d5 | 101 | static int config_file_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) |
c0335005 | 102 | { |
22a2d3d5 | 103 | config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); |
dda708e7 | 104 | int res; |
c0335005 | 105 | |
22a2d3d5 UG |
106 | b->level = level; |
107 | b->repo = repo; | |
744cc03e | 108 | |
22a2d3d5 | 109 | if ((res = git_config_entries_new(&b->entries)) < 0) |
40ed4990 | 110 | return res; |
fefd4551 | 111 | |
e579e0f7 | 112 | if (!git_fs_path_exists(b->file.path)) |
dda708e7 VM |
113 | return 0; |
114 | ||
22a2d3d5 UG |
115 | /* |
116 | * git silently ignores configuration files that are not | |
117 | * readable. We emulate that behavior. This is particularly | |
118 | * important for sandboxed applications on macOS where the | |
119 | * git configuration files may not be readable. | |
120 | */ | |
121 | if (p_access(b->file.path, R_OK) < 0) | |
122 | return GIT_ENOTFOUND; | |
123 | ||
124 | if (res < 0 || (res = config_file_read(b->entries, repo, &b->file, level, 0)) < 0) { | |
125 | git_config_entries_free(b->entries); | |
126 | b->entries = NULL; | |
dda708e7 | 127 | } |
c0335005 | 128 | |
744cc03e RB |
129 | return res; |
130 | } | |
131 | ||
22a2d3d5 | 132 | static int config_file_is_modified(int *modified, config_file *file) |
bd95f836 | 133 | { |
22a2d3d5 | 134 | config_file *include; |
e579e0f7 MB |
135 | git_str buf = GIT_STR_INIT; |
136 | unsigned char checksum[GIT_HASH_SHA1_SIZE]; | |
eae0bfdc | 137 | uint32_t i; |
bd95f836 CMN |
138 | int error = 0; |
139 | ||
eae0bfdc | 140 | *modified = 0; |
bd95f836 | 141 | |
22a2d3d5 UG |
142 | if (!git_futils_filestamp_check(&file->stamp, file->path)) |
143 | goto check_includes; | |
144 | ||
eae0bfdc PP |
145 | if ((error = git_futils_readbuffer(&buf, file->path)) < 0) |
146 | goto out; | |
bd95f836 | 147 | |
e579e0f7 | 148 | if ((error = git_hash_buf(checksum, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) |
bd95f836 CMN |
149 | goto out; |
150 | ||
e579e0f7 | 151 | if (memcmp(checksum, file->checksum, GIT_HASH_SHA1_SIZE) != 0) { |
eae0bfdc | 152 | *modified = 1; |
836447e5 PS |
153 | goto out; |
154 | } | |
4b99b8f5 | 155 | |
22a2d3d5 | 156 | check_includes: |
eae0bfdc | 157 | git_array_foreach(file->includes, i, include) { |
22a2d3d5 | 158 | if ((error = config_file_is_modified(modified, include)) < 0 || *modified) |
eae0bfdc PP |
159 | goto out; |
160 | } | |
bd95f836 CMN |
161 | |
162 | out: | |
e579e0f7 | 163 | git_str_dispose(&buf); |
eae0bfdc | 164 | |
bd95f836 CMN |
165 | return error; |
166 | } | |
167 | ||
c25aa7cd PP |
168 | static void config_file_clear_includes(config_file_backend *cfg) |
169 | { | |
170 | config_file *include; | |
171 | uint32_t i; | |
172 | ||
173 | git_array_foreach(cfg->file.includes, i, include) | |
174 | config_file_clear(include); | |
175 | git_array_clear(cfg->file.includes); | |
176 | } | |
177 | ||
22a2d3d5 | 178 | static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries) |
744cc03e | 179 | { |
22a2d3d5 UG |
180 | config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); |
181 | git_config_entries *old = NULL; | |
22a2d3d5 | 182 | int error; |
744cc03e | 183 | |
22a2d3d5 UG |
184 | if (b->parent.readonly) { |
185 | git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); | |
186 | return -1; | |
187 | } | |
a9fb7989 | 188 | |
22a2d3d5 UG |
189 | if ((error = git_mutex_lock(&b->values_mutex)) < 0) { |
190 | git_error_set(GIT_ERROR_OS, "failed to lock config backend"); | |
eae0bfdc | 191 | goto out; |
22a2d3d5 | 192 | } |
eae0bfdc | 193 | |
22a2d3d5 UG |
194 | old = b->entries; |
195 | b->entries = entries; | |
eae0bfdc | 196 | |
22a2d3d5 | 197 | git_mutex_unlock(&b->values_mutex); |
a9fb7989 | 198 | |
22a2d3d5 UG |
199 | out: |
200 | git_config_entries_free(old); | |
201 | return error; | |
202 | } | |
203 | ||
204 | static int config_file_refresh_from_buffer(git_config_backend *cfg, const char *buf, size_t buflen) | |
205 | { | |
206 | config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); | |
207 | git_config_entries *entries = NULL; | |
208 | int error; | |
eae0bfdc | 209 | |
c25aa7cd PP |
210 | config_file_clear_includes(b); |
211 | ||
22a2d3d5 UG |
212 | if ((error = git_config_entries_new(&entries)) < 0 || |
213 | (error = config_file_read_buffer(entries, b->repo, &b->file, | |
214 | b->level, 0, buf, buflen)) < 0 || | |
215 | (error = config_file_set_entries(cfg, entries)) < 0) | |
eae0bfdc PP |
216 | goto out; |
217 | ||
22a2d3d5 UG |
218 | entries = NULL; |
219 | out: | |
220 | git_config_entries_free(entries); | |
221 | return error; | |
222 | } | |
223 | ||
224 | static int config_file_refresh(git_config_backend *cfg) | |
225 | { | |
226 | config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); | |
227 | git_config_entries *entries = NULL; | |
228 | int error, modified; | |
229 | ||
230 | if (cfg->readonly) | |
231 | return 0; | |
232 | ||
233 | if ((error = config_file_is_modified(&modified, &b->file)) < 0 && error != GIT_ENOTFOUND) | |
eae0bfdc | 234 | goto out; |
a9fb7989 | 235 | |
22a2d3d5 UG |
236 | if (!modified) |
237 | return 0; | |
744cc03e | 238 | |
c25aa7cd PP |
239 | config_file_clear_includes(b); |
240 | ||
22a2d3d5 UG |
241 | if ((error = git_config_entries_new(&entries)) < 0 || |
242 | (error = config_file_read(entries, b->repo, &b->file, b->level, 0)) < 0 || | |
243 | (error = config_file_set_entries(cfg, entries)) < 0) | |
244 | goto out; | |
eae0bfdc | 245 | |
22a2d3d5 | 246 | entries = NULL; |
eae0bfdc | 247 | out: |
ac3d33df | 248 | git_config_entries_free(entries); |
eae0bfdc PP |
249 | |
250 | return (error == GIT_ENOTFOUND) ? 0 : error; | |
c0335005 CMN |
251 | } |
252 | ||
22a2d3d5 | 253 | static void config_file_free(git_config_backend *_backend) |
c0335005 | 254 | { |
22a2d3d5 | 255 | config_file_backend *backend = GIT_CONTAINER_OF(_backend, config_file_backend, parent); |
c0335005 CMN |
256 | |
257 | if (backend == NULL) | |
258 | return; | |
259 | ||
eae0bfdc | 260 | config_file_clear(&backend->file); |
22a2d3d5 UG |
261 | git_config_entries_free(backend->entries); |
262 | git_mutex_free(&backend->values_mutex); | |
3286c408 | 263 | git__free(backend); |
c0335005 CMN |
264 | } |
265 | ||
22a2d3d5 | 266 | static int config_file_iterator( |
eba73992 | 267 | git_config_iterator **iter, |
22a2d3d5 | 268 | struct git_config_backend *backend) |
eba73992 | 269 | { |
22a2d3d5 UG |
270 | config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent); |
271 | git_config_entries *dupped = NULL, *entries = NULL; | |
0500a1ef | 272 | int error; |
b3ff1dab | 273 | |
22a2d3d5 UG |
274 | if ((error = config_file_refresh(backend)) < 0 || |
275 | (error = config_file_entries_take(&entries, b)) < 0 || | |
276 | (error = git_config_entries_dup(&dupped, entries)) < 0 || | |
277 | (error = git_config_entries_iterator_new(iter, dupped)) < 0) | |
ac3d33df | 278 | goto out; |
a603c191 | 279 | |
ac3d33df JK |
280 | out: |
281 | /* Let iterator delete duplicated entries when it's done */ | |
282 | git_config_entries_free(entries); | |
22a2d3d5 | 283 | git_config_entries_free(dupped); |
ac3d33df | 284 | return error; |
c0335005 CMN |
285 | } |
286 | ||
22a2d3d5 | 287 | static int config_file_snapshot(git_config_backend **out, git_config_backend *backend) |
c0335005 | 288 | { |
22a2d3d5 UG |
289 | return git_config_backend_snapshot(out, backend); |
290 | } | |
291 | ||
292 | static int config_file_set(git_config_backend *cfg, const char *name, const char *value) | |
293 | { | |
294 | config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); | |
ac3d33df JK |
295 | git_config_entries *entries; |
296 | git_config_entry *existing; | |
49938cad | 297 | char *key, *esc_value = NULL; |
ac3d33df | 298 | int error; |
fefd4551 | 299 | |
ac3d33df JK |
300 | if ((error = git_config__normalize_name(name, &key)) < 0) |
301 | return error; | |
c0335005 | 302 | |
22a2d3d5 UG |
303 | if ((error = config_file_entries_take(&entries, b)) < 0) |
304 | return error; | |
aa13bf05 | 305 | |
ac3d33df JK |
306 | /* Check whether we'd be modifying an included or multivar key */ |
307 | if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) { | |
308 | if (error != GIT_ENOTFOUND) | |
4b99b8f5 | 309 | goto out; |
ac3d33df JK |
310 | error = 0; |
311 | } else if ((!existing->value && !value) || | |
312 | (existing->value && value && !strcmp(existing->value, value))) { | |
aa13bf05 | 313 | /* don't update if old and new values already match */ |
ac3d33df JK |
314 | error = 0; |
315 | goto out; | |
29dca088 CMN |
316 | } |
317 | ||
eaf37034 | 318 | /* No early returns due to sanity checks, let's write it out and refresh */ |
dda708e7 | 319 | if (value) { |
49938cad | 320 | esc_value = escape_value(value); |
ac3d33df | 321 | GIT_ERROR_CHECK_ALLOC(esc_value); |
c0335005 CMN |
322 | } |
323 | ||
22a2d3d5 | 324 | if ((error = config_file_write(b, name, key, NULL, esc_value)) < 0) |
eaf37034 | 325 | goto out; |
3df9cc59 | 326 | |
eaf37034 | 327 | out: |
ac3d33df | 328 | git_config_entries_free(entries); |
eaf37034 CMN |
329 | git__free(esc_value); |
330 | git__free(key); | |
ac3d33df | 331 | return error; |
c0335005 CMN |
332 | } |
333 | ||
9a97f49e | 334 | /* release the map containing the entry as an equivalent to freeing it */ |
22a2d3d5 | 335 | static void config_file_entry_free(git_config_entry *entry) |
9a97f49e | 336 | { |
ac3d33df JK |
337 | git_config_entries *entries = (git_config_entries *) entry->payload; |
338 | git_config_entries_free(entries); | |
9a97f49e CMN |
339 | } |
340 | ||
c0335005 CMN |
341 | /* |
342 | * Internal function that actually gets the value in string form | |
343 | */ | |
22a2d3d5 | 344 | static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out) |
c0335005 | 345 | { |
22a2d3d5 | 346 | config_file_backend *h = GIT_CONTAINER_OF(cfg, config_file_backend, parent); |
ac3d33df JK |
347 | git_config_entries *entries = NULL; |
348 | git_config_entry *entry; | |
9a97f49e | 349 | int error = 0; |
523032cd | 350 | |
22a2d3d5 | 351 | if (!h->parent.readonly && ((error = config_file_refresh(cfg)) < 0)) |
523032cd CMN |
352 | return error; |
353 | ||
22a2d3d5 UG |
354 | if ((error = config_file_entries_take(&entries, h)) < 0) |
355 | return error; | |
c0335005 | 356 | |
ac3d33df JK |
357 | if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { |
358 | git_config_entries_free(entries); | |
359 | return error; | |
4b99b8f5 | 360 | } |
c0335005 | 361 | |
22a2d3d5 | 362 | entry->free = config_file_entry_free; |
ac3d33df JK |
363 | entry->payload = entries; |
364 | *out = entry; | |
9a97f49e | 365 | |
ac3d33df | 366 | return 0; |
c0335005 CMN |
367 | } |
368 | ||
22a2d3d5 | 369 | static int config_file_set_multivar( |
54b2a37a | 370 | git_config_backend *cfg, const char *name, const char *regexp, const char *value) |
3005855f | 371 | { |
22a2d3d5 UG |
372 | config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); |
373 | git_regexp preg; | |
dda708e7 | 374 | int result; |
22a2d3d5 | 375 | char *key; |
3005855f | 376 | |
c25aa7cd | 377 | GIT_ASSERT_ARG(regexp); |
3005855f | 378 | |
f4be8209 | 379 | if ((result = git_config__normalize_name(name, &key)) < 0) |
1e7799e8 | 380 | return result; |
3005855f | 381 | |
22a2d3d5 | 382 | if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) |
4b99b8f5 | 383 | goto out; |
3005855f | 384 | |
22a2d3d5 UG |
385 | /* If we do have it, set call config_file_write() and reload */ |
386 | if ((result = config_file_write(b, name, key, &preg, value)) < 0) | |
eaf37034 | 387 | goto out; |
3005855f | 388 | |
eaf37034 | 389 | out: |
2bc8fa02 | 390 | git__free(key); |
22a2d3d5 | 391 | git_regexp_dispose(&preg); |
dda708e7 VM |
392 | |
393 | return result; | |
3005855f CMN |
394 | } |
395 | ||
22a2d3d5 | 396 | static int config_file_delete(git_config_backend *cfg, const char *name) |
80a665aa | 397 | { |
22a2d3d5 | 398 | config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); |
ac3d33df JK |
399 | git_config_entries *entries = NULL; |
400 | git_config_entry *entry; | |
401 | char *key = NULL; | |
402 | int error; | |
fefd4551 | 403 | |
ac3d33df JK |
404 | if ((error = git_config__normalize_name(name, &key)) < 0) |
405 | goto out; | |
fefd4551 | 406 | |
22a2d3d5 | 407 | if ((error = config_file_entries_take(&entries, b)) < 0) |
ac3d33df | 408 | goto out; |
80a665aa | 409 | |
ac3d33df JK |
410 | /* Check whether we'd be modifying an included or multivar key */ |
411 | if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) { | |
412 | if (error == GIT_ENOTFOUND) | |
413 | git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); | |
414 | goto out; | |
eae0bfdc PP |
415 | } |
416 | ||
22a2d3d5 | 417 | if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0) |
ac3d33df | 418 | goto out; |
fefd4551 | 419 | |
ac3d33df JK |
420 | out: |
421 | git_config_entries_free(entries); | |
422 | git__free(key); | |
423 | return error; | |
80a665aa CMN |
424 | } |
425 | ||
22a2d3d5 | 426 | static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) |
3793fa9b | 427 | { |
22a2d3d5 | 428 | config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); |
ac3d33df JK |
429 | git_config_entries *entries = NULL; |
430 | git_config_entry *entry = NULL; | |
22a2d3d5 | 431 | git_regexp preg = GIT_REGEX_INIT; |
ac3d33df | 432 | char *key = NULL; |
3793fa9b | 433 | int result; |
3793fa9b DRT |
434 | |
435 | if ((result = git_config__normalize_name(name, &key)) < 0) | |
ac3d33df | 436 | goto out; |
3793fa9b | 437 | |
22a2d3d5 | 438 | if ((result = config_file_entries_take(&entries, b)) < 0) |
ac3d33df | 439 | goto out; |
3793fa9b | 440 | |
ac3d33df JK |
441 | if ((result = git_config_entries_get(&entry, entries, key)) < 0) { |
442 | if (result == GIT_ENOTFOUND) | |
443 | git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); | |
444 | goto out; | |
445 | } | |
4b99b8f5 | 446 | |
22a2d3d5 | 447 | if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) |
8c1f4ab4 | 448 | goto out; |
3793fa9b | 449 | |
22a2d3d5 | 450 | if ((result = config_file_write(b, name, key, &preg, NULL)) < 0) |
ac3d33df | 451 | goto out; |
3793fa9b | 452 | |
8c1f4ab4 | 453 | out: |
ac3d33df | 454 | git_config_entries_free(entries); |
3793fa9b | 455 | git__free(key); |
22a2d3d5 | 456 | git_regexp_dispose(&preg); |
3793fa9b DRT |
457 | return result; |
458 | } | |
459 | ||
22a2d3d5 | 460 | static int config_file_lock(git_config_backend *_cfg) |
b1667039 | 461 | { |
22a2d3d5 | 462 | config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); |
b1667039 CMN |
463 | int error; |
464 | ||
eae0bfdc | 465 | if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0) |
b1667039 CMN |
466 | return error; |
467 | ||
eae0bfdc | 468 | error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path); |
b1667039 CMN |
469 | if (error < 0 && error != GIT_ENOTFOUND) { |
470 | git_filebuf_cleanup(&cfg->locked_buf); | |
471 | return error; | |
472 | } | |
473 | ||
474 | cfg->locked = true; | |
475 | return 0; | |
476 | ||
477 | } | |
478 | ||
22a2d3d5 | 479 | static int config_file_unlock(git_config_backend *_cfg, int success) |
b1667039 | 480 | { |
22a2d3d5 | 481 | config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); |
b1667039 CMN |
482 | int error = 0; |
483 | ||
484 | if (success) { | |
485 | git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size); | |
486 | error = git_filebuf_commit(&cfg->locked_buf); | |
487 | } | |
488 | ||
489 | git_filebuf_cleanup(&cfg->locked_buf); | |
e579e0f7 | 490 | git_str_dispose(&cfg->locked_content); |
b1667039 CMN |
491 | cfg->locked = false; |
492 | ||
493 | return error; | |
494 | } | |
495 | ||
ac3d33df | 496 | int git_config_backend_from_file(git_config_backend **out, const char *path) |
c0335005 | 497 | { |
22a2d3d5 | 498 | config_file_backend *backend; |
c0335005 | 499 | |
22a2d3d5 | 500 | backend = git__calloc(1, sizeof(config_file_backend)); |
ac3d33df | 501 | GIT_ERROR_CHECK_ALLOC(backend); |
c0335005 | 502 | |
22a2d3d5 UG |
503 | backend->parent.version = GIT_CONFIG_BACKEND_VERSION; |
504 | git_mutex_init(&backend->values_mutex); | |
c0335005 | 505 | |
eae0bfdc | 506 | backend->file.path = git__strdup(path); |
ac3d33df | 507 | GIT_ERROR_CHECK_ALLOC(backend->file.path); |
eae0bfdc | 508 | git_array_init(backend->file.includes); |
c0335005 | 509 | |
22a2d3d5 UG |
510 | backend->parent.open = config_file_open; |
511 | backend->parent.get = config_file_get; | |
512 | backend->parent.set = config_file_set; | |
513 | backend->parent.set_multivar = config_file_set_multivar; | |
514 | backend->parent.del = config_file_delete; | |
515 | backend->parent.del_multivar = config_file_delete_multivar; | |
516 | backend->parent.iterator = config_file_iterator; | |
517 | backend->parent.snapshot = config_file_snapshot; | |
518 | backend->parent.lock = config_file_lock; | |
519 | backend->parent.unlock = config_file_unlock; | |
520 | backend->parent.free = config_file_free; | |
c0335005 | 521 | |
54b2a37a | 522 | *out = (git_config_backend *)backend; |
c0335005 | 523 | |
dda708e7 | 524 | return 0; |
c0335005 CMN |
525 | } |
526 | ||
e579e0f7 | 527 | static int included_path(git_str *out, const char *dir, const char *path) |
d8d25acb CMN |
528 | { |
529 | /* From the user's home */ | |
530 | if (path[0] == '~' && path[1] == '/') | |
29aef948 | 531 | return git_sysdir_expand_global_file(out, &path[1]); |
d8d25acb | 532 | |
e579e0f7 | 533 | return git_fs_path_join_unrooted(out, path, dir, NULL); |
d8d25acb CMN |
534 | } |
535 | ||
49938cad CMN |
536 | /* Escape the values to write them to the file */ |
537 | static char *escape_value(const char *ptr) | |
538 | { | |
e579e0f7 | 539 | git_str buf; |
49938cad CMN |
540 | size_t len; |
541 | const char *esc; | |
542 | ||
c25aa7cd | 543 | GIT_ASSERT_ARG_WITH_RETVAL(ptr, NULL); |
49938cad CMN |
544 | |
545 | len = strlen(ptr); | |
c57f6682 NV |
546 | if (!len) |
547 | return git__calloc(1, sizeof(char)); | |
548 | ||
e579e0f7 | 549 | if (git_str_init(&buf, len) < 0) |
a693b873 | 550 | return NULL; |
49938cad CMN |
551 | |
552 | while (*ptr != '\0') { | |
eae0bfdc | 553 | if ((esc = strchr(git_config_escaped, *ptr)) != NULL) { |
e579e0f7 MB |
554 | git_str_putc(&buf, '\\'); |
555 | git_str_putc(&buf, git_config_escapes[esc - git_config_escaped]); | |
49938cad | 556 | } else { |
e579e0f7 | 557 | git_str_putc(&buf, *ptr); |
49938cad CMN |
558 | } |
559 | ptr++; | |
560 | } | |
561 | ||
e579e0f7 | 562 | if (git_str_oom(&buf)) |
49938cad | 563 | return NULL; |
49938cad | 564 | |
e579e0f7 | 565 | return git_str_detach(&buf); |
49938cad CMN |
566 | } |
567 | ||
22a2d3d5 | 568 | static int parse_include(config_file_parse_data *parse_data, const char *file) |
c0335005 | 569 | { |
22a2d3d5 | 570 | config_file *include; |
e579e0f7 | 571 | git_str path = GIT_STR_INIT; |
eae0bfdc PP |
572 | char *dir; |
573 | int result; | |
c0335005 | 574 | |
6c7cee42 RD |
575 | if (!file) |
576 | return 0; | |
577 | ||
e579e0f7 | 578 | if ((result = git_fs_path_dirname_r(&path, parse_data->file->path)) < 0) |
eae0bfdc | 579 | return result; |
c0335005 | 580 | |
e579e0f7 | 581 | dir = git_str_detach(&path); |
eae0bfdc PP |
582 | result = included_path(&path, dir, file); |
583 | git__free(dir); | |
c0335005 | 584 | |
eae0bfdc PP |
585 | if (result < 0) |
586 | return result; | |
e009a705 | 587 | |
22a2d3d5 | 588 | include = git_array_alloc(parse_data->file->includes); |
6147f643 | 589 | GIT_ERROR_CHECK_ALLOC(include); |
eae0bfdc PP |
590 | memset(include, 0, sizeof(*include)); |
591 | git_array_init(include->includes); | |
e579e0f7 | 592 | include->path = git_str_detach(&path); |
c0335005 | 593 | |
22a2d3d5 UG |
594 | result = config_file_read(parse_data->entries, parse_data->repo, include, |
595 | parse_data->level, parse_data->depth+1); | |
c0335005 | 596 | |
eae0bfdc | 597 | if (result == GIT_ENOTFOUND) { |
ac3d33df | 598 | git_error_clear(); |
eae0bfdc PP |
599 | result = 0; |
600 | } | |
c0335005 | 601 | |
eae0bfdc | 602 | return result; |
2c8c00c6 ET |
603 | } |
604 | ||
eae0bfdc PP |
605 | static int do_match_gitdir( |
606 | int *matches, | |
607 | const git_repository *repo, | |
608 | const char *cfg_file, | |
22a2d3d5 | 609 | const char *condition, |
eae0bfdc | 610 | bool case_insensitive) |
2c8c00c6 | 611 | { |
e579e0f7 | 612 | git_str pattern = GIT_STR_INIT, gitdir = GIT_STR_INIT; |
22a2d3d5 UG |
613 | int error; |
614 | ||
e579e0f7 MB |
615 | if (condition[0] == '.' && git_fs_path_is_dirsep(condition[1])) { |
616 | git_fs_path_dirname_r(&pattern, cfg_file); | |
617 | git_str_joinpath(&pattern, pattern.ptr, condition + 2); | |
618 | } else if (condition[0] == '~' && git_fs_path_is_dirsep(condition[1])) | |
22a2d3d5 | 619 | git_sysdir_expand_global_file(&pattern, condition + 1); |
e579e0f7 MB |
620 | else if (!git_fs_path_is_absolute(condition)) |
621 | git_str_joinpath(&pattern, "**", condition); | |
eae0bfdc | 622 | else |
e579e0f7 | 623 | git_str_sets(&pattern, condition); |
22a2d3d5 | 624 | |
e579e0f7 MB |
625 | if (git_fs_path_is_dirsep(condition[strlen(condition) - 1])) |
626 | git_str_puts(&pattern, "**"); | |
2c8c00c6 | 627 | |
e579e0f7 | 628 | if (git_str_oom(&pattern)) { |
eae0bfdc PP |
629 | error = -1; |
630 | goto out; | |
2c8c00c6 ET |
631 | } |
632 | ||
e579e0f7 | 633 | if ((error = git_repository__item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0) |
eae0bfdc | 634 | goto out; |
2c8c00c6 | 635 | |
e579e0f7 MB |
636 | if (git_fs_path_is_dirsep(gitdir.ptr[gitdir.size - 1])) |
637 | git_str_truncate(&gitdir, gitdir.size - 1); | |
2c8c00c6 | 638 | |
22a2d3d5 UG |
639 | *matches = wildmatch(pattern.ptr, gitdir.ptr, |
640 | WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH; | |
eae0bfdc | 641 | out: |
e579e0f7 MB |
642 | git_str_dispose(&pattern); |
643 | git_str_dispose(&gitdir); | |
eae0bfdc | 644 | return error; |
2c8c00c6 ET |
645 | } |
646 | ||
eae0bfdc PP |
647 | static int conditional_match_gitdir( |
648 | int *matches, | |
649 | const git_repository *repo, | |
650 | const char *cfg_file, | |
651 | const char *value) | |
c0335005 | 652 | { |
eae0bfdc | 653 | return do_match_gitdir(matches, repo, cfg_file, value, false); |
c0335005 | 654 | } |
bf99390e | 655 | |
eae0bfdc PP |
656 | static int conditional_match_gitdir_i( |
657 | int *matches, | |
658 | const git_repository *repo, | |
659 | const char *cfg_file, | |
660 | const char *value) | |
bf99390e | 661 | { |
eae0bfdc PP |
662 | return do_match_gitdir(matches, repo, cfg_file, value, true); |
663 | } | |
bf99390e | 664 | |
22a2d3d5 UG |
665 | static int conditional_match_onbranch( |
666 | int *matches, | |
667 | const git_repository *repo, | |
668 | const char *cfg_file, | |
669 | const char *condition) | |
670 | { | |
e579e0f7 | 671 | git_str reference = GIT_STR_INIT, buf = GIT_STR_INIT; |
22a2d3d5 UG |
672 | int error; |
673 | ||
674 | GIT_UNUSED(cfg_file); | |
675 | ||
676 | /* | |
677 | * NOTE: you cannot use `git_repository_head` here. Looking up the | |
678 | * HEAD reference will create the ODB, which causes us to read the | |
679 | * repo's config for keys like core.precomposeUnicode. As we're | |
680 | * just parsing the config right now, though, this would result in | |
681 | * an endless recursion. | |
682 | */ | |
683 | ||
e579e0f7 | 684 | if ((error = git_str_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 || |
22a2d3d5 UG |
685 | (error = git_futils_readbuffer(&reference, buf.ptr)) < 0) |
686 | goto out; | |
e579e0f7 | 687 | git_str_rtrim(&reference); |
22a2d3d5 UG |
688 | |
689 | if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) | |
690 | goto out; | |
e579e0f7 | 691 | git_str_consume(&reference, reference.ptr + strlen(GIT_SYMREF)); |
22a2d3d5 UG |
692 | |
693 | if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR))) | |
694 | goto out; | |
e579e0f7 | 695 | git_str_consume(&reference, reference.ptr + strlen(GIT_REFS_HEADS_DIR)); |
22a2d3d5 UG |
696 | |
697 | /* | |
698 | * If the condition ends with a '/', then we should treat it as if | |
699 | * it had '**' appended. | |
700 | */ | |
e579e0f7 | 701 | if ((error = git_str_sets(&buf, condition)) < 0) |
22a2d3d5 | 702 | goto out; |
e579e0f7 MB |
703 | if (git_fs_path_is_dirsep(condition[strlen(condition) - 1]) && |
704 | (error = git_str_puts(&buf, "**")) < 0) | |
22a2d3d5 UG |
705 | goto out; |
706 | ||
707 | *matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH; | |
708 | out: | |
e579e0f7 MB |
709 | git_str_dispose(&reference); |
710 | git_str_dispose(&buf); | |
22a2d3d5 UG |
711 | |
712 | return error; | |
713 | ||
714 | } | |
715 | ||
eae0bfdc PP |
716 | static const struct { |
717 | const char *prefix; | |
718 | int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value); | |
719 | } conditions[] = { | |
720 | { "gitdir:", conditional_match_gitdir }, | |
22a2d3d5 UG |
721 | { "gitdir/i:", conditional_match_gitdir_i }, |
722 | { "onbranch:", conditional_match_onbranch } | |
eae0bfdc | 723 | }; |
bf99390e | 724 | |
22a2d3d5 | 725 | static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file) |
eae0bfdc PP |
726 | { |
727 | char *condition; | |
e579e0f7 | 728 | size_t section_len, i; |
eae0bfdc | 729 | int error = 0, matches; |
2a950c94 | 730 | |
6c7cee42 | 731 | if (!parse_data->repo || !file) |
eae0bfdc | 732 | return 0; |
bf99390e | 733 | |
e579e0f7 MB |
734 | section_len = strlen(section); |
735 | ||
736 | /* | |
737 | * We checked that the string starts with `includeIf.` and ends | |
738 | * in `.path` to get here. Make sure it consists of more. | |
739 | */ | |
740 | if (section_len < CONST_STRLEN("includeIf.") + CONST_STRLEN(".path")) | |
741 | return 0; | |
742 | ||
743 | condition = git__substrdup(section + CONST_STRLEN("includeIf."), | |
744 | section_len - CONST_STRLEN("includeIf.") - CONST_STRLEN(".path")); | |
745 | ||
746 | GIT_ERROR_CHECK_ALLOC(condition); | |
bf99390e | 747 | |
eae0bfdc PP |
748 | for (i = 0; i < ARRAY_SIZE(conditions); i++) { |
749 | if (git__prefixcmp(condition, conditions[i].prefix)) | |
750 | continue; | |
bf99390e | 751 | |
eae0bfdc PP |
752 | if ((error = conditions[i].matches(&matches, |
753 | parse_data->repo, | |
22a2d3d5 | 754 | parse_data->file->path, |
eae0bfdc | 755 | condition + strlen(conditions[i].prefix))) < 0) |
bf99390e ET |
756 | break; |
757 | ||
eae0bfdc | 758 | if (matches) |
22a2d3d5 | 759 | error = parse_include(parse_data, file); |
bf99390e | 760 | |
eae0bfdc | 761 | break; |
bf99390e ET |
762 | } |
763 | ||
eae0bfdc PP |
764 | git__free(condition); |
765 | return error; | |
bf99390e ET |
766 | } |
767 | ||
2a950c94 | 768 | static int read_on_variable( |
eae0bfdc | 769 | git_config_parser *reader, |
2a950c94 | 770 | const char *current_section, |
ac3d33df JK |
771 | const char *var_name, |
772 | const char *var_value, | |
2a950c94 ET |
773 | const char *line, |
774 | size_t line_len, | |
775 | void *data) | |
bf99390e | 776 | { |
22a2d3d5 | 777 | config_file_parse_data *parse_data = (config_file_parse_data *)data; |
e579e0f7 | 778 | git_str buf = GIT_STR_INIT; |
ac3d33df JK |
779 | git_config_entry *entry; |
780 | const char *c; | |
bf99390e ET |
781 | int result = 0; |
782 | ||
22a2d3d5 | 783 | GIT_UNUSED(reader); |
63c0cc65 ET |
784 | GIT_UNUSED(line); |
785 | GIT_UNUSED(line_len); | |
786 | ||
6c7cee42 RD |
787 | if (current_section) { |
788 | /* TODO: Once warnings lang, we should likely warn | |
789 | * here. Git appears to warn in most cases if it sees | |
790 | * un-namespaced config options. | |
791 | */ | |
e579e0f7 MB |
792 | git_str_puts(&buf, current_section); |
793 | git_str_putc(&buf, '.'); | |
6c7cee42 | 794 | } |
bf99390e | 795 | |
ac3d33df | 796 | for (c = var_name; *c; c++) |
e579e0f7 | 797 | git_str_putc(&buf, git__tolower(*c)); |
bf99390e | 798 | |
e579e0f7 | 799 | if (git_str_oom(&buf)) |
ac3d33df | 800 | return -1; |
bf99390e | 801 | |
ac3d33df JK |
802 | entry = git__calloc(1, sizeof(git_config_entry)); |
803 | GIT_ERROR_CHECK_ALLOC(entry); | |
e579e0f7 | 804 | entry->name = git_str_detach(&buf); |
ac3d33df JK |
805 | entry->value = var_value ? git__strdup(var_value) : NULL; |
806 | entry->level = parse_data->level; | |
807 | entry->include_depth = parse_data->depth; | |
bf99390e | 808 | |
ac3d33df | 809 | if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) |
bf99390e ET |
810 | return result; |
811 | ||
812 | result = 0; | |
813 | ||
814 | /* Add or append the new config option */ | |
ac3d33df | 815 | if (!git__strcmp(entry->name, "include.path")) |
22a2d3d5 | 816 | result = parse_include(parse_data, entry->value); |
ac3d33df JK |
817 | else if (!git__prefixcmp(entry->name, "includeif.") && |
818 | !git__suffixcmp(entry->name, ".path")) | |
22a2d3d5 | 819 | result = parse_conditional_include(parse_data, entry->name, entry->value); |
bf99390e ET |
820 | |
821 | return result; | |
822 | } | |
823 | ||
22a2d3d5 | 824 | static int config_file_read_buffer( |
ac3d33df | 825 | git_config_entries *entries, |
eae0bfdc | 826 | const git_repository *repo, |
22a2d3d5 | 827 | config_file *file, |
eae0bfdc | 828 | git_config_level_t level, |
22a2d3d5 UG |
829 | int depth, |
830 | const char *buf, | |
831 | size_t buflen) | |
bf99390e | 832 | { |
22a2d3d5 | 833 | config_file_parse_data parse_data; |
eae0bfdc | 834 | git_config_parser reader; |
eae0bfdc | 835 | int error; |
bf99390e ET |
836 | |
837 | if (depth >= MAX_INCLUDE_DEPTH) { | |
ac3d33df | 838 | git_error_set(GIT_ERROR_CONFIG, "maximum config include depth reached"); |
bf99390e ET |
839 | return -1; |
840 | } | |
841 | ||
842 | /* Initialize the reading position */ | |
22a2d3d5 UG |
843 | reader.path = file->path; |
844 | git_parse_ctx_init(&reader.ctx, buf, buflen); | |
bf99390e ET |
845 | |
846 | /* If the file is empty, there's nothing for us to do */ | |
22a2d3d5 UG |
847 | if (!reader.ctx.content || *reader.ctx.content == '\0') { |
848 | error = 0; | |
eae0bfdc | 849 | goto out; |
22a2d3d5 | 850 | } |
bf99390e | 851 | |
eae0bfdc | 852 | parse_data.repo = repo; |
22a2d3d5 | 853 | parse_data.file = file; |
ac3d33df | 854 | parse_data.entries = entries; |
bf99390e ET |
855 | parse_data.level = level; |
856 | parse_data.depth = depth; | |
857 | ||
eae0bfdc PP |
858 | error = git_config_parse(&reader, NULL, read_on_variable, NULL, NULL, &parse_data); |
859 | ||
22a2d3d5 UG |
860 | out: |
861 | return error; | |
862 | } | |
863 | ||
864 | static int config_file_read( | |
865 | git_config_entries *entries, | |
866 | const git_repository *repo, | |
867 | config_file *file, | |
868 | git_config_level_t level, | |
869 | int depth) | |
870 | { | |
e579e0f7 | 871 | git_str contents = GIT_STR_INIT; |
22a2d3d5 UG |
872 | struct stat st; |
873 | int error; | |
874 | ||
875 | if (p_stat(file->path, &st) < 0) { | |
e579e0f7 | 876 | error = git_fs_path_set_error(errno, file->path, "stat"); |
22a2d3d5 UG |
877 | goto out; |
878 | } | |
879 | ||
880 | if ((error = git_futils_readbuffer(&contents, file->path)) < 0) | |
881 | goto out; | |
882 | ||
883 | git_futils_filestamp_set_from_stat(&file->stamp, &st); | |
e579e0f7 | 884 | if ((error = git_hash_buf(file->checksum, contents.ptr, contents.size, GIT_HASH_ALGORITHM_SHA1)) < 0) |
22a2d3d5 UG |
885 | goto out; |
886 | ||
887 | if ((error = config_file_read_buffer(entries, repo, file, level, depth, | |
888 | contents.ptr, contents.size)) < 0) | |
889 | goto out; | |
890 | ||
eae0bfdc | 891 | out: |
e579e0f7 | 892 | git_str_dispose(&contents); |
eae0bfdc | 893 | return error; |
bf99390e ET |
894 | } |
895 | ||
e579e0f7 | 896 | static int write_section(git_str *fbuf, const char *key) |
bf99390e ET |
897 | { |
898 | int result; | |
899 | const char *dot; | |
e579e0f7 | 900 | git_str buf = GIT_STR_INIT; |
bf99390e ET |
901 | |
902 | /* All of this just for [section "subsection"] */ | |
903 | dot = strchr(key, '.'); | |
e579e0f7 | 904 | git_str_putc(&buf, '['); |
bf99390e | 905 | if (dot == NULL) { |
e579e0f7 | 906 | git_str_puts(&buf, key); |
bf99390e ET |
907 | } else { |
908 | char *escaped; | |
e579e0f7 | 909 | git_str_put(&buf, key, dot - key); |
bf99390e | 910 | escaped = escape_value(dot + 1); |
ac3d33df | 911 | GIT_ERROR_CHECK_ALLOC(escaped); |
e579e0f7 | 912 | git_str_printf(&buf, " \"%s\"", escaped); |
bf99390e ET |
913 | git__free(escaped); |
914 | } | |
e579e0f7 | 915 | git_str_puts(&buf, "]\n"); |
bf99390e | 916 | |
e579e0f7 | 917 | if (git_str_oom(&buf)) |
bf99390e ET |
918 | return -1; |
919 | ||
e579e0f7 MB |
920 | result = git_str_put(fbuf, git_str_cstr(&buf), buf.size); |
921 | git_str_dispose(&buf); | |
bf99390e ET |
922 | |
923 | return result; | |
924 | } | |
925 | ||
926 | static const char *quotes_for_value(const char *value) | |
927 | { | |
928 | const char *ptr; | |
929 | ||
930 | if (value[0] == ' ' || value[0] == '\0') | |
931 | return "\""; | |
932 | ||
933 | for (ptr = value; *ptr; ++ptr) { | |
934 | if (*ptr == ';' || *ptr == '#') | |
935 | return "\""; | |
936 | } | |
937 | ||
938 | if (ptr[-1] == ' ') | |
939 | return "\""; | |
940 | ||
941 | return ""; | |
942 | } | |
943 | ||
944 | struct write_data { | |
e579e0f7 MB |
945 | git_str *buf; |
946 | git_str buffered_comment; | |
bf99390e ET |
947 | unsigned int in_section : 1, |
948 | preg_replaced : 1; | |
eae0bfdc | 949 | const char *orig_section; |
bf99390e | 950 | const char *section; |
eae0bfdc | 951 | const char *orig_name; |
bf99390e | 952 | const char *name; |
22a2d3d5 | 953 | const git_regexp *preg; |
bf99390e ET |
954 | const char *value; |
955 | }; | |
956 | ||
e579e0f7 | 957 | static int write_line_to(git_str *buf, const char *line, size_t line_len) |
2a950c94 | 958 | { |
e579e0f7 | 959 | int result = git_str_put(buf, line, line_len); |
2a950c94 ET |
960 | |
961 | if (!result && line_len && line[line_len-1] != '\n') | |
e579e0f7 | 962 | result = git_str_printf(buf, "\n"); |
2a950c94 ET |
963 | |
964 | return result; | |
965 | } | |
966 | ||
cd677b8f CMN |
967 | static int write_line(struct write_data *write_data, const char *line, size_t line_len) |
968 | { | |
969 | return write_line_to(write_data->buf, line, line_len); | |
970 | } | |
971 | ||
bf99390e ET |
972 | static int write_value(struct write_data *write_data) |
973 | { | |
974 | const char *q; | |
975 | int result; | |
976 | ||
977 | q = quotes_for_value(write_data->value); | |
e579e0f7 | 978 | result = git_str_printf(write_data->buf, |
eae0bfdc | 979 | "\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q); |
bf99390e ET |
980 | |
981 | /* If we are updating a single name/value, we're done. Setting `value` | |
982 | * to `NULL` will prevent us from trying to write it again later (in | |
983 | * `write_on_section`) if we see the same section repeated. | |
984 | */ | |
985 | if (!write_data->preg) | |
986 | write_data->value = NULL; | |
987 | ||
988 | return result; | |
989 | } | |
990 | ||
2a950c94 | 991 | static int write_on_section( |
eae0bfdc | 992 | git_config_parser *reader, |
2a950c94 ET |
993 | const char *current_section, |
994 | const char *line, | |
995 | size_t line_len, | |
996 | void *data) | |
bf99390e ET |
997 | { |
998 | struct write_data *write_data = (struct write_data *)data; | |
999 | int result = 0; | |
1000 | ||
63c0cc65 ET |
1001 | GIT_UNUSED(reader); |
1002 | ||
bf99390e ET |
1003 | /* If we were previously in the correct section (but aren't anymore) |
1004 | * and haven't written our value (for a simple name/value set, not | |
1005 | * a multivar), then append it to the end of the section before writing | |
1006 | * the new one. | |
1007 | */ | |
1008 | if (write_data->in_section && !write_data->preg && write_data->value) | |
1009 | result = write_value(write_data); | |
1010 | ||
1011 | write_data->in_section = strcmp(current_section, write_data->section) == 0; | |
1012 | ||
cd677b8f CMN |
1013 | /* |
1014 | * If there were comments just before this section, dump them as well. | |
1015 | */ | |
1016 | if (!result) { | |
e579e0f7 MB |
1017 | result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size); |
1018 | git_str_clear(&write_data->buffered_comment); | |
cd677b8f CMN |
1019 | } |
1020 | ||
bf99390e | 1021 | if (!result) |
2a950c94 | 1022 | result = write_line(write_data, line, line_len); |
bf99390e ET |
1023 | |
1024 | return result; | |
1025 | } | |
1026 | ||
2a950c94 | 1027 | static int write_on_variable( |
eae0bfdc | 1028 | git_config_parser *reader, |
2a950c94 | 1029 | const char *current_section, |
ac3d33df JK |
1030 | const char *var_name, |
1031 | const char *var_value, | |
2a950c94 ET |
1032 | const char *line, |
1033 | size_t line_len, | |
1034 | void *data) | |
bf99390e ET |
1035 | { |
1036 | struct write_data *write_data = (struct write_data *)data; | |
1037 | bool has_matched = false; | |
cd677b8f | 1038 | int error; |
63c0cc65 ET |
1039 | |
1040 | GIT_UNUSED(reader); | |
1041 | GIT_UNUSED(current_section); | |
bf99390e | 1042 | |
cd677b8f CMN |
1043 | /* |
1044 | * If there were comments just before this variable, let's dump them as well. | |
1045 | */ | |
e579e0f7 | 1046 | if ((error = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) |
cd677b8f CMN |
1047 | return error; |
1048 | ||
e579e0f7 | 1049 | git_str_clear(&write_data->buffered_comment); |
cd677b8f | 1050 | |
bf99390e ET |
1051 | /* See if we are to update this name/value pair; first examine name */ |
1052 | if (write_data->in_section && | |
1053 | strcasecmp(write_data->name, var_name) == 0) | |
1054 | has_matched = true; | |
1055 | ||
1056 | /* If we have a regex to match the value, see if it matches */ | |
1057 | if (has_matched && write_data->preg != NULL) | |
22a2d3d5 | 1058 | has_matched = (git_regexp_match(write_data->preg, var_value) == 0); |
bf99390e | 1059 | |
bf99390e ET |
1060 | /* If this isn't the name/value we're looking for, simply dump the |
1061 | * existing data back out and continue on. | |
1062 | */ | |
2a950c94 ET |
1063 | if (!has_matched) |
1064 | return write_line(write_data, line, line_len); | |
bf99390e ET |
1065 | |
1066 | write_data->preg_replaced = 1; | |
1067 | ||
1068 | /* If value is NULL, we are deleting this value; write nothing. */ | |
1069 | if (!write_data->value) | |
1070 | return 0; | |
1071 | ||
1072 | return write_value(write_data); | |
1073 | } | |
1074 | ||
eae0bfdc | 1075 | static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data) |
2a950c94 | 1076 | { |
63c0cc65 ET |
1077 | struct write_data *write_data; |
1078 | ||
1079 | GIT_UNUSED(reader); | |
1080 | ||
1081 | write_data = (struct write_data *)data; | |
cd677b8f | 1082 | return write_line_to(&write_data->buffered_comment, line, line_len); |
2a950c94 ET |
1083 | } |
1084 | ||
e25e1ca1 | 1085 | static int write_on_eof( |
eae0bfdc | 1086 | git_config_parser *reader, const char *current_section, void *data) |
bf99390e ET |
1087 | { |
1088 | struct write_data *write_data = (struct write_data *)data; | |
1089 | int result = 0; | |
1090 | ||
63c0cc65 ET |
1091 | GIT_UNUSED(reader); |
1092 | ||
cd677b8f CMN |
1093 | /* |
1094 | * If we've buffered comments when reaching EOF, make sure to dump them. | |
1095 | */ | |
e579e0f7 | 1096 | if ((result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) |
cd677b8f CMN |
1097 | return result; |
1098 | ||
bf99390e ET |
1099 | /* If we are at the EOF and have not written our value (again, for a |
1100 | * simple name/value set, not a multivar) then we have never seen the | |
1101 | * section in question and should create a new section and write the | |
1102 | * value. | |
1103 | */ | |
1104 | if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) { | |
e25e1ca1 ET |
1105 | /* write the section header unless we're already in it */ |
1106 | if (!current_section || strcmp(current_section, write_data->section)) | |
eae0bfdc | 1107 | result = write_section(write_data->buf, write_data->orig_section); |
e25e1ca1 ET |
1108 | |
1109 | if (!result) | |
bf99390e ET |
1110 | result = write_value(write_data); |
1111 | } | |
1112 | ||
1113 | return result; | |
1114 | } | |
1115 | ||
1116 | /* | |
1117 | * This is pretty much the parsing, except we write out anything we don't have | |
1118 | */ | |
c25aa7cd | 1119 | static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value) |
22a2d3d5 | 1120 | |
bf99390e | 1121 | { |
22a2d3d5 | 1122 | char *orig_section = NULL, *section = NULL, *orig_name, *name, *ldot; |
e579e0f7 | 1123 | git_str buf = GIT_STR_INIT, contents = GIT_STR_INIT; |
22a2d3d5 UG |
1124 | git_config_parser parser = GIT_CONFIG_PARSER_INIT; |
1125 | git_filebuf file = GIT_FILEBUF_INIT; | |
bf99390e | 1126 | struct write_data write_data; |
22a2d3d5 | 1127 | int error; |
bf99390e | 1128 | |
22a2d3d5 | 1129 | memset(&write_data, 0, sizeof(write_data)); |
eae0bfdc | 1130 | |
b1667039 | 1131 | if (cfg->locked) { |
e579e0f7 | 1132 | error = git_str_puts(&contents, git_str_cstr(&cfg->locked_content) == NULL ? "" : git_str_cstr(&cfg->locked_content)); |
b1667039 | 1133 | } else { |
22a2d3d5 UG |
1134 | if ((error = git_filebuf_open(&file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS, |
1135 | GIT_CONFIG_FILE_MODE)) < 0) | |
1136 | goto done; | |
bf99390e | 1137 | |
b1667039 | 1138 | /* We need to read in our own config file */ |
22a2d3d5 | 1139 | error = git_futils_readbuffer(&contents, cfg->file.path); |
b1667039 | 1140 | } |
22a2d3d5 UG |
1141 | if (error < 0 && error != GIT_ENOTFOUND) |
1142 | goto done; | |
bf99390e | 1143 | |
22a2d3d5 UG |
1144 | if ((git_config_parser_init(&parser, cfg->file.path, contents.ptr, contents.size)) < 0) |
1145 | goto done; | |
bf99390e | 1146 | |
bf99390e ET |
1147 | ldot = strrchr(key, '.'); |
1148 | name = ldot + 1; | |
1149 | section = git__strndup(key, ldot - key); | |
ac3d33df | 1150 | GIT_ERROR_CHECK_ALLOC(section); |
eae0bfdc PP |
1151 | |
1152 | ldot = strrchr(orig_key, '.'); | |
1153 | orig_name = ldot + 1; | |
1154 | orig_section = git__strndup(orig_key, ldot - orig_key); | |
ac3d33df | 1155 | GIT_ERROR_CHECK_ALLOC(orig_section); |
bf99390e | 1156 | |
3ce9e4d2 | 1157 | write_data.buf = &buf; |
eae0bfdc | 1158 | write_data.orig_section = orig_section; |
bf99390e | 1159 | write_data.section = section; |
eae0bfdc | 1160 | write_data.orig_name = orig_name; |
bf99390e ET |
1161 | write_data.name = name; |
1162 | write_data.preg = preg; | |
1163 | write_data.value = value; | |
1164 | ||
22a2d3d5 UG |
1165 | if ((error = git_config_parse(&parser, write_on_section, write_on_variable, |
1166 | write_on_comment, write_on_eof, &write_data)) < 0) | |
bf99390e | 1167 | goto done; |
bf99390e | 1168 | |
b1667039 CMN |
1169 | if (cfg->locked) { |
1170 | size_t len = buf.asize; | |
1171 | /* Update our copy with the modified contents */ | |
e579e0f7 MB |
1172 | git_str_dispose(&cfg->locked_content); |
1173 | git_str_attach(&cfg->locked_content, git_str_detach(&buf), len); | |
b1667039 | 1174 | } else { |
e579e0f7 | 1175 | git_filebuf_write(&file, git_str_cstr(&buf), git_str_len(&buf)); |
22a2d3d5 UG |
1176 | |
1177 | if ((error = git_filebuf_commit(&file)) < 0) | |
1178 | goto done; | |
1179 | ||
1180 | if ((error = config_file_refresh_from_buffer(&cfg->parent, buf.ptr, buf.size)) < 0) | |
1181 | goto done; | |
b1667039 | 1182 | } |
bf99390e ET |
1183 | |
1184 | done: | |
22a2d3d5 UG |
1185 | git__free(section); |
1186 | git__free(orig_section); | |
e579e0f7 MB |
1187 | git_str_dispose(&write_data.buffered_comment); |
1188 | git_str_dispose(&buf); | |
1189 | git_str_dispose(&contents); | |
22a2d3d5 UG |
1190 | git_filebuf_cleanup(&file); |
1191 | git_config_parser_dispose(&parser); | |
1192 | ||
1193 | return error; | |
bf99390e | 1194 | } |