Skip to content

Commit d7bc852

Browse files
committed
Merge branch 'ab/align-parse-options-help'
When "git cmd -h" shows more than one line of usage text (e.g. the cmd subcommand may take sub-sub-command), parse-options API learned to align these lines, even across i18n/l10n. * ab/align-parse-options-help: parse-options: properly align continued usage output git rev-parse --parseopt tests: add more usagestr tests send-pack: properly use parse_options() API for usage string parse-options API users: align usage output in C-strings
2 parents 62f035a + 4631cfc commit d7bc852

File tree

8 files changed

+132
-26
lines changed

8 files changed

+132
-26
lines changed

Documentation/git-send-pack.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ git-send-pack - Push objects over Git protocol to another repository
99
SYNOPSIS
1010
--------
1111
[verse]
12-
'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>]
12+
'git send-pack' [--dry-run] [--force] [--receive-pack=<git-receive-pack>]
1313
[--verbose] [--thin] [--atomic]
1414
[--[no-]signed|--signed=(true|false|if-asked)]
15-
[<host>:]<directory> [<ref>...]
15+
[<host>:]<directory> (--all | <ref>...)
1616

1717
DESCRIPTION
1818
-----------

builtin/ls-remote.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
static const char * const ls_remote_usage[] = {
99
N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n"
10-
" [-q | --quiet] [--exit-code] [--get-url]\n"
11-
" [--symref] [<repository> [<refs>...]]"),
10+
" [-q | --quiet] [--exit-code] [--get-url]\n"
11+
" [--symref] [<repository> [<refs>...]]"),
1212
NULL
1313
};
1414

builtin/send-pack.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
#include "protocol.h"
1818

1919
static const char * const send_pack_usage[] = {
20-
N_("git send-pack [--all | --mirror] [--dry-run] [--force] "
21-
"[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] "
22-
"[<host>:]<directory> [<ref>...]\n"
23-
" --all and explicit <ref> specification are mutually exclusive."),
20+
N_("git send-pack [--mirror] [--dry-run] [--force]\n"
21+
" [--receive-pack=<git-receive-pack>]\n"
22+
" [--verbose] [--thin] [--atomic]\n"
23+
" [<host>:]<directory> (--all | <ref>...)"),
2424
NULL,
2525
};
2626

builtin/show-branch.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
static const char* show_branch_usage[] = {
1313
N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n"
14-
" [--current] [--color[=<when>] | --no-color] [--sparse]\n"
15-
" [--more=<n> | --list | --independent | --merge-base]\n"
16-
" [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"),
14+
" [--current] [--color[=<when>] | --no-color] [--sparse]\n"
15+
" [--more=<n> | --list | --independent | --merge-base]\n"
16+
" [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"),
1717
N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"),
1818
NULL
1919
};

builtin/stash.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ static const char * const git_stash_push_usage[] = {
8585

8686
static const char * const git_stash_save_usage[] = {
8787
N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
88-
" [-u|--include-untracked] [-a|--all] [<message>]"),
88+
" [-u|--include-untracked] [-a|--all] [<message>]"),
8989
NULL
9090
};
9191

builtin/tag.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323

2424
static const char * const git_tag_usage[] = {
2525
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n"
26-
"\t\t<tagname> [<head>]"),
26+
" <tagname> [<head>]"),
2727
N_("git tag -d <tagname>..."),
2828
N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n"
29-
"\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
29+
" [--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
3030
N_("git tag -v [--format=<format>] <tagname>..."),
3131
NULL
3232
};

parse-options.c

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -904,25 +904,77 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
904904
FILE *outfile = err ? stderr : stdout;
905905
int need_newline;
906906

907+
const char *usage_prefix = _("usage: %s");
908+
/*
909+
* The translation could be anything, but we can count on
910+
* msgfmt(1)'s --check option to have asserted that "%s" is in
911+
* the translation. So compute the length of the "usage: "
912+
* part. We are assuming that the translator wasn't overly
913+
* clever and used e.g. "%1$s" instead of "%s", there's only
914+
* one "%s" in "usage_prefix" above, so there's no reason to
915+
* do so even with a RTL language.
916+
*/
917+
size_t usage_len = strlen(usage_prefix) - strlen("%s");
918+
/*
919+
* TRANSLATORS: the colon here should align with the
920+
* one in "usage: %s" translation.
921+
*/
922+
const char *or_prefix = _(" or: %s");
923+
/*
924+
* TRANSLATORS: You should only need to translate this format
925+
* string if your language is a RTL language (e.g. Arabic,
926+
* Hebrew etc.), not if it's a LTR language (e.g. German,
927+
* Russian, Chinese etc.).
928+
*
929+
* When a translated usage string has an embedded "\n" it's
930+
* because options have wrapped to the next line. The line
931+
* after the "\n" will then be padded to align with the
932+
* command name, such as N_("git cmd [opt]\n<8
933+
* spaces>[opt2]"), where the 8 spaces are the same length as
934+
* "git cmd ".
935+
*
936+
* This format string prints out that already-translated
937+
* line. The "%*s" is whitespace padding to account for the
938+
* padding at the start of the line that we add in this
939+
* function. The "%s" is a line in the (hopefully already
940+
* translated) N_() usage string, which contained embedded
941+
* newlines before we split it up.
942+
*/
943+
const char *usage_continued = _("%*s%s");
944+
const char *prefix = usage_prefix;
945+
int saw_empty_line = 0;
946+
907947
if (!usagestr)
908948
return PARSE_OPT_HELP;
909949

910950
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
911951
fprintf(outfile, "cat <<\\EOF\n");
912952

913-
fprintf_ln(outfile, _("usage: %s"), _(*usagestr++));
914-
while (*usagestr && **usagestr)
915-
/*
916-
* TRANSLATORS: the colon here should align with the
917-
* one in "usage: %s" translation.
918-
*/
919-
fprintf_ln(outfile, _(" or: %s"), _(*usagestr++));
920953
while (*usagestr) {
921-
if (**usagestr)
922-
fprintf_ln(outfile, _(" %s"), _(*usagestr));
923-
else
924-
fputc('\n', outfile);
925-
usagestr++;
954+
const char *str = _(*usagestr++);
955+
struct string_list list = STRING_LIST_INIT_DUP;
956+
unsigned int j;
957+
958+
if (!saw_empty_line && !*str)
959+
saw_empty_line = 1;
960+
961+
string_list_split(&list, str, '\n', -1);
962+
for (j = 0; j < list.nr; j++) {
963+
const char *line = list.items[j].string;
964+
965+
if (saw_empty_line && *line)
966+
fprintf_ln(outfile, _(" %s"), line);
967+
else if (saw_empty_line)
968+
fputc('\n', outfile);
969+
else if (!j)
970+
fprintf_ln(outfile, prefix, line);
971+
else
972+
fprintf_ln(outfile, usage_continued,
973+
(int)usage_len, "", line);
974+
}
975+
string_list_clear(&list, 0);
976+
977+
prefix = or_prefix;
926978
}
927979

928980
need_newline = 1;

t/t1502-rev-parse-parseopt.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,58 @@ test_expect_success 'test --parseopt --stuck-long and short option with unset op
282282
test_cmp expect output
283283
'
284284

285+
test_expect_success 'test --parseopt help output: "wrapped" options normal "or:" lines' '
286+
sed -e "s/^|//" >spec <<-\EOF &&
287+
|cmd [--some-option]
288+
| [--another-option]
289+
|cmd [--yet-another-option]
290+
|--
291+
|h,help show the help
292+
EOF
293+
294+
sed -e "s/^|//" >expect <<-\END_EXPECT &&
295+
|cat <<\EOF
296+
|usage: cmd [--some-option]
297+
| or: [--another-option]
298+
| or: cmd [--yet-another-option]
299+
|
300+
| -h, --help show the help
301+
|
302+
|EOF
303+
END_EXPECT
304+
305+
test_must_fail git rev-parse --parseopt -- -h >out <spec >actual &&
306+
test_cmp expect actual
307+
'
308+
309+
test_expect_success 'test --parseopt help output: multi-line blurb after empty line' '
310+
sed -e "s/^|//" >spec <<-\EOF &&
311+
|cmd [--some-option]
312+
| [--another-option]
313+
|
314+
|multi
315+
|line
316+
|blurb
317+
|--
318+
|h,help show the help
319+
EOF
320+
321+
sed -e "s/^|//" >expect <<-\END_EXPECT &&
322+
|cat <<\EOF
323+
|usage: cmd [--some-option]
324+
| or: [--another-option]
325+
|
326+
| multi
327+
| line
328+
| blurb
329+
|
330+
| -h, --help show the help
331+
|
332+
|EOF
333+
END_EXPECT
334+
335+
test_must_fail git rev-parse --parseopt -- -h >out <spec >actual &&
336+
test_cmp expect actual
337+
'
338+
285339
test_done

0 commit comments

Comments
 (0)