config: allow tweaking whitespace between value and comment

Extending the previous step, this allows the whitespace placed after the value before the "# comment message" to be tweaked by tweaking the preprocessing rule to: * If the given comment string begins with one or more whitespace characters followed by '#', it is passed intact. * If the given comment string begins with '#', a Space is prepended. * Otherwise, " # " (Space, '#', Space) is prefixed. * A string with LF in it cannot be used as a comment string. Unlike the previous step, which unconditionally added a space after the value before writing the "# comment string", because the above preprocessing already gives a whitespace before the '#', the resulting string is written immediately after copying the value. And the sanity checking rule becomes * comment string after the above massaging that comes into git_config_set_multivar_in_file_gently() must - begin with zero or more whitespace characters followed by '#'. - not have a LF in it. I personally think this is over-engineered, but since I thought things through anyway, here it is in the patch form. The logic to tweak end-user supplied comment string is encapsulated in a new helper function, git_config_prepare_comment_string(), so if new front-end callers would want to use the same massaging rules, it is easily reused. Unfortunately I do not think of a way to tweak the preprocessing rules further to optionally allow having no blank after the value, i.e. to produce [section] variable = value#comment (which is a valid way to say section.variable=value, by the way) without sacrificing the ergonomics for the more usual case, so this time I really stop here. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Mar 15, 2024 at 15:26 UTC 31399a6b6166cf76cc533bc9915878211607ed80
5 files changed +76 -20
Documentation/git-config.txt
+9 -3
@@ -89,9 +89,15 @@ OPTIONS
89
90 --comment <message>::
91 Append a comment at the end of new or modified lines.
92 - Unless _<message>_ begins with "#", a string "# " (hash
93 - followed by a space) is prepended to it. The _<message>_ must not
94 - contain linefeed characters (no multi-line comments are permitted).
92 +
93 + If _<message>_ begins with one or more whitespaces followed
94 + by "#", it is used as-is. If it begins with "#", a space is
95 + prepended before it is used. Otherwise, a string " # " (a
96 + space followed by a hash followed by a space) is prepended
97 + to it. And the resulting string is placed immediately after
98 + the value defined for the variable. The _<message>_ must
99 + not contain linefeed characters (no multi-line comments are
100 + permitted).
101
102 --get::
103 Get the value for a given key (optionally filtered by a regex
builtin/config.c
+1 -6
@@ -841,12 +841,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
841 flags |= CONFIG_FLAGS_FIXED_VALUE;
842 }
843
844 - if (comment) {
845 - if (strchr(comment, '\n'))
846 - die(_("no multi-line comment allowed: '%s'"), comment);
847 - if (comment[0] != '#')
848 - comment = xstrfmt("# %s", comment);
849 - }
844 + comment = git_config_prepare_comment_string(comment);
845
846 if (actions & PAGING_ACTIONS)
847 setup_auto_pager("config", 1);
config.c
+58 -11
@@ -3044,7 +3044,7 @@ static ssize_t write_pair(int fd, const char *key, const char *value,
3044 }
3045
3046 if (comment)
3047 - strbuf_addf(&sb, "%s %s\n", quote, comment);
3047 + strbuf_addf(&sb, "%s%s\n", quote, comment);
3048 else
3049 strbuf_addf(&sb, "%s\n", quote);
3050
@@ -3172,6 +3172,62 @@ void git_config_set(const char *key, const char *value)
3172 trace2_cmd_set_config(key, value);
3173 }
3174
3175 +/*
3176 + * The ownership rule is that the caller will own the string
3177 + * if it receives a piece of memory different from what it passed
3178 + * as the parameter.
3179 + */
3180 +const char *git_config_prepare_comment_string(const char *comment)
3181 +{
3182 + size_t leading_blanks;
3183 +
3184 + if (!comment)
3185 + return NULL;
3186 +
3187 + if (strchr(comment, '\n'))
3188 + die(_("no multi-line comment allowed: '%s'"), comment);
3189 +
3190 + /*
3191 + * If it begins with one or more leading whitespace characters
3192 + * followed by '#", the comment string is used as-is.
3193 + *
3194 + * If it begins with '#', a SP is inserted between the comment
3195 + * and the value the comment is about.
3196 + *
3197 + * Otherwise, the value is followed by a SP followed by '#'
3198 + * followed by SP and then the comment string comes.
3199 + */
3200 +
3201 + leading_blanks = strspn(comment, " \t");
3202 + if (leading_blanks && comment[leading_blanks] == '#')
3203 + ; /* use it as-is */
3204 + else if (comment[0] == '#')
3205 + comment = xstrfmt(" %s", comment);
3206 + else
3207 + comment = xstrfmt(" # %s", comment);
3208 +
3209 + return comment;
3210 +}
3211 +
3212 +static void validate_comment_string(const char *comment)
3213 +{
3214 + size_t leading_blanks;
3215 +
3216 + if (!comment)
3217 + return;
3218 + /*
3219 + * The front-end must have massaged the comment string
3220 + * properly before calling us.
3221 + */
3222 + if (strchr(comment, '\n'))
3223 + BUG("multi-line comments are not permitted: '%s'", comment);
3224 +
3225 + leading_blanks = strspn(comment, " \t");
3226 + if (!leading_blanks || comment[leading_blanks] != '#')
3227 + BUG("comment must begin with one or more SP followed by '#': '%s'",
3228 + comment);
3229 +}
3230 +
3231 /*
3232 * If value==NULL, unset in (remove from) config,
3233 * if value_pattern!=NULL, disregard key/value pairs where value does not match.
@@ -3211,16 +3267,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
3267 size_t contents_sz;
3268 struct config_store_data store = CONFIG_STORE_INIT;
3269
3214 - if (comment) {
3215 - /*
3216 - * The front-end must have massaged the comment string
3217 - * properly before calling us.
3218 - */
3219 - if (strchr(comment, '\n'))
3220 - BUG("multi-line comments are not permitted: '%s'", comment);
3221 - if (comment[0] != '#')
3222 - BUG("comment should begin with '#': '%s'", comment);
3223 - }
3270 + validate_comment_string(comment);
3271
3272 /* parse-key returns negative; flip the sign to feed exit(3) */
3273 ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
config.h
+2
@@ -338,6 +338,8 @@ void git_config_set_multivar(const char *, const char *, const char *, unsigned)
338 int repo_config_set_multivar_gently(struct repository *, const char *, const char *, const char *, unsigned);
339 int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, const char *, unsigned);
340
341 +const char *git_config_prepare_comment_string(const char *);
342 +
343 /**
344 * takes four parameters:
345 *
t/t1300-config.sh
+6
@@ -74,6 +74,8 @@ cat > expect << EOF
74 penguin = gentoo # Pygoscelis papua
75 disposition = peckish # find fish
76 foo = bar #abc
77 + spsp = value # and comment
78 + htsp = value # and comment
79 [Sections]
80 WhatEver = Second
81 EOF
@@ -82,6 +84,10 @@ test_expect_success 'append comments' '
84 git config --replace-all --comment="Pygoscelis papua" section.penguin gentoo &&
85 git config --comment="find fish" section.disposition peckish &&
86 git config --comment="#abc" section.foo bar &&
87 +
88 + git config --comment="and comment" section.spsp value &&
89 + git config --comment=" # and comment" section.htsp value &&
90 +
91 test_cmp expect .git/config
92 '
93