|
41 | 41 | #include "commit-reach.h" |
42 | 42 | #include "commit-graph.h" |
43 | 43 | #include "pretty.h" |
| 44 | +#include "quote.h" |
| 45 | +#include "strmap.h" |
44 | 46 | #include "trailer.h" |
45 | 47 |
|
46 | 48 | static const char * const builtin_commit_usage[] = { |
@@ -684,12 +686,201 @@ static int author_date_is_interesting(void) |
684 | 686 | } |
685 | 687 |
|
686 | 688 | #ifndef WITH_BREAKING_CHANGES |
| 689 | +struct comment_char_cfg { |
| 690 | + unsigned last_key_id; |
| 691 | + int auto_set_in_file; |
| 692 | + struct strintmap key_flags; |
| 693 | + size_t alloc, nr; |
| 694 | + struct comment_char_cfg_item { |
| 695 | + unsigned key_id; |
| 696 | + char *path; |
| 697 | + enum config_scope scope; |
| 698 | + } *item; |
| 699 | +}; |
| 700 | + |
| 701 | +#define COMMENT_CHAR_CFG_INIT { .key_flags = STRINTMAP_INIT } |
| 702 | + |
| 703 | +static void comment_char_cfg_release(struct comment_char_cfg *cfg) |
| 704 | +{ |
| 705 | + strintmap_clear(&cfg->key_flags); |
| 706 | + for (size_t i = 0; i < cfg->nr; i++) |
| 707 | + free(cfg->item[i].path); |
| 708 | + |
| 709 | + free(cfg->item); |
| 710 | +} |
| 711 | + |
| 712 | +/* Used to track whether the key occurs more than once in a given file */ |
| 713 | +#define KEY_SEEN_ONCE 1u |
| 714 | +#define KEY_SEEN_TWICE 2u |
| 715 | +#define COMMENT_KEY_SHIFT(id) (2 * (id)) |
| 716 | +#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id)) |
| 717 | + |
| 718 | +static void set_comment_key_flags(struct comment_char_cfg *cfg, |
| 719 | + const char *path, unsigned id, unsigned value) |
| 720 | +{ |
| 721 | + unsigned old = strintmap_get(&cfg->key_flags, path); |
| 722 | + unsigned new = (old & ~COMMENT_KEY_MASK(id)) | |
| 723 | + value << COMMENT_KEY_SHIFT(id); |
| 724 | + |
| 725 | + strintmap_set(&cfg->key_flags, path, new); |
| 726 | +} |
| 727 | + |
| 728 | +static unsigned get_comment_key_flags(struct comment_char_cfg *cfg, |
| 729 | + const char *path, unsigned id) |
| 730 | +{ |
| 731 | + unsigned value = strintmap_get(&cfg->key_flags, path); |
| 732 | + |
| 733 | + return (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id); |
| 734 | +} |
| 735 | + |
| 736 | +static const char* comment_key_name(unsigned id) |
| 737 | +{ |
| 738 | + static const char *name[] = { |
| 739 | + "core.commentChar", "core.commentString", |
| 740 | + }; |
| 741 | + |
| 742 | + if (id >= ARRAY_SIZE(name)) |
| 743 | + BUG("invalid comment key id"); |
| 744 | + |
| 745 | + return name[id]; |
| 746 | +} |
| 747 | + |
| 748 | +static int comment_char_config_cb(const char *key, const char *value, |
| 749 | + const struct config_context *ctx, void *data) |
| 750 | +{ |
| 751 | + struct comment_char_cfg *cfg = data; |
| 752 | + const struct key_value_info *kvi = ctx->kvi; |
| 753 | + unsigned key_id; |
| 754 | + |
| 755 | + if (!strcmp(key, "core.commentchar")) |
| 756 | + key_id = 0; |
| 757 | + else if (!strcmp(key, "core.commentstring")) |
| 758 | + key_id = 1; |
| 759 | + else |
| 760 | + return 0; |
| 761 | + |
| 762 | + cfg->last_key_id = key_id; |
| 763 | + if (!kvi->path) { |
| 764 | + return 0; |
| 765 | + } else if (get_comment_key_flags(cfg, kvi->path, key_id)) { |
| 766 | + set_comment_key_flags(cfg, kvi->path, key_id, KEY_SEEN_TWICE); |
| 767 | + } else { |
| 768 | + struct comment_char_cfg_item *item; |
| 769 | + |
| 770 | + ALLOC_GROW_BY(cfg->item, cfg->nr, 1, cfg->alloc); |
| 771 | + item = &cfg->item[cfg->nr - 1]; |
| 772 | + item->key_id = key_id; |
| 773 | + item->scope = kvi->scope; |
| 774 | + item->path = xstrdup(kvi->path); |
| 775 | + set_comment_key_flags(cfg, kvi->path, key_id, KEY_SEEN_ONCE); |
| 776 | + } |
| 777 | + cfg->auto_set_in_file = value && !strcmp(value, "auto"); |
| 778 | + |
| 779 | + return 0; |
| 780 | +} |
| 781 | + |
| 782 | +static void add_config_scope_arg(struct strbuf *buf, |
| 783 | + struct comment_char_cfg_item *item) |
| 784 | +{ |
| 785 | + char *global_config = git_global_config(); |
| 786 | + char *system_config = git_system_config(); |
| 787 | + |
| 788 | + if (item->scope == CONFIG_SCOPE_SYSTEM && |
| 789 | + fspatheq(item->path, system_config)) { |
| 790 | + strbuf_addstr(buf, "--system "); |
| 791 | + } else if (item->scope == CONFIG_SCOPE_GLOBAL && |
| 792 | + fspatheq(item->path, global_config)) { |
| 793 | + strbuf_addstr(buf, "--global "); |
| 794 | + } else if (item->scope == CONFIG_SCOPE_LOCAL && |
| 795 | + fspatheq(item->path, |
| 796 | + mkpath("%s/config", |
| 797 | + repo_get_git_dir(the_repository)))) { |
| 798 | + ; /* --local is the default */ |
| 799 | + } else if (item->scope == CONFIG_SCOPE_WORKTREE && |
| 800 | + fspatheq(item->path, |
| 801 | + mkpath("%s/config.worktree", |
| 802 | + repo_get_common_dir(the_repository)))) { |
| 803 | + strbuf_addstr(buf, "--worktree "); |
| 804 | + } else { |
| 805 | + const char *path = item->path; |
| 806 | + const char *home = getenv("HOME"); |
| 807 | + |
| 808 | + strbuf_addstr(buf, "--file "); |
| 809 | + if (home && skip_prefix(path, home, &path)) { |
| 810 | + skip_prefix(path, "/", &path); |
| 811 | + strbuf_addstr(buf, "~/"); |
| 812 | + } |
| 813 | + sq_quote_buf_pretty(buf, path); |
| 814 | + strbuf_addch(buf, ' '); |
| 815 | + } |
| 816 | + |
| 817 | + free(global_config); |
| 818 | + free(system_config); |
| 819 | +} |
| 820 | + |
| 821 | +static void add_optional_comment_char_advice(struct comment_char_cfg *cfg) |
| 822 | +{ |
| 823 | + struct strbuf buf = STRBUF_INIT; |
| 824 | + struct comment_char_cfg_item *item; |
| 825 | + /* TRANSLATORS this is a place holder for the value of core.commentString */ |
| 826 | + const char *placeholder = _("<comment string>"); |
| 827 | + |
| 828 | + /* |
| 829 | + * If auto is set in the last file that we saw advise the user how to |
| 830 | + * update their config. |
| 831 | + */ |
| 832 | + if (!cfg->auto_set_in_file) |
| 833 | + return; |
| 834 | + |
| 835 | + for (size_t i = 0; i < cfg->nr; i++) { |
| 836 | + item = &cfg->item[i]; |
| 837 | + |
| 838 | + strbuf_addstr(&buf, " git config unset "); |
| 839 | + add_config_scope_arg(&buf, item); |
| 840 | + if (get_comment_key_flags(cfg, item->path, item->key_id) == KEY_SEEN_TWICE) |
| 841 | + strbuf_addstr(&buf, "--all "); |
| 842 | + strbuf_addf(&buf, "%s\n", comment_key_name(item->key_id)); |
| 843 | + } |
| 844 | + advise(_("\nTo use the default comment string (#) please run\n\n%s"), |
| 845 | + buf.buf); |
| 846 | + |
| 847 | + item = &cfg->item[cfg->nr - 1]; |
| 848 | + strbuf_reset(&buf); |
| 849 | + strbuf_addstr(&buf, " git config set "); |
| 850 | + add_config_scope_arg(&buf, item); |
| 851 | + strbuf_addf(&buf, "%s %s\n", comment_key_name(item->key_id), |
| 852 | + placeholder); |
| 853 | + advise(_("\nTo set a custom comment string please run\n\n" |
| 854 | + "%s\nwhere '%s' is the string you wish to use.\n"), |
| 855 | + buf.buf, placeholder); |
| 856 | + strbuf_release(&buf); |
| 857 | +} |
| 858 | + |
| 859 | +static void advise_auto_comment_char(void) |
| 860 | +{ |
| 861 | + struct comment_char_cfg cfg = COMMENT_CHAR_CFG_INIT; |
| 862 | + struct config_options opts = { |
| 863 | + .commondir = repo_get_common_dir(the_repository), |
| 864 | + .git_dir = repo_get_git_dir(the_repository), |
| 865 | + .respect_includes = 1, |
| 866 | + }; |
| 867 | + |
| 868 | + config_with_options(comment_char_config_cb, &cfg, NULL, the_repository, |
| 869 | + &opts); |
| 870 | + advise(_("Support for '%s=auto' is deprecated and will be removed in " |
| 871 | + "git 3.0\n"), comment_key_name(cfg.last_key_id)); |
| 872 | + add_optional_comment_char_advice(&cfg); |
| 873 | + comment_char_cfg_release(&cfg); |
| 874 | +} |
| 875 | + |
687 | 876 | static void adjust_comment_line_char(const struct strbuf *sb) |
688 | 877 | { |
689 | 878 | char candidates[] = "#;@!$%^&|:"; |
690 | 879 | char *candidate; |
691 | 880 | const char *p; |
692 | 881 |
|
| 882 | + advise_auto_comment_char(); |
| 883 | + |
693 | 884 | if (!memchr(sb->buf, candidates[0], sb->len)) { |
694 | 885 | free(comment_line_str_to_free); |
695 | 886 | comment_line_str = comment_line_str_to_free = |
|
0 commit comments