1 /* Copyright (c) 2006-2015 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 (!strcmp(option
[i
].type
, "view_settings") &&
53 enum_equals_prefix(option
[i
], name
, namelen
))
56 if (enum_equals(option
[i
], name
, namelen
))
59 if (enum_name_prefixed(prefixed
, sizeof(prefixed
), prefix
, option
[i
].name
) &&
60 namelen
== strlen(prefixed
) &&
61 !string_enum_compare(prefixed
, name
, namelen
))
68 static struct option_info
*
69 find_option_info_by_value(void *value
)
73 for (i
= 0; i
< ARRAY_SIZE(option_info
); i
++)
74 if (option_info
[i
].value
== value
)
75 return &option_info
[i
];
81 mark_option_seen(void *value
)
83 struct option_info
*option
= find_option_info_by_value(value
);
90 find_column_option_info(enum view_column_type type
, union view_column_options
*opts
,
91 const char *option
, struct option_info
*column_info
,
92 const char **column_name
)
94 #define DEFINE_COLUMN_OPTION_INFO(name, type, flags) \
95 { #name, STRING_SIZE(#name), #type, &opt->name, flags },
97 #define DEFINE_COLUMN_OPTION_INFO_CHECK(name, id, options) \
98 if (type == VIEW_COLUMN_##id) { \
99 struct name##_options *opt = &opts->name; \
100 struct option_info info[] = { \
101 options(DEFINE_COLUMN_OPTION_INFO) \
103 struct option_info *match; \
104 match = find_option_info(info, ARRAY_SIZE(info), #name, option); \
106 *column_info = *match; \
107 *column_name = #name; \
108 return column_info; \
112 COLUMN_OPTIONS(DEFINE_COLUMN_OPTION_INFO_CHECK
);
122 iconv_t opt_iconv_out
= ICONV_NONE
;
123 char opt_editor
[SIZEOF_STR
] = "";
124 const char **opt_cmdline_args
= NULL
;
127 * Mapping between options and command argument mapping.
133 static char opt_diff_context_arg
[9] = "";
135 if (opt_diff_context
< 0 ||
136 !string_format(opt_diff_context_arg
, "-U%u", opt_diff_context
))
139 return opt_diff_context_arg
;
145 return opt_mailmap
? "--use-mailmap" : "";
149 log_custom_pretty_arg(void)
152 ? "--pretty=format:commit %m %H %P%x00%aN <%aE> %ad%x00%s"
153 : "--pretty=format:commit %m %H %P%x00%an <%ae> %ad%x00%s";
156 #define ENUM_ARG(enum_name, arg_string) ENUM_MAP_ENTRY(arg_string, enum_name)
158 static const struct enum_map_entry ignore_space_arg_map
[] = {
159 ENUM_ARG(IGNORE_SPACE_NO
, ""),
160 ENUM_ARG(IGNORE_SPACE_ALL
, "--ignore-all-space"),
161 ENUM_ARG(IGNORE_SPACE_SOME
, "--ignore-space-change"),
162 ENUM_ARG(IGNORE_SPACE_AT_EOL
, "--ignore-space-at-eol"),
168 return ignore_space_arg_map
[opt_ignore_space
].name
;
171 static const struct enum_map_entry commit_order_arg_map
[] = {
172 ENUM_ARG(COMMIT_ORDER_AUTO
, ""),
173 ENUM_ARG(COMMIT_ORDER_DEFAULT
, ""),
174 ENUM_ARG(COMMIT_ORDER_TOPO
, "--topo-order"),
175 ENUM_ARG(COMMIT_ORDER_DATE
, "--date-order"),
176 ENUM_ARG(COMMIT_ORDER_AUTHOR_DATE
, "--author-date-order"),
177 ENUM_ARG(COMMIT_ORDER_REVERSE
, "--reverse"),
183 return commit_order_arg_map
[opt_commit_order
].name
;
187 commit_order_arg_with_graph(enum graph_display graph_display
)
189 enum commit_order commit_order
= opt_commit_order
;
191 if (commit_order
== COMMIT_ORDER_AUTO
&&
192 graph_display
!= GRAPH_DISPLAY_NO
)
193 commit_order
= COMMIT_ORDER_TOPO
;
195 return commit_order_arg_map
[commit_order
].name
;
198 /* Use --show-notes to support Git >= 1.7.6 */
199 #define NOTES_ARG "--show-notes"
200 #define NOTES_EQ_ARG NOTES_ARG "="
202 static char opt_notes_arg
[SIZEOF_STR
] = NOTES_ARG
;
208 return opt_notes_arg
;
209 /* Notes are disabled by default when passing --pretty args. */
214 update_options_from_argv(const char *argv
[])
218 for (next
= flags_pos
= 0; argv
[next
]; next
++) {
219 const char *flag
= argv
[next
];
222 if (map_enum(&value
, commit_order_arg_map
, flag
)) {
223 opt_commit_order
= value
;
224 mark_option_seen(&opt_commit_order
);
228 if (map_enum(&value
, ignore_space_arg_map
, flag
)) {
229 opt_ignore_space
= value
;
230 mark_option_seen(&opt_ignore_space
);
234 if (!strcmp(flag
, "--no-notes")) {
235 opt_show_notes
= false;
236 mark_option_seen(&opt_show_notes
);
240 if (!prefixcmp(flag
, "--show-notes") ||
241 !prefixcmp(flag
, "--notes")) {
242 opt_show_notes
= true;
243 string_ncopy(opt_notes_arg
, flag
, strlen(flag
));
244 mark_option_seen(&opt_show_notes
);
248 if (!prefixcmp(flag
, "-U")
249 && parse_int(&value
, flag
+ 2, 0, 999999) == SUCCESS
) {
250 opt_diff_context
= value
;
251 mark_option_seen(&opt_diff_context
);
255 argv
[flags_pos
++] = flag
;
258 argv
[flags_pos
] = NULL
;
262 * User config file handling.
265 static const struct enum_map_entry color_map
[] = {
266 #define COLOR_MAP(name) ENUM_MAP_ENTRY(#name, COLOR_##name)
278 static const struct enum_map_entry attr_map
[] = {
279 #define ATTR_MAP(name) ENUM_MAP_ENTRY(#name, A_##name)
289 #define set_attribute(attr, name) map_enum(attr, attr_map, name)
292 parse_step(double *opt
, const char *arg
)
294 int value
= atoi(arg
);
296 if (!value
&& !isdigit(*arg
))
297 return error("Invalid double or percentage");
300 if (!strchr(arg
, '%'))
303 /* "Shift down" so 100% and 1 does not conflict. */
307 return error("Percentage is larger than 100%%");
311 return error("Percentage is less than 0%%");
317 parse_int(int *opt
, const char *arg
, int min
, int max
)
319 int value
= atoi(arg
);
321 if (min
<= value
&& value
<= max
) {
326 return error("Value must be between %d and %d", min
, max
);
330 set_color(int *color
, const char *name
)
332 if (map_enum(color
, color_map
, name
))
334 /* Git expects a plain int w/o prefix, however, color<int> is
335 * the preferred Tig color notation. */
336 if (!prefixcmp(name
, "color"))
338 return string_isnumber(name
) &&
339 parse_int(color
, name
, 0, 255) == SUCCESS
;
342 #define is_quoted(c) ((c) == '"' || (c) == '\'')
344 static enum status_code
345 parse_color_name(const char *color
, struct line_rule
*rule
, const char **prefix_ptr
)
347 const char *prefixend
= is_quoted(*color
) ? NULL
: strchr(color
, '.');
350 struct keymap
*keymap
= get_keymap(color
, prefixend
- color
);
353 return error("Unknown key map: %.*s", (int) (prefixend
- color
), color
);
355 *prefix_ptr
= keymap
->name
;
356 color
= prefixend
+ 1;
359 memset(rule
, 0, sizeof(*rule
));
360 if (is_quoted(*color
)) {
361 rule
->line
= color
+ 1;
362 rule
->linelen
= strlen(color
) - 2;
365 rule
->namelen
= strlen(color
);
372 find_remapped(const char *remapped
[][2], size_t remapped_size
, const char *arg
)
374 size_t arglen
= strlen(arg
);
377 for (i
= 0; i
< remapped_size
; i
++) {
378 const char *name
= remapped
[i
][0];
379 size_t namelen
= strlen(name
);
381 if (arglen
== namelen
&&
382 !string_enum_compare(arg
, name
, namelen
))
389 /* Wants: object fgcolor bgcolor [attribute] */
390 static enum status_code
391 option_color_command(int argc
, const char *argv
[])
393 struct line_rule rule
= {0};
394 const char *prefix
= NULL
;
395 struct line_info
*info
;
396 enum status_code code
;
399 return error("Invalid color mapping: color area fgcolor bgcolor [attrs]");
401 code
= parse_color_name(argv
[0], &rule
, &prefix
);
405 info
= add_line_rule(prefix
, &rule
);
407 static const char *obsolete
[][2] = {
408 { "acked", "' Acked-by'" },
409 { "diff-copy-from", "'copy from '" },
410 { "diff-copy-to", "'copy to '" },
411 { "diff-deleted-file-mode", "'deleted file mode '" },
412 { "diff-dissimilarity", "'dissimilarity '" },
413 { "diff-rename-from", "'rename from '" },
414 { "diff-rename-to", "'rename to '" },
415 { "diff-tree", "'diff-tree '" },
416 { "filename", "file" },
417 { "help-keymap", "help.section" },
418 { "main-revgraph", "" },
419 { "pp-adate", "'AuthorDate: '" },
420 { "pp-author", "'Author: '" },
421 { "pp-cdate", "'CommitDate: '" },
422 { "pp-commit", "'Commit: '" },
423 { "pp-date", "'Date: '" },
424 { "reviewed", "' Reviewed-by'" },
425 { "signoff", "' Signed-off-by'" },
426 { "stat-head", "status.header" },
427 { "stat-section", "status.section" },
428 { "tested", "' Tested-by'" },
429 { "tree-dir", "tree.directory" },
430 { "tree-file", "tree.file" },
431 { "tree-head", "tree.header" },
435 index
= find_remapped(obsolete
, ARRAY_SIZE(obsolete
), rule
.name
);
437 if (!*obsolete
[index
][1])
438 return error("%s is obsolete", argv
[0]);
439 /* Keep the initial prefix if defined. */
440 code
= parse_color_name(obsolete
[index
][1], &rule
, prefix
? NULL
: &prefix
);
443 info
= add_line_rule(prefix
, &rule
);
447 return error("Unknown color name: %s", argv
[0]);
449 code
= error("%s has been replaced by %s",
450 obsolete
[index
][0], obsolete
[index
][1]);
453 if (!set_color(&info
->fg
, argv
[1]))
454 return error("Unknown color: %s", argv
[1]);
456 if (!set_color(&info
->bg
, argv
[2]))
457 return error("Unknown color: %s", argv
[2]);
463 if (!set_attribute(&attr
, argv
[argc
]))
464 return error("Unknown color attribute: %s", argv
[argc
]);
471 static enum status_code
472 parse_bool(bool *opt
, const char *arg
)
474 *opt
= (!strcmp(arg
, "1") || !strcmp(arg
, "true") || !strcmp(arg
, "yes"))
476 if (*opt
|| !strcmp(arg
, "0") || !strcmp(arg
, "false") || !strcmp(arg
, "no"))
478 return error("Non-boolean value treated as false: %s", arg
);
481 static enum status_code
482 parse_enum(const char *name
, unsigned int *opt
, const char *arg
,
483 const struct enum_map
*map
)
486 enum status_code code
;
488 assert(map
->size
> 1);
490 if (map_enum_do(map
->entries
, map
->size
, (int *) opt
, arg
))
493 code
= parse_bool(&is_true
, arg
);
494 *opt
= is_true
? map
->entries
[1].value
: map
->entries
[0].value
;
498 if (!strcmp(name
, "date-display")) {
499 const char *msg
= "";
501 if (!strcasecmp(arg
, "local"))
502 msg
= ", use the 'date-local' column option";
503 else if (!strcasecmp(arg
, "short"))
504 msg
= ", use the 'custom' display mode and set 'date-format'";
506 *opt
= map
->entries
[1].value
;
507 return error("'%s' is no longer supported for %s%s", arg
, name
, msg
);
510 return error("'%s' is not a valid value for %s; using %s",
511 arg
, name
, enum_name(map
->entries
[*opt
].name
));
514 static enum status_code
515 parse_string(char *opt
, const char *arg
, size_t optsize
)
517 int arglen
= strlen(arg
);
522 if (arglen
== 1 || arg
[arglen
- 1] != arg
[0])
523 return ERROR_UNMATCHED_QUOTATION
;
524 arg
+= 1; arglen
-= 2;
526 string_ncopy_do(opt
, optsize
, arg
, arglen
);
531 static enum status_code
532 parse_encoding(struct encoding
**encoding_ref
, const char *arg
, bool priority
)
534 char buf
[SIZEOF_STR
];
535 enum status_code code
= parse_string(buf
, arg
, sizeof(buf
));
537 if (code
== SUCCESS
) {
538 struct encoding
*encoding
= *encoding_ref
;
540 if (encoding
&& !priority
)
542 encoding
= encoding_open(buf
);
544 *encoding_ref
= encoding
;
550 static enum status_code
551 parse_args(const char ***args
, const char *argv
[])
553 if (!argv_copy(args
, argv
))
554 return ERROR_OUT_OF_MEMORY
;
559 parse_option(struct option_info
*option
, const char *prefix
, const char *arg
)
561 char name
[SIZEOF_STR
];
563 if (!enum_name_prefixed(name
, sizeof(name
), prefix
, option
->name
))
564 return error("Failed to parse option");
566 if (!strcmp("show-notes", name
)) {
567 bool *value
= option
->value
;
568 enum status_code res
;
570 if (parse_bool(option
->value
, arg
) == SUCCESS
)
574 string_copy(opt_notes_arg
, NOTES_EQ_ARG
);
575 res
= parse_string(opt_notes_arg
+ STRING_SIZE(NOTES_EQ_ARG
), arg
,
576 sizeof(opt_notes_arg
) - STRING_SIZE(NOTES_EQ_ARG
));
577 if (res
== SUCCESS
&& !opt_notes_arg
[STRING_SIZE(NOTES_EQ_ARG
)])
578 opt_notes_arg
[STRING_SIZE(NOTES_ARG
)] = 0;
582 if (!strcmp(option
->type
, "bool"))
583 return parse_bool(option
->value
, arg
);
585 if (!strcmp(option
->type
, "double"))
586 return parse_step(option
->value
, arg
);
588 if (!strncmp(option
->type
, "enum", 4)) {
589 const char *type
= option
->type
+ STRING_SIZE("enum ");
590 const struct enum_map
*map
= find_enum_map(type
);
592 return parse_enum(name
, option
->value
, arg
, map
);
595 if (!strcmp(option
->type
, "int")) {
596 if (strstr(name
, "title-overflow")) {
597 bool enabled
= false;
598 int *value
= option
->value
;
600 /* We try to parse it as a boolean (and set the
601 * value to 0 if fale), otherwise we parse it as
602 * an integer and use the given value. */
603 if (parse_bool(&enabled
, arg
) == SUCCESS
) {
612 if (!strcmp(name
, "line-number-interval") ||
613 !strcmp(name
, "tab-size"))
614 return parse_int(option
->value
, arg
, 1, 1024);
615 else if (!strcmp(name
, "id-width"))
616 return parse_int(option
->value
, arg
, 0, SIZEOF_REV
- 1);
618 return parse_int(option
->value
, arg
, 0, 1024);
621 if (!strcmp(option
->type
, "const char *")) {
622 const char *alloc
= NULL
;
623 const char **value
= option
->value
;
626 if (arg
[0] == '"' && arg
[strlen(arg
) - 1] == '"')
627 alloc
= strndup(arg
+ 1, strlen(arg
+ 1) - 1);
631 return ERROR_OUT_OF_MEMORY
;
634 free((void *) *value
);
639 return error("Unhandled option: %s", name
);
642 static enum status_code
643 parse_view_settings(struct view_column
**view_column
, const char *name_
, const char *argv
[])
645 char buf
[SIZEOF_STR
];
646 const char *name
= enum_name_copy(buf
, sizeof(buf
), name_
) ? buf
: name_
;
647 const char *prefixed
;
649 if ((prefixed
= strstr(name
, "-view-"))) {
650 const char *column_name
= prefixed
+ STRING_SIZE("-view-");
651 size_t column_namelen
= strlen(column_name
);
652 enum view_column_type type
;
654 for (type
= 0; type
< view_column_type_map
->size
; type
++) {
655 const struct enum_map_entry
*column
= &view_column_type_map
->entries
[type
];
657 if (enum_equals(*column
, column_name
, column_namelen
))
658 return parse_view_column_config(name
, type
, NULL
, argv
);
660 if (enum_equals_prefix(*column
, column_name
, column_namelen
))
661 return parse_view_column_config(name
, type
,
662 column_name
+ column
->namelen
+ 1,
667 return parse_view_config(view_column
, name
, argv
);
670 /* Wants: name = value */
671 static enum status_code
672 option_set_command(int argc
, const char *argv
[])
674 struct option_info
*option
;
675 enum status_code code
;
678 return error("Invalid set command: set option = value");
680 if (strcmp(argv
[1], "="))
681 return error("No value assigned to %s", argv
[0]);
683 option
= find_option_info(option_info
, ARRAY_SIZE(option_info
), "", argv
[0]);
688 if (!strcmp(option
->type
, "const char **"))
689 return parse_args(option
->value
, argv
+ 2);
692 return error("Invalid set command: set option = value");
694 if (!strcmp(option
->type
, "view_settings"))
695 return parse_view_settings(option
->value
, argv
[0], argv
+ 2);
697 if (!strcmp(option
->type
, "struct ref_format **"))
698 return parse_ref_formats(option
->value
, argv
+ 2);
700 code
= parse_option(option
, "", argv
[2]);
701 if (code
== SUCCESS
&& argc
!= 3)
702 return error("Option %s only takes one value", argv
[0]);
709 const char *obsolete
[][2] = {
710 { "author-width", "author" },
711 { "filename-width", "file-name" },
712 { "line-number-interval", "line-number" },
713 { "show-author", "author" },
714 { "show-date", "date" },
715 { "show-file-size", "file-size" },
716 { "show-filename", "file-name" },
718 { "show-line-numbers", "line-number" },
719 { "show-refs", "commit-title" },
720 { "show-rev-graph", "commit-title" },
721 { "title-overflow", "commit-title and text" },
723 int index
= find_remapped(obsolete
, ARRAY_SIZE(obsolete
), argv
[0]);
726 return error("%s is obsolete; see tigrc(5) for how to set the %s column option",
727 obsolete
[index
][0], obsolete
[index
][1]);
729 if (!strcmp(argv
[0], "read-git-colors"))
730 return error("read-git-colors has been obsoleted by the git-colors option");
732 if (!strcmp(argv
[0], "cmdline-args"))
733 return error("cmdline-args is obsolete; use view-specific options instead, e.g. main-options");
736 return error("Unknown option name: %s", argv
[0]);
739 /* Wants: mode request key */
740 static enum status_code
741 option_bind_command(int argc
, const char *argv
[])
745 enum request request
;
746 struct keymap
*keymap
;
750 return error("Invalid key binding: bind keymap key action");
752 if (!(keymap
= get_keymap(argv
[0], strlen(argv
[0])))) {
753 if (!strcmp(argv
[0], "branch"))
754 keymap
= get_keymap("refs", strlen("refs"));
756 return error("Unknown key map: %s", argv
[0]);
759 for (keys
= 0, key_arg
= argv
[1]; *key_arg
&& keys
< ARRAY_SIZE(key
); keys
++) {
760 enum status_code code
= get_key_value(&key_arg
, &key
[keys
]);
766 if (*key_arg
&& keys
== ARRAY_SIZE(key
))
767 return error("Except for <Esc> combos only one key is allowed "
768 "in key combos: %s", argv
[1]);
770 request
= get_request(argv
[2]);
771 if (request
== REQ_UNKNOWN
) {
772 static const char *obsolete
[][2] = {
773 { "view-branch", "view-refs" },
775 static const char *toggles
[][2] = {
776 { "diff-context-down", "diff-context" },
777 { "diff-context-up", "diff-context" },
778 { "stage-next", ":/^@@" },
779 { "toggle-author", "author" },
780 { "toggle-changes", "show-changes" },
781 { "toggle-commit-order", "show-commit-order" },
782 { "toggle-date", "date" },
783 { "toggle-files", "file-filter" },
784 { "toggle-file-filter", "file-filter" },
785 { "toggle-file-size", "file-size" },
786 { "toggle-filename", "filename" },
787 { "toggle-graphic", "show-graphic" },
788 { "toggle-id", "id" },
789 { "toggle-ignore-space", "show-ignore-space" },
790 { "toggle-lineno", "line-number" },
791 { "toggle-refs", "commit-title-refs" },
792 { "toggle-rev-graph", "commit-title-graph" },
793 { "toggle-show-changes", "show-changes" },
794 { "toggle-sort-field", "sort-field" },
795 { "toggle-sort-order", "sort-order" },
796 { "toggle-title-overflow", "commit-title-overflow" },
797 { "toggle-untracked-dirs", "status-untracked-dirs" },
798 { "toggle-vertical-split", "show-vertical-split" },
802 alias
= find_remapped(obsolete
, ARRAY_SIZE(obsolete
), argv
[2]);
804 const char *action
= obsolete
[alias
][1];
806 add_keybinding(keymap
, get_request(action
), key
, keys
);
807 return error("%s has been renamed to %s",
808 obsolete
[alias
][0], action
);
811 alias
= find_remapped(toggles
, ARRAY_SIZE(toggles
), argv
[2]);
813 const char *action
= toggles
[alias
][0];
814 const char *arg
= prefixcmp(action
, "diff-context-")
815 ? NULL
: (strstr(action
, "-down") ? "-1" : "+1");
816 const char *mapped
= toggles
[alias
][1];
817 const char *toggle
[] = { ":toggle", mapped
, arg
, NULL
};
818 const char *other
[] = { mapped
, NULL
};
819 const char **prompt
= *mapped
== ':' ? other
: toggle
;
820 enum status_code code
= add_run_request(keymap
, key
, keys
, prompt
);
823 code
= error("%s has been replaced by `%s%s%s%s'",
824 action
, prompt
== other
? mapped
: ":toggle ",
825 prompt
== other
? "" : mapped
,
826 arg
? " " : "", arg
? arg
: "");
831 if (request
== REQ_UNKNOWN
)
832 return add_run_request(keymap
, key
, keys
, argv
+ 2);
834 return add_keybinding(keymap
, request
, key
, keys
);
838 static enum status_code
load_option_file(const char *path
);
840 static enum status_code
841 option_source_command(int argc
, const char *argv
[])
843 enum status_code code
;
846 return error("Invalid source command: source path");
848 code
= load_option_file(argv
[0]);
850 return code
== ERROR_FILE_DOES_NOT_EXIST
851 ? error("File does not exist: %s", argv
[0]) : code
;
855 set_option(const char *opt
, int argc
, const char *argv
[])
857 if (!strcmp(opt
, "color"))
858 return option_color_command(argc
, argv
);
860 if (!strcmp(opt
, "set"))
861 return option_set_command(argc
, argv
);
863 if (!strcmp(opt
, "bind"))
864 return option_bind_command(argc
, argv
);
866 if (!strcmp(opt
, "source"))
867 return option_source_command(argc
, argv
);
869 return error("Unknown option command: %s", opt
);
872 struct config_state
{
878 static enum status_code
879 read_option(char *opt
, size_t optlen
, char *value
, size_t valuelen
, void *data
)
881 struct config_state
*config
= data
;
882 enum status_code status
= ERROR_NO_OPTION_VALUE
;
884 /* Check for comment markers, since read_properties() will
885 * only ensure opt and value are split at first " \t". */
886 optlen
= strcspn(opt
, "#");
890 if (opt
[optlen
] == 0) {
891 /* Look for comment endings in the value. */
892 size_t len
= strcspn(value
, "#");
893 const char *argv
[SIZEOF_ARG
];
896 if (len
< valuelen
) {
901 if (!argv_from_string(argv
, &argc
, value
))
902 status
= error("Too many option arguments for %s", opt
);
904 status
= set_option(opt
, argc
, argv
);
907 if (status
!= SUCCESS
) {
908 warn("%s:%zu: %s", config
->path
, config
->lineno
,
909 get_status_message(status
));
910 config
->errors
= true;
913 /* Always keep going if errors are encountered. */
917 static enum status_code
918 load_option_file(const char *path
)
920 struct config_state config
= { path
, 0, false };
922 char buf
[SIZEOF_STR
];
924 /* Do not read configuration from stdin if set to "" */
925 if (!path
|| !strlen(path
))
928 if (!prefixcmp(path
, "~/")) {
929 const char *home
= getenv("HOME");
931 if (!home
|| !string_format(buf
, "%s/%s", home
, path
+ 2))
932 return error("Failed to expand ~ to user home directory");
936 /* It's OK that the file doesn't exist. */
937 if (!io_open(&io
, "%s", path
)) {
938 /* XXX: Must return ERROR_FILE_DOES_NOT_EXIST so missing
939 * system tigrc is detected properly. */
940 if (io_error(&io
) == ENOENT
)
941 return ERROR_FILE_DOES_NOT_EXIST
;
942 return error("Error loading file %s: %s", path
, io_strerror(&io
));
945 if (io_load_span(&io
, " \t", &config
.lineno
, read_option
, &config
) != SUCCESS
||
946 config
.errors
== true)
947 warn("Errors while loading %s.", path
);
951 extern const char *builtin_config
;
956 const char *tigrc_user
= getenv("TIGRC_USER");
957 const char *tigrc_system
= getenv("TIGRC_SYSTEM");
958 const char *tig_diff_opts
= getenv("TIG_DIFF_OPTS");
959 const bool diff_opts_from_args
= !!opt_diff_options
;
960 bool custom_tigrc_system
= !!tigrc_system
;
961 char buf
[SIZEOF_STR
];
963 opt_file_filter
= true;
964 if (!find_option_info_by_value(&opt_diff_context
)->seen
)
965 opt_diff_context
= -3;
967 if (!custom_tigrc_system
)
968 tigrc_system
= SYSCONFDIR
"/tigrc";
970 if (!*tigrc_system
||
971 (load_option_file(tigrc_system
) == ERROR_FILE_DOES_NOT_EXIST
&& !custom_tigrc_system
)) {
972 struct config_state config
= { "<built-in>", 0, false };
975 if (!io_from_string(&io
, builtin_config
))
976 return error("Failed to get built-in config");
977 if (io_load_span(&io
, " \t", &config
.lineno
, read_option
, &config
) != SUCCESS
|| config
.errors
== true)
978 return error("Error in built-in config");
982 const char *xdg_config_home
= getenv("XDG_CONFIG_HOME");
984 if (!xdg_config_home
)
985 tigrc_user
= TIG_USER_CONFIG
;
986 else if (!string_format(buf
, "%s/tig/config", xdg_config_home
))
987 return error("Failed to expand $XDG_CONFIG_HOME");
992 load_option_file(tigrc_user
);
994 if (!diff_opts_from_args
&& tig_diff_opts
&& *tig_diff_opts
) {
995 static const char *diff_opts
[SIZEOF_ARG
] = { NULL
};
996 char buf
[SIZEOF_STR
];
999 if (!string_format(buf
, "%s", tig_diff_opts
) ||
1000 !argv_from_string(diff_opts
, &argc
, buf
))
1001 return error("TIG_DIFF_OPTS contains too many arguments");
1002 else if (!argv_copy(&opt_diff_options
, diff_opts
))
1003 return error("Failed to format TIG_DIFF_OPTS arguments");
1010 format_option_value(const struct option_info
*option
, char buf
[], size_t bufsize
)
1014 if (!strcmp(option
->type
, "bool")) {
1015 bool *opt
= option
->value
;
1017 if (string_nformat(buf
, bufsize
, NULL
, "%s", *opt
? "yes" : "no"))
1020 } else if (!strncmp(option
->type
, "enum", 4)) {
1021 const char *type
= option
->type
+ STRING_SIZE("enum ");
1022 enum author
*opt
= option
->value
;
1023 const struct enum_map
*map
= find_enum_map(type
);
1025 if (enum_name_copy(buf
, bufsize
, map
->entries
[*opt
].name
))
1028 } else if (!strcmp(option
->type
, "int")) {
1029 int *opt
= option
->value
;
1031 if (opt
== &opt_diff_context
&& *opt
< 0)
1034 if (string_nformat(buf
, bufsize
, NULL
, "%d", *opt
))
1037 } else if (!strcmp(option
->type
, "double")) {
1038 double *opt
= option
->value
;
1041 if (string_nformat(buf
, bufsize
, NULL
, "%d", (int) *opt
))
1044 } else if (string_nformat(buf
, bufsize
, NULL
, "%.0f%%", (*opt
) * 100)) {
1048 } else if (!strcmp(option
->type
, "const char *")) {
1049 const char **opt
= option
->value
;
1054 if (!string_nformat(buf
, bufsize
, &bufpos
, "\"%s\"", *opt
))
1058 } else if (!strcmp(option
->type
, "const char **")) {
1059 const char *sep
= "";
1060 const char ***opt
= option
->value
;
1064 for (i
= 0; (*opt
) && (*opt
)[i
]; i
++) {
1065 const char *arg
= (*opt
)[i
];
1067 if (!string_nformat(buf
, bufsize
, &bufpos
, "%s%s", sep
, arg
))
1075 } else if (!strcmp(option
->type
, "struct ref_format **")) {
1076 struct ref_format
***opt
= option
->value
;
1078 if (format_ref_formats(*opt
, buf
, bufsize
) == SUCCESS
)
1081 } else if (!strcmp(option
->type
, "view_settings")) {
1082 struct view_column
**opt
= option
->value
;
1084 if (format_view_config(*opt
, buf
, bufsize
) == SUCCESS
)
1088 if (string_nformat(buf
, bufsize
, NULL
, "<%s>", option
->type
))
1096 save_option_settings(FILE *file
)
1098 char buf
[SIZEOF_STR
];
1101 if (!io_fprintf(file
, "%s", "\n## Settings\n"))
1104 for (i
= 0; i
< ARRAY_SIZE(option_info
); i
++) {
1105 struct option_info
*option
= &option_info
[i
];
1106 const char *name
= enum_name(option
->name
);
1107 const char *value
= format_option_value(option
, buf
, sizeof(buf
));
1112 if (!suffixcmp(name
, strlen(name
), "-args"))
1115 if (!io_fprintf(file
, "\nset %-25s = %s", name
, value
))
1123 save_option_keybinding(void *data
, const char *group
, struct keymap
*keymap
,
1124 enum request request
, const char *key
,
1125 const struct request_info
*req_info
,
1126 const struct run_request
*run_req
)
1130 if (group
&& !io_fprintf(file
, "\n# %s", group
))
1133 if (!io_fprintf(file
, "\nbind %-10s %-15s ", enum_name(keymap
->name
), key
))
1137 return io_fprintf(file
, "%s", enum_name(req_info
->name
));
1140 const char *sep
= format_run_request_flags(run_req
);
1143 for (i
= 0; run_req
->argv
[i
]; i
++) {
1144 if (!io_fprintf(file
, "%s%s", sep
, run_req
->argv
[i
]))
1154 save_option_keybindings(FILE *file
)
1156 if (!io_fprintf(file
, "%s", "\n\n## Keybindings\n"))
1159 return foreach_key(save_option_keybinding
, file
, false);
1163 save_option_color_name(FILE *file
, int color
)
1167 for (i
= 0; i
< ARRAY_SIZE(color_map
); i
++)
1168 if (color_map
[i
].value
== color
)
1169 return io_fprintf(file
, " %-8s", enum_name(color_map
[i
].name
));
1171 return io_fprintf(file
, " color%d", color
);
1175 save_option_color_attr(FILE *file
, int attr
)
1179 for (i
= 0; i
< ARRAY_SIZE(attr_map
); i
++)
1180 if ((attr
& attr_map
[i
].value
) &&
1181 !io_fprintf(file
, " %s", enum_name(attr_map
[i
].name
)))
1188 save_option_color(void *data
, const struct line_rule
*rule
)
1191 const struct line_info
*info
;
1193 for (info
= &rule
->info
; info
; info
= info
->next
) {
1194 const char *prefix
= info
->prefix
? info
->prefix
: "";
1195 const char *prefix_sep
= info
->prefix
? "." : "";
1196 const char *quote
= *rule
->line
? "\"" : "";
1197 const char *name
= *rule
->line
? rule
->line
: enum_name(rule
->name
);
1198 int name_width
= strlen(prefix
) + strlen(prefix_sep
) + 2 * strlen(quote
) + strlen(name
);
1199 int padding
= name_width
> 30 ? 0 : 30 - name_width
;
1201 if (!io_fprintf(file
, "\ncolor %s%s%s%s%s%-*s",
1202 prefix
, prefix_sep
, quote
, name
, quote
, padding
, "")
1203 || !save_option_color_name(file
, info
->fg
)
1204 || !save_option_color_name(file
, info
->bg
)
1205 || !save_option_color_attr(file
, info
->attr
))
1213 save_option_colors(FILE *file
)
1215 if (!io_fprintf(file
, "%s", "\n\n## Colors\n"))
1218 return foreach_line_rule(save_option_color
, file
);
1222 save_options(const char *path
)
1224 int fd
= open(path
, O_WRONLY
| O_CREAT
| O_EXCL
, 0666);
1225 FILE *file
= fd
!= -1 ? fdopen(fd
, "w") : NULL
;
1226 enum status_code code
= SUCCESS
;
1229 return error("%s", strerror(errno
));
1231 if (!io_fprintf(file
, "%s", "# Saved by Tig\n")
1232 || !save_option_settings(file
)
1233 || !save_option_keybindings(file
)
1234 || !save_option_colors(file
))
1235 code
= error("Write returned an error");
1242 * Repository properties
1246 set_remote_branch(const char *name
, const char *value
, size_t valuelen
)
1248 if (!strcmp(name
, ".remote")) {
1249 string_ncopy(repo
.remote
, value
, valuelen
);
1251 } else if (*repo
.remote
&& !strcmp(name
, ".merge")) {
1252 size_t from
= strlen(repo
.remote
);
1254 if (!prefixcmp(value
, "refs/heads/"))
1255 value
+= STRING_SIZE("refs/heads/");
1257 if (!string_format_from(repo
.remote
, &from
, "/%s", value
))
1263 set_repo_config_option(char *name
, char *value
, enum status_code (*cmd
)(int, const char **))
1265 const char *argv
[SIZEOF_ARG
] = { name
, "=" };
1266 int argc
= 1 + (cmd
== option_set_command
);
1267 enum status_code code
;
1269 if (!argv_from_string(argv
, &argc
, value
))
1270 code
= error("Too many arguments");
1272 code
= cmd(argc
, argv
);
1274 if (code
!= SUCCESS
)
1275 warn("Option 'tig.%s': %s", name
, get_status_message(code
));
1279 set_work_tree(const char *value
)
1281 char cwd
[SIZEOF_STR
];
1283 if (!getcwd(cwd
, sizeof(cwd
)))
1284 die("Failed to get cwd path: %s", strerror(errno
));
1286 die("Failed to chdir(%s): %s", cwd
, strerror(errno
));
1287 if (chdir(repo
.git_dir
) < 0)
1288 die("Failed to chdir(%s): %s", repo
.git_dir
, strerror(errno
));
1289 if (!getcwd(repo
.git_dir
, sizeof(repo
.git_dir
)))
1290 die("Failed to get git path: %s", strerror(errno
));
1291 if (chdir(value
) < 0)
1292 die("Failed to chdir(%s): %s", value
, strerror(errno
));
1293 if (!getcwd(cwd
, sizeof(cwd
)))
1294 die("Failed to get cwd path: %s", strerror(errno
));
1295 if (setenv("GIT_WORK_TREE", cwd
, true))
1296 die("Failed to set GIT_WORK_TREE to '%s'", cwd
);
1297 if (setenv("GIT_DIR", repo
.git_dir
, true))
1298 die("Failed to set GIT_DIR to '%s'", repo
.git_dir
);
1299 repo
.is_inside_work_tree
= true;
1302 static struct line_info
*
1303 parse_git_color_option(struct line_info
*info
, char *value
)
1305 const char *argv
[SIZEOF_ARG
];
1307 bool first_color
= true;
1310 if (!argv_from_string(argv
, &argc
, value
))
1313 info
->fg
= COLOR_DEFAULT
;
1314 info
->bg
= COLOR_DEFAULT
;
1317 for (i
= 0; i
< argc
; i
++) {
1320 if (set_attribute(&attr
, argv
[i
])) {
1323 } else if (set_color(&attr
, argv
[i
])) {
1328 first_color
= false;
1335 set_git_color_option(const char *name
, char *value
)
1337 struct line_info parsed
= {0};
1338 struct line_info
*color
= NULL
;
1339 size_t namelen
= strlen(name
);
1342 if (!opt_git_colors
)
1345 for (i
= 0; opt_git_colors
[i
]; i
++) {
1346 struct line_rule rule
= {0};
1347 const char *prefix
= NULL
;
1348 struct line_info
*info
;
1349 const char *alias
= opt_git_colors
[i
];
1350 const char *sep
= strchr(alias
, '=');
1352 if (!sep
|| namelen
!= sep
- alias
||
1353 string_enum_compare(name
, alias
, namelen
))
1357 color
= parse_git_color_option(&parsed
, value
);
1362 if (parse_color_name(sep
+ 1, &rule
, &prefix
) == SUCCESS
&&
1363 (info
= add_line_rule(prefix
, &rule
))) {
1364 info
->fg
= parsed
.fg
;
1365 info
->bg
= parsed
.bg
;
1366 info
->attr
= parsed
.attr
;
1372 set_encoding(struct encoding
**encoding_ref
, const char *arg
, bool priority
)
1374 if (!strcasecmp(arg
, "utf-8") || !strcasecmp(arg
, "utf8"))
1376 if (parse_encoding(encoding_ref
, arg
, priority
) == SUCCESS
)
1377 encoding_arg
[0] = 0;
1380 static enum status_code
1381 read_repo_config_option(char *name
, size_t namelen
, char *value
, size_t valuelen
, void *data
)
1383 if (!strcmp(name
, "i18n.commitencoding"))
1384 set_encoding(&default_encoding
, value
, false);
1386 else if (!strcmp(name
, "gui.encoding"))
1387 set_encoding(&default_encoding
, value
, true);
1389 else if (!strcmp(name
, "core.editor"))
1390 string_ncopy(opt_editor
, value
, valuelen
);
1392 else if (!strcmp(name
, "core.worktree"))
1393 set_work_tree(value
);
1395 else if (!strcmp(name
, "core.abbrev"))
1396 parse_int(&opt_id_width
, value
, 0, SIZEOF_REV
- 1);
1398 else if (!prefixcmp(name
, "tig.color."))
1399 set_repo_config_option(name
+ 10, value
, option_color_command
);
1401 else if (!prefixcmp(name
, "tig.bind."))
1402 set_repo_config_option(name
+ 9, value
, option_bind_command
);
1404 else if (!prefixcmp(name
, "tig."))
1405 set_repo_config_option(name
+ 4, value
, option_set_command
);
1407 else if (!prefixcmp(name
, "color."))
1408 set_git_color_option(name
+ STRING_SIZE("color."), value
);
1410 else if (*repo
.head
&& !prefixcmp(name
, "branch.") &&
1411 !strncmp(name
+ 7, repo
.head
, strlen(repo
.head
)))
1412 set_remote_branch(name
+ 7 + strlen(repo
.head
), value
, valuelen
);
1414 else if (!strcmp(name
, "diff.context")) {
1415 if (!find_option_info_by_value(&opt_diff_context
)->seen
)
1416 opt_diff_context
= -atoi(value
);
1418 } else if (!strcmp(name
, "format.pretty")) {
1419 if (!prefixcmp(value
, "format:") && strstr(value
, "%C("))
1420 argv_append(&opt_log_options
, "--pretty=medium");
1427 load_git_config(void)
1429 const char *config_list_argv
[] = { "git", "config", "--list", NULL
};
1431 return io_run_load(config_list_argv
, "=", read_repo_config_option
, NULL
);
1434 /* vim: set ts=8 sw=8 noexpandtab: */