config_file.c 29.8 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7 8
 */

#include "config.h"
9

10
#include "git2/config.h"
11
#include "git2/sys/config.h"
12

13
#include "array.h"
14 15
#include "buffer.h"
#include "config_backend.h"
16
#include "config_entries.h"
17 18
#include "config_parse.h"
#include "filebuf.h"
19
#include "regexp.h"
20
#include "sysdir.h"
21
#include "wildmatch.h"
22

23 24
/* Max depth for [include] directives */
#define MAX_INCLUDE_DEPTH 10
25

26
typedef struct config_file {
27 28 29
	git_futils_filestamp stamp;
	git_oid checksum;
	char *path;
30 31
	git_array_t(struct config_file) includes;
} config_file;
32

33
typedef struct {
34 35
	git_config_backend parent;
	git_mutex values_mutex;
36
	git_config_entries *entries;
37
	const git_repository *repo;
38
	git_config_level_t level;
39

40 41
	git_array_t(git_config_parser) readers;

42 43 44 45
	bool locked;
	git_filebuf locked_buf;
	git_buf locked_content;

46 47
	config_file file;
} config_file_backend;
48

49
typedef struct {
50
	const git_repository *repo;
51
	config_file *file;
52
	git_config_entries *entries;
53 54
	git_config_level_t level;
	unsigned int depth;
55
} config_file_parse_data;
56

57 58 59
static int config_file_read(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth);
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);
static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value);
60
static char *escape_value(const char *ptr);
61

62 63 64 65 66
/**
 * Take the current values map from the backend and increase its
 * refcount. This is its own function to make sure we use the mutex to
 * avoid the map pointer from changing under us.
 */
67
static int config_file_entries_take(git_config_entries **out, config_file_backend *b)
68
{
69
	int error;
70

71 72 73
	if ((error = git_mutex_lock(&b->values_mutex)) < 0) {
		git_error_set(GIT_ERROR_OS, "failed to lock config backend");
		return error;
74
	}
75

76 77
	git_config_entries_incref(b->entries);
	*out = b->entries;
78

79
	git_mutex_unlock(&b->values_mutex);
80

81
	return 0;
82 83
}

84
static void config_file_clear(config_file *file)
85
{
86
	config_file *include;
87 88 89 90 91 92 93 94 95 96 97 98 99
	uint32_t i;

	if (file == NULL)
		return;

	git_array_foreach(file->includes, i, include) {
		config_file_clear(include);
	}
	git_array_clear(file->includes);

	git__free(file->path);
}

100
static int config_file_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo)
101
{
102
	config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
103
	int res;
104

105 106
	b->level = level;
	b->repo = repo;
107

108
	if ((res = git_config_entries_new(&b->entries)) < 0)
109
		return res;
110

111
	if (!git_path_exists(b->file.path))
112 113
		return 0;

114 115 116 117 118 119 120 121 122
	/*
	 * git silently ignores configuration files that are not
	 * readable.  We emulate that behavior.  This is particularly
	 * important for sandboxed applications on macOS where the
	 * git configuration files may not be readable.
	 */
	if (p_access(b->file.path, R_OK) < 0)
		return GIT_ENOTFOUND;

123
	if (res < 0 || (res = config_file_read(b->entries, repo, &b->file, level, 0)) < 0) {
124 125
		git_config_entries_free(b->entries);
		b->entries = NULL;
126
	}
127

128 129 130
	return res;
}

131
static int config_file_is_modified(int *modified, config_file *file)
132
{
133
	config_file *include;
134 135 136
	git_buf buf = GIT_BUF_INIT;
	git_oid hash;
	uint32_t i;
137
	int error = 0;
138

139 140
	*modified = 0;

141 142 143
	if (!git_futils_filestamp_check(&file->stamp, file->path))
		goto check_includes;

144
	if ((error = git_futils_readbuffer(&buf, file->path)) < 0)
145
		goto out;
146

147
	if ((error = git_hash_buf(&hash, buf.ptr, buf.size)) < 0)
148
		goto out;
149

150 151 152 153 154
	if (!git_oid_equal(&hash, &file->checksum)) {
		*modified = 1;
		goto out;
	}

155
check_includes:
156
	git_array_foreach(file->includes, i, include) {
157
		if ((error = config_file_is_modified(modified, include)) < 0 || *modified)
158 159
			goto out;
	}
160 161

out:
162
	git_buf_dispose(&buf);
163

164 165 166
	return error;
}

167
static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries)
168
{
169
	config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
170
	git_config_entries *old = NULL;
171
	config_file *include;
172
	int error;
173
	uint32_t i;
174

175 176 177 178
	if (b->parent.readonly) {
		git_error_set(GIT_ERROR_CONFIG, "this backend is read-only");
		return -1;
	}
179

180
	git_array_foreach(b->file.includes, i, include)
181 182
		config_file_clear(include);
	git_array_clear(b->file.includes);
183

184
	if ((error = git_mutex_lock(&b->values_mutex)) < 0) {
185
		git_error_set(GIT_ERROR_OS, "failed to lock config backend");
186 187
		goto out;
	}
188

189 190
	old = b->entries;
	b->entries = entries;
191

192
	git_mutex_unlock(&b->values_mutex);
193

194
out:
195 196 197 198
	git_config_entries_free(old);
	return error;
}

199
static int config_file_refresh_from_buffer(git_config_backend *cfg, const char *buf, size_t buflen)
200
{
201
	config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
202 203 204 205
	git_config_entries *entries = NULL;
	int error;

	if ((error = git_config_entries_new(&entries)) < 0 ||
206 207 208
	    (error = config_file_read_buffer(entries, b->repo, &b->file,
					     b->level, 0, buf, buflen)) < 0 ||
	    (error = config_file_set_entries(cfg, entries)) < 0)
209 210 211 212 213 214 215 216
		goto out;

	entries = NULL;
out:
	git_config_entries_free(entries);
	return error;
}

217
static int config_file_refresh(git_config_backend *cfg)
218
{
219
	config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
220 221 222
	git_config_entries *entries = NULL;
	int error, modified;

223 224 225
	if (cfg->readonly)
		return 0;

226
	if ((error = config_file_is_modified(&modified, &b->file)) < 0 && error != GIT_ENOTFOUND)
227 228 229 230 231 232
		goto out;

	if (!modified)
		return 0;

	if ((error = git_config_entries_new(&entries)) < 0 ||
233 234
	    (error = config_file_read(entries, b->repo, &b->file, b->level, 0)) < 0 ||
	    (error = config_file_set_entries(cfg, entries)) < 0)
235 236 237 238
		goto out;

	entries = NULL;
out:
239
	git_config_entries_free(entries);
240

241
	return (error == GIT_ENOTFOUND) ? 0 : error;
242 243
}

244
static void config_file_free(git_config_backend *_backend)
245
{
246
	config_file_backend *backend = GIT_CONTAINER_OF(_backend, config_file_backend, parent);
247 248 249 250

	if (backend == NULL)
		return;

251
	config_file_clear(&backend->file);
252 253
	git_config_entries_free(backend->entries);
	git_mutex_free(&backend->values_mutex);
254
	git__free(backend);
255 256
}

257
static int config_file_iterator(
258
	git_config_iterator **iter,
259
	struct git_config_backend *backend)
260
{
261
	config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent);
262
	git_config_entries *dupped = NULL, *entries = NULL;
263
	int error;
264

265
	if ((error = config_file_refresh(backend)) < 0 ||
266 267 268
	    (error = config_file_entries_take(&entries, b)) < 0 ||
	    (error = git_config_entries_dup(&dupped, entries)) < 0 ||
	    (error = git_config_entries_iterator_new(iter, dupped)) < 0)
269
		goto out;
270

271 272 273
out:
	/* Let iterator delete duplicated entries when it's done */
	git_config_entries_free(entries);
274
	git_config_entries_free(dupped);
275
	return error;
276 277
}

278 279 280 281 282 283
static int config_file_snapshot(git_config_backend **out, git_config_backend *backend)
{
	return git_config_backend_snapshot(out, backend);
}

static int config_file_set(git_config_backend *cfg, const char *name, const char *value)
284
{
285
	config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
286
	git_config_entries *entries;
287
	git_config_entry *existing;
288
	char *key, *esc_value = NULL;
289
	int error;
290

291 292
	if ((error = git_config__normalize_name(name, &key)) < 0)
		return error;
293

294 295
	if ((error = config_file_entries_take(&entries, b)) < 0)
		return error;
296

297 298 299
	/* Check whether we'd be modifying an included or multivar key */
	if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) {
		if (error != GIT_ENOTFOUND)
300
			goto out;
301 302 303
		error = 0;
	} else if ((!existing->value && !value) ||
		   (existing->value && value && !strcmp(existing->value, value))) {
Russell Belfer committed
304
		/* don't update if old and new values already match */
305 306
		error = 0;
		goto out;
307 308
	}

309
	/* No early returns due to sanity checks, let's write it out and refresh */
310
	if (value) {
311
		esc_value = escape_value(value);
312
		GIT_ERROR_CHECK_ALLOC(esc_value);
313 314
	}

315
	if ((error = config_file_write(b, name, key, NULL, esc_value)) < 0)
316
		goto out;
317

318
out:
319
	git_config_entries_free(entries);
320 321
	git__free(esc_value);
	git__free(key);
322
	return error;
323 324
}

325
/* release the map containing the entry as an equivalent to freeing it */
326
static void config_file_entry_free(git_config_entry *entry)
327
{
328 329
	git_config_entries *entries = (git_config_entries *) entry->payload;
	git_config_entries_free(entries);
330 331
}

332 333 334
/*
 * Internal function that actually gets the value in string form
 */
335
static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out)
336
{
337
	config_file_backend *h = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
338 339
	git_config_entries *entries = NULL;
	git_config_entry *entry;
340
	int error = 0;
341

342
	if (!h->parent.readonly && ((error = config_file_refresh(cfg)) < 0))
343 344
		return error;

345 346
	if ((error = config_file_entries_take(&entries, h)) < 0)
		return error;
347

348
	if ((error = (git_config_entries_get(&entry, entries, key))) < 0) {
349
		git_config_entries_free(entries);
350
		return error;
351
	}
352

353
	entry->free = config_file_entry_free;
354 355
	entry->payload = entries;
	*out = entry;
356

357
	return 0;
358 359
}

360
static int config_file_set_multivar(
Ben Straub committed
361
	git_config_backend *cfg, const char *name, const char *regexp, const char *value)
362
{
363
	config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
364
	git_regexp preg;
365
	int result;
366
	char *key;
367

368
	GIT_ASSERT_ARG(regexp);
369

370
	if ((result = git_config__normalize_name(name, &key)) < 0)
371
		return result;
372

373
	if ((result = git_regexp_compile(&preg, regexp, 0)) < 0)
374
		goto out;
375

376 377
	/* If we do have it, set call config_file_write() and reload */
	if ((result = config_file_write(b, name, key, &preg, value)) < 0)
378
		goto out;
379

380
out:
381
	git__free(key);
382
	git_regexp_dispose(&preg);
383 384

	return result;
385 386
}

387
static int config_file_delete(git_config_backend *cfg, const char *name)
388
{
389
	config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
390 391 392 393
	git_config_entries *entries = NULL;
	git_config_entry *entry;
	char *key = NULL;
	int error;
394

395 396
	if ((error = git_config__normalize_name(name, &key)) < 0)
		goto out;
397

398
	if ((error = config_file_entries_take(&entries, b)) < 0)
399
		goto out;
400

401 402 403
	/* Check whether we'd be modifying an included or multivar key */
	if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) {
		if (error == GIT_ENOTFOUND)
404
			git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name);
405
		goto out;
406 407
	}

408
	if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0)
409
		goto out;
410

411 412 413 414
out:
	git_config_entries_free(entries);
	git__free(key);
	return error;
415 416
}

417
static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
418
{
419
	config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
420 421
	git_config_entries *entries = NULL;
	git_config_entry *entry = NULL;
422
	git_regexp preg = GIT_REGEX_INIT;
423
	char *key = NULL;
424 425 426
	int result;

	if ((result = git_config__normalize_name(name, &key)) < 0)
427
		goto out;
428

429
	if ((result = config_file_entries_take(&entries, b)) < 0)
430
		goto out;
431

432 433
	if ((result = git_config_entries_get(&entry, entries, key)) < 0) {
		if (result == GIT_ENOTFOUND)
434
			git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name);
435 436
		goto out;
	}
437

438
	if ((result = git_regexp_compile(&preg, regexp, 0)) < 0)
439
		goto out;
440

441
	if ((result = config_file_write(b, name, key, &preg, NULL)) < 0)
442
		goto out;
443

444
out:
445
	git_config_entries_free(entries);
446
	git__free(key);
447
	git_regexp_dispose(&preg);
448 449 450
	return result;
}

451
static int config_file_lock(git_config_backend *_cfg)
452
{
453
	config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent);
454 455
	int error;

456
	if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0)
457 458
		return error;

459
	error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path);
460 461 462 463 464 465 466 467 468 469
	if (error < 0 && error != GIT_ENOTFOUND) {
		git_filebuf_cleanup(&cfg->locked_buf);
		return error;
	}

	cfg->locked = true;
	return 0;

}

470
static int config_file_unlock(git_config_backend *_cfg, int success)
471
{
472
	config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent);
473 474 475 476 477 478 479 480
	int error = 0;

	if (success) {
		git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size);
		error = git_filebuf_commit(&cfg->locked_buf);
	}

	git_filebuf_cleanup(&cfg->locked_buf);
481
	git_buf_dispose(&cfg->locked_content);
482 483 484 485 486
	cfg->locked = false;

	return error;
}

487
int git_config_backend_from_file(git_config_backend **out, const char *path)
488
{
489
	config_file_backend *backend;
490

491
	backend = git__calloc(1, sizeof(config_file_backend));
492
	GIT_ERROR_CHECK_ALLOC(backend);
493

494 495
	backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
	git_mutex_init(&backend->values_mutex);
496

497
	backend->file.path = git__strdup(path);
498
	GIT_ERROR_CHECK_ALLOC(backend->file.path);
499
	git_array_init(backend->file.includes);
500

501 502 503 504 505 506 507 508 509 510 511
	backend->parent.open = config_file_open;
	backend->parent.get = config_file_get;
	backend->parent.set = config_file_set;
	backend->parent.set_multivar = config_file_set_multivar;
	backend->parent.del = config_file_delete;
	backend->parent.del_multivar = config_file_delete_multivar;
	backend->parent.iterator = config_file_iterator;
	backend->parent.snapshot = config_file_snapshot;
	backend->parent.lock = config_file_lock;
	backend->parent.unlock = config_file_unlock;
	backend->parent.free = config_file_free;
512 513 514 515 516 517

	*out = (git_config_backend *)backend;

	return 0;
}

518 519 520 521
static int included_path(git_buf *out, const char *dir, const char *path)
{
	/* From the user's home */
	if (path[0] == '~' && path[1] == '/')
522
		return git_sysdir_expand_global_file(out, &path[1]);
523 524 525 526

	return git_path_join_unrooted(out, path, dir, NULL);
}

527 528 529
/* Escape the values to write them to the file */
static char *escape_value(const char *ptr)
{
530
	git_buf buf;
531 532 533
	size_t len;
	const char *esc;

534
	GIT_ASSERT_ARG_WITH_RETVAL(ptr, NULL);
535 536

	len = strlen(ptr);
537 538 539
	if (!len)
		return git__calloc(1, sizeof(char));

540 541
	if (git_buf_init(&buf, len) < 0)
		return NULL;
542 543

	while (*ptr != '\0') {
544
		if ((esc = strchr(git_config_escaped, *ptr)) != NULL) {
545
			git_buf_putc(&buf, '\\');
546
			git_buf_putc(&buf, git_config_escapes[esc - git_config_escaped]);
547 548 549 550 551 552
		} else {
			git_buf_putc(&buf, *ptr);
		}
		ptr++;
	}

553
	if (git_buf_oom(&buf))
554 555 556 557 558
		return NULL;

	return git_buf_detach(&buf);
}

559
static int parse_include(config_file_parse_data *parse_data, const char *file)
560
{
561
	config_file *include;
562 563 564 565
	git_buf path = GIT_BUF_INIT;
	char *dir;
	int result;

566 567 568
	if (!file)
		return 0;

569
	if ((result = git_path_dirname_r(&path, parse_data->file->path)) < 0)
570 571 572 573 574 575 576 577 578
		return result;

	dir = git_buf_detach(&path);
	result = included_path(&path, dir, file);
	git__free(dir);

	if (result < 0)
		return result;

579
	include = git_array_alloc(parse_data->file->includes);
580
	GIT_ERROR_CHECK_ALLOC(include);
581 582 583 584
	memset(include, 0, sizeof(*include));
	git_array_init(include->includes);
	include->path = git_buf_detach(&path);

585 586
	result = config_file_read(parse_data->entries, parse_data->repo, include,
				  parse_data->level, parse_data->depth+1);
587 588

	if (result == GIT_ENOTFOUND) {
589
		git_error_clear();
590 591 592 593 594 595
		result = 0;
	}

	return result;
}

596
static int do_match_gitdir(
597 598 599
	int *matches,
	const git_repository *repo,
	const char *cfg_file,
600
	const char *condition,
601
	bool case_insensitive)
602
{
603
	git_buf pattern = GIT_BUF_INIT, gitdir = GIT_BUF_INIT;
604
	int error;
605

606 607 608 609 610 611 612
	if (condition[0] == '.' && git_path_is_dirsep(condition[1])) {
		git_path_dirname_r(&pattern, cfg_file);
		git_buf_joinpath(&pattern, pattern.ptr, condition + 2);
	} else if (condition[0] == '~' && git_path_is_dirsep(condition[1]))
		git_sysdir_expand_global_file(&pattern, condition + 1);
	else if (!git_path_is_absolute(condition))
		git_buf_joinpath(&pattern, "**", condition);
613
	else
614 615 616 617
		git_buf_sets(&pattern, condition);

	if (git_path_is_dirsep(condition[strlen(condition) - 1]))
		git_buf_puts(&pattern, "**");
618

619
	if (git_buf_oom(&pattern)) {
620 621 622 623
		error = -1;
		goto out;
	}

624 625 626 627 628 629
	if ((error = git_repository_item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0)
		goto out;

	if (git_path_is_dirsep(gitdir.ptr[gitdir.size - 1]))
		git_buf_truncate(&gitdir, gitdir.size - 1);

630 631
	*matches = wildmatch(pattern.ptr, gitdir.ptr,
			     WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH;
632
out:
633
	git_buf_dispose(&pattern);
634
	git_buf_dispose(&gitdir);
635 636 637
	return error;
}

638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
static int conditional_match_gitdir(
	int *matches,
	const git_repository *repo,
	const char *cfg_file,
	const char *value)
{
	return do_match_gitdir(matches, repo, cfg_file, value, false);
}

static int conditional_match_gitdir_i(
	int *matches,
	const git_repository *repo,
	const char *cfg_file,
	const char *value)
{
	return do_match_gitdir(matches, repo, cfg_file, value, true);
}

656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
static int conditional_match_onbranch(
	int *matches,
	const git_repository *repo,
	const char *cfg_file,
	const char *condition)
{
	git_buf reference = GIT_BUF_INIT, buf = GIT_BUF_INIT;
	int error;

	GIT_UNUSED(cfg_file);

	/*
	 * NOTE: you cannot use `git_repository_head` here. Looking up the
	 * HEAD reference will create the ODB, which causes us to read the
	 * repo's config for keys like core.precomposeUnicode. As we're
	 * just parsing the config right now, though, this would result in
	 * an endless recursion.
	 */

	if ((error = git_buf_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 ||
	    (error = git_futils_readbuffer(&reference, buf.ptr)) < 0)
		goto out;
	git_buf_rtrim(&reference);

	if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF)))
		goto out;
	git_buf_consume(&reference, reference.ptr + strlen(GIT_SYMREF));

	if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR)))
		goto out;
	git_buf_consume(&reference, reference.ptr + strlen(GIT_REFS_HEADS_DIR));

	/*
	 * If the condition ends with a '/', then we should treat it as if
	 * it had '**' appended.
	 */
	if ((error = git_buf_sets(&buf, condition)) < 0)
		goto out;
	if (git_path_is_dirsep(condition[strlen(condition) - 1]) &&
	    (error = git_buf_puts(&buf, "**")) < 0)
		goto out;

	*matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH;
out:
	git_buf_dispose(&reference);
	git_buf_dispose(&buf);

	return error;

}

707 708 709 710
static const struct {
	const char *prefix;
	int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value);
} conditions[] = {
711
	{ "gitdir:", conditional_match_gitdir },
712 713
	{ "gitdir/i:", conditional_match_gitdir_i },
	{ "onbranch:", conditional_match_onbranch }
714 715
};

716
static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file)
717 718 719 720 721
{
	char *condition;
	size_t i;
	int error = 0, matches;

722
	if (!parse_data->repo || !file)
723 724 725 726 727 728 729 730 731 732 733
		return 0;

	condition = git__substrdup(section + strlen("includeIf."),
				   strlen(section) - strlen("includeIf.") - strlen(".path"));

	for (i = 0; i < ARRAY_SIZE(conditions); i++) {
		if (git__prefixcmp(condition, conditions[i].prefix))
			continue;

		if ((error = conditions[i].matches(&matches,
						   parse_data->repo,
734
						   parse_data->file->path,
735 736 737 738
						   condition + strlen(conditions[i].prefix))) < 0)
			break;

		if (matches)
739
			error = parse_include(parse_data, file);
740 741 742 743 744 745 746 747

		break;
	}

	git__free(condition);
	return error;
}

748
static int read_on_variable(
749
	git_config_parser *reader,
750
	const char *current_section,
751 752
	const char *var_name,
	const char *var_value,
753 754 755
	const char *line,
	size_t line_len,
	void *data)
756
{
757
	config_file_parse_data *parse_data = (config_file_parse_data *)data;
758
	git_buf buf = GIT_BUF_INIT;
759
	git_config_entry *entry;
760
	const char *c;
761 762
	int result = 0;

763
	GIT_UNUSED(reader);
764 765 766
	GIT_UNUSED(line);
	GIT_UNUSED(line_len);

767
	if (current_section) {
Nelson Elhage committed
768 769 770 771
		/* TODO: Once warnings lang, we should likely warn
		 * here. Git appears to warn in most cases if it sees
		 * un-namespaced config options.
		 */
772 773
		git_buf_puts(&buf, current_section);
		git_buf_putc(&buf, '.');
774
	}
775

776 777
	for (c = var_name; *c; c++)
		git_buf_putc(&buf, git__tolower(*c));
778

779
	if (git_buf_oom(&buf))
780 781
		return -1;

782
	entry = git__calloc(1, sizeof(git_config_entry));
783
	GIT_ERROR_CHECK_ALLOC(entry);
784
	entry->name = git_buf_detach(&buf);
785
	entry->value = var_value ? git__strdup(var_value) : NULL;
786
	entry->level = parse_data->level;
787
	entry->include_depth = parse_data->depth;
788

789
	if ((result = git_config_entries_append(parse_data->entries, entry)) < 0)
790 791 792 793 794
		return result;

	result = 0;

	/* Add or append the new config option */
795
	if (!git__strcmp(entry->name, "include.path"))
796
		result = parse_include(parse_data, entry->value);
797 798
	else if (!git__prefixcmp(entry->name, "includeif.") &&
	         !git__suffixcmp(entry->name, ".path"))
799
		result = parse_conditional_include(parse_data, entry->name, entry->value);
800

801 802 803
	return result;
}

804
static int config_file_read_buffer(
805
	git_config_entries *entries,
806
	const git_repository *repo,
807
	config_file *file,
808
	git_config_level_t level,
809 810 811
	int depth,
	const char *buf,
	size_t buflen)
812
{
813
	config_file_parse_data parse_data;
814
	git_config_parser reader;
815
	int error;
816 817

	if (depth >= MAX_INCLUDE_DEPTH) {
818
		git_error_set(GIT_ERROR_CONFIG, "maximum config include depth reached");
819 820 821 822
		return -1;
	}

	/* Initialize the reading position */
823
	reader.path = file->path;
824
	git_parse_ctx_init(&reader.ctx, buf, buflen);
825 826

	/* If the file is empty, there's nothing for us to do */
827 828
	if (!reader.ctx.content || *reader.ctx.content == '\0') {
		error = 0;
829
		goto out;
830
	}
831

832
	parse_data.repo = repo;
833
	parse_data.file = file;
834
	parse_data.entries = entries;
835 836 837
	parse_data.level = level;
	parse_data.depth = depth;

838
	error = git_config_parse(&reader, NULL, read_on_variable, NULL, NULL, &parse_data);
839 840

out:
841 842 843
	return error;
}

844
static int config_file_read(
845 846
	git_config_entries *entries,
	const git_repository *repo,
847
	config_file *file,
848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
	git_config_level_t level,
	int depth)
{
	git_buf contents = GIT_BUF_INIT;
	struct stat st;
	int error;

	if (p_stat(file->path, &st) < 0) {
		error = git_path_set_error(errno, file->path, "stat");
		goto out;
	}

	if ((error = git_futils_readbuffer(&contents, file->path)) < 0)
		goto out;

	git_futils_filestamp_set_from_stat(&file->stamp, &st);
	if ((error = git_hash_buf(&file->checksum, contents.ptr, contents.size)) < 0)
		goto out;

867 868
	if ((error = config_file_read_buffer(entries, repo, file, level, depth,
					     contents.ptr, contents.size)) < 0)
869 870 871
		goto out;

out:
872
	git_buf_dispose(&contents);
873
	return error;
874 875
}

876
static int write_section(git_buf *fbuf, const char *key)
877 878 879 880 881 882 883 884 885 886 887 888 889 890
{
	int result;
	const char *dot;
	git_buf buf = GIT_BUF_INIT;

	/* All of this just for [section "subsection"] */
	dot = strchr(key, '.');
	git_buf_putc(&buf, '[');
	if (dot == NULL) {
		git_buf_puts(&buf, key);
	} else {
		char *escaped;
		git_buf_put(&buf, key, dot - key);
		escaped = escape_value(dot + 1);
891
		GIT_ERROR_CHECK_ALLOC(escaped);
892 893 894 895 896 897 898 899
		git_buf_printf(&buf, " \"%s\"", escaped);
		git__free(escaped);
	}
	git_buf_puts(&buf, "]\n");

	if (git_buf_oom(&buf))
		return -1;

900
	result = git_buf_put(fbuf, git_buf_cstr(&buf), buf.size);
901
	git_buf_dispose(&buf);
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924

	return result;
}

static const char *quotes_for_value(const char *value)
{
	const char *ptr;

	if (value[0] == ' ' || value[0] == '\0')
		return "\"";

	for (ptr = value; *ptr; ++ptr) {
		if (*ptr == ';' || *ptr == '#')
			return "\"";
	}

	if (ptr[-1] == ' ')
		return "\"";

	return "";
}

struct write_data {
925
	git_buf *buf;
926
	git_buf buffered_comment;
927 928
	unsigned int in_section : 1,
		preg_replaced : 1;
929
	const char *orig_section;
930
	const char *section;
931
	const char *orig_name;
932
	const char *name;
933
	const git_regexp *preg;
934 935 936
	const char *value;
};

937
static int write_line_to(git_buf *buf, const char *line, size_t line_len)
938
{
939
	int result = git_buf_put(buf, line, line_len);
940 941

	if (!result && line_len && line[line_len-1] != '\n')
942
		result = git_buf_printf(buf, "\n");
943 944 945 946

	return result;
}

947 948 949 950 951
static int write_line(struct write_data *write_data, const char *line, size_t line_len)
{
	return write_line_to(write_data->buf, line, line_len);
}

952 953 954 955 956 957
static int write_value(struct write_data *write_data)
{
	const char *q;
	int result;

	q = quotes_for_value(write_data->value);
958
	result = git_buf_printf(write_data->buf,
959
		"\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q);
960 961 962 963 964 965 966 967 968 969 970

	/* If we are updating a single name/value, we're done.  Setting `value`
	 * to `NULL` will prevent us from trying to write it again later (in
	 * `write_on_section`) if we see the same section repeated.
	 */
	if (!write_data->preg)
		write_data->value = NULL;

	return result;
}

971
static int write_on_section(
972
	git_config_parser *reader,
973 974 975 976
	const char *current_section,
	const char *line,
	size_t line_len,
	void *data)
977 978 979 980
{
	struct write_data *write_data = (struct write_data *)data;
	int result = 0;

981 982
	GIT_UNUSED(reader);

983 984 985 986 987 988 989 990 991 992
	/* If we were previously in the correct section (but aren't anymore)
	 * and haven't written our value (for a simple name/value set, not
	 * a multivar), then append it to the end of the section before writing
	 * the new one.
	 */
	if (write_data->in_section && !write_data->preg && write_data->value)
		result = write_value(write_data);

	write_data->in_section = strcmp(current_section, write_data->section) == 0;

993 994 995 996 997 998 999 1000
	/*
	 * If there were comments just before this section, dump them as well.
	 */
	if (!result) {
		result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size);
		git_buf_clear(&write_data->buffered_comment);
	}

1001
	if (!result)
1002
		result = write_line(write_data, line, line_len);
1003 1004 1005 1006

	return result;
}

1007
static int write_on_variable(
1008
	git_config_parser *reader,
1009
	const char *current_section,
1010 1011
	const char *var_name,
	const char *var_value,
1012 1013 1014
	const char *line,
	size_t line_len,
	void *data)
1015 1016 1017
{
	struct write_data *write_data = (struct write_data *)data;
	bool has_matched = false;
1018
	int error;
1019 1020 1021

	GIT_UNUSED(reader);
	GIT_UNUSED(current_section);
1022

1023 1024 1025 1026 1027 1028 1029 1030
	/*
	 * If there were comments just before this variable, let's dump them as well.
	 */
	if ((error = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0)
		return error;

	git_buf_clear(&write_data->buffered_comment);

1031 1032 1033 1034 1035 1036 1037
	/* See if we are to update this name/value pair; first examine name */
	if (write_data->in_section &&
		strcasecmp(write_data->name, var_name) == 0)
		has_matched = true;

	/* If we have a regex to match the value, see if it matches */
	if (has_matched && write_data->preg != NULL)
1038
		has_matched = (git_regexp_match(write_data->preg, var_value) == 0);
1039 1040 1041 1042

	/* If this isn't the name/value we're looking for, simply dump the
	 * existing data back out and continue on.
	 */
1043 1044
	if (!has_matched)
		return write_line(write_data, line, line_len);
1045 1046 1047 1048 1049 1050 1051 1052 1053 1054

	write_data->preg_replaced = 1;

	/* If value is NULL, we are deleting this value; write nothing. */
	if (!write_data->value)
		return 0;

	return write_value(write_data);
}

1055
static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data)
1056
{
1057 1058 1059 1060 1061
	struct write_data *write_data;

	GIT_UNUSED(reader);

	write_data = (struct write_data *)data;
1062
	return write_line_to(&write_data->buffered_comment, line, line_len);
1063 1064
}

1065
static int write_on_eof(
1066
	git_config_parser *reader, const char *current_section, void *data)
1067 1068 1069 1070
{
	struct write_data *write_data = (struct write_data *)data;
	int result = 0;

1071 1072
	GIT_UNUSED(reader);

1073 1074 1075 1076 1077 1078
	/*
	 * If we've buffered comments when reaching EOF, make sure to dump them.
	 */
	if ((result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0)
		return result;

1079 1080 1081 1082 1083 1084
	/* If we are at the EOF and have not written our value (again, for a
	 * simple name/value set, not a multivar) then we have never seen the
	 * section in question and should create a new section and write the
	 * value.
	 */
	if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) {
1085 1086
		/* write the section header unless we're already in it */
		if (!current_section || strcmp(current_section, write_data->section))
1087
			result = write_section(write_data->buf, write_data->orig_section);
1088 1089

		if (!result)
1090 1091 1092 1093 1094 1095 1096 1097 1098
			result = write_value(write_data);
	}

	return result;
}

/*
 * This is pretty much the parsing, except we write out anything we don't have
 */
1099
static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char* value)
1100

1101
{
1102
	char *orig_section = NULL, *section = NULL, *orig_name, *name, *ldot;
1103
	git_buf buf = GIT_BUF_INIT, contents = GIT_BUF_INIT;
1104
	git_config_parser parser = GIT_CONFIG_PARSER_INIT;
1105
	git_filebuf file = GIT_FILEBUF_INIT;
1106
	struct write_data write_data;
1107
	int error;
1108

1109
	memset(&write_data, 0, sizeof(write_data));
1110

1111
	if (cfg->locked) {
1112
		error = git_buf_puts(&contents, git_buf_cstr(&cfg->locked_content) == NULL ? "" : git_buf_cstr(&cfg->locked_content));
1113
	} else {
1114 1115 1116
		if ((error = git_filebuf_open(&file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS,
					      GIT_CONFIG_FILE_MODE)) < 0)
			goto done;
1117

1118
		/* We need to read in our own config file */
1119
		error = git_futils_readbuffer(&contents, cfg->file.path);
1120
	}
1121 1122
	if (error < 0 && error != GIT_ENOTFOUND)
		goto done;
1123

1124 1125
	if ((git_config_parser_init(&parser, cfg->file.path, contents.ptr, contents.size)) < 0)
		goto done;
1126 1127 1128 1129

	ldot = strrchr(key, '.');
	name = ldot + 1;
	section = git__strndup(key, ldot - key);
1130
	GIT_ERROR_CHECK_ALLOC(section);
1131

1132 1133 1134
	ldot = strrchr(orig_key, '.');
	orig_name = ldot + 1;
	orig_section = git__strndup(orig_key, ldot - orig_key);
1135
	GIT_ERROR_CHECK_ALLOC(orig_section);
1136

1137
	write_data.buf = &buf;
1138
	write_data.orig_section = orig_section;
1139
	write_data.section = section;
1140
	write_data.orig_name = orig_name;
1141 1142 1143 1144
	write_data.name = name;
	write_data.preg = preg;
	write_data.value = value;

1145
	if ((error = git_config_parse(&parser, write_on_section, write_on_variable,
1146
				      write_on_comment, write_on_eof, &write_data)) < 0)
1147 1148
		goto done;

1149 1150 1151
	if (cfg->locked) {
		size_t len = buf.asize;
		/* Update our copy with the modified contents */
1152
		git_buf_dispose(&cfg->locked_content);
1153 1154 1155
		git_buf_attach(&cfg->locked_content, git_buf_detach(&buf), len);
	} else {
		git_filebuf_write(&file, git_buf_cstr(&buf), git_buf_len(&buf));
1156

1157
		if ((error = git_filebuf_commit(&file)) < 0)
1158 1159
			goto done;

1160
		if ((error = config_file_refresh_from_buffer(&cfg->parent, buf.ptr, buf.size)) < 0)
1161
			goto done;
1162
	}
1163 1164

done:
1165 1166 1167
	git__free(section);
	git__free(orig_section);
	git_buf_dispose(&write_data.buffered_comment);
1168 1169
	git_buf_dispose(&buf);
	git_buf_dispose(&contents);
1170
	git_filebuf_cleanup(&file);
1171
	git_config_parser_dispose(&parser);
1172 1173

	return error;
1174
}