Partly restore old refresh behavior
[tig.git] / src / options.c
blob3ec167b96fb0967f0d0815e572bb900d603d74d2
1 /* Copyright (c) 2006-2014 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/tig.h"
15 #include "tig/types.h"
16 #include "tig/argv.h"
17 #include "tig/io.h"
18 #include "tig/repo.h"
19 #include "tig/refdb.h"
20 #include "tig/options.h"
21 #include "tig/request.h"
22 #include "tig/line.h"
23 #include "tig/keys.h"
24 #include "tig/view.h"
27 * Option variables.
30 #define DEFINE_OPTION_VARIABLES(name, type, flags) type opt_##name;
31 OPTION_INFO(DEFINE_OPTION_VARIABLES);
33 static struct option_info option_info[] = {
34 #define DEFINE_OPTION_INFO(name, type, flags) { #name, STRING_SIZE(#name), #type, &opt_##name },
35 OPTION_INFO(DEFINE_OPTION_INFO)
38 struct option_info *
39 find_option_info(struct option_info *option, size_t options, const char *name)
41 size_t namelen = strlen(name);
42 int i;
44 for (i = 0; i < options; i++)
45 if (enum_equals(option[i], name, namelen))
46 return &option[i];
48 return NULL;
51 static struct option_info *
52 find_option_info_by_value(void *value)
54 int i;
56 for (i = 0; i < ARRAY_SIZE(option_info); i++)
57 if (option_info[i].value == value)
58 return &option_info[i];
60 return NULL;
63 static void
64 mark_option_seen(void *value)
66 struct option_info *option = find_option_info_by_value(value);
68 if (option)
69 option->seen = TRUE;
73 * State variables.
76 iconv_t opt_iconv_out = ICONV_NONE;
77 char opt_editor[SIZEOF_STR] = "";
78 const char **opt_cmdline_argv = NULL;
79 const char **opt_rev_argv = NULL;
80 const char **opt_file_argv = NULL;
81 char opt_env_lines[64] = "";
82 char opt_env_columns[64] = "";
83 char *opt_env[] = { opt_env_lines, opt_env_columns, NULL };
86 * Mapping between options and command argument mapping.
89 const char *
90 diff_context_arg()
92 static char opt_diff_context_arg[9] = "";
94 if (opt_diff_context < 0 ||
95 !string_format(opt_diff_context_arg, "-U%u", opt_diff_context))
96 return "";
98 return opt_diff_context_arg;
102 #define ENUM_ARG(enum_name, arg_string) ENUM_MAP_ENTRY(arg_string, enum_name)
104 static const struct enum_map_entry ignore_space_arg_map[] = {
105 ENUM_ARG(IGNORE_SPACE_NO, ""),
106 ENUM_ARG(IGNORE_SPACE_ALL, "--ignore-all-space"),
107 ENUM_ARG(IGNORE_SPACE_SOME, "--ignore-space-change"),
108 ENUM_ARG(IGNORE_SPACE_AT_EOL, "--ignore-space-at-eol"),
111 const char *
112 ignore_space_arg()
114 return ignore_space_arg_map[opt_ignore_space].name;
117 static const struct enum_map_entry commit_order_arg_map[] = {
118 ENUM_ARG(COMMIT_ORDER_DEFAULT, ""),
119 ENUM_ARG(COMMIT_ORDER_TOPO, "--topo-order"),
120 ENUM_ARG(COMMIT_ORDER_DATE, "--date-order"),
121 ENUM_ARG(COMMIT_ORDER_AUTHOR_DATE, "--author-date-order"),
122 ENUM_ARG(COMMIT_ORDER_REVERSE, "--reverse"),
125 const char *
126 commit_order_arg()
128 return commit_order_arg_map[opt_commit_order].name;
131 /* Use --show-notes to support Git >= 1.7.6 */
132 #define NOTES_ARG "--show-notes"
133 #define NOTES_EQ_ARG NOTES_ARG "="
135 static char opt_notes_arg[SIZEOF_STR] = NOTES_ARG;
137 const char *
138 show_notes_arg()
140 if (opt_show_notes)
141 return opt_notes_arg;
142 /* Notes are disabled by default when passing --pretty args. */
143 return "";
146 void
147 update_options_from_argv(const char *argv[])
149 int next, flags_pos;
151 for (next = flags_pos = 0; argv[next]; next++) {
152 const char *flag = argv[next];
153 int value = -1;
155 if (map_enum(&value, commit_order_arg_map, flag)) {
156 opt_commit_order = value;
157 mark_option_seen(&opt_commit_order);
158 continue;
161 if (map_enum(&value, ignore_space_arg_map, flag)) {
162 opt_ignore_space = value;
163 mark_option_seen(&opt_ignore_space);
164 continue;
167 if (!strcmp(flag, "--no-notes")) {
168 opt_show_notes = FALSE;
169 mark_option_seen(&opt_show_notes);
170 continue;
173 if (!prefixcmp(flag, "--show-notes") ||
174 !prefixcmp(flag, "--notes")) {
175 opt_show_notes = TRUE;
176 string_ncopy(opt_notes_arg, flag, strlen(flag));
177 mark_option_seen(&opt_show_notes);
178 continue;
181 if (!prefixcmp(flag, "-U")
182 && parse_int(&value, flag + 2, 0, 999999) == SUCCESS) {
183 opt_diff_context = value;
184 mark_option_seen(&opt_diff_context);
185 continue;
188 argv[flags_pos++] = flag;
191 argv[flags_pos] = NULL;
195 * User config file handling.
198 static const struct enum_map_entry color_map[] = {
199 #define COLOR_MAP(name) ENUM_MAP_ENTRY(#name, COLOR_##name)
200 COLOR_MAP(DEFAULT),
201 COLOR_MAP(BLACK),
202 COLOR_MAP(BLUE),
203 COLOR_MAP(CYAN),
204 COLOR_MAP(GREEN),
205 COLOR_MAP(MAGENTA),
206 COLOR_MAP(RED),
207 COLOR_MAP(WHITE),
208 COLOR_MAP(YELLOW),
211 static const struct enum_map_entry attr_map[] = {
212 #define ATTR_MAP(name) ENUM_MAP_ENTRY(#name, A_##name)
213 ATTR_MAP(NORMAL),
214 ATTR_MAP(BLINK),
215 ATTR_MAP(BOLD),
216 ATTR_MAP(DIM),
217 ATTR_MAP(REVERSE),
218 ATTR_MAP(STANDOUT),
219 ATTR_MAP(UNDERLINE),
222 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
224 enum status_code
225 parse_step(double *opt, const char *arg)
227 int value = atoi(arg);
229 if (!value && !isdigit(*arg))
230 return error("Invalid double or percentage");
232 *opt = value;
233 if (!strchr(arg, '%'))
234 return SUCCESS;
236 /* "Shift down" so 100% and 1 does not conflict. */
237 *opt = (*opt - 1) / 100;
238 if (*opt >= 1.0) {
239 *opt = 0.99;
240 return error("Percentage is larger than 100%%");
242 if (*opt < 0.0) {
243 *opt = 1;
244 return error("Percentage is less than 0%%");
246 return SUCCESS;
249 enum status_code
250 parse_int(int *opt, const char *arg, int min, int max)
252 int value = atoi(arg);
254 if (min <= value && value <= max) {
255 *opt = value;
256 return SUCCESS;
259 return error("Value must be between %d and %d", min, max);
262 static bool
263 set_color(int *color, const char *name)
265 if (map_enum(color, color_map, name))
266 return TRUE;
267 /* Git expects a plain int w/o prefix, however, color<int> is
268 * the preferred Tig color notation. */
269 if (!prefixcmp(name, "color"))
270 name += 5;
271 return string_isnumber(name) &&
272 parse_int(color, name, 0, 255) == SUCCESS;
275 #define is_quoted(c) ((c) == '"' || (c) == '\'')
277 static enum status_code
278 parse_color_name(const char *color, struct line_rule *rule, const char **prefix_ptr)
280 const char *prefixend = is_quoted(*color) ? NULL : strchr(color, '.');
282 if (prefixend) {
283 struct keymap *keymap = get_keymap(color, prefixend - color);
285 if (!keymap)
286 return error("Unknown key map: %.*s", (int) (prefixend - color), color);
287 if (prefix_ptr)
288 *prefix_ptr = keymap->name;
289 color = prefixend + 1;
292 memset(rule, 0, sizeof(*rule));
293 if (is_quoted(*color)) {
294 rule->line = color + 1;
295 rule->linelen = strlen(color) - 2;
296 } else {
297 rule->name = color;
298 rule->namelen = strlen(color);
301 return SUCCESS;
304 static int
305 find_remapped(const char *remapped[][2], size_t remapped_size, const char *arg)
307 size_t arglen = strlen(arg);
308 int i;
310 for (i = 0; i < remapped_size; i++) {
311 const char *name = remapped[i][0];
312 size_t namelen = strlen(name);
314 if (arglen == namelen &&
315 !string_enum_compare(arg, name, namelen))
316 return i;
319 return -1;
322 /* Wants: object fgcolor bgcolor [attribute] */
323 static enum status_code
324 option_color_command(int argc, const char *argv[])
326 struct line_rule rule = {};
327 const char *prefix = NULL;
328 struct line_info *info;
329 enum status_code code;
331 if (argc < 3)
332 return error("Invalid color mapping: color area fgcolor bgcolor [attrs]");
334 code = parse_color_name(argv[0], &rule, &prefix);
335 if (code != SUCCESS)
336 return code;
338 info = add_line_rule(prefix, &rule);
339 if (!info) {
340 static const char *obsolete[][2] = {
341 { "acked", "' Acked-by'" },
342 { "diff-copy-from", "'copy from '" },
343 { "diff-copy-to", "'copy to '" },
344 { "diff-deleted-file-mode", "'deleted file mode '" },
345 { "diff-dissimilarity", "'dissimilarity '" },
346 { "diff-rename-from", "'rename from '" },
347 { "diff-rename-to", "'rename to '" },
348 { "diff-tree", "'diff-tree '" },
349 { "filename", "file" },
350 { "help-keymap", "help.section" },
351 { "main-revgraph", "" },
352 { "pp-adate", "'AuthorDate: '" },
353 { "pp-author", "'Author: '" },
354 { "pp-cdate", "'CommitDate: '" },
355 { "pp-commit", "'Commit: '" },
356 { "pp-date", "'Date: '" },
357 { "reviewed", "' Reviewed-by'" },
358 { "signoff", "' Signed-off-by'" },
359 { "stat-head", "status.header" },
360 { "stat-section", "status.section" },
361 { "tested", "' Tested-by'" },
362 { "tree-dir", "tree.directory" },
363 { "tree-file", "tree.file" },
364 { "tree-head", "tree.header" },
366 int index;
368 index = find_remapped(obsolete, ARRAY_SIZE(obsolete), rule.name);
369 if (index != -1) {
370 if (!*obsolete[index][1])
371 return error("%s is obsolete", argv[0]);
372 /* Keep the initial prefix if defined. */
373 code = parse_color_name(obsolete[index][1], &rule, prefix ? NULL : &prefix);
374 if (code != SUCCESS)
375 return code;
376 info = add_line_rule(prefix, &rule);
379 if (!info)
380 return error("Unknown color name: %s", argv[0]);
382 code = error("%s has been replaced by %s",
383 obsolete[index][0], obsolete[index][1]);
386 if (!set_color(&info->fg, argv[1]))
387 return error("Unknown color: %s", argv[1]);
389 if (!set_color(&info->bg, argv[2]))
390 return error("Unknown color: %s", argv[2]);
392 info->attr = 0;
393 while (argc-- > 3) {
394 int attr;
396 if (!set_attribute(&attr, argv[argc]))
397 return error("Unknown color attribute: %s", argv[argc]);
398 info->attr |= attr;
401 return code;
404 static enum status_code
405 parse_bool(bool *opt, const char *arg)
407 *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
408 ? TRUE : FALSE;
409 if (*opt || !strcmp(arg, "0") || !strcmp(arg, "false") || !strcmp(arg, "no"))
410 return SUCCESS;
411 return error("Non-boolean value treated as false: %s", arg);
414 static enum status_code
415 parse_enum(const char *name, unsigned int *opt, const char *arg,
416 const struct enum_map *map)
418 bool is_true;
419 enum status_code code;
421 assert(map->size > 1);
423 if (map_enum_do(map->entries, map->size, (int *) opt, arg))
424 return SUCCESS;
426 code = parse_bool(&is_true, arg);
427 *opt = is_true ? map->entries[1].value : map->entries[0].value;
428 if (code == SUCCESS)
429 return code;
430 return error("'%s' is not a valid value for %s; using %s",
431 arg, name, enum_name(map->entries[*opt].name));
434 static enum status_code
435 parse_string(char *opt, const char *arg, size_t optsize)
437 int arglen = strlen(arg);
439 switch (arg[0]) {
440 case '\"':
441 case '\'':
442 if (arglen == 1 || arg[arglen - 1] != arg[0])
443 return ERROR_UNMATCHED_QUOTATION;
444 arg += 1; arglen -= 2;
445 default:
446 string_ncopy_do(opt, optsize, arg, arglen);
447 return SUCCESS;
451 static enum status_code
452 parse_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
454 char buf[SIZEOF_STR];
455 enum status_code code = parse_string(buf, arg, sizeof(buf));
457 if (code == SUCCESS) {
458 struct encoding *encoding = *encoding_ref;
460 if (encoding && !priority)
461 return code;
462 encoding = encoding_open(buf);
463 if (encoding)
464 *encoding_ref = encoding;
467 return code;
470 static enum status_code
471 parse_args(const char ***args, const char *argv[])
473 if (!argv_copy(args, argv))
474 return ERROR_OUT_OF_MEMORY;
475 return SUCCESS;
478 enum status_code
479 parse_option(struct option_info *option, const char *prefix, const char *arg)
481 char name[SIZEOF_STR];
483 if (!enum_name_prefixed(name, sizeof(name), prefix, option->name))
484 return error("Failed to parse option");
486 if (!strcmp("show-notes", name)) {
487 bool *value = option->value;
488 enum status_code res;
490 if (parse_bool(option->value, arg) == SUCCESS)
491 return SUCCESS;
493 *value = TRUE;
494 string_copy(opt_notes_arg, NOTES_EQ_ARG);
495 res = parse_string(opt_notes_arg + STRING_SIZE(NOTES_EQ_ARG), arg,
496 sizeof(opt_notes_arg) - STRING_SIZE(NOTES_EQ_ARG));
497 if (res == SUCCESS && !opt_notes_arg[STRING_SIZE(NOTES_EQ_ARG)])
498 opt_notes_arg[STRING_SIZE(NOTES_ARG)] = 0;
499 return res;
502 if (!strcmp(option->type, "bool"))
503 return parse_bool(option->value, arg);
505 if (!strcmp(option->type, "double"))
506 return parse_step(option->value, arg);
508 if (!strncmp(option->type, "enum", 4)) {
509 const char *type = option->type + STRING_SIZE("enum ");
510 const struct enum_map *map = find_enum_map(type);
512 return parse_enum(name, option->value, arg, map);
515 if (!strcmp(option->type, "int")) {
516 if (strstr(name, "title-overflow")) {
517 bool enabled = FALSE;
518 int *value = option->value;
520 /* We try to parse it as a boolean (and set the
521 * value to 0 if fale), otherwise we parse it as
522 * an integer and use the given value. */
523 if (parse_bool(&enabled, arg) == SUCCESS) {
524 if (!enabled) {
525 *value = 0;
526 return SUCCESS;
528 arg = "50";
532 if (!strcmp(name, "line-number-interval") ||
533 !strcmp(name, "tab-size"))
534 return parse_int(option->value, arg, 1, 1024);
535 else if (!strcmp(name, "id-width"))
536 return parse_int(option->value, arg, 0, SIZEOF_REV - 1);
537 else
538 return parse_int(option->value, arg, 0, 1024);
541 return error("Unhandled option: %s", name);
544 struct view_config {
545 const char *name;
546 const char ***argv;
549 static struct view_config view_configs[] = {
550 { "blame-view", &opt_blame_view },
551 { "blob-view", &opt_blob_view },
552 { "diff-view", &opt_diff_view },
553 { "grep-view", &opt_grep_view },
554 { "log-view", &opt_log_view },
555 { "main-view", &opt_main_view },
556 { "pager-view", &opt_pager_view },
557 { "refs-view", &opt_refs_view },
558 { "stage-view", &opt_stage_view },
559 { "stash-view", &opt_stash_view },
560 { "status-view", &opt_status_view },
561 { "tree-view", &opt_tree_view },
564 static enum status_code
565 check_view_config(struct option_info *option, const char *argv[])
567 const char *name = enum_name(option->name);
568 int i;
570 for (i = 0; i < ARRAY_SIZE(view_configs); i++)
571 if (!strcmp(name, view_configs[i].name))
572 return parse_view_config(name, argv);
574 return SUCCESS;
577 /* Wants: name = value */
578 static enum status_code
579 option_set_command(int argc, const char *argv[])
581 struct option_info *option;
583 if (argc < 3)
584 return error("Invalid set command: set option = value");
586 if (strcmp(argv[1], "="))
587 return error("No value assigned to %s", argv[0]);
589 if (!strcmp(argv[0], "reference-format"))
590 return parse_ref_formats(argv + 2);
592 option = find_option_info(option_info, ARRAY_SIZE(option_info), argv[0]);
593 if (option) {
594 enum status_code code;
596 if (option->seen)
597 return SUCCESS;
599 if (!strcmp(option->type, "const char **")) {
600 code = check_view_config(option, argv + 2);
601 if (code != SUCCESS)
602 return code;
603 return parse_args(option->value, argv + 2);
606 code = parse_option(option, "", argv[2]);
607 if (code == SUCCESS && argc != 3)
608 return error("Option %s only takes one value", argv[0]);
610 return code;
615 const char *obsolete[][2] = {
616 { "author-width", "author" },
617 { "filename-width", "file-name" },
618 { "line-number-interval", "line-number" },
619 { "show-author", "author" },
620 { "show-date", "date" },
621 { "show-file-size", "file-size" },
622 { "show-filename", "file-name" },
623 { "show-id", "id" },
624 { "show-line-numbers", "line-number" },
625 { "show-refs", "commit-title" },
626 { "show-rev-graph", "commit-title" },
627 { "title-overflow", "commit-title and text" },
629 int index = find_remapped(obsolete, ARRAY_SIZE(obsolete), argv[0]);
631 if (index != -1)
632 return error("%s is obsolete; see tigrc(5) for how to set the %s column option",
633 obsolete[index][0], obsolete[index][1]);
635 if (!strcmp(argv[0], "read-git-colors"))
636 return error("read-git-colors has been obsoleted by the git-colors option");
639 return error("Unknown option name: %s", argv[0]);
642 /* Wants: mode request key */
643 static enum status_code
644 option_bind_command(int argc, const char *argv[])
646 struct key key[1];
647 size_t keys = 0;
648 enum request request;
649 struct keymap *keymap;
650 const char *key_arg;
652 if (argc < 3)
653 return error("Invalid key binding: bind keymap key action");
655 if (!(keymap = get_keymap(argv[0], strlen(argv[0])))) {
656 if (!strcmp(argv[0], "branch"))
657 keymap = get_keymap("refs", strlen("refs"));
658 if (!keymap)
659 return error("Unknown key map: %s", argv[0]);
662 for (keys = 0, key_arg = argv[1]; *key_arg && keys < ARRAY_SIZE(key); keys++) {
663 enum status_code code = get_key_value(&key_arg, &key[keys]);
665 if (code != SUCCESS)
666 return code;
669 if (*key_arg && keys == ARRAY_SIZE(key))
670 return error("Except for <Esc> combos only one key is allowed "
671 "in key combos: %s", argv[1]);
673 request = get_request(argv[2]);
674 if (request == REQ_UNKNOWN) {
675 static const char *obsolete[][2] = {
676 { "view-branch", "view-refs" },
678 static const char *toggles[][2] = {
679 { "diff-context-down", "diff-context" },
680 { "diff-context-up", "diff-context" },
681 { "stage-next", ":/^@@" },
682 { "toggle-author", "author" },
683 { "toggle-changes", "show-changes" },
684 { "toggle-commit-order", "show-commit-order" },
685 { "toggle-date", "date" },
686 { "toggle-files", "file-filter" },
687 { "toggle-file-filter", "file-filter" },
688 { "toggle-file-size", "file-size" },
689 { "toggle-filename", "filename" },
690 { "toggle-graphic", "show-graphic" },
691 { "toggle-id", "id" },
692 { "toggle-ignore-space", "show-ignore-space" },
693 { "toggle-lineno", "line-number" },
694 { "toggle-refs", "commit-title-refs" },
695 { "toggle-rev-graph", "commit-title-graph" },
696 { "toggle-show-changes", "show-changes" },
697 { "toggle-sort-field", "sort-field" },
698 { "toggle-sort-order", "sort-order" },
699 { "toggle-title-overflow", "commit-title-overflow" },
700 { "toggle-untracked-dirs", "status-untracked-dirs" },
701 { "toggle-vertical-split", "show-vertical-split" },
703 int alias;
705 alias = find_remapped(obsolete, ARRAY_SIZE(obsolete), argv[2]);
706 if (alias != -1) {
707 const char *action = obsolete[alias][1];
709 add_keybinding(keymap, get_request(action), key, keys);
710 return error("%s has been renamed to %s",
711 obsolete[alias][0], action);
714 alias = find_remapped(toggles, ARRAY_SIZE(toggles), argv[2]);
715 if (alias != -1) {
716 const char *action = toggles[alias][0];
717 const char *arg = prefixcmp(action, "diff-context-")
718 ? NULL : (strstr(action, "-down") ? "-1" : "+1");
719 const char *mapped = toggles[alias][1];
720 const char *toggle[] = { ":toggle", mapped, arg, NULL};
721 const char *other[] = { mapped, NULL };
722 const char **prompt = *mapped == ':' ? other : toggle;
723 enum status_code code = add_run_request(keymap, key, keys, prompt);
725 if (code == SUCCESS)
726 code = error("%s has been replaced by `%s%s%s%s'",
727 action, prompt == other ? mapped : ":toggle ",
728 prompt == other ? "" : mapped,
729 arg ? " " : "", arg ? arg : "");
730 return code;
734 if (request == REQ_UNKNOWN)
735 return add_run_request(keymap, key, keys, argv + 2);
737 return add_keybinding(keymap, request, key, keys);
741 static enum status_code load_option_file(const char *path);
743 static enum status_code
744 option_source_command(int argc, const char *argv[])
746 enum status_code code;
748 if (argc < 1)
749 return error("Invalid source command: source path");
751 code = load_option_file(argv[0]);
753 return code == ERROR_FILE_DOES_NOT_EXIST
754 ? error("File does not exist: %s", argv[0]) : code;
757 enum status_code
758 set_option(const char *opt, int argc, const char *argv[])
760 if (!strcmp(opt, "color"))
761 return option_color_command(argc, argv);
763 if (!strcmp(opt, "set"))
764 return option_set_command(argc, argv);
766 if (!strcmp(opt, "bind"))
767 return option_bind_command(argc, argv);
769 if (!strcmp(opt, "source"))
770 return option_source_command(argc, argv);
772 return error("Unknown option command: %s", opt);
775 struct config_state {
776 const char *path;
777 size_t lineno;
778 bool errors;
781 static int
782 read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
784 struct config_state *config = data;
785 enum status_code status = ERROR_NO_OPTION_VALUE;
787 /* Check for comment markers, since read_properties() will
788 * only ensure opt and value are split at first " \t". */
789 optlen = strcspn(opt, "#");
790 if (optlen == 0)
791 return OK;
793 if (opt[optlen] == 0) {
794 /* Look for comment endings in the value. */
795 size_t len = strcspn(value, "#");
796 const char *argv[SIZEOF_ARG];
797 int argc = 0;
799 if (len < valuelen) {
800 valuelen = len;
801 value[valuelen] = 0;
804 if (!argv_from_string(argv, &argc, value))
805 status = error("Too many option arguments for %s", opt);
806 else
807 status = set_option(opt, argc, argv);
810 if (status != SUCCESS) {
811 warn("%s:%zu: %s", config->path, config->lineno,
812 get_status_message(status));
813 config->errors = TRUE;
816 /* Always keep going if errors are encountered. */
817 return OK;
820 static enum status_code
821 load_option_file(const char *path)
823 struct config_state config = { path, 0, FALSE };
824 struct io io;
825 char buf[SIZEOF_STR];
827 /* Do not read configuration from stdin if set to "" */
828 if (!path || !strlen(path))
829 return SUCCESS;
831 if (!prefixcmp(path, "~/")) {
832 const char *home = getenv("HOME");
834 if (!home || !string_format(buf, "%s/%s", home, path + 2))
835 return error("Failed to expand ~ to user home directory");
836 path = buf;
839 /* It's OK that the file doesn't exist. */
840 if (!io_open(&io, "%s", path)) {
841 /* XXX: Must return ERROR_FILE_DOES_NOT_EXIST so missing
842 * system tigrc is detected properly. */
843 if (io_error(&io) == ENOENT)
844 return ERROR_FILE_DOES_NOT_EXIST;
845 return error("Error loading file %s: %s", path, strerror(io_error(&io)));
848 if (io_load_span(&io, " \t", &config.lineno, read_option, &config) == ERR ||
849 config.errors == TRUE)
850 warn("Errors while loading %s.", path);
851 return SUCCESS;
854 extern const char *builtin_config;
857 load_options(void)
859 const char *tigrc_user = getenv("TIGRC_USER");
860 const char *tigrc_system = getenv("TIGRC_SYSTEM");
861 const char *tig_diff_opts = getenv("TIG_DIFF_OPTS");
862 const bool diff_opts_from_args = !!opt_diff_options;
863 bool custom_tigrc_system = !!tigrc_system;
865 opt_file_filter = TRUE;
866 if (!find_option_info_by_value(&opt_diff_context)->seen)
867 opt_diff_context = -3;
869 if (!custom_tigrc_system)
870 tigrc_system = SYSCONFDIR "/tigrc";
872 if (!*tigrc_system ||
873 (load_option_file(tigrc_system) == ERROR_FILE_DOES_NOT_EXIST && !custom_tigrc_system)) {
874 struct config_state config = { "<built-in>", 0, FALSE };
875 struct io io;
877 if (!io_from_string(&io, builtin_config))
878 die("Failed to get built-in config");
879 if (!io_load_span(&io, " \t", &config.lineno, read_option, &config) == ERR || config.errors == TRUE)
880 die("Error in built-in config");
883 if (!tigrc_user)
884 tigrc_user = "~/.tigrc";
885 load_option_file(tigrc_user);
887 if (!diff_opts_from_args && tig_diff_opts && *tig_diff_opts) {
888 static const char *diff_opts[SIZEOF_ARG] = { NULL };
889 char buf[SIZEOF_STR];
890 int argc = 0;
892 if (!string_format(buf, "%s", tig_diff_opts) ||
893 !argv_from_string(diff_opts, &argc, buf))
894 die("TIG_DIFF_OPTS contains too many arguments");
895 else if (!argv_copy(&opt_diff_options, diff_opts))
896 die("Failed to format TIG_DIFF_OPTS arguments");
899 return OK;
903 * Repository properties
906 static void
907 set_remote_branch(const char *name, const char *value, size_t valuelen)
909 if (!strcmp(name, ".remote")) {
910 string_ncopy(repo.remote, value, valuelen);
912 } else if (*repo.remote && !strcmp(name, ".merge")) {
913 size_t from = strlen(repo.remote);
915 if (!prefixcmp(value, "refs/heads/"))
916 value += STRING_SIZE("refs/heads/");
918 if (!string_format_from(repo.remote, &from, "/%s", value))
919 repo.remote[0] = 0;
923 static void
924 set_repo_config_option(char *name, char *value, enum status_code (*cmd)(int, const char **))
926 const char *argv[SIZEOF_ARG] = { name, "=" };
927 int argc = 1 + (cmd == option_set_command);
928 enum status_code code;
930 if (!argv_from_string(argv, &argc, value))
931 code = error("Too many arguments");
932 else
933 code = cmd(argc, argv);
935 if (code != SUCCESS)
936 warn("Option 'tig.%s': %s", name, get_status_message(code));
939 static void
940 set_work_tree(const char *value)
942 char cwd[SIZEOF_STR];
944 if (!getcwd(cwd, sizeof(cwd)))
945 die("Failed to get cwd path: %s", strerror(errno));
946 if (chdir(cwd) < 0)
947 die("Failed to chdir(%s): %s", cwd, strerror(errno));
948 if (chdir(repo.git_dir) < 0)
949 die("Failed to chdir(%s): %s", repo.git_dir, strerror(errno));
950 if (!getcwd(repo.git_dir, sizeof(repo.git_dir)))
951 die("Failed to get git path: %s", strerror(errno));
952 if (chdir(value) < 0)
953 die("Failed to chdir(%s): %s", value, strerror(errno));
954 if (!getcwd(cwd, sizeof(cwd)))
955 die("Failed to get cwd path: %s", strerror(errno));
956 if (setenv("GIT_WORK_TREE", cwd, TRUE))
957 die("Failed to set GIT_WORK_TREE to '%s'", cwd);
958 if (setenv("GIT_DIR", repo.git_dir, TRUE))
959 die("Failed to set GIT_DIR to '%s'", repo.git_dir);
960 repo.is_inside_work_tree = TRUE;
963 static struct line_info *
964 parse_git_color_option(struct line_info *info, char *value)
966 const char *argv[SIZEOF_ARG];
967 int argc = 0;
968 bool first_color = TRUE;
969 int i;
971 if (!argv_from_string(argv, &argc, value))
972 return NULL;
974 info->fg = COLOR_DEFAULT;
975 info->bg = COLOR_DEFAULT;
976 info->attr = 0;
978 for (i = 0; i < argc; i++) {
979 int attr = 0;
981 if (set_attribute(&attr, argv[i])) {
982 info->attr |= attr;
984 } else if (set_color(&attr, argv[i])) {
985 if (first_color)
986 info->fg = attr;
987 else
988 info->bg = attr;
989 first_color = FALSE;
992 return info;
995 static void
996 set_git_color_option(const char *name, char *value)
998 struct line_info parsed = {};
999 struct line_info *color = NULL;
1000 size_t namelen = strlen(name);
1001 int i;
1003 if (!opt_git_colors)
1004 return;
1006 for (i = 0; opt_git_colors[i]; i++) {
1007 struct line_rule rule = {};
1008 const char *prefix = NULL;
1009 struct line_info *info;
1010 const char *alias = opt_git_colors[i];
1011 const char *sep = strchr(alias, '=');
1013 if (!sep || namelen != sep - alias ||
1014 string_enum_compare(name, alias, namelen))
1015 continue;
1017 if (!color) {
1018 color = parse_git_color_option(&parsed, value);
1019 if (!color)
1020 return;
1023 if (parse_color_name(sep + 1, &rule, &prefix) == SUCCESS &&
1024 (info = add_line_rule(prefix, &rule))) {
1025 info->fg = parsed.fg;
1026 info->bg = parsed.bg;
1027 info->attr = parsed.attr;
1032 static void
1033 set_encoding(struct encoding **encoding_ref, const char *arg, bool priority)
1035 if (!strcasecmp(arg, "utf-8") || !strcasecmp(arg, "utf8"))
1036 return;
1037 if (parse_encoding(encoding_ref, arg, priority) == SUCCESS)
1038 encoding_arg[0] = 0;
1041 static int
1042 read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
1044 if (!strcmp(name, "i18n.commitencoding"))
1045 set_encoding(&default_encoding, value, FALSE);
1047 else if (!strcmp(name, "gui.encoding"))
1048 set_encoding(&default_encoding, value, TRUE);
1050 else if (!strcmp(name, "core.editor"))
1051 string_ncopy(opt_editor, value, valuelen);
1053 else if (!strcmp(name, "core.worktree"))
1054 set_work_tree(value);
1056 else if (!strcmp(name, "core.abbrev"))
1057 parse_int(&opt_id_width, value, 0, SIZEOF_REV - 1);
1059 else if (!prefixcmp(name, "tig.color."))
1060 set_repo_config_option(name + 10, value, option_color_command);
1062 else if (!prefixcmp(name, "tig.bind."))
1063 set_repo_config_option(name + 9, value, option_bind_command);
1065 else if (!prefixcmp(name, "tig."))
1066 set_repo_config_option(name + 4, value, option_set_command);
1068 else if (!prefixcmp(name, "color."))
1069 set_git_color_option(name + STRING_SIZE("color."), value);
1071 else if (*repo.head && !prefixcmp(name, "branch.") &&
1072 !strncmp(name + 7, repo.head, strlen(repo.head)))
1073 set_remote_branch(name + 7 + strlen(repo.head), value, valuelen);
1075 else if (!strcmp(name, "diff.context")) {
1076 if (!find_option_info_by_value(&opt_diff_context)->seen)
1077 opt_diff_context = -atoi(value);
1080 return OK;
1084 load_git_config(void)
1086 const char *config_list_argv[] = { "git", "config", "--list", NULL };
1088 return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
1091 /* vim: set ts=8 sw=8 noexpandtab: */