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.
15 #include "tig/types.h"
19 #include "tig/refdb.h"
20 #include "tig/options.h"
21 #include "tig/request.h"
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
)
39 find_option_info(struct option_info
*option
, size_t options
, const char *prefix
, const char *name
)
41 size_t namelen
= strlen(name
);
42 char prefixed
[SIZEOF_STR
];
45 if (*prefix
&& namelen
== strlen(prefix
) &&
46 !string_enum_compare(prefix
, name
, namelen
)) {
48 namelen
= strlen(name
);
51 for (i
= 0; i
< options
; i
++) {
52 if (enum_equals(option
[i
], name
, namelen
))
55 if (enum_name_prefixed(prefixed
, sizeof(prefixed
), prefix
, option
[i
].name
) &&
56 namelen
== strlen(prefixed
) &&
57 !string_enum_compare(prefixed
, name
, namelen
))
64 static struct option_info
*
65 find_option_info_by_value(void *value
)
69 for (i
= 0; i
< ARRAY_SIZE(option_info
); i
++)
70 if (option_info
[i
].value
== value
)
71 return &option_info
[i
];
77 mark_option_seen(void *value
)
79 struct option_info
*option
= find_option_info_by_value(value
);
86 find_column_option_info(enum view_column_type type
, union view_column_options
*opts
,
87 const char *option
, struct option_info
*column_info
,
88 const char **column_name
)
90 size_t optionlen
= strlen(option
);
92 #define DEFINE_COLUMN_OPTION_INFO(name, type, flags) \
93 { #name, STRING_SIZE(#name), #type, &opt->name, flags },
95 #define DEFINE_COLUMN_OPTION_INFO_CHECK(name, id, options) \
96 if (type == VIEW_COLUMN_##id) { \
97 struct name##_options *opt = &opts->name; \
98 struct option_info info[] = { \
99 options(DEFINE_COLUMN_OPTION_INFO) \
101 struct option_info *match; \
102 match = find_option_info(info, ARRAY_SIZE(info), #name, option); \
104 *column_info = *match; \
105 *column_name = #name; \
106 return column_info; \
110 COLUMN_OPTIONS(DEFINE_COLUMN_OPTION_INFO_CHECK
);
120 iconv_t opt_iconv_out
= ICONV_NONE
;
121 char opt_editor
[SIZEOF_STR
] = "";
122 const char **opt_cmdline_argv
= NULL
;
123 const char **opt_rev_argv
= NULL
;
124 const char **opt_file_argv
= NULL
;
125 char opt_env_lines
[64] = "";
126 char opt_env_columns
[64] = "";
127 char *opt_env
[] = { opt_env_lines
, opt_env_columns
, NULL
};
130 * Mapping between options and command argument mapping.
136 static char opt_diff_context_arg
[9] = "";
138 if (opt_diff_context
< 0 ||
139 !string_format(opt_diff_context_arg
, "-U%u", opt_diff_context
))
142 return opt_diff_context_arg
;
146 #define ENUM_ARG(enum_name, arg_string) ENUM_MAP_ENTRY(arg_string, enum_name)
148 static const struct enum_map_entry ignore_space_arg_map
[] = {
149 ENUM_ARG(IGNORE_SPACE_NO
, ""),
150 ENUM_ARG(IGNORE_SPACE_ALL
, "--ignore-all-space"),
151 ENUM_ARG(IGNORE_SPACE_SOME
, "--ignore-space-change"),
152 ENUM_ARG(IGNORE_SPACE_AT_EOL
, "--ignore-space-at-eol"),
158 return ignore_space_arg_map
[opt_ignore_space
].name
;
161 static const struct enum_map_entry commit_order_arg_map
[] = {
162 ENUM_ARG(COMMIT_ORDER_DEFAULT
, ""),
163 ENUM_ARG(COMMIT_ORDER_TOPO
, "--topo-order"),
164 ENUM_ARG(COMMIT_ORDER_DATE
, "--date-order"),
165 ENUM_ARG(COMMIT_ORDER_AUTHOR_DATE
, "--author-date-order"),
166 ENUM_ARG(COMMIT_ORDER_REVERSE
, "--reverse"),
172 return commit_order_arg_map
[opt_commit_order
].name
;
176 commit_order_arg_with_graph(enum graph_display graph_display
)
178 enum commit_order commit_order
= opt_commit_order
;
180 if (graph_display
== GRAPH_DISPLAY_YES
&&
181 commit_order
!= COMMIT_ORDER_TOPO
&&
182 commit_order
!= COMMIT_ORDER_DATE
&&
183 commit_order
!= COMMIT_ORDER_AUTHOR_DATE
)
184 commit_order
= COMMIT_ORDER_TOPO
;
186 return commit_order_arg_map
[commit_order
].name
;
189 /* Use --show-notes to support Git >= 1.7.6 */
190 #define NOTES_ARG "--show-notes"
191 #define NOTES_EQ_ARG NOTES_ARG "="
193 static char opt_notes_arg
[SIZEOF_STR
] = NOTES_ARG
;
199 return opt_notes_arg
;
200 /* Notes are disabled by default when passing --pretty args. */
205 update_options_from_argv(const char *argv
[])
209 for (next
= flags_pos
= 0; argv
[next
]; next
++) {
210 const char *flag
= argv
[next
];
213 if (map_enum(&value
, commit_order_arg_map
, flag
)) {
214 opt_commit_order
= value
;
215 mark_option_seen(&opt_commit_order
);
219 if (map_enum(&value
, ignore_space_arg_map
, flag
)) {
220 opt_ignore_space
= value
;
221 mark_option_seen(&opt_ignore_space
);
225 if (!strcmp(flag
, "--no-notes")) {
226 opt_show_notes
= FALSE
;
227 mark_option_seen(&opt_show_notes
);
231 if (!prefixcmp(flag
, "--show-notes") ||
232 !prefixcmp(flag
, "--notes")) {
233 opt_show_notes
= TRUE
;
234 string_ncopy(opt_notes_arg
, flag
, strlen(flag
));
235 mark_option_seen(&opt_show_notes
);
239 if (!prefixcmp(flag
, "-U")
240 && parse_int(&value
, flag
+ 2, 0, 999999) == SUCCESS
) {
241 opt_diff_context
= value
;
242 mark_option_seen(&opt_diff_context
);
246 argv
[flags_pos
++] = flag
;
249 argv
[flags_pos
] = NULL
;
253 * User config file handling.
256 static const struct enum_map_entry color_map
[] = {
257 #define COLOR_MAP(name) ENUM_MAP_ENTRY(#name, COLOR_##name)
269 static const struct enum_map_entry attr_map
[] = {
270 #define ATTR_MAP(name) ENUM_MAP_ENTRY(#name, A_##name)
280 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
283 parse_step(double *opt
, const char *arg
)
285 int value
= atoi(arg
);
287 if (!value
&& !isdigit(*arg
))
288 return error("Invalid double or percentage");
291 if (!strchr(arg
, '%'))
294 /* "Shift down" so 100% and 1 does not conflict. */
295 *opt
= (*opt
- 1) / 100;
298 return error("Percentage is larger than 100%%");
302 return error("Percentage is less than 0%%");
308 parse_int(int *opt
, const char *arg
, int min
, int max
)
310 int value
= atoi(arg
);
312 if (min
<= value
&& value
<= max
) {
317 return error("Value must be between %d and %d", min
, max
);
321 set_color(int *color
, const char *name
)
323 if (map_enum(color
, color_map
, name
))
325 /* Git expects a plain int w/o prefix, however, color<int> is
326 * the preferred Tig color notation. */
327 if (!prefixcmp(name
, "color"))
329 return string_isnumber(name
) &&
330 parse_int(color
, name
, 0, 255) == SUCCESS
;
333 #define is_quoted(c) ((c) == '"' || (c) == '\'')
335 static enum status_code
336 parse_color_name(const char *color
, struct line_rule
*rule
, const char **prefix_ptr
)
338 const char *prefixend
= is_quoted(*color
) ? NULL
: strchr(color
, '.');
341 struct keymap
*keymap
= get_keymap(color
, prefixend
- color
);
344 return error("Unknown key map: %.*s", (int) (prefixend
- color
), color
);
346 *prefix_ptr
= keymap
->name
;
347 color
= prefixend
+ 1;
350 memset(rule
, 0, sizeof(*rule
));
351 if (is_quoted(*color
)) {
352 rule
->line
= color
+ 1;
353 rule
->linelen
= strlen(color
) - 2;
356 rule
->namelen
= strlen(color
);
363 find_remapped(const char *remapped
[][2], size_t remapped_size
, const char *arg
)
365 size_t arglen
= strlen(arg
);
368 for (i
= 0; i
< remapped_size
; i
++) {
369 const char *name
= remapped
[i
][0];
370 size_t namelen
= strlen(name
);
372 if (arglen
== namelen
&&
373 !string_enum_compare(arg
, name
, namelen
))
380 /* Wants: object fgcolor bgcolor [attribute] */
381 static enum status_code
382 option_color_command(int argc
, const char *argv
[])
384 struct line_rule rule
= {};
385 const char *prefix
= NULL
;
386 struct line_info
*info
;
387 enum status_code code
;
390 return error("Invalid color mapping: color area fgcolor bgcolor [attrs]");
392 code
= parse_color_name(argv
[0], &rule
, &prefix
);
396 info
= add_line_rule(prefix
, &rule
);
398 static const char *obsolete
[][2] = {
399 { "acked", "' Acked-by'" },
400 { "diff-copy-from", "'copy from '" },
401 { "diff-copy-to", "'copy to '" },
402 { "diff-deleted-file-mode", "'deleted file mode '" },
403 { "diff-dissimilarity", "'dissimilarity '" },
404 { "diff-rename-from", "'rename from '" },
405 { "diff-rename-to", "'rename to '" },
406 { "diff-tree", "'diff-tree '" },
407 { "filename", "file" },
408 { "help-keymap", "help.section" },
409 { "main-revgraph", "" },
410 { "pp-adate", "'AuthorDate: '" },
411 { "pp-author", "'Author: '" },
412 { "pp-cdate", "'CommitDate: '" },
413 { "pp-commit", "'Commit: '" },
414 { "pp-date", "'Date: '" },
415 { "reviewed", "' Reviewed-by'" },
416 { "signoff", "' Signed-off-by'" },
417 { "stat-head", "status.header" },
418 { "stat-section", "status.section" },
419 { "tested", "' Tested-by'" },
420 { "tree-dir", "tree.directory" },
421 { "tree-file", "tree.file" },
422 { "tree-head", "tree.header" },
426 index
= find_remapped(obsolete
, ARRAY_SIZE(obsolete
), rule
.name
);
428 if (!*obsolete
[index
][1])
429 return error("%s is obsolete", argv
[0]);
430 /* Keep the initial prefix if defined. */
431 code
= parse_color_name(obsolete
[index
][1], &rule
, prefix
? NULL
: &prefix
);
434 info
= add_line_rule(prefix
, &rule
);
438 return error("Unknown color name: %s", argv
[0]);
440 code
= error("%s has been replaced by %s",
441 obsolete
[index
][0], obsolete
[index
][1]);
444 if (!set_color(&info
->fg
, argv
[1]))
445 return error("Unknown color: %s", argv
[1]);
447 if (!set_color(&info
->bg
, argv
[2]))
448 return error("Unknown color: %s", argv
[2]);
454 if (!set_attribute(&attr
, argv
[argc
]))
455 return error("Unknown color attribute: %s", argv
[argc
]);
462 static enum status_code
463 parse_bool(bool *opt
, const char *arg
)
465 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
467 if (*opt
|| !strcmp(arg
, "0") || !strcmp(arg
, "false") || !strcmp(arg
, "no"))
469 return error("Non-boolean value treated as false: %s", arg
);
472 static enum status_code
473 parse_enum(const char *name
, unsigned int *opt
, const char *arg
,
474 const struct enum_map
*map
)
477 enum status_code code
;
479 assert(map
->size
> 1);
481 if (map_enum_do(map
->entries
, map
->size
, (int *) opt
, arg
))
484 code
= parse_bool(&is_true
, arg
);
485 *opt
= is_true
? map
->entries
[1].value
: map
->entries
[0].value
;
488 return error("'%s' is not a valid value for %s; using %s",
489 arg
, name
, enum_name(map
->entries
[*opt
].name
));
492 static enum status_code
493 parse_string(char *opt
, const char *arg
, size_t optsize
)
495 int arglen
= strlen(arg
);
500 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0])
501 return ERROR_UNMATCHED_QUOTATION
;
502 arg
+= 1; arglen
-= 2;
504 string_ncopy_do(opt
, optsize
, arg
, arglen
);
509 static enum status_code
510 parse_encoding(struct encoding
**encoding_ref
, const char *arg
, bool priority
)
512 char buf
[SIZEOF_STR
];
513 enum status_code code
= parse_string(buf
, arg
, sizeof(buf
));
515 if (code
== SUCCESS
) {
516 struct encoding
*encoding
= *encoding_ref
;
518 if (encoding
&& !priority
)
520 encoding
= encoding_open(buf
);
522 *encoding_ref
= encoding
;
528 static enum status_code
529 parse_args(const char ***args
, const char *argv
[])
531 if (!argv_copy(args
, argv
))
532 return ERROR_OUT_OF_MEMORY
;
537 parse_option(struct option_info
*option
, const char *prefix
, const char *arg
)
539 char name
[SIZEOF_STR
];
541 if (!enum_name_prefixed(name
, sizeof(name
), prefix
, option
->name
))
542 return error("Failed to parse option");
544 if (!strcmp("show-notes", name
)) {
545 bool *value
= option
->value
;
546 enum status_code res
;
548 if (parse_bool(option
->value
, arg
) == SUCCESS
)
552 string_copy(opt_notes_arg
, NOTES_EQ_ARG
);
553 res
= parse_string(opt_notes_arg
+ STRING_SIZE(NOTES_EQ_ARG
), arg
,
554 sizeof(opt_notes_arg
) - STRING_SIZE(NOTES_EQ_ARG
));
555 if (res
== SUCCESS
&& !opt_notes_arg
[STRING_SIZE(NOTES_EQ_ARG
)])
556 opt_notes_arg
[STRING_SIZE(NOTES_ARG
)] = 0;
560 if (!strcmp(option
->type
, "bool"))
561 return parse_bool(option
->value
, arg
);
563 if (!strcmp(option
->type
, "double"))
564 return parse_step(option
->value
, arg
);
566 if (!strncmp(option
->type
, "enum", 4)) {
567 const char *type
= option
->type
+ STRING_SIZE("enum ");
568 const struct enum_map
*map
= find_enum_map(type
);
570 return parse_enum(name
, option
->value
, arg
, map
);
573 if (!strcmp(option
->type
, "int")) {
574 if (strstr(name
, "title-overflow")) {
575 bool enabled
= FALSE
;
576 int *value
= option
->value
;
578 /* We try to parse it as a boolean (and set the
579 * value to 0 if fale), otherwise we parse it as
580 * an integer and use the given value. */
581 if (parse_bool(&enabled
, arg
) == SUCCESS
) {
590 if (!strcmp(name
, "line-number-interval") ||
591 !strcmp(name
, "tab-size"))
592 return parse_int(option
->value
, arg
, 1, 1024);
593 else if (!strcmp(name
, "id-width"))
594 return parse_int(option
->value
, arg
, 0, SIZEOF_REV
- 1);
596 return parse_int(option
->value
, arg
, 0, 1024);
599 return error("Unhandled option: %s", name
);
608 #define VIEW_CONFIG(name) { #name, STRING_SIZE(#name), &opt_ ## name }
609 static struct view_config view_configs
[] = {
610 VIEW_CONFIG(blame_view
),
611 VIEW_CONFIG(blob_view
),
612 VIEW_CONFIG(diff_view
),
613 VIEW_CONFIG(grep_view
),
614 VIEW_CONFIG(log_view
),
615 VIEW_CONFIG(main_view
),
616 VIEW_CONFIG(pager_view
),
617 VIEW_CONFIG(refs_view
),
618 VIEW_CONFIG(stage_view
),
619 VIEW_CONFIG(stash_view
),
620 VIEW_CONFIG(status_view
),
621 VIEW_CONFIG(tree_view
),
624 static enum status_code
625 check_view_config(const char *name
, const char *argv
[])
627 size_t namelen
= strlen(name
);
630 for (i
= 0; i
< ARRAY_SIZE(view_configs
); i
++)
631 if (enum_equals(view_configs
[i
], name
, namelen
))
632 return parse_view_config(enum_name(name
), argv
);
637 /* Wants: name = value */
638 static enum status_code
639 option_set_command(int argc
, const char *argv
[])
641 struct option_info
*option
;
644 return error("Invalid set command: set option = value");
646 if (strcmp(argv
[1], "="))
647 return error("No value assigned to %s", argv
[0]);
649 if (!strcmp(argv
[0], "reference-format"))
650 return parse_ref_formats(argv
+ 2);
652 option
= find_option_info(option_info
, ARRAY_SIZE(option_info
), "", argv
[0]);
654 enum status_code code
;
659 if (!strcmp(option
->type
, "const char **")) {
660 code
= check_view_config(option
->name
, argv
+ 2);
663 return parse_args(option
->value
, argv
+ 2);
666 code
= parse_option(option
, "", argv
[2]);
667 if (code
== SUCCESS
&& argc
!= 3)
668 return error("Option %s only takes one value", argv
[0]);
675 const char *obsolete
[][2] = {
676 { "author-width", "author" },
677 { "filename-width", "file-name" },
678 { "line-number-interval", "line-number" },
679 { "show-author", "author" },
680 { "show-date", "date" },
681 { "show-file-size", "file-size" },
682 { "show-filename", "file-name" },
684 { "show-line-numbers", "line-number" },
685 { "show-refs", "commit-title" },
686 { "show-rev-graph", "commit-title" },
687 { "title-overflow", "commit-title and text" },
689 int index
= find_remapped(obsolete
, ARRAY_SIZE(obsolete
), argv
[0]);
692 return error("%s is obsolete; see tigrc(5) for how to set the %s column option",
693 obsolete
[index
][0], obsolete
[index
][1]);
695 if (!strcmp(argv
[0], "read-git-colors"))
696 return error("read-git-colors has been obsoleted by the git-colors option");
699 return error("Unknown option name: %s", argv
[0]);
702 /* Wants: mode request key */
703 static enum status_code
704 option_bind_command(int argc
, const char *argv
[])
708 enum request request
;
709 struct keymap
*keymap
;
713 return error("Invalid key binding: bind keymap key action");
715 if (!(keymap
= get_keymap(argv
[0], strlen(argv
[0])))) {
716 if (!strcmp(argv
[0], "branch"))
717 keymap
= get_keymap("refs", strlen("refs"));
719 return error("Unknown key map: %s", argv
[0]);
722 for (keys
= 0, key_arg
= argv
[1]; *key_arg
&& keys
< ARRAY_SIZE(key
); keys
++) {
723 enum status_code code
= get_key_value(&key_arg
, &key
[keys
]);
729 if (*key_arg
&& keys
== ARRAY_SIZE(key
))
730 return error("Except for <Esc> combos only one key is allowed "
731 "in key combos: %s", argv
[1]);
733 request
= get_request(argv
[2]);
734 if (request
== REQ_UNKNOWN
) {
735 static const char *obsolete
[][2] = {
736 { "view-branch", "view-refs" },
738 static const char *toggles
[][2] = {
739 { "diff-context-down", "diff-context" },
740 { "diff-context-up", "diff-context" },
741 { "stage-next", ":/^@@" },
742 { "toggle-author", "author" },
743 { "toggle-changes", "show-changes" },
744 { "toggle-commit-order", "show-commit-order" },
745 { "toggle-date", "date" },
746 { "toggle-files", "file-filter" },
747 { "toggle-file-filter", "file-filter" },
748 { "toggle-file-size", "file-size" },
749 { "toggle-filename", "filename" },
750 { "toggle-graphic", "show-graphic" },
751 { "toggle-id", "id" },
752 { "toggle-ignore-space", "show-ignore-space" },
753 { "toggle-lineno", "line-number" },
754 { "toggle-refs", "commit-title-refs" },
755 { "toggle-rev-graph", "commit-title-graph" },
756 { "toggle-show-changes", "show-changes" },
757 { "toggle-sort-field", "sort-field" },
758 { "toggle-sort-order", "sort-order" },
759 { "toggle-title-overflow", "commit-title-overflow" },
760 { "toggle-untracked-dirs", "status-untracked-dirs" },
761 { "toggle-vertical-split", "show-vertical-split" },
765 alias
= find_remapped(obsolete
, ARRAY_SIZE(obsolete
), argv
[2]);
767 const char *action
= obsolete
[alias
][1];
769 add_keybinding(keymap
, get_request(action
), key
, keys
);
770 return error("%s has been renamed to %s",
771 obsolete
[alias
][0], action
);
774 alias
= find_remapped(toggles
, ARRAY_SIZE(toggles
), argv
[2]);
776 const char *action
= toggles
[alias
][0];
777 const char *arg
= prefixcmp(action
, "diff-context-")
778 ? NULL
: (strstr(action
, "-down") ? "-1" : "+1");
779 const char *mapped
= toggles
[alias
][1];
780 const char *toggle
[] = { ":toggle", mapped
, arg
, NULL
};
781 const char *other
[] = { mapped
, NULL
};
782 const char **prompt
= *mapped
== ':' ? other
: toggle
;
783 enum status_code code
= add_run_request(keymap
, key
, keys
, prompt
);
786 code
= error("%s has been replaced by `%s%s%s%s'",
787 action
, prompt
== other
? mapped
: ":toggle ",
788 prompt
== other
? "" : mapped
,
789 arg
? " " : "", arg
? arg
: "");
794 if (request
== REQ_UNKNOWN
)
795 return add_run_request(keymap
, key
, keys
, argv
+ 2);
797 return add_keybinding(keymap
, request
, key
, keys
);
801 static enum status_code
load_option_file(const char *path
);
803 static enum status_code
804 option_source_command(int argc
, const char *argv
[])
806 enum status_code code
;
809 return error("Invalid source command: source path");
811 code
= load_option_file(argv
[0]);
813 return code
== ERROR_FILE_DOES_NOT_EXIST
814 ? error("File does not exist: %s", argv
[0]) : code
;
818 set_option(const char *opt
, int argc
, const char *argv
[])
820 if (!strcmp(opt
, "color"))
821 return option_color_command(argc
, argv
);
823 if (!strcmp(opt
, "set"))
824 return option_set_command(argc
, argv
);
826 if (!strcmp(opt
, "bind"))
827 return option_bind_command(argc
, argv
);
829 if (!strcmp(opt
, "source"))
830 return option_source_command(argc
, argv
);
832 return error("Unknown option command: %s", opt
);
835 struct config_state
{
842 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
, void *data
)
844 struct config_state
*config
= data
;
845 enum status_code status
= ERROR_NO_OPTION_VALUE
;
847 /* Check for comment markers, since read_properties() will
848 * only ensure opt and value are split at first " \t". */
849 optlen
= strcspn(opt
, "#");
853 if (opt
[optlen
] == 0) {
854 /* Look for comment endings in the value. */
855 size_t len
= strcspn(value
, "#");
856 const char *argv
[SIZEOF_ARG
];
859 if (len
< valuelen
) {
864 if (!argv_from_string(argv
, &argc
, value
))
865 status
= error("Too many option arguments for %s", opt
);
867 status
= set_option(opt
, argc
, argv
);
870 if (status
!= SUCCESS
) {
871 warn("%s:%zu: %s", config
->path
, config
->lineno
,
872 get_status_message(status
));
873 config
->errors
= TRUE
;
876 /* Always keep going if errors are encountered. */
880 static enum status_code
881 load_option_file(const char *path
)
883 struct config_state config
= { path
, 0, FALSE
};
885 char buf
[SIZEOF_STR
];
887 /* Do not read configuration from stdin if set to "" */
888 if (!path
|| !strlen(path
))
891 if (!prefixcmp(path
, "~/")) {
892 const char *home
= getenv("HOME");
894 if (!home
|| !string_format(buf
, "%s/%s", home
, path
+ 2))
895 return error("Failed to expand ~ to user home directory");
899 /* It's OK that the file doesn't exist. */
900 if (!io_open(&io
, "%s", path
)) {
901 /* XXX: Must return ERROR_FILE_DOES_NOT_EXIST so missing
902 * system tigrc is detected properly. */
903 if (io_error(&io
) == ENOENT
)
904 return ERROR_FILE_DOES_NOT_EXIST
;
905 return error("Error loading file %s: %s", path
, strerror(io_error(&io
)));
908 if (io_load_span(&io
, " \t", &config
.lineno
, read_option
, &config
) == ERR
||
909 config
.errors
== TRUE
)
910 warn("Errors while loading %s.", path
);
914 extern const char *builtin_config
;
919 const char *tigrc_user
= getenv("TIGRC_USER");
920 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
921 const char *tig_diff_opts
= getenv("TIG_DIFF_OPTS");
922 const bool diff_opts_from_args
= !!opt_diff_options
;
923 bool custom_tigrc_system
= !!tigrc_system
;
925 opt_file_filter
= TRUE
;
926 if (!find_option_info_by_value(&opt_diff_context
)->seen
)
927 opt_diff_context
= -3;
929 if (!custom_tigrc_system
)
930 tigrc_system
= SYSCONFDIR
"/tigrc";
932 if (!*tigrc_system
||
933 (load_option_file(tigrc_system
) == ERROR_FILE_DOES_NOT_EXIST
&& !custom_tigrc_system
)) {
934 struct config_state config
= { "<built-in>", 0, FALSE
};
937 if (!io_from_string(&io
, builtin_config
))
938 die("Failed to get built-in config");
939 if (io_load_span(&io
, " \t", &config
.lineno
, read_option
, &config
) == ERR
|| config
.errors
== TRUE
)
940 die("Error in built-in config");
944 tigrc_user
= "~/.tigrc";
945 load_option_file(tigrc_user
);
947 if (!diff_opts_from_args
&& tig_diff_opts
&& *tig_diff_opts
) {
948 static const char *diff_opts
[SIZEOF_ARG
] = { NULL
};
949 char buf
[SIZEOF_STR
];
952 if (!string_format(buf
, "%s", tig_diff_opts
) ||
953 !argv_from_string(diff_opts
, &argc
, buf
))
954 die("TIG_DIFF_OPTS contains too many arguments");
955 else if (!argv_copy(&opt_diff_options
, diff_opts
))
956 die("Failed to format TIG_DIFF_OPTS arguments");
963 * Repository properties
967 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
969 if (!strcmp(name
, ".remote")) {
970 string_ncopy(repo
.remote
, value
, valuelen
);
972 } else if (*repo
.remote
&& !strcmp(name
, ".merge")) {
973 size_t from
= strlen(repo
.remote
);
975 if (!prefixcmp(value
, "refs/heads/"))
976 value
+= STRING_SIZE("refs/heads/");
978 if (!string_format_from(repo
.remote
, &from
, "/%s", value
))
984 set_repo_config_option(char *name
, char *value
, enum status_code (*cmd
)(int, const char **))
986 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
987 int argc
= 1 + (cmd
== option_set_command
);
988 enum status_code code
;
990 if (!argv_from_string(argv
, &argc
, value
))
991 code
= error("Too many arguments");
993 code
= cmd(argc
, argv
);
996 warn("Option 'tig.%s': %s", name
, get_status_message(code
));
1000 set_work_tree(const char *value
)
1002 char cwd
[SIZEOF_STR
];
1004 if (!getcwd(cwd
, sizeof(cwd
)))
1005 die("Failed to get cwd path: %s", strerror(errno
));
1007 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
1008 if (chdir(repo
.git_dir
) < 0)
1009 die("Failed to chdir(%s): %s", repo
.git_dir
, strerror(errno
));
1010 if (!getcwd(repo
.git_dir
, sizeof(repo
.git_dir
)))
1011 die("Failed to get git path: %s", strerror(errno
));
1012 if (chdir(value
) < 0)
1013 die("Failed to chdir(%s): %s", value
, strerror(errno
));
1014 if (!getcwd(cwd
, sizeof(cwd
)))
1015 die("Failed to get cwd path: %s", strerror(errno
));
1016 if (setenv("GIT_WORK_TREE", cwd
, TRUE
))
1017 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
1018 if (setenv("GIT_DIR", repo
.git_dir
, TRUE
))
1019 die("Failed to set GIT_DIR to '%s'", repo
.git_dir
);
1020 repo
.is_inside_work_tree
= TRUE
;
1023 static struct line_info
*
1024 parse_git_color_option(struct line_info
*info
, char *value
)
1026 const char *argv
[SIZEOF_ARG
];
1028 bool first_color
= TRUE
;
1031 if (!argv_from_string(argv
, &argc
, value
))
1034 info
->fg
= COLOR_DEFAULT
;
1035 info
->bg
= COLOR_DEFAULT
;
1038 for (i
= 0; i
< argc
; i
++) {
1041 if (set_attribute(&attr
, argv
[i
])) {
1044 } else if (set_color(&attr
, argv
[i
])) {
1049 first_color
= FALSE
;
1056 set_git_color_option(const char *name
, char *value
)
1058 struct line_info parsed
= {};
1059 struct line_info
*color
= NULL
;
1060 size_t namelen
= strlen(name
);
1063 if (!opt_git_colors
)
1066 for (i
= 0; opt_git_colors
[i
]; i
++) {
1067 struct line_rule rule
= {};
1068 const char *prefix
= NULL
;
1069 struct line_info
*info
;
1070 const char *alias
= opt_git_colors
[i
];
1071 const char *sep
= strchr(alias
, '=');
1073 if (!sep
|| namelen
!= sep
- alias
||
1074 string_enum_compare(name
, alias
, namelen
))
1078 color
= parse_git_color_option(&parsed
, value
);
1083 if (parse_color_name(sep
+ 1, &rule
, &prefix
) == SUCCESS
&&
1084 (info
= add_line_rule(prefix
, &rule
))) {
1085 info
->fg
= parsed
.fg
;
1086 info
->bg
= parsed
.bg
;
1087 info
->attr
= parsed
.attr
;
1093 set_encoding(struct encoding
**encoding_ref
, const char *arg
, bool priority
)
1095 if (!strcasecmp(arg
, "utf-8") || !strcasecmp(arg
, "utf8"))
1097 if (parse_encoding(encoding_ref
, arg
, priority
) == SUCCESS
)
1098 encoding_arg
[0] = 0;
1102 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
, void *data
)
1104 if (!strcmp(name
, "i18n.commitencoding"))
1105 set_encoding(&default_encoding
, value
, FALSE
);
1107 else if (!strcmp(name
, "gui.encoding"))
1108 set_encoding(&default_encoding
, value
, TRUE
);
1110 else if (!strcmp(name
, "core.editor"))
1111 string_ncopy(opt_editor
, value
, valuelen
);
1113 else if (!strcmp(name
, "core.worktree"))
1114 set_work_tree(value
);
1116 else if (!strcmp(name
, "core.abbrev"))
1117 parse_int(&opt_id_width
, value
, 0, SIZEOF_REV
- 1);
1119 else if (!prefixcmp(name
, "tig.color."))
1120 set_repo_config_option(name
+ 10, value
, option_color_command
);
1122 else if (!prefixcmp(name
, "tig.bind."))
1123 set_repo_config_option(name
+ 9, value
, option_bind_command
);
1125 else if (!prefixcmp(name
, "tig."))
1126 set_repo_config_option(name
+ 4, value
, option_set_command
);
1128 else if (!prefixcmp(name
, "color."))
1129 set_git_color_option(name
+ STRING_SIZE("color."), value
);
1131 else if (*repo
.head
&& !prefixcmp(name
, "branch.") &&
1132 !strncmp(name
+ 7, repo
.head
, strlen(repo
.head
)))
1133 set_remote_branch(name
+ 7 + strlen(repo
.head
), value
, valuelen
);
1135 else if (!strcmp(name
, "diff.context")) {
1136 if (!find_option_info_by_value(&opt_diff_context
)->seen
)
1137 opt_diff_context
= -atoi(value
);
1139 } else if (!strcmp(name
, "format.pretty")) {
1140 if (!prefixcmp(value
, "format:") && strstr(value
, "%C("))
1141 argv_append(&opt_log_options
, "--pretty=medium");
1148 load_git_config(void)
1150 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
1152 return io_run_load(config_list_argv
, "=", read_repo_config_option
, NULL
);
1155 /* vim: set ts=8 sw=8 noexpandtab: */