diff options
author | A. Wilcox <AWilcox@Wilcox-Tech.com> | 2025-05-27 06:46:41 -0500 |
---|---|---|
committer | A. Wilcox <AWilcox@Wilcox-Tech.com> | 2025-05-27 06:46:41 -0500 |
commit | b9bf390c64120104cfae92a5843e15d482ffeade (patch) | |
tree | 205e0cbcf97eef48e2d6c0b7e0822bc3934ec512 | |
parent | b068788aef5d32875d3312a38a421b9a2952801f (diff) | |
download | packages-awilfox/system-next-25Q2.tar.gz packages-awilfox/system-next-25Q2.tar.bz2 packages-awilfox/system-next-25Q2.tar.xz packages-awilfox/system-next-25Q2.zip |
system/git: Update to 2.49.0awilfox/system-next-25Q2
Lots of user-facing changes. Backported fixes to ensure proper
functioning on big endian systems.
-rw-r--r-- | system/git/APKBUILD | 20 | ||||
-rw-r--r-- | system/git/disable-t2082-2.patch | 36 | ||||
-rw-r--r-- | system/git/dont-test-other-encodings.patch | 24 | ||||
-rw-r--r-- | system/git/endian.patch | 2305 | ||||
-rw-r--r-- | system/git/perl-getopt-long.patch | 459 |
5 files changed, 2321 insertions, 523 deletions
diff --git a/system/git/APKBUILD b/system/git/APKBUILD index 0562bfba2..2b9c75221 100644 --- a/system/git/APKBUILD +++ b/system/git/APKBUILD @@ -1,8 +1,8 @@ # Contributor: Łukasz Jendrysik <scadu@yandex.com> # Maintainer: A. Wilcox <awilfox@adelielinux.org> pkgname=git -pkgver=2.40.0 -pkgrel=1 +pkgver=2.49.0 +pkgrel=0 pkgdesc="Distributed version control system" url="https://www.git-scm.com/" arch="all" @@ -26,11 +26,9 @@ subpackages="$pkgname-doc replaces="git-perl perl-git" source="https://www.kernel.org/pub/software/scm/git/git-$pkgver.tar.xz dont-test-other-encodings.patch + endian.patch git-daemon.initd git-daemon.confd - - disable-t2082-2.patch - perl-getopt-long.patch " _gitcoredir=/usr/libexec/git-core @@ -59,6 +57,7 @@ prepare() { cat >> config.mak <<-EOF NO_SVN_TESTS=YesPlease NO_REGEX=YesPlease + NO_ICONV=YesPlease USE_ASCIIDOCTOR=1 USE_LIBPCRE2=YesPlease NO_NSEC=YesPlease @@ -72,7 +71,7 @@ build() { } check() { - make prefix=/usr DESTDIR="$pkgdir" -j1 test + GIT_SKIP_TESTS='t5564.6' make prefix=/usr DESTDIR="$pkgdir" -j1 test } package() { @@ -168,9 +167,8 @@ subtree() { make install prefix=/usr DESTDIR="$subpkgdir" } -sha512sums="a2720f8f9a0258c0bb5e23badcfd68a147682e45a5d039a42c47128296c508109d5039029db89311a35db97a9008585e84ed11b400846502c9be913d67f0fd90 git-2.40.0.tar.xz -4bcc8367478601c856e0977d46fc4842f62daf300093a576704ad27ccd9fae975f95d3fbfcb00e9fa7254b1db64cd074f49a94fb5cf0abd8d72d7edc9ab8798c dont-test-other-encodings.patch +sha512sums="81a16415890305fc6cfd14ade8bee76779feba01f51c5446f40c14211654342c68ef0911859fa6e8e9ff0a718847bb44ee4156d03a19c9165df19ba91e09e1f0 git-2.49.0.tar.xz +829d7a5cb1cfc162eee2920cfcd09798af967657751a9040f18046c9537d0b7a7b314fb84edef1ebe4114cdabafb41253cff60709779ecf91cfa49cd7542f923 dont-test-other-encodings.patch +e8c63db09327ff01527f4d5f56185c602ef8cb10e2c24f0e3e02b567166ae226053fc1428263ddecd2708d89e6ea8d99d8bf4c1f19541b7a4d4c367898e1777f endian.patch 89528cdd14c51fd568aa61cf6c5eae08ea0844e59f9af9292da5fc6c268261f4166017d002d494400945e248df6b844e2f9f9cd2d9345d516983f5a110e4c42a git-daemon.initd -fbf1f425206a76e2a8f82342537ed939ff7e623d644c086ca2ced5f69b36734695f9f80ebda1728f75a94d6cd2fcb71bf845b64239368caab418e4d368c141ec git-daemon.confd -6d7cbb701584a078328056a67bfd32dde5795a80c0911734b38bd534699fb0165ac2b486b267c5c39b90bbb0d7c5ab0ab6ada1d068748865617326da28304eb4 disable-t2082-2.patch -9800318f9e6a8b6bfd8c700cce5cc326522a607b89236a868ef46940efe0566fdadf5d69dc3e72f989d61df66be8510b8989bd4ce3fc780f017f30652c7e9efa perl-getopt-long.patch" +fbf1f425206a76e2a8f82342537ed939ff7e623d644c086ca2ced5f69b36734695f9f80ebda1728f75a94d6cd2fcb71bf845b64239368caab418e4d368c141ec git-daemon.confd" diff --git a/system/git/disable-t2082-2.patch b/system/git/disable-t2082-2.patch deleted file mode 100644 index b18a9caa7..000000000 --- a/system/git/disable-t2082-2.patch +++ /dev/null @@ -1,36 +0,0 @@ ---- a/t/t2082-parallel-checkout-attributes.sh 2021-11-24 19:29:21.000000000 +0000 -+++ b/t/t2082-parallel-checkout-attributes.sh 2022-01-12 17:29:58.890000000 +0000 -@@ -33,33 +33,6 @@ - ) - ' - --test_expect_success 'parallel-checkout with re-encoding' ' -- set_checkout_config 2 0 && -- git init encoding && -- ( -- cd encoding && -- echo text >utf8-text && -- write_utf16 <utf8-text >utf16-text && -- -- echo "A working-tree-encoding=UTF-16" >.gitattributes && -- cp utf16-text A && -- cp utf8-text B && -- git add A B .gitattributes && -- git commit -m encoding && -- -- # Check that A is stored in UTF-8 -- git cat-file -p :A >A.internal && -- test_cmp_bin utf8-text A.internal && -- -- rm A B && -- test_checkout_workers 2 git checkout A B && -- -- # Check that A (and only A) is re-encoded during checkout -- test_cmp_bin utf16-text A && -- test_cmp_bin utf8-text B -- ) --' -- - test_expect_success 'parallel-checkout with eol conversions' ' - set_checkout_config 2 0 && - git init eol && diff --git a/system/git/dont-test-other-encodings.patch b/system/git/dont-test-other-encodings.patch index fed4a6e21..d1c917821 100644 --- a/system/git/dont-test-other-encodings.patch +++ b/system/git/dont-test-other-encodings.patch @@ -1,18 +1,8 @@ We can't support Japanese people using musl ---- git-2.14.1/t/t5100-mailinfo.sh.old 2017-08-09 14:54:31.000000000 -0500 -+++ git-2.14.1/t/t5100-mailinfo.sh 2017-09-10 23:26:39.206126739 -0500 -@@ -27,6 +27,7 @@ - - for mail in 00* - do -+ if [ $mail == 0004 ]; then continue; fi - test_expect_success "mailinfo $mail" ' - check_mailinfo "$mail" "" && - if test -f "$DATA/msg$mail--scissors" --- git-2.14.1/t/t8005-blame-i18n.sh.old 2017-08-09 14:54:31.000000000 -0500 +++ git-2.14.1/t/t8005-blame-i18n.sh 2017-09-16 00:42:35.123728809 -0500 -@@ -24,43 +24,10 @@ +@@ -31,43 +31,10 @@ git commit --author "$SJIS_NAME <sjis@localhost>" -m "$SJIS_MSG" ' @@ -58,7 +48,7 @@ We can't support Japanese people using musl summary $UTF8_MSG --- git-2.21.0/t/t0028-working-tree-encoding.sh.old 2019-02-24 16:31:46.000000000 +0000 +++ git-2.21.0/t/t0028-working-tree-encoding.sh 2019-03-08 00:49:07.580000000 +0000 -@@ -51,14 +51,6 @@ +@@ -58,14 +58,6 @@ test_cmp_bin test.utf8.raw test.utf16.git ' @@ -73,17 +63,17 @@ We can't support Japanese people using musl test_expect_success 're-encode to UTF-16-LE-BOM on checkout' ' rm test.utf16lebom && git checkout test.utf16lebom && -@@ -123,41 +115,6 @@ - test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out && - test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out +@@ -131,41 +123,6 @@ + test_grep "fatal: BOM is required .* utf-${i}" err.out && + test_grep "use UTF-${i}BE or UTF-${i}LE" err.out ' - - test_expect_success "eol conversion for UTF-${i} encoded files on checkout" ' - test_when_finished "rm -f crlf.utf${i}.raw lf.utf${i}.raw" && - test_when_finished "git reset --hard HEAD^" && - -- cat lf.utf8.raw | write_utf${i} >lf.utf${i}.raw && -- cat crlf.utf8.raw | write_utf${i} >crlf.utf${i}.raw && +- write_utf${i} <lf.utf8.raw >lf.utf${i}.raw && +- write_utf${i} <crlf.utf8.raw >crlf.utf${i}.raw && - cp crlf.utf${i}.raw eol.utf${i} && - - cat >expectIndexLF <<-EOF && diff --git a/system/git/endian.patch b/system/git/endian.patch new file mode 100644 index 000000000..83fa3e4f1 --- /dev/null +++ b/system/git/endian.patch @@ -0,0 +1,2305 @@ +From mboxrd@z Thu Jan 1 00:00:00 1970 +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Apr 2025 12:49:36 +0200 +Subject: [PATCH v4 1/7] parse: fix off-by-one for minimum signed values +References: <20250417-b4-pks-parse-options-integers-v4-0-9cbc76b61cfe@pks.im> +To: git@vger.kernel.org +X-Mailer: b4 0.14.2 + +We accept a maximum value in `git_parse_signed()` that restricts the +range of accepted integers. As the intent is to pass `INT*_MAX` values +here, this maximum doesn't only act as the upper bound, but also as the +implicit lower bound of the accepted range. + +This lower bound is calculated by negating the maximum. But given that +the maximum value of a signed integer with N bits is `2^(N-1)-1` whereas +the minimum value is `-2^(N-1)` we have an off-by-one error in the lower +bound. + +Fix this off-by-one error by using `-max - 1` as lower bound instead. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +--- + parse.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/parse.c b/parse.c +index 7a60a4f816c..3c47448ca67 100644 +--- a/parse.c ++++ b/parse.c +@@ -38,7 +38,7 @@ int git_parse_signed(const char *value, intmax_t *ret, intmax_t max) + errno = EINVAL; + return 0; + } +- if ((val < 0 && -max / factor > val) || ++ if ((val < 0 && (-max - 1) / factor > val) || + (val > 0 && max / factor < val)) { + errno = ERANGE; + return 0; + +-- +2.49.0.805.g082f7c87e0.dirty + + +From mboxrd@z Thu Jan 1 00:00:00 1970 +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Apr 2025 12:49:37 +0200 +Subject: [PATCH v4 2/7] global: use designated initializers for options +References: <20250417-b4-pks-parse-options-integers-v4-0-9cbc76b61cfe@pks.im> +To: git@vger.kernel.org +X-Mailer: b4 0.14.2 + +While we expose macros for most of our different option types understood +by the "parse-options" subsystem, not every combination of fields that +has one as that would otherwise quickly lead to an explosion of macros. +Instead, we just initialize structures manually for those variants of +fields that don't have a macro. + +Callsites that open-code these structure initialization don't use +designated initializers though and instead just provide values for each +of the fields that they want to initialize. This has three significant +downsides: + + - Callsites need to specify all values up to the last field that they + care about. This often includes fields that should simply be left at + their default zero-initialized state, which adds distraction. + + - Any reader not deeply familiar with the layout of the structure + has a hard time figuring out what the respective initializers mean. + + - Reordering or introducing new fields in the middle of the structure + is impossible without adapting all callsites. + +Convert all sites to instead use designated initializers, which we have +started using in our codebase quite a while ago. This allows us to skip +any default-initialized fields, gives the reader context by specifying +the field names and allows us to reorder or introduce new fields where +we want to. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +--- + archive.c | 35 ++++++++--- + builtin/am.c | 28 ++++++--- + builtin/clone.c | 13 ++++- + builtin/commit-tree.c | 12 +++- + builtin/commit.c | 62 +++++++++++++++----- + builtin/config.c | 13 ++++- + builtin/describe.c | 24 ++++++-- + builtin/fetch.c | 10 +++- + builtin/fmt-merge-msg.c | 25 +++++--- + builtin/gc.c | 12 +++- + builtin/grep.c | 14 +++-- + builtin/init-db.c | 13 +++-- + builtin/ls-remote.c | 11 +++- + builtin/merge.c | 37 +++++++++--- + builtin/read-tree.c | 11 +++- + builtin/rebase.c | 25 ++++++-- + builtin/revert.c | 12 +++- + builtin/show-branch.c | 12 +++- + builtin/tag.c | 23 ++++++-- + builtin/update-index.c | 131 +++++++++++++++++++++++++++++------------- + builtin/write-tree.c | 12 ++-- + diff.c | 13 +++-- + ref-filter.h | 15 +++-- + t/helper/test-parse-options.c | 38 +++++++++--- + 24 files changed, 443 insertions(+), 158 deletions(-) + +diff --git a/archive.c b/archive.c +index 8be4e7ac8db..67bba3cd301 100644 +--- a/archive.c ++++ b/archive.c +@@ -650,20 +650,37 @@ static int parse_archive_args(int argc, const char **argv, + OPT_STRING(0, "format", &format, N_("fmt"), N_("archive format")), + OPT_STRING(0, "prefix", &base, N_("prefix"), + N_("prepend prefix to each pathname in the archive")), +- { OPTION_CALLBACK, 0, "add-file", args, N_("file"), +- N_("add untracked file to archive"), 0, add_file_cb, +- (intptr_t)&base }, +- { OPTION_CALLBACK, 0, "add-virtual-file", args, +- N_("path:content"), N_("add untracked file to archive"), 0, +- add_file_cb, (intptr_t)&base }, ++ { ++ .type = OPTION_CALLBACK, ++ .long_name = "add-file", ++ .value = args, ++ .argh = N_("file"), ++ .help = N_("add untracked file to archive"), ++ .callback = add_file_cb, ++ .defval = (intptr_t) &base, ++ }, ++ { ++ .type = OPTION_CALLBACK, ++ .long_name = "add-virtual-file", ++ .value = args, ++ .argh = N_("path:content"), ++ .help = N_("add untracked file to archive"), ++ .callback = add_file_cb, ++ .defval = (intptr_t) &base, ++ }, + OPT_STRING('o', "output", &output, N_("file"), + N_("write the archive to this file")), + OPT_BOOL(0, "worktree-attributes", &worktree_attributes, + N_("read .gitattributes in working directory")), + OPT__VERBOSE(&verbose, N_("report archived files on stderr")), +- { OPTION_STRING, 0, "mtime", &mtime_option, N_("time"), +- N_("set modification time of archive entries"), +- PARSE_OPT_NONEG }, ++ { ++ .type = OPTION_STRING, ++ .long_name = "mtime", ++ .value = &mtime_option, ++ .argh = N_("time"), ++ .help = N_("set modification time of archive entries"), ++ .flags = PARSE_OPT_NONEG, ++ }, + OPT_NUMBER_CALLBACK(&compression_level, + N_("set compression level"), number_callback), + OPT_GROUP(""), +diff --git a/builtin/am.c b/builtin/am.c +index 3b61bd4c333..4afb519830f 100644 +--- a/builtin/am.c ++++ b/builtin/am.c +@@ -2399,11 +2399,16 @@ int cmd_am(int argc, + OPT_CMDMODE(0, "quit", &resume_mode, + N_("abort the patching operation but keep HEAD where it is"), + RESUME_QUIT), +- { OPTION_CALLBACK, 0, "show-current-patch", &resume_mode, +- "(diff|raw)", +- N_("show the patch being applied"), +- PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, +- parse_opt_show_current_patch, RESUME_SHOW_PATCH_RAW }, ++ { ++ .type = OPTION_CALLBACK, ++ .long_name = "show-current-patch", ++ .value = &resume_mode, ++ .argh = "(diff|raw)", ++ .help = N_("show the patch being applied"), ++ .flags = PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, ++ .callback = parse_opt_show_current_patch, ++ .defval = RESUME_SHOW_PATCH_RAW, ++ }, + OPT_CMDMODE(0, "retry", &resume_mode, + N_("try to apply current patch again"), + RESUME_APPLY), +@@ -2416,9 +2421,16 @@ int cmd_am(int argc, + OPT_BOOL(0, "ignore-date", &state.ignore_date, + N_("use current timestamp for author date")), + OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate), +- { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"), +- N_("GPG-sign commits"), +- PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'S', ++ .long_name = "gpg-sign", ++ .value = &state.sign_commit, ++ .argh = N_("key-id"), ++ .help = N_("GPG-sign commits"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t) "", ++ }, + OPT_CALLBACK_F(0, "empty", &state.empty_type, "(stop|drop|keep)", + N_("how to handle empty patches"), + PARSE_OPT_NONEG, am_option_parse_empty), +diff --git a/builtin/clone.c b/builtin/clone.c +index 88276e5b7ab..9c3547f41e3 100644 +--- a/builtin/clone.c ++++ b/builtin/clone.c +@@ -928,9 +928,16 @@ int cmd_clone(int argc, + N_("don't use local hardlinks, always copy")), + OPT_BOOL('s', "shared", &option_shared, + N_("setup as shared repository")), +- { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules, +- N_("pathspec"), N_("initialize submodules in the clone"), +- PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." }, ++ { ++ .type = OPTION_CALLBACK, ++ .long_name = "recurse-submodules", ++ .value = &option_recurse_submodules, ++ .argh = N_("pathspec"), ++ .help = N_("initialize submodules in the clone"), ++ .flags = PARSE_OPT_OPTARG, ++ .callback = recurse_submodules_cb, ++ .defval = (intptr_t)".", ++ }, + OPT_ALIAS(0, "recursive", "recurse-submodules"), + OPT_INTEGER('j', "jobs", &max_jobs, + N_("number of submodules cloned in parallel")), +diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c +index 38457600a4e..c787133d004 100644 +--- a/builtin/commit-tree.c ++++ b/builtin/commit-tree.c +@@ -111,8 +111,16 @@ int cmd_commit_tree(int argc, + OPT_CALLBACK_F('F', NULL, &buffer, N_("file"), + N_("read commit log message from file"), PARSE_OPT_NONEG, + parse_file_arg_callback), +- { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), +- N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'S', ++ .long_name = "gpg-sign", ++ .value = &sign_commit, ++ .argh = N_("key-id"), ++ .help = N_("GPG sign commit"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t) "", ++ }, + OPT_END() + }; + int ret; +diff --git a/builtin/commit.c b/builtin/commit.c +index 2f459682221..66bd91fd523 100644 +--- a/builtin/commit.c ++++ b/builtin/commit.c +@@ -1542,17 +1542,34 @@ struct repository *repo UNUSED) + STATUS_FORMAT_LONG), + OPT_BOOL('z', "null", &s.null_termination, + N_("terminate entries with NUL")), +- { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, +- N_("mode"), +- N_("show untracked files, optional modes: all, normal, no. (Default: all)"), +- PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, +- { OPTION_STRING, 0, "ignored", &ignored_arg, +- N_("mode"), +- N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"), +- PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" }, +- { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"), +- N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"), +- PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'u', ++ .long_name = "untracked-files", ++ .value = &untracked_files_arg, ++ .argh = N_("mode"), ++ .help = N_("show untracked files, optional modes: all, normal, no. (Default: all)"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t)"all", ++ }, ++ { ++ .type = OPTION_STRING, ++ .long_name = "ignored", ++ .value = &ignored_arg, ++ .argh = N_("mode"), ++ .help = N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t)"traditional", ++ }, ++ { ++ .type = OPTION_STRING, ++ .long_name = "ignore-submodules", ++ .value = &ignore_submodule_arg, ++ .argh = N_("when"), ++ .help = N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t)"all", ++ }, + OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")), + OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")), + OPT_CALLBACK_F('M', "find-renames", &rename_score_arg, +@@ -1688,8 +1705,16 @@ int cmd_commit(int argc, + OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), + OPT_CLEANUP(&cleanup_arg), + OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")), +- { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), +- N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'S', ++ .long_name = "gpg-sign", ++ .value = &sign_commit, ++ .argh = N_("key-id"), ++ .help = N_("GPG sign commit"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t) "", ++ }, + /* end commit message options */ + + OPT_GROUP(N_("Commit contents options")), +@@ -1714,7 +1739,16 @@ int cmd_commit(int argc, + N_("terminate entries with NUL")), + OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), + OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), +- { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'u', ++ .long_name = "untracked-files", ++ .value = &untracked_files_arg, ++ .argh = N_("mode"), ++ .help = N_("show untracked files, optional modes: all, normal, no. (Default: all)"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t)"all", ++ }, + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), + /* end commit contents options */ +diff --git a/builtin/config.c b/builtin/config.c +index 53a90094e31..f70d6354772 100644 +--- a/builtin/config.c ++++ b/builtin/config.c +@@ -131,9 +131,16 @@ struct config_display_options { + #define TYPE_COLOR 6 + #define TYPE_BOOL_OR_STR 7 + +-#define OPT_CALLBACK_VALUE(s, l, v, h, i) \ +- { OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \ +- PARSE_OPT_NONEG, option_parse_type, (i) } ++#define OPT_CALLBACK_VALUE(s, l, v, h, i) { \ ++ .type = OPTION_CALLBACK, \ ++ .short_name = (s), \ ++ .long_name = (l), \ ++ .value = (v), \ ++ .help = (h), \ ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, \ ++ .callback = option_parse_type, \ ++ .defval = (i), \ ++} + + static int option_parse_type(const struct option *opt, const char *arg, + int unset) +diff --git a/builtin/describe.c b/builtin/describe.c +index e2e73f3d757..2da9f4fed01 100644 +--- a/builtin/describe.c ++++ b/builtin/describe.c +@@ -601,12 +601,24 @@ int cmd_describe(int argc, + N_("do not consider tags matching <pattern>")), + OPT_BOOL(0, "always", &always, + N_("show abbreviated commit object as fallback")), +- {OPTION_STRING, 0, "dirty", &dirty, N_("mark"), +- N_("append <mark> on dirty working tree (default: \"-dirty\")"), +- PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, +- {OPTION_STRING, 0, "broken", &broken, N_("mark"), +- N_("append <mark> on broken working tree (default: \"-broken\")"), +- PARSE_OPT_OPTARG, NULL, (intptr_t) "-broken"}, ++ { ++ .type = OPTION_STRING, ++ .long_name = "dirty", ++ .value = &dirty, ++ .argh = N_("mark"), ++ .help = N_("append <mark> on dirty working tree (default: \"-dirty\")"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t) "-dirty", ++ }, ++ { ++ .type = OPTION_STRING, ++ .long_name = "broken", ++ .value = &broken, ++ .argh = N_("mark"), ++ .help = N_("append <mark> on broken working tree (default: \"-broken\")"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t) "-broken", ++ }, + OPT_END(), + }; + +diff --git a/builtin/fetch.c b/builtin/fetch.c +index 02af5054690..3a5159d9e69 100644 +--- a/builtin/fetch.c ++++ b/builtin/fetch.c +@@ -2367,8 +2367,14 @@ int cmd_fetch(int argc, + OPT_SET_INT_F(0, "refetch", &refetch, + N_("re-fetch without negotiating common commits"), + 1, PARSE_OPT_NONEG), +- { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"), +- N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN }, ++ { ++ .type = OPTION_STRING, ++ .long_name = "submodule-prefix", ++ .value = &submodule_prefix, ++ .argh = N_("dir"), ++ .help = N_("prepend this to submodule path output"), ++ .flags = PARSE_OPT_HIDDEN, ++ }, + OPT_CALLBACK_F(0, "recurse-submodules-default", + &recurse_submodules_default, N_("on-demand"), + N_("default for recursive fetching of submodules " +diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c +index 189cd1096a0..240cdb474bc 100644 +--- a/builtin/fmt-merge-msg.c ++++ b/builtin/fmt-merge-msg.c +@@ -20,13 +20,24 @@ int cmd_fmt_merge_msg(int argc, + char *into_name = NULL; + int shortlog_len = -1; + struct option options[] = { +- { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"), +- N_("populate log with at most <n> entries from shortlog"), +- PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, +- { OPTION_INTEGER, 0, "summary", &shortlog_len, N_("n"), +- N_("alias for --log (deprecated)"), +- PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL, +- DEFAULT_MERGE_LOG_LEN }, ++ { ++ .type = OPTION_INTEGER, ++ .long_name = "log", ++ .value = &shortlog_len, ++ .argh = N_("n"), ++ .help = N_("populate log with at most <n> entries from shortlog"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = DEFAULT_MERGE_LOG_LEN, ++ }, ++ { ++ .type = OPTION_INTEGER, ++ .long_name = "summary", ++ .value = &shortlog_len, ++ .argh = N_("n"), ++ .help = N_("alias for --log (deprecated)"), ++ .flags = PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, ++ .defval = DEFAULT_MERGE_LOG_LEN, ++ }, + OPT_STRING('m', "message", &message, N_("text"), + N_("use <text> as start of message")), + OPT_STRING(0, "into-name", &into_name, N_("name"), +diff --git a/builtin/gc.c b/builtin/gc.c +index 99431fd4674..6707a26bc6e 100644 +--- a/builtin/gc.c ++++ b/builtin/gc.c +@@ -699,9 +699,15 @@ struct repository *repo UNUSED) + int ret; + struct option builtin_gc_options[] = { + OPT__QUIET(&quiet, N_("suppress progress reporting")), +- { OPTION_STRING, 0, "prune", &prune_expire_arg, N_("date"), +- N_("prune unreferenced objects"), +- PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire_arg }, ++ { ++ .type = OPTION_STRING, ++ .long_name = "prune", ++ .value = &prune_expire_arg, ++ .argh = N_("date"), ++ .help = N_("prune unreferenced objects"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t)prune_expire_arg, ++ }, + OPT_BOOL(0, "cruft", &cfg.cruft_packs, N_("pack unreferenced objects separately")), + OPT_MAGNITUDE(0, "max-cruft-size", &cfg.max_cruft_size, + N_("with --cruft, limit the size of new cruft packs")), +diff --git a/builtin/grep.c b/builtin/grep.c +index d1427290f77..c4869733e1b 100644 +--- a/builtin/grep.c ++++ b/builtin/grep.c +@@ -1017,10 +1017,16 @@ int cmd_grep(int argc, + OPT_BOOL(0, "all-match", &opt.all_match, + N_("show only matches from files that match all patterns")), + OPT_GROUP(""), +- { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, +- N_("pager"), N_("show matching files in the pager"), +- PARSE_OPT_OPTARG | PARSE_OPT_NOCOMPLETE, +- NULL, (intptr_t)default_pager }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'O', ++ .long_name = "open-files-in-pager", ++ .value = &show_in_pager, ++ .argh = N_("pager"), ++ .help = N_("show matching files in the pager"), ++ .flags = PARSE_OPT_OPTARG | PARSE_OPT_NOCOMPLETE, ++ .defval = (intptr_t)default_pager, ++ }, + OPT_BOOL_F(0, "ext-grep", &external_grep_allowed__ignored, + N_("allow calling of grep(1) (ignored by this build)"), + PARSE_OPT_NOCOMPLETE), +diff --git a/builtin/init-db.c b/builtin/init-db.c +index 196dccdd77a..4a950e44d8d 100644 +--- a/builtin/init-db.c ++++ b/builtin/init-db.c +@@ -93,10 +93,15 @@ int cmd_init_db(int argc, + N_("directory from which templates will be used")), + OPT_SET_INT(0, "bare", &is_bare_repository_cfg, + N_("create a bare repository"), 1), +- { OPTION_CALLBACK, 0, "shared", &init_shared_repository, +- N_("permissions"), +- N_("specify that the git repository is to be shared amongst several users"), +- PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, ++ { ++ .type = OPTION_CALLBACK, ++ .long_name = "shared", ++ .value = &init_shared_repository, ++ .argh = N_("permissions"), ++ .help = N_("specify that the git repository is to be shared amongst several users"), ++ .flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG, ++ .callback = shared_callback ++ }, + OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET), + OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), + N_("separate git dir from working tree")), +diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c +index 42f34e12361..01a4d4daa1f 100644 +--- a/builtin/ls-remote.c ++++ b/builtin/ls-remote.c +@@ -67,9 +67,14 @@ int cmd_ls_remote(int argc, + OPT__QUIET(&quiet, N_("do not print remote URL")), + OPT_STRING(0, "upload-pack", &uploadpack, N_("exec"), + N_("path of git-upload-pack on the remote host")), +- { OPTION_STRING, 0, "exec", &uploadpack, N_("exec"), +- N_("path of git-upload-pack on the remote host"), +- PARSE_OPT_HIDDEN }, ++ { ++ .type = OPTION_STRING, ++ .long_name = "exec", ++ .value = &uploadpack, ++ .argh = N_("exec"), ++ .help = N_("path of git-upload-pack on the remote host"), ++ .flags = PARSE_OPT_HIDDEN, ++ }, + OPT_BIT('t', "tags", &flags, N_("limit to tags"), REF_TAGS), + OPT_BIT('b', "branches", &flags, N_("limit to branches"), REF_BRANCHES), + OPT_BIT_F('h', "heads", &flags, +diff --git a/builtin/merge.c b/builtin/merge.c +index ba9faf126aa..21787d45165 100644 +--- a/builtin/merge.c ++++ b/builtin/merge.c +@@ -250,9 +250,15 @@ static struct option builtin_merge_options[] = { + OPT_BOOL(0, "stat", &show_diffstat, + N_("show a diffstat at the end of the merge")), + OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")), +- { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"), +- N_("add (at most <n>) entries from shortlog to merge commit message"), +- PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, ++ { ++ .type = OPTION_INTEGER, ++ .long_name = "log", ++ .value = &shortlog_len, ++ .argh = N_("n"), ++ .help = N_("add (at most <n>) entries from shortlog to merge commit message"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = DEFAULT_MERGE_LOG_LEN, ++ }, + OPT_BOOL(0, "squash", &squash, + N_("create a single commit instead of doing a merge")), + OPT_BOOL(0, "commit", &option_commit, +@@ -274,9 +280,16 @@ static struct option builtin_merge_options[] = { + OPT_CALLBACK('m', "message", &merge_msg, N_("message"), + N_("merge commit message (for a non-fast-forward merge)"), + option_parse_message), +- { OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"), +- N_("read message from file"), PARSE_OPT_NONEG, +- NULL, 0, option_read_message }, ++ { ++ .type = OPTION_LOWLEVEL_CALLBACK, ++ .short_name = 'F', ++ .long_name = "file", ++ .value = &merge_msg, ++ .argh = N_("path"), ++ .help = N_("read message from file"), ++ .flags = PARSE_OPT_NONEG, ++ .ll_callback = option_read_message, ++ }, + OPT_STRING(0, "into-name", &into_name, N_("name"), + N_("use <name> instead of the real target")), + OPT__VERBOSITY(&verbosity), +@@ -289,8 +302,16 @@ static struct option builtin_merge_options[] = { + OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, + N_("allow merging unrelated histories")), + OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), +- { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), +- N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'S', ++ .long_name = "gpg-sign", ++ .value = &sign_commit, ++ .argh = N_("key-id"), ++ .help = N_("GPG sign commit"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t) "", ++ }, + OPT_AUTOSTASH(&autostash), + OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), + OPT_BOOL(0, "signoff", &signoff, N_("add a Signed-off-by trailer")), +diff --git a/builtin/read-tree.c b/builtin/read-tree.c +index d2a807a828b..a8f352f7cd9 100644 +--- a/builtin/read-tree.c ++++ b/builtin/read-tree.c +@@ -135,9 +135,14 @@ int cmd_read_tree(int argc, + N_("3-way merge in presence of adds and removes")), + OPT_BOOL(0, "reset", &opts.reset, + N_("same as -m, but discard unmerged entries")), +- { OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"), +- N_("read the tree into the index under <subdirectory>/"), +- PARSE_OPT_NONEG }, ++ { ++ .type = OPTION_STRING, ++ .long_name = "prefix", ++ .value = &opts.prefix, ++ .argh = N_("<subdirectory>/"), ++ .help = N_("read the tree into the index under <subdirectory>/"), ++ .flags = PARSE_OPT_NONEG, ++ }, + OPT_BOOL('u', NULL, &opts.update, + N_("update working tree with merge result")), + OPT_CALLBACK_F(0, "exclude-per-directory", &opts, +diff --git a/builtin/rebase.c b/builtin/rebase.c +index d4715ed35d7..d4083350090 100644 +--- a/builtin/rebase.c ++++ b/builtin/rebase.c +@@ -1122,9 +1122,15 @@ int cmd_rebase(int argc, + OPT_BIT('v', "verbose", &options.flags, + N_("display a diffstat of what changed upstream"), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), +- {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, +- N_("do not show diffstat of what changed upstream"), +- PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, ++ { ++ .type = OPTION_NEGBIT, ++ .short_name = 'n', ++ .long_name = "no-stat", ++ .value = &options.flags, ++ .help = N_("do not show diffstat of what changed upstream"), ++ .flags = PARSE_OPT_NOARG, ++ .defval = REBASE_DIFFSTAT, ++ }, + OPT_BOOL(0, "signoff", &options.signoff, + N_("add a Signed-off-by trailer to each commit")), + OPT_BOOL(0, "committer-date-is-author-date", +@@ -1190,9 +1196,16 @@ int cmd_rebase(int argc, + OPT_BOOL(0, "update-refs", &options.update_refs, + N_("update branches that point to commits " + "that are being rebased")), +- { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), +- N_("GPG-sign commits"), +- PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'S', ++ .long_name = "gpg-sign", ++ .value = &gpg_sign, ++ .argh = N_("key-id"), ++ .help = N_("GPG-sign commits"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t) "", ++ }, + OPT_AUTOSTASH(&options.autostash), + OPT_STRING_LIST('x', "exec", &options.exec, N_("exec"), + N_("add exec lines after each commit of the " +diff --git a/builtin/revert.c b/builtin/revert.c +index aca6c293cdf..4f5ef975494 100644 +--- a/builtin/revert.c ++++ b/builtin/revert.c +@@ -132,8 +132,16 @@ static int run_sequencer(int argc, const char **argv, const char *prefix, + OPT_STRING(0, "strategy", &strategy, N_("strategy"), N_("merge strategy")), + OPT_STRVEC('X', "strategy-option", &opts->xopts, N_("option"), + N_("option for merge strategy")), +- { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), +- N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, ++ { ++ .type = OPTION_STRING, ++ .short_name = 'S', ++ .long_name = "gpg-sign", ++ .value = &gpg_sign, ++ .argh = N_("key-id"), ++ .help = N_("GPG sign commit"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = (intptr_t) "", ++ }, + OPT_END() + }; + struct option *options = base_options; +diff --git a/builtin/show-branch.c b/builtin/show-branch.c +index fce6b404e92..dab37019d29 100644 +--- a/builtin/show-branch.c ++++ b/builtin/show-branch.c +@@ -667,9 +667,15 @@ int cmd_show_branch(int ac, + N_("show remote-tracking branches")), + OPT__COLOR(&showbranch_use_color, + N_("color '*!+-' corresponding to the branch")), +- { OPTION_INTEGER, 0, "more", &extra, N_("n"), +- N_("show <n> more commits after the common ancestor"), +- PARSE_OPT_OPTARG, NULL, (intptr_t)1 }, ++ { ++ .type = OPTION_INTEGER, ++ .long_name = "more", ++ .value = &extra, ++ .argh = N_("n"), ++ .help = N_("show <n> more commits after the common ancestor"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = 1, ++ }, + OPT_SET_INT(0, "list", &extra, N_("synonym to more=-1"), -1), + OPT_BOOL(0, "no-name", &no_name, N_("suppress naming strings")), + OPT_BOOL(0, "current", &with_current_branch, +diff --git a/builtin/tag.c b/builtin/tag.c +index d3e0943b734..b266f12bb48 100644 +--- a/builtin/tag.c ++++ b/builtin/tag.c +@@ -479,9 +479,15 @@ int cmd_tag(int argc, + int edit_flag = 0; + struct option options[] = { + OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), +- { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), +- N_("print <n> lines of each tag message"), +- PARSE_OPT_OPTARG, NULL, 1 }, ++ { ++ .type = OPTION_INTEGER, ++ .short_name = 'n', ++ .value = &filter.lines, ++ .argh = N_("n"), ++ .help = N_("print <n> lines of each tag message"), ++ .flags = PARSE_OPT_OPTARG, ++ .defval = 1, ++ }, + OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'), + OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'), + +@@ -513,9 +519,14 @@ int cmd_tag(int argc, + N_("do not output a newline after empty formatted refs")), + OPT_REF_SORT(&sorting_options), + { +- OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), +- N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT, +- parse_opt_object_name, (intptr_t) "HEAD" ++ .type = OPTION_CALLBACK, ++ .long_name = "points-at", ++ .value = &filter.points_at, ++ .argh = N_("object"), ++ .help = N_("print only tags of the object"), ++ .flags = PARSE_OPT_LASTARG_DEFAULT, ++ .callback = parse_opt_object_name, ++ .defval = (intptr_t) "HEAD", + }, + OPT_STRING( 0 , "format", &format.format, N_("format"), + N_("format to use for the output")), +diff --git a/builtin/update-index.c b/builtin/update-index.c +index b2f6b1a3fbb..ee64b022679 100644 +--- a/builtin/update-index.c ++++ b/builtin/update-index.c +@@ -964,29 +964,51 @@ int cmd_update_index(int argc, + N_("like --refresh, but ignore assume-unchanged setting"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + really_refresh_callback), +- {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL, +- N_("<mode>,<object>,<path>"), +- N_("add the specified entry to the index"), +- PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ +- PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, +- NULL, 0, +- cacheinfo_callback}, ++ { ++ .type = OPTION_LOWLEVEL_CALLBACK, ++ .long_name = "cacheinfo", ++ .argh = N_("<mode>,<object>,<path>"), ++ .help = N_("add the specified entry to the index"), ++ .flags = PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ ++ PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, ++ .ll_callback = cacheinfo_callback, ++ }, + OPT_CALLBACK_F(0, "chmod", &set_executable_bit, "(+|-)x", + N_("override the executable bit of the listed files"), + PARSE_OPT_NONEG, + chmod_callback), +- {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL, +- N_("mark files as \"not changing\""), +- PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, +- {OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL, +- N_("clear assumed-unchanged bit"), +- PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, +- {OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL, +- N_("mark files as \"index-only\""), +- PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, +- {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, +- N_("clear skip-worktree bit"), +- PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, ++ { ++ .type = OPTION_SET_INT, ++ .long_name = "assume-unchanged", ++ .value = &mark_valid_only, ++ .help = N_("mark files as \"not changing\""), ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ .defval = MARK_FLAG, ++ }, ++ { ++ .type = OPTION_SET_INT, ++ .long_name = "no-assume-unchanged", ++ .value = &mark_valid_only, ++ .help = N_("clear assumed-unchanged bit"), ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ .defval = UNMARK_FLAG, ++ }, ++ { ++ .type = OPTION_SET_INT, ++ .long_name = "skip-worktree", ++ .value = &mark_skip_worktree_only, ++ .help = N_("mark files as \"index-only\""), ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ .defval = MARK_FLAG, ++ }, ++ { ++ .type = OPTION_SET_INT, ++ .long_name = "no-skip-worktree", ++ .value = &mark_skip_worktree_only, ++ .help = N_("clear skip-worktree bit"), ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ .defval = UNMARK_FLAG, ++ }, + OPT_BOOL(0, "ignore-skip-worktree-entries", &ignore_skip_worktree_entries, + N_("do not touch index-only entries")), + OPT_SET_INT(0, "info-only", &info_only, +@@ -995,22 +1017,39 @@ int cmd_update_index(int argc, + N_("remove named paths even if present in worktree"), 1), + OPT_BOOL('z', NULL, &nul_term_line, + N_("with --stdin: input lines are terminated by null bytes")), +- {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, +- N_("read list of paths to be updated from standard input"), +- PARSE_OPT_NONEG | PARSE_OPT_NOARG, +- NULL, 0, stdin_callback}, +- {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL, +- N_("add entries from standard input to the index"), +- PARSE_OPT_NONEG | PARSE_OPT_NOARG, +- NULL, 0, stdin_cacheinfo_callback}, +- {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL, +- N_("repopulate stages #2 and #3 for the listed paths"), +- PARSE_OPT_NONEG | PARSE_OPT_NOARG, +- NULL, 0, unresolve_callback}, +- {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL, +- N_("only update entries that differ from HEAD"), +- PARSE_OPT_NONEG | PARSE_OPT_NOARG, +- NULL, 0, reupdate_callback}, ++ { ++ .type = OPTION_LOWLEVEL_CALLBACK, ++ .long_name = "stdin", ++ .value = &read_from_stdin, ++ .help = N_("read list of paths to be updated from standard input"), ++ .flags = PARSE_OPT_NONEG | PARSE_OPT_NOARG, ++ .ll_callback = stdin_callback, ++ }, ++ { ++ .type = OPTION_LOWLEVEL_CALLBACK, ++ .long_name = "index-info", ++ .value = &nul_term_line, ++ .help = N_("add entries from standard input to the index"), ++ .flags = PARSE_OPT_NONEG | PARSE_OPT_NOARG, ++ .ll_callback = stdin_cacheinfo_callback, ++ }, ++ { ++ .type = OPTION_LOWLEVEL_CALLBACK, ++ .long_name = "unresolve", ++ .value = &has_errors, ++ .help = N_("repopulate stages #2 and #3 for the listed paths"), ++ .flags = PARSE_OPT_NONEG | PARSE_OPT_NOARG, ++ .ll_callback = unresolve_callback, ++ }, ++ { ++ .type = OPTION_LOWLEVEL_CALLBACK, ++ .short_name = 'g', ++ .long_name = "again", ++ .value = &has_errors, ++ .help = N_("only update entries that differ from HEAD"), ++ .flags = PARSE_OPT_NONEG | PARSE_OPT_NOARG, ++ .ll_callback = reupdate_callback, ++ }, + OPT_BIT(0, "ignore-missing", &refresh_args.flags, + N_("ignore files missing from worktree"), + REFRESH_IGNORE_MISSING), +@@ -1036,12 +1075,22 @@ int cmd_update_index(int argc, + N_("write out the index even if is not flagged as changed"), 1), + OPT_BOOL(0, "fsmonitor", &fsmonitor, + N_("enable or disable file system monitor")), +- {OPTION_SET_INT, 0, "fsmonitor-valid", &mark_fsmonitor_only, NULL, +- N_("mark files as fsmonitor valid"), +- PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, +- {OPTION_SET_INT, 0, "no-fsmonitor-valid", &mark_fsmonitor_only, NULL, +- N_("clear fsmonitor valid bit"), +- PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, ++ { ++ .type = OPTION_SET_INT, ++ .long_name = "fsmonitor-valid", ++ .value = &mark_fsmonitor_only, ++ .help = N_("mark files as fsmonitor valid"), ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ .defval = MARK_FLAG, ++ }, ++ { ++ .type = OPTION_SET_INT, ++ .long_name = "no-fsmonitor-valid", ++ .value = &mark_fsmonitor_only, ++ .help = N_("clear fsmonitor valid bit"), ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ .defval = UNMARK_FLAG, ++ }, + OPT_END() + }; + +diff --git a/builtin/write-tree.c b/builtin/write-tree.c +index 43f233e69b0..5a8dc377ec0 100644 +--- a/builtin/write-tree.c ++++ b/builtin/write-tree.c +@@ -31,10 +31,14 @@ int cmd_write_tree(int argc, + WRITE_TREE_MISSING_OK), + OPT_STRING(0, "prefix", &tree_prefix, N_("<prefix>/"), + N_("write tree object for a subdirectory <prefix>")), +- { OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL, +- N_("only useful for debugging"), +- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL, +- WRITE_TREE_IGNORE_CACHE_TREE }, ++ { ++ .type = OPTION_BIT, ++ .long_name = "ignore-cache-tree", ++ .value = &flags, ++ .help = N_("only useful for debugging"), ++ .flags = PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, ++ .defval = WRITE_TREE_IGNORE_CACHE_TREE, ++ }, + OPT_END() + }; + +diff --git a/diff.c b/diff.c +index 08f5e00a2cc..f2fcc7f3c22 100644 +--- a/diff.c ++++ b/diff.c +@@ -5892,10 +5892,15 @@ struct option *add_diff_options(const struct option *opts, + OPT_CALLBACK_F(0, "diff-filter", options, N_("[(A|C|D|M|R|T|U|X|B)...[*]]"), + N_("select files by diff type"), + PARSE_OPT_NONEG, diff_opt_diff_filter), +- { OPTION_CALLBACK, 0, "output", options, N_("<file>"), +- N_("output to a specific file"), +- PARSE_OPT_NONEG, NULL, 0, diff_opt_output }, +- ++ { ++ .type = OPTION_CALLBACK, ++ .long_name = "output", ++ .value = options, ++ .argh = N_("<file>"), ++ .help = N_("output to a specific file"), ++ .flags = PARSE_OPT_NONEG, ++ .ll_callback = diff_opt_output, ++ }, + OPT_END() + }; + +diff --git a/ref-filter.h b/ref-filter.h +index 013d4cfa64b..c98c4fbd4c1 100644 +--- a/ref-filter.h ++++ b/ref-filter.h +@@ -114,11 +114,16 @@ struct ref_format { + } + + /* Macros for checking --merged and --no-merged options */ +-#define _OPT_MERGED_NO_MERGED(option, filter, h) \ +- { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \ +- PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \ +- parse_opt_merge_filter, (intptr_t) "HEAD" \ +- } ++#define _OPT_MERGED_NO_MERGED(option, filter, h) { \ ++ .type = OPTION_CALLBACK, \ ++ .long_name = option, \ ++ .value = (filter), \ ++ .argh = N_("commit"), \ ++ .help = (h), \ ++ .flags = PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \ ++ .callback = parse_opt_merge_filter, \ ++ .defval = (intptr_t) "HEAD", \ ++} + #define OPT_MERGED(f, h) _OPT_MERGED_NO_MERGED("merged", f, h) + #define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h) + +diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c +index bfe45ec68b0..997f55fd45b 100644 +--- a/t/helper/test-parse-options.c ++++ b/t/helper/test-parse-options.c +@@ -124,8 +124,15 @@ int cmd__parse_options(int argc, const char **argv) + struct option options[] = { + OPT_BOOL(0, "yes", &boolean, "get a boolean"), + OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"), +- { OPTION_SET_INT, 'B', "no-fear", &boolean, NULL, +- "be brave", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, ++ { ++ .type = OPTION_SET_INT, ++ .short_name = 'B', ++ .long_name = "no-fear", ++ .value = &boolean, ++ .help = "be brave", ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ .defval = 1, ++ }, + OPT_COUNTUP('b', "boolean", &boolean, "increment by one"), + OPT_BIT('4', "or4", &boolean, + "bitwise-or boolean with ...0100", 4), +@@ -155,12 +162,27 @@ int cmd__parse_options(int argc, const char **argv) + OPT_GROUP("Magic arguments"), + OPT_NUMBER_CALLBACK(&integer, "set integer to NUM", + number_callback), +- { OPTION_COUNTUP, '+', NULL, &boolean, NULL, "same as -b", +- PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH }, +- { OPTION_COUNTUP, 0, "ambiguous", &ambiguous, NULL, +- "positive ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, +- { OPTION_COUNTUP, 0, "no-ambiguous", &ambiguous, NULL, +- "negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, ++ { ++ .type = OPTION_COUNTUP, ++ .short_name = '+', ++ .value = &boolean, ++ .help = "same as -b", ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, ++ }, ++ { ++ .type = OPTION_COUNTUP, ++ .long_name = "ambiguous", ++ .value = &ambiguous, ++ .help = "positive ambiguity", ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ }, ++ { ++ .type = OPTION_COUNTUP, ++ .long_name = "no-ambiguous", ++ .value = &ambiguous, ++ .help = "negative ambiguity", ++ .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, ++ }, + OPT_GROUP("Standard options"), + OPT__ABBREV(&abbrev), + OPT__VERBOSE(&verbose, "be verbose"), + +-- +2.49.0.805.g082f7c87e0.dirty + + +From mboxrd@z Thu Jan 1 00:00:00 1970 +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Apr 2025 12:49:38 +0200 +Subject: [PATCH v4 3/7] parse-options: support unit factors in + `OPT_INTEGER()` +References: <20250417-b4-pks-parse-options-integers-v4-0-9cbc76b61cfe@pks.im> +To: git@vger.kernel.org +X-Mailer: b4 0.14.2 + +There are two main differences between `OPT_INTEGER()` and +`OPT_MAGNITUDE()`: + + - The former parses signed integers whereas the latter parses unsigned + integers. + + - The latter parses unit factors like 'k', 'm' or 'g'. + +While the first difference makes obvious sense, there isn't really a +good reason why signed integers shouldn't support unit factors, too. + +This inconsistency will also become a bit of a problem with subsequent +commits, where we will fix a couple of callsites that pass an unsigned +integer to `OPT_INTEGER()`. There are three options: + + - We could adapt those users to instead pass a signed integer, but + this would needlessly extend the range of accepted integer values. + + - We could convert them to use `OPT_MAGNITUDE()`, as it only accepts + unsigned integers. But now we have the inconsistency that we also + start to accept unit factors. + + - We could introduce `OPT_UNSIGNED()` as equivalent to `OPT_INTEGER()` + so that it knows to only accept unsigned integers without unit + suffix. + +Introducing a whole new option type feels a bit excessive. There also +isn't really a good reason why `OPT_INTEGER()` cannot be extended to +also accept unit factors: all valid values passed to such options cannot +have a unit factors right now, so there wouldn't be any ambiguity. + +Refactor `OPT_INTEGER()` to use `git_parse_int()`, which knows to +interpret unit factors. This removes the inconsistency between the +signed and unsigned options so that we can easily fix up callsites that +pass the wrong integer type right now. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +--- + Documentation/technical/api-parse-options.adoc | 6 ++++-- + parse-options.c | 8 ++++---- + t/t0040-parse-options.sh | 4 +++- + 3 files changed, 11 insertions(+), 7 deletions(-) + +diff --git a/Documentation/technical/api-parse-options.adoc b/Documentation/technical/api-parse-options.adoc +index 61fa6ee1678..63acfb419bd 100644 +--- a/Documentation/technical/api-parse-options.adoc ++++ b/Documentation/technical/api-parse-options.adoc +@@ -211,8 +211,10 @@ There are some macros to easily define options: + Use of `--no-option` will clear the list of preceding values. + + `OPT_INTEGER(short, long, &int_var, description)`:: +- Introduce an option with integer argument. +- The integer is put into `int_var`. ++ Introduce an option with integer argument. The argument must be a ++ integer and may include a suffix of 'k', 'm' or 'g' to ++ scale the provided value by 1024, 1024^2 or 1024^3 respectively. ++ The scaled value is put into `int_var`. + + `OPT_MAGNITUDE(short, long, &unsigned_long_var, description)`:: + Introduce an option with a size argument. The argument must be a +diff --git a/parse-options.c b/parse-options.c +index 35fbb3b0d63..b287436e81a 100644 +--- a/parse-options.c ++++ b/parse-options.c +@@ -73,7 +73,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, + enum opt_parsed flags, + const char **argp) + { +- const char *s, *arg; ++ const char *arg; + const int unset = flags & OPT_UNSET; + int err; + +@@ -185,9 +185,9 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, + if (!*arg) + return error(_("%s expects a numerical value"), + optname(opt, flags)); +- *(int *)opt->value = strtol(arg, (char **)&s, 10); +- if (*s) +- return error(_("%s expects a numerical value"), ++ if (!git_parse_int(arg, opt->value)) ++ return error(_("%s expects an integer value" ++ " with an optional k/m/g suffix"), + optname(opt, flags)); + return 0; + +diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh +index 2fe3522305f..0c538c4b437 100755 +--- a/t/t0040-parse-options.sh ++++ b/t/t0040-parse-options.sh +@@ -111,7 +111,9 @@ test_expect_success 'OPT_BOOL() no negation #2' 'check_unknown_i18n --no-no-fear + + test_expect_success 'OPT_BOOL() positivation' 'check boolean: 0 -D --doubt' + +-test_expect_success 'OPT_INT() negative' 'check integer: -2345 -i -2345' ++test_expect_success 'OPT_INTEGER() negative' 'check integer: -2345 -i -2345' ++test_expect_success 'OPT_INTEGER() kilo' 'check integer: 239616 -i 234k' ++test_expect_success 'OPT_INTEGER() negative kilo' 'check integer: -239616 -i -234k' + + test_expect_success 'OPT_MAGNITUDE() simple' ' + check magnitude: 2345678 -m 2345678 + +-- +2.49.0.805.g082f7c87e0.dirty + + +From mboxrd@z Thu Jan 1 00:00:00 1970 +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Apr 2025 12:49:39 +0200 +Subject: [PATCH v4 4/7] parse-options: rename `OPT_MAGNITUDE()` to + `OPT_UNSIGNED()` +References: <20250417-b4-pks-parse-options-integers-v4-0-9cbc76b61cfe@pks.im> +To: git@vger.kernel.org +X-Mailer: b4 0.14.2 + +With the preceding commit, `OPT_INTEGER()` has learned to support unit +factors. Consequently, the major differencen between `OPT_INTEGER()` and +`OPT_MAGNITUDE()` isn't the support of unit factors anymore, as both of +them do support them now. Instead, the difference is that one handles +signed and the other handles unsigned integers. + +Adapt the name of `OPT_MAGNITUDE()` accordingly by renaming it to +`OPT_UNSIGNED()`. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +--- + Documentation/technical/api-parse-options.adoc | 4 +-- + builtin/gc.c | 4 +-- + builtin/multi-pack-index.c | 2 +- + builtin/pack-objects.c | 8 ++--- + builtin/repack.c | 8 ++--- + parse-options.c | 6 ++-- + parse-options.h | 6 ++-- + t/helper/test-parse-options.c | 6 ++-- + t/t0040-parse-options.sh | 50 +++++++++++++------------- + 9 files changed, 47 insertions(+), 47 deletions(-) + +diff --git a/Documentation/technical/api-parse-options.adoc b/Documentation/technical/api-parse-options.adoc +index 63acfb419bd..880eb946425 100644 +--- a/Documentation/technical/api-parse-options.adoc ++++ b/Documentation/technical/api-parse-options.adoc +@@ -216,8 +216,8 @@ There are some macros to easily define options: + scale the provided value by 1024, 1024^2 or 1024^3 respectively. + The scaled value is put into `int_var`. + +-`OPT_MAGNITUDE(short, long, &unsigned_long_var, description)`:: +- Introduce an option with a size argument. The argument must be a ++`OPT_UNSIGNED(short, long, &unsigned_long_var, description)`:: ++ Introduce an option with an unsigned integer argument. The argument must be a + non-negative integer and may include a suffix of 'k', 'm' or 'g' to + scale the provided value by 1024, 1024^2 or 1024^3 respectively. + The scaled value is put into `unsigned_long_var`. +diff --git a/builtin/gc.c b/builtin/gc.c +index 6707a26bc6e..b32cf937cdf 100644 +--- a/builtin/gc.c ++++ b/builtin/gc.c +@@ -709,8 +709,8 @@ struct repository *repo UNUSED) + .defval = (intptr_t)prune_expire_arg, + }, + OPT_BOOL(0, "cruft", &cfg.cruft_packs, N_("pack unreferenced objects separately")), +- OPT_MAGNITUDE(0, "max-cruft-size", &cfg.max_cruft_size, +- N_("with --cruft, limit the size of new cruft packs")), ++ OPT_UNSIGNED(0, "max-cruft-size", &cfg.max_cruft_size, ++ N_("with --cruft, limit the size of new cruft packs")), + OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")), + OPT_BOOL_F(0, "auto", &opts.auto_flag, N_("enable auto-gc mode"), + PARSE_OPT_NOCOMPLETE), +diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c +index 2a938466f53..e4820fd721a 100644 +--- a/builtin/multi-pack-index.c ++++ b/builtin/multi-pack-index.c +@@ -245,7 +245,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, + { + struct option *options; + static struct option builtin_multi_pack_index_repack_options[] = { +- OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, ++ OPT_UNSIGNED(0, "batch-size", &opts.batch_size, + N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), +diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c +index 79e1e6fb52b..9328812e286 100644 +--- a/builtin/pack-objects.c ++++ b/builtin/pack-objects.c +@@ -4399,16 +4399,16 @@ int cmd_pack_objects(int argc, + OPT_CALLBACK_F(0, "index-version", &pack_idx_opts, N_("<version>[,<offset>]"), + N_("write the pack index file in the specified idx format version"), + PARSE_OPT_NONEG, option_parse_index_version), +- OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit, +- N_("maximum size of each output pack file")), ++ OPT_UNSIGNED(0, "max-pack-size", &pack_size_limit, ++ N_("maximum size of each output pack file")), + OPT_BOOL(0, "local", &local, + N_("ignore borrowed objects from alternate object store")), + OPT_BOOL(0, "incremental", &incremental, + N_("ignore packed objects")), + OPT_INTEGER(0, "window", &window, + N_("limit pack window by objects")), +- OPT_MAGNITUDE(0, "window-memory", &window_memory_limit, +- N_("limit pack window by memory in addition to object limit")), ++ OPT_UNSIGNED(0, "window-memory", &window_memory_limit, ++ N_("limit pack window by memory in addition to object limit")), + OPT_INTEGER(0, "depth", &depth, + N_("maximum length of delta chain allowed in the resulting pack")), + OPT_BOOL(0, "reuse-delta", &reuse_delta, +diff --git a/builtin/repack.c b/builtin/repack.c +index 75e3752353a..8bf9941b2c2 100644 +--- a/builtin/repack.c ++++ b/builtin/repack.c +@@ -1202,8 +1202,8 @@ int cmd_repack(int argc, + PACK_CRUFT), + OPT_STRING(0, "cruft-expiration", &cruft_expiration, N_("approxidate"), + N_("with --cruft, expire objects older than this")), +- OPT_MAGNITUDE(0, "max-cruft-size", &cruft_po_args.max_pack_size, +- N_("with --cruft, limit the size of new cruft packs")), ++ OPT_UNSIGNED(0, "max-cruft-size", &cruft_po_args.max_pack_size, ++ N_("with --cruft, limit the size of new cruft packs")), + OPT_BOOL('d', NULL, &delete_redundant, + N_("remove redundant packs, and run git-prune-packed")), + OPT_BOOL('f', NULL, &po_args.no_reuse_delta, +@@ -1233,8 +1233,8 @@ int cmd_repack(int argc, + N_("limits the maximum delta depth")), + OPT_STRING(0, "threads", &opt_threads, N_("n"), + N_("limits the maximum number of threads")), +- OPT_MAGNITUDE(0, "max-pack-size", &po_args.max_pack_size, +- N_("maximum size of each packfile")), ++ OPT_UNSIGNED(0, "max-pack-size", &po_args.max_pack_size, ++ N_("maximum size of each packfile")), + OPT_PARSE_LIST_OBJECTS_FILTER(&po_args.filter_options), + OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects, + N_("repack objects in packs marked with .keep")), +diff --git a/parse-options.c b/parse-options.c +index b287436e81a..d23e587e98b 100644 +--- a/parse-options.c ++++ b/parse-options.c +@@ -191,7 +191,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, + optname(opt, flags)); + return 0; + +- case OPTION_MAGNITUDE: ++ case OPTION_UNSIGNED: + if (unset) { + *(unsigned long *)opt->value = 0; + return 0; +@@ -656,7 +656,7 @@ static void show_negated_gitcomp(const struct option *opts, int show_all, + case OPTION_STRING: + case OPTION_FILENAME: + case OPTION_INTEGER: +- case OPTION_MAGNITUDE: ++ case OPTION_UNSIGNED: + case OPTION_CALLBACK: + case OPTION_BIT: + case OPTION_NEGBIT: +@@ -708,7 +708,7 @@ static int show_gitcomp(const struct option *opts, int show_all) + case OPTION_STRING: + case OPTION_FILENAME: + case OPTION_INTEGER: +- case OPTION_MAGNITUDE: ++ case OPTION_UNSIGNED: + case OPTION_CALLBACK: + if (opts->flags & PARSE_OPT_NOARG) + break; +diff --git a/parse-options.h b/parse-options.h +index 997ffbee805..14e4df1ee21 100644 +--- a/parse-options.h ++++ b/parse-options.h +@@ -25,7 +25,7 @@ enum parse_opt_type { + /* options with arguments (usually) */ + OPTION_STRING, + OPTION_INTEGER, +- OPTION_MAGNITUDE, ++ OPTION_UNSIGNED, + OPTION_CALLBACK, + OPTION_LOWLEVEL_CALLBACK, + OPTION_FILENAME +@@ -270,8 +270,8 @@ struct option { + #define OPT_CMDMODE(s, l, v, h, i) OPT_CMDMODE_F(s, l, v, h, i, 0) + + #define OPT_INTEGER(s, l, v, h) OPT_INTEGER_F(s, l, v, h, 0) +-#define OPT_MAGNITUDE(s, l, v, h) { \ +- .type = OPTION_MAGNITUDE, \ ++#define OPT_UNSIGNED(s, l, v, h) { \ ++ .type = OPTION_UNSIGNED, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ +diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c +index 997f55fd45b..fc3e2861c26 100644 +--- a/t/helper/test-parse-options.c ++++ b/t/helper/test-parse-options.c +@@ -6,7 +6,7 @@ + + static int boolean = 0; + static int integer = 0; +-static unsigned long magnitude = 0; ++static unsigned long unsigned_integer = 0; + static timestamp_t timestamp; + static int abbrev = 7; + static int verbose = -1; /* unspecified */ +@@ -140,7 +140,7 @@ int cmd__parse_options(int argc, const char **argv) + OPT_GROUP(""), + OPT_INTEGER('i', "integer", &integer, "get a integer"), + OPT_INTEGER('j', NULL, &integer, "get a integer, too"), +- OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"), ++ OPT_UNSIGNED('u', "unsigned", &unsigned_integer, "get an unsigned integer"), + OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), + OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1), + OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2), +@@ -210,7 +210,7 @@ int cmd__parse_options(int argc, const char **argv) + } + show(&expect, &ret, "boolean: %d", boolean); + show(&expect, &ret, "integer: %d", integer); +- show(&expect, &ret, "magnitude: %lu", magnitude); ++ show(&expect, &ret, "unsigned: %lu", unsigned_integer); + show(&expect, &ret, "timestamp: %"PRItime, timestamp); + show(&expect, &ret, "string: %s", string ? string : "(not set)"); + show(&expect, &ret, "abbrev: %d", abbrev); +diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh +index 0c538c4b437..65a11c8dbc8 100755 +--- a/t/t0040-parse-options.sh ++++ b/t/t0040-parse-options.sh +@@ -23,7 +23,7 @@ usage: test-tool parse-options <options> + -i, --[no-]integer <n> + get a integer + -j <n> get a integer, too +- -m, --magnitude <n> get a magnitude ++ -u, --unsigned <n> get an unsigned integer + --[no-]set23 set integer to 23 + --mode1 set integer to 1 (cmdmode option) + --mode2 set integer to 2 (cmdmode option) +@@ -115,30 +115,30 @@ test_expect_success 'OPT_INTEGER() negative' 'check integer: -2345 -i -2345' + test_expect_success 'OPT_INTEGER() kilo' 'check integer: 239616 -i 234k' + test_expect_success 'OPT_INTEGER() negative kilo' 'check integer: -239616 -i -234k' + +-test_expect_success 'OPT_MAGNITUDE() simple' ' +- check magnitude: 2345678 -m 2345678 ++test_expect_success 'OPT_UNSIGNED() simple' ' ++ check unsigned: 2345678 -u 2345678 + ' + +-test_expect_success 'OPT_MAGNITUDE() kilo' ' +- check magnitude: 239616 -m 234k ++test_expect_success 'OPT_UNSIGNED() kilo' ' ++ check unsigned: 239616 -u 234k + ' + +-test_expect_success 'OPT_MAGNITUDE() mega' ' +- check magnitude: 104857600 -m 100m ++test_expect_success 'OPT_UNSIGNED() mega' ' ++ check unsigned: 104857600 -u 100m + ' + +-test_expect_success 'OPT_MAGNITUDE() giga' ' +- check magnitude: 1073741824 -m 1g ++test_expect_success 'OPT_UNSIGNED() giga' ' ++ check unsigned: 1073741824 -u 1g + ' + +-test_expect_success 'OPT_MAGNITUDE() 3giga' ' +- check magnitude: 3221225472 -m 3g ++test_expect_success 'OPT_UNSIGNED() 3giga' ' ++ check unsigned: 3221225472 -u 3g + ' + + cat >expect <<\EOF + boolean: 2 + integer: 1729 +-magnitude: 16384 ++unsigned: 16384 + timestamp: 0 + string: 123 + abbrev: 7 +@@ -149,7 +149,7 @@ file: prefix/my.file + EOF + + test_expect_success 'short options' ' +- test-tool parse-options -s123 -b -i 1729 -m 16k -b -vv -n -F my.file \ ++ test-tool parse-options -s123 -b -i 1729 -u 16k -b -vv -n -F my.file \ + >output 2>output.err && + test_cmp expect output && + test_must_be_empty output.err +@@ -158,7 +158,7 @@ test_expect_success 'short options' ' + cat >expect <<\EOF + boolean: 2 + integer: 1729 +-magnitude: 16384 ++unsigned: 16384 + timestamp: 0 + string: 321 + abbrev: 10 +@@ -169,7 +169,7 @@ file: prefix/fi.le + EOF + + test_expect_success 'long options' ' +- test-tool parse-options --boolean --integer 1729 --magnitude 16k \ ++ test-tool parse-options --boolean --integer 1729 --unsigned 16k \ + --boolean --string2=321 --verbose --verbose --no-dry-run \ + --abbrev=10 --file fi.le --obsolete \ + >output 2>output.err && +@@ -181,7 +181,7 @@ test_expect_success 'abbreviate to something longer than SHA1 length' ' + cat >expect <<-EOF && + boolean: 0 + integer: 0 +- magnitude: 0 ++ unsigned: 0 + timestamp: 0 + string: (not set) + abbrev: 100 +@@ -255,7 +255,7 @@ test_expect_success 'superfluous value provided: cmdmode' ' + cat >expect <<\EOF + boolean: 1 + integer: 13 +-magnitude: 0 ++unsigned: 0 + timestamp: 0 + string: 123 + abbrev: 7 +@@ -278,7 +278,7 @@ test_expect_success 'intermingled arguments' ' + cat >expect <<\EOF + boolean: 0 + integer: 2 +-magnitude: 0 ++unsigned: 0 + timestamp: 0 + string: (not set) + abbrev: 7 +@@ -345,7 +345,7 @@ cat >expect <<\EOF + Callback: "four", 0 + boolean: 5 + integer: 4 +-magnitude: 0 ++unsigned: 0 + timestamp: 0 + string: (not set) + abbrev: 7 +@@ -370,7 +370,7 @@ test_expect_success 'OPT_CALLBACK() and callback errors work' ' + cat >expect <<\EOF + boolean: 1 + integer: 23 +-magnitude: 0 ++unsigned: 0 + timestamp: 0 + string: (not set) + abbrev: 7 +@@ -449,7 +449,7 @@ test_expect_success 'OPT_NUMBER_CALLBACK() works' ' + cat >expect <<\EOF + boolean: 0 + integer: 0 +-magnitude: 0 ++unsigned: 0 + timestamp: 0 + string: (not set) + abbrev: 7 +@@ -773,14 +773,14 @@ test_expect_success 'subcommands are incompatible with KEEP_DASHDASH unless in c + grep ^BUG err + ' + +-test_expect_success 'negative magnitude' ' +- test_must_fail test-tool parse-options --magnitude -1 >out 2>err && ++test_expect_success 'negative unsigned' ' ++ test_must_fail test-tool parse-options --unsigned -1 >out 2>err && + grep "non-negative integer" err && + test_must_be_empty out + ' + +-test_expect_success 'magnitude with units but no numbers' ' +- test_must_fail test-tool parse-options --magnitude m >out 2>err && ++test_expect_success 'unsigned with units but no numbers' ' ++ test_must_fail test-tool parse-options --unsigned m >out 2>err && + grep "non-negative integer" err && + test_must_be_empty out + ' + +-- +2.49.0.805.g082f7c87e0.dirty + + +From mboxrd@z Thu Jan 1 00:00:00 1970 +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Apr 2025 12:49:40 +0200 +Subject: [PATCH v4 5/7] parse-options: introduce precision handling for + `OPTION_INTEGER` +References: <20250417-b4-pks-parse-options-integers-v4-0-9cbc76b61cfe@pks.im> +To: git@vger.kernel.org +X-Mailer: b4 0.14.2 + +The `OPTION_INTEGER` option type accepts a signed integer. The type of +the underlying integer is a simple `int`, which restricts the range of +values accepted by such options. But there is a catch: because the +caller provides a pointer to the value via the `.value` field, which is +a simple void pointer. This has two consequences: + + - There is no check whether the passed value is sufficiently long to + store the entire range of `int`. This can lead to integer wraparound + in the best case and out-of-bounds writes in the worst case. + + - Even when a caller knows that they want to store a value larger than + `INT_MAX` they don't have a way to do so. + +In practice this doesn't tend to be a huge issue because users typically +don't end up passing huge values to most commands. But the parsing logic +is demonstrably broken, and it is too easy to get the calling convention +wrong. + +Improve the situation by introducing a new `precision` field into the +structure. This field gets assigned automatically by `OPT_INTEGER_F()` +and tracks the size of the passed value. Like this it becomes possible +for the caller to pass arbitrarily-sized integers and the underlying +logic knows to handle it correctly by doing range checks. Furthermore, +convert the code to use `strtoimax()` intstead of `strtol()` so that we +can also parse values larger than `LONG_MAX`. + +Note that we do not yet assert signedness of the passed variable, which +is another source of bugs. This will be handled in a subsequent commit. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +--- + builtin/fmt-merge-msg.c | 2 ++ + builtin/merge.c | 1 + + builtin/show-branch.c | 1 + + builtin/tag.c | 1 + + parse-options.c | 52 ++++++++++++++++++++++++++++++++----------- + parse-options.h | 6 +++++ + t/helper/test-parse-options.c | 3 +++ + t/t0040-parse-options.sh | 23 ++++++++++++++++++- + 8 files changed, 75 insertions(+), 14 deletions(-) + +diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c +index 240cdb474bc..3b6aac2cf7f 100644 +--- a/builtin/fmt-merge-msg.c ++++ b/builtin/fmt-merge-msg.c +@@ -24,6 +24,7 @@ int cmd_fmt_merge_msg(int argc, + .type = OPTION_INTEGER, + .long_name = "log", + .value = &shortlog_len, ++ .precision = sizeof(shortlog_len), + .argh = N_("n"), + .help = N_("populate log with at most <n> entries from shortlog"), + .flags = PARSE_OPT_OPTARG, +@@ -33,6 +34,7 @@ int cmd_fmt_merge_msg(int argc, + .type = OPTION_INTEGER, + .long_name = "summary", + .value = &shortlog_len, ++ .precision = sizeof(shortlog_len), + .argh = N_("n"), + .help = N_("alias for --log (deprecated)"), + .flags = PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, +diff --git a/builtin/merge.c b/builtin/merge.c +index 21787d45165..9ab10c7db0a 100644 +--- a/builtin/merge.c ++++ b/builtin/merge.c +@@ -254,6 +254,7 @@ static struct option builtin_merge_options[] = { + .type = OPTION_INTEGER, + .long_name = "log", + .value = &shortlog_len, ++ .precision = sizeof(shortlog_len), + .argh = N_("n"), + .help = N_("add (at most <n>) entries from shortlog to merge commit message"), + .flags = PARSE_OPT_OPTARG, +diff --git a/builtin/show-branch.c b/builtin/show-branch.c +index dab37019d29..b549d8c3f5b 100644 +--- a/builtin/show-branch.c ++++ b/builtin/show-branch.c +@@ -671,6 +671,7 @@ int cmd_show_branch(int ac, + .type = OPTION_INTEGER, + .long_name = "more", + .value = &extra, ++ .precision = sizeof(extra), + .argh = N_("n"), + .help = N_("show <n> more commits after the common ancestor"), + .flags = PARSE_OPT_OPTARG, +diff --git a/builtin/tag.c b/builtin/tag.c +index b266f12bb48..7597d93c71b 100644 +--- a/builtin/tag.c ++++ b/builtin/tag.c +@@ -483,6 +483,7 @@ int cmd_tag(int argc, + .type = OPTION_INTEGER, + .short_name = 'n', + .value = &filter.lines, ++ .precision = sizeof(filter.lines), + .argh = N_("n"), + .help = N_("print <n> lines of each tag message"), + .flags = PARSE_OPT_OPTARG, +diff --git a/parse-options.c b/parse-options.c +index d23e587e98b..768718a3972 100644 +--- a/parse-options.c ++++ b/parse-options.c +@@ -172,25 +172,51 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, + return (*opt->ll_callback)(p, opt, p_arg, p_unset); + } + case OPTION_INTEGER: ++ { ++ intmax_t upper_bound = INTMAX_MAX >> (bitsizeof(intmax_t) - CHAR_BIT * opt->precision); ++ intmax_t lower_bound = -upper_bound - 1; ++ intmax_t value; ++ + if (unset) { +- *(int *)opt->value = 0; +- return 0; +- } +- if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { +- *(int *)opt->value = opt->defval; +- return 0; +- } +- if (get_arg(p, opt, flags, &arg)) ++ value = 0; ++ } else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { ++ value = opt->defval; ++ } else if (get_arg(p, opt, flags, &arg)) { + return -1; +- if (!*arg) ++ } else if (!*arg) { + return error(_("%s expects a numerical value"), + optname(opt, flags)); +- if (!git_parse_int(arg, opt->value)) +- return error(_("%s expects an integer value" +- " with an optional k/m/g suffix"), ++ } else if (!git_parse_signed(arg, &value, upper_bound)) { ++ if (errno == ERANGE) ++ return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"), ++ arg, optname(opt, flags), lower_bound, upper_bound); ++ ++ return error(_("%s expects an integer value with an optional k/m/g suffix"), + optname(opt, flags)); +- return 0; ++ } ++ ++ if (value < lower_bound) ++ return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"), ++ arg, optname(opt, flags), lower_bound, upper_bound); + ++ switch (opt->precision) { ++ case 1: ++ *(int8_t *)opt->value = value; ++ return 0; ++ case 2: ++ *(int16_t *)opt->value = value; ++ return 0; ++ case 4: ++ *(int32_t *)opt->value = value; ++ return 0; ++ case 8: ++ *(int64_t *)opt->value = value; ++ return 0; ++ default: ++ BUG("invalid precision for option %s", ++ optname(opt, flags)); ++ } ++ } + case OPTION_UNSIGNED: + if (unset) { + *(unsigned long *)opt->value = 0; +diff --git a/parse-options.h b/parse-options.h +index 14e4df1ee21..4c430c7273c 100644 +--- a/parse-options.h ++++ b/parse-options.h +@@ -92,6 +92,10 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv, + * `value`:: + * stores pointers to the values to be filled. + * ++ * `precision`:: ++ * precision of the integer pointed to by `value` in number of bytes. Should ++ * typically be its `sizeof()`. ++ * + * `argh`:: + * token to explain the kind of argument this option wants. Does not + * begin in capital letter, and does not end with a full stop. +@@ -151,6 +155,7 @@ struct option { + int short_name; + const char *long_name; + void *value; ++ size_t precision; + const char *argh; + const char *help; + +@@ -214,6 +219,7 @@ struct option { + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ ++ .precision = sizeof(*v), \ + .argh = N_("n"), \ + .help = (h), \ + .flags = (f), \ +diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c +index fc3e2861c26..3689aee8315 100644 +--- a/t/helper/test-parse-options.c ++++ b/t/helper/test-parse-options.c +@@ -120,6 +120,7 @@ int cmd__parse_options(int argc, const char **argv) + }; + struct string_list expect = STRING_LIST_INIT_NODUP; + struct string_list list = STRING_LIST_INIT_NODUP; ++ int16_t i16 = 0; + + struct option options[] = { + OPT_BOOL(0, "yes", &boolean, "get a boolean"), +@@ -139,6 +140,7 @@ int cmd__parse_options(int argc, const char **argv) + OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4), + OPT_GROUP(""), + OPT_INTEGER('i', "integer", &integer, "get a integer"), ++ OPT_INTEGER(0, "i16", &i16, "get a 16 bit integer"), + OPT_INTEGER('j', NULL, &integer, "get a integer, too"), + OPT_UNSIGNED('u', "unsigned", &unsigned_integer, "get an unsigned integer"), + OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), +@@ -210,6 +212,7 @@ int cmd__parse_options(int argc, const char **argv) + } + show(&expect, &ret, "boolean: %d", boolean); + show(&expect, &ret, "integer: %d", integer); ++ show(&expect, &ret, "i16: %"PRIdMAX, (intmax_t) i16); + show(&expect, &ret, "unsigned: %lu", unsigned_integer); + show(&expect, &ret, "timestamp: %"PRItime, timestamp); + show(&expect, &ret, "string: %s", string ? string : "(not set)"); +diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh +index 65a11c8dbc8..be785547ead 100755 +--- a/t/t0040-parse-options.sh ++++ b/t/t0040-parse-options.sh +@@ -22,6 +22,7 @@ usage: test-tool parse-options <options> + + -i, --[no-]integer <n> + get a integer ++ --[no-]i16 <n> get a 16 bit integer + -j <n> get a integer, too + -u, --unsigned <n> get an unsigned integer + --[no-]set23 set integer to 23 +@@ -138,6 +139,7 @@ test_expect_success 'OPT_UNSIGNED() 3giga' ' + cat >expect <<\EOF + boolean: 2 + integer: 1729 ++i16: 0 + unsigned: 16384 + timestamp: 0 + string: 123 +@@ -158,6 +160,7 @@ test_expect_success 'short options' ' + cat >expect <<\EOF + boolean: 2 + integer: 1729 ++i16: 9000 + unsigned: 16384 + timestamp: 0 + string: 321 +@@ -169,7 +172,7 @@ file: prefix/fi.le + EOF + + test_expect_success 'long options' ' +- test-tool parse-options --boolean --integer 1729 --unsigned 16k \ ++ test-tool parse-options --boolean --integer 1729 --i16 9000 --unsigned 16k \ + --boolean --string2=321 --verbose --verbose --no-dry-run \ + --abbrev=10 --file fi.le --obsolete \ + >output 2>output.err && +@@ -181,6 +184,7 @@ test_expect_success 'abbreviate to something longer than SHA1 length' ' + cat >expect <<-EOF && + boolean: 0 + integer: 0 ++ i16: 0 + unsigned: 0 + timestamp: 0 + string: (not set) +@@ -255,6 +259,7 @@ test_expect_success 'superfluous value provided: cmdmode' ' + cat >expect <<\EOF + boolean: 1 + integer: 13 ++i16: 0 + unsigned: 0 + timestamp: 0 + string: 123 +@@ -278,6 +283,7 @@ test_expect_success 'intermingled arguments' ' + cat >expect <<\EOF + boolean: 0 + integer: 2 ++i16: 0 + unsigned: 0 + timestamp: 0 + string: (not set) +@@ -345,6 +351,7 @@ cat >expect <<\EOF + Callback: "four", 0 + boolean: 5 + integer: 4 ++i16: 0 + unsigned: 0 + timestamp: 0 + string: (not set) +@@ -370,6 +377,7 @@ test_expect_success 'OPT_CALLBACK() and callback errors work' ' + cat >expect <<\EOF + boolean: 1 + integer: 23 ++i16: 0 + unsigned: 0 + timestamp: 0 + string: (not set) +@@ -449,6 +457,7 @@ test_expect_success 'OPT_NUMBER_CALLBACK() works' ' + cat >expect <<\EOF + boolean: 0 + integer: 0 ++i16: 0 + unsigned: 0 + timestamp: 0 + string: (not set) +@@ -785,4 +794,16 @@ test_expect_success 'unsigned with units but no numbers' ' + test_must_be_empty out + ' + ++test_expect_success 'i16 limits range' ' ++ test-tool parse-options --i16 32767 >out && ++ test_grep "i16: 32767" out && ++ test_must_fail test-tool parse-options --i16 32768 2>err && ++ test_grep "value 32768 for option .i16. not in range \[-32768,32767\]" err && ++ ++ test-tool parse-options --i16 -32768 >out && ++ test_grep "i16: -32768" out && ++ test_must_fail test-tool parse-options --i16 -32769 2>err && ++ test_grep "value -32769 for option .i16. not in range \[-32768,32767\]" err ++' ++ + test_done + +-- +2.49.0.805.g082f7c87e0.dirty + + +From mboxrd@z Thu Jan 1 00:00:00 1970 +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Apr 2025 12:49:41 +0200 +Subject: [PATCH v4 6/7] parse-options: introduce precision handling for + `OPTION_UNSIGNED` +References: <20250417-b4-pks-parse-options-integers-v4-0-9cbc76b61cfe@pks.im> +To: git@vger.kernel.org +X-Mailer: b4 0.14.2 + +This commit is the equivalent to the preceding commit, but instead of +introducing precision handling for `OPTION_INTEGER` we introduce it for +`OPTION_UNSIGNED`. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +--- + parse-options.c | 48 +++++++++++++++++++++++++++++++++---------- + parse-options.h | 1 + + parse.c | 2 +- + parse.h | 1 + + t/helper/test-parse-options.c | 3 +++ + t/t0040-parse-options.sh | 18 +++++++++++++++- + 6 files changed, 60 insertions(+), 13 deletions(-) + +diff --git a/parse-options.c b/parse-options.c +index 768718a3972..a9a39ecaef6 100644 +--- a/parse-options.c ++++ b/parse-options.c +@@ -197,7 +197,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, + + if (value < lower_bound) + return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"), +- arg, optname(opt, flags), lower_bound, upper_bound); ++ arg, optname(opt, flags), (intmax_t)lower_bound, (intmax_t)upper_bound); + + switch (opt->precision) { + case 1: +@@ -218,21 +218,47 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, + } + } + case OPTION_UNSIGNED: ++ { ++ uintmax_t upper_bound = UINTMAX_MAX >> (bitsizeof(uintmax_t) - CHAR_BIT * opt->precision); ++ uintmax_t value; ++ + if (unset) { +- *(unsigned long *)opt->value = 0; +- return 0; +- } +- if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { +- *(unsigned long *)opt->value = opt->defval; +- return 0; +- } +- if (get_arg(p, opt, flags, &arg)) ++ value = 0; ++ } else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { ++ value = opt->defval; ++ } else if (get_arg(p, opt, flags, &arg)) { + return -1; +- if (!git_parse_ulong(arg, opt->value)) ++ } else if (!*arg) { ++ return error(_("%s expects a numerical value"), ++ optname(opt, flags)); ++ } else if (!git_parse_unsigned(arg, &value, upper_bound)) { ++ if (errno == ERANGE) ++ return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"), ++ arg, optname(opt, flags), (uintmax_t) 0, upper_bound); ++ + return error(_("%s expects a non-negative integer value" + " with an optional k/m/g suffix"), + optname(opt, flags)); +- return 0; ++ } ++ ++ switch (opt->precision) { ++ case 1: ++ *(uint8_t *)opt->value = value; ++ return 0; ++ case 2: ++ *(uint16_t *)opt->value = value; ++ return 0; ++ case 4: ++ *(uint32_t *)opt->value = value; ++ return 0; ++ case 8: ++ *(uint64_t *)opt->value = value; ++ return 0; ++ default: ++ BUG("invalid precision for option %s", ++ optname(opt, flags)); ++ } ++ } + + default: + BUG("opt->type %d should not happen", opt->type); +diff --git a/parse-options.h b/parse-options.h +index 4c430c7273c..dc460a26ff1 100644 +--- a/parse-options.h ++++ b/parse-options.h +@@ -281,6 +281,7 @@ struct option { + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ ++ .precision = sizeof(*v), \ + .argh = N_("n"), \ + .help = (h), \ + .flags = PARSE_OPT_NONEG, \ +diff --git a/parse.c b/parse.c +index 3c47448ca67..48313571aab 100644 +--- a/parse.c ++++ b/parse.c +@@ -51,7 +51,7 @@ int git_parse_signed(const char *value, intmax_t *ret, intmax_t max) + return 0; + } + +-static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max) ++int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max) + { + if (value && *value) { + char *end; +diff --git a/parse.h b/parse.h +index 6bb9a54d9ac..ea32de9a91f 100644 +--- a/parse.h ++++ b/parse.h +@@ -2,6 +2,7 @@ + #define PARSE_H + + int git_parse_signed(const char *value, intmax_t *ret, intmax_t max); ++int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max); + int git_parse_ssize_t(const char *, ssize_t *); + int git_parse_ulong(const char *, unsigned long *); + int git_parse_int(const char *value, int *ret); +diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c +index 3689aee8315..f2663dd0c07 100644 +--- a/t/helper/test-parse-options.c ++++ b/t/helper/test-parse-options.c +@@ -120,6 +120,7 @@ int cmd__parse_options(int argc, const char **argv) + }; + struct string_list expect = STRING_LIST_INIT_NODUP; + struct string_list list = STRING_LIST_INIT_NODUP; ++ uint16_t u16 = 0; + int16_t i16 = 0; + + struct option options[] = { +@@ -143,6 +144,7 @@ int cmd__parse_options(int argc, const char **argv) + OPT_INTEGER(0, "i16", &i16, "get a 16 bit integer"), + OPT_INTEGER('j', NULL, &integer, "get a integer, too"), + OPT_UNSIGNED('u', "unsigned", &unsigned_integer, "get an unsigned integer"), ++ OPT_UNSIGNED(0, "u16", &u16, "get a 16 bit unsigned integer"), + OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), + OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1), + OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2), +@@ -214,6 +216,7 @@ int cmd__parse_options(int argc, const char **argv) + show(&expect, &ret, "integer: %d", integer); + show(&expect, &ret, "i16: %"PRIdMAX, (intmax_t) i16); + show(&expect, &ret, "unsigned: %lu", unsigned_integer); ++ show(&expect, &ret, "u16: %"PRIuMAX, (uintmax_t) u16); + show(&expect, &ret, "timestamp: %"PRItime, timestamp); + show(&expect, &ret, "string: %s", string ? string : "(not set)"); + show(&expect, &ret, "abbrev: %d", abbrev); +diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh +index be785547ead..ca55ea8228c 100755 +--- a/t/t0040-parse-options.sh ++++ b/t/t0040-parse-options.sh +@@ -25,6 +25,7 @@ usage: test-tool parse-options <options> + --[no-]i16 <n> get a 16 bit integer + -j <n> get a integer, too + -u, --unsigned <n> get an unsigned integer ++ --u16 <n> get a 16 bit unsigned integer + --[no-]set23 set integer to 23 + --mode1 set integer to 1 (cmdmode option) + --mode2 set integer to 2 (cmdmode option) +@@ -141,6 +142,7 @@ boolean: 2 + integer: 1729 + i16: 0 + unsigned: 16384 ++u16: 0 + timestamp: 0 + string: 123 + abbrev: 7 +@@ -162,6 +164,7 @@ boolean: 2 + integer: 1729 + i16: 9000 + unsigned: 16384 ++u16: 32768 + timestamp: 0 + string: 321 + abbrev: 10 +@@ -173,7 +176,7 @@ EOF + + test_expect_success 'long options' ' + test-tool parse-options --boolean --integer 1729 --i16 9000 --unsigned 16k \ +- --boolean --string2=321 --verbose --verbose --no-dry-run \ ++ --u16 32k --boolean --string2=321 --verbose --verbose --no-dry-run \ + --abbrev=10 --file fi.le --obsolete \ + >output 2>output.err && + test_must_be_empty output.err && +@@ -186,6 +189,7 @@ test_expect_success 'abbreviate to something longer than SHA1 length' ' + integer: 0 + i16: 0 + unsigned: 0 ++ u16: 0 + timestamp: 0 + string: (not set) + abbrev: 100 +@@ -261,6 +265,7 @@ boolean: 1 + integer: 13 + i16: 0 + unsigned: 0 ++u16: 0 + timestamp: 0 + string: 123 + abbrev: 7 +@@ -285,6 +290,7 @@ boolean: 0 + integer: 2 + i16: 0 + unsigned: 0 ++u16: 0 + timestamp: 0 + string: (not set) + abbrev: 7 +@@ -353,6 +359,7 @@ boolean: 5 + integer: 4 + i16: 0 + unsigned: 0 ++u16: 0 + timestamp: 0 + string: (not set) + abbrev: 7 +@@ -379,6 +386,7 @@ boolean: 1 + integer: 23 + i16: 0 + unsigned: 0 ++u16: 0 + timestamp: 0 + string: (not set) + abbrev: 7 +@@ -459,6 +467,7 @@ boolean: 0 + integer: 0 + i16: 0 + unsigned: 0 ++u16: 0 + timestamp: 0 + string: (not set) + abbrev: 7 +@@ -806,4 +815,11 @@ test_expect_success 'i16 limits range' ' + test_grep "value -32769 for option .i16. not in range \[-32768,32767\]" err + ' + ++test_expect_success 'u16 limits range' ' ++ test-tool parse-options --u16 65535 >out && ++ test_grep "u16: 65535" out && ++ test_must_fail test-tool parse-options --u16 65536 2>err && ++ test_grep "value 65536 for option .u16. not in range \[0,65535\]" err ++' ++ + test_done + +-- +2.49.0.805.g082f7c87e0.dirty + + +From mboxrd@z Thu Jan 1 00:00:00 1970 +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Apr 2025 12:49:42 +0200 +Subject: [PATCH v4 7/7] parse-options: detect mismatches in integer + signedness +References: <20250417-b4-pks-parse-options-integers-v4-0-9cbc76b61cfe@pks.im> +To: git@vger.kernel.org +X-Mailer: b4 0.14.2 + +It was reported that "t5620-backfill.sh" fails on s390x and sparc64 in a +test that exercises the "--min-batch-size" command line option. The +symptom was that the option didn't seem to have an effect: we didn't +fetch objects with a batch size of 20, but instead fetched all objects +at once. + +As it turns out, the root cause is that `--min-batch-size` uses +`OPT_INTEGER()` to parse the command line option. While this macro +expects the caller to pass a pointer to an integer, we instead pass a +pointer to a `size_t`. This coincidentally works on most platforms, but +it breaks apart on the mentioned platforms because they are big endian. + +This issue isn't specific to git-backfill(1): there are a couple of +other places where we have the same type confusion going on. This +indicates that the issue really is the interface that the parse-options +subsystem provides -- it is simply too easy to get this wrong as there +isn't any kind of compiler warning, and things just work on the most +common systems. + +Address the systemic issue by introducing two new build asserts +`BARF_UNLESS_SIGNED()` and `BARF_UNLESS_UNSIGNED()`. As the names +already hint at, those macros will cause a compiler error when passed a +value that is not signed or unsigned, respectively. + +Adapt `OPT_INTEGER()`, `OPT_UNSIGNED()` as well as `OPT_MAGNITUDE()` to +use those asserts. This uncovers a small set of sites where we indeed +have the same bug as in git-backfill(1). Adapt all of them to use the +correct option. + +Reported-by: Todd Zullinger <tmz@pobox.com> +Reported-by: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de> +Helped-by: SZEDER Gábor <szeder.dev@gmail.com> +Helped-by: Jeff King <peff@peff.net> +Signed-off-by: Patrick Steinhardt <ps@pks.im> +--- + apply.c | 4 ++-- + builtin/backfill.c | 4 ++-- + builtin/column.c | 2 +- + builtin/grep.c | 4 ++-- + git-compat-util.h | 7 +++++++ + parse-options.h | 4 ++-- + 6 files changed, 16 insertions(+), 9 deletions(-) + +diff --git a/apply.c b/apply.c +index f274a379487..a850c7d75fe 100644 +--- a/apply.c ++++ b/apply.c +@@ -5123,8 +5123,8 @@ int apply_parse_options(int argc, const char **argv, + /* Think twice before adding "--nul" synonym to this */ + OPT_SET_INT('z', NULL, &state->line_termination, + N_("paths are separated with NUL character"), '\0'), +- OPT_INTEGER('C', NULL, &state->p_context, +- N_("ensure at least <n> lines of context match")), ++ OPT_UNSIGNED('C', NULL, &state->p_context, ++ N_("ensure at least <n> lines of context match")), + OPT_CALLBACK(0, "whitespace", state, N_("action"), + N_("detect new or modified lines that have whitespace errors"), + apply_option_parse_whitespace), +diff --git a/builtin/backfill.c b/builtin/backfill.c +index 33e1ea2f84f..d95d7a2d4d6 100644 +--- a/builtin/backfill.c ++++ b/builtin/backfill.c +@@ -123,8 +123,8 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit + .sparse = 0, + }; + struct option options[] = { +- OPT_INTEGER(0, "min-batch-size", &ctx.min_batch_size, +- N_("Minimum number of objects to request at a time")), ++ OPT_UNSIGNED(0, "min-batch-size", &ctx.min_batch_size, ++ N_("Minimum number of objects to request at a time")), + OPT_BOOL(0, "sparse", &ctx.sparse, + N_("Restrict the missing objects to the current sparse-checkout")), + OPT_END(), +diff --git a/builtin/column.c b/builtin/column.c +index 50314cc2559..ce6443d5fac 100644 +--- a/builtin/column.c ++++ b/builtin/column.c +@@ -31,7 +31,7 @@ int cmd_column(int argc, + struct option options[] = { + OPT_STRING(0, "command", &real_command, N_("name"), N_("lookup config vars")), + OPT_COLUMN(0, "mode", &colopts, N_("layout to use")), +- OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")), ++ OPT_UNSIGNED(0, "raw-mode", &colopts, N_("layout to use")), + OPT_INTEGER(0, "width", &copts.width, N_("maximum width")), + OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")), + OPT_STRING(0, "nl", &copts.nl, N_("string"), N_("padding space on right border")), +diff --git a/builtin/grep.c b/builtin/grep.c +index c4869733e1b..f23a6f1dc86 100644 +--- a/builtin/grep.c ++++ b/builtin/grep.c +@@ -983,9 +983,9 @@ int cmd_grep(int argc, + OPT_CALLBACK('C', "context", &opt, N_("n"), + N_("show <n> context lines before and after matches"), + context_callback), +- OPT_INTEGER('B', "before-context", &opt.pre_context, ++ OPT_UNSIGNED('B', "before-context", &opt.pre_context, + N_("show <n> context lines before matches")), +- OPT_INTEGER('A', "after-context", &opt.post_context, ++ OPT_UNSIGNED('A', "after-context", &opt.post_context, + N_("show <n> context lines after matches")), + OPT_INTEGER(0, "threads", &num_threads, + N_("use <n> worker threads")), +diff --git a/git-compat-util.h b/git-compat-util.h +index cf733b38acd..1218fcf81a4 100644 +--- a/git-compat-util.h ++++ b/git-compat-util.h +@@ -110,12 +110,19 @@ DISABLE_WARNING(-Wsign-compare) + # define BARF_UNLESS_COPYABLE(dst, src) \ + BUILD_ASSERT_OR_ZERO(__builtin_types_compatible_p(__typeof__(*(dst)), \ + __typeof__(*(src)))) ++ ++# define BARF_UNLESS_SIGNED(var) BUILD_ASSERT_OR_ZERO(((__typeof__(var)) -1) < 0) ++# define BARF_UNLESS_UNSIGNED(var) BUILD_ASSERT_OR_ZERO(((__typeof__(var)) -1) > 0) + #else + # define BARF_UNLESS_AN_ARRAY(arr) 0 + # define BARF_UNLESS_COPYABLE(dst, src) \ + BUILD_ASSERT_OR_ZERO(0 ? ((*(dst) = *(src)), 0) : \ + sizeof(*(dst)) == sizeof(*(src))) ++ ++# define BARF_UNLESS_SIGNED(var) 0 ++# define BARF_UNLESS_UNSIGNED(var) 0 + #endif ++ + /* + * ARRAY_SIZE - get the number of elements in a visible array + * @x: the array whose size you want. +diff --git a/parse-options.h b/parse-options.h +index dc460a26ff1..91c3e3c29b3 100644 +--- a/parse-options.h ++++ b/parse-options.h +@@ -218,7 +218,7 @@ struct option { + .type = OPTION_INTEGER, \ + .short_name = (s), \ + .long_name = (l), \ +- .value = (v), \ ++ .value = (v) + BARF_UNLESS_SIGNED(*(v)), \ + .precision = sizeof(*v), \ + .argh = N_("n"), \ + .help = (h), \ +@@ -280,7 +280,7 @@ struct option { + .type = OPTION_UNSIGNED, \ + .short_name = (s), \ + .long_name = (l), \ +- .value = (v), \ ++ .value = (v) + BARF_UNLESS_UNSIGNED(*(v)), \ + .precision = sizeof(*v), \ + .argh = N_("n"), \ + .help = (h), \ + +-- +2.49.0.805.g082f7c87e0.dirty + + diff --git a/system/git/perl-getopt-long.patch b/system/git/perl-getopt-long.patch deleted file mode 100644 index dd5776033..000000000 --- a/system/git/perl-getopt-long.patch +++ /dev/null @@ -1,459 +0,0 @@ -From 46edab516bf04c190cb2e100419dee817d3f33f6 Mon Sep 17 00:00:00 2001 -From: Todd Zullinger <tmz@pobox.com> -Date: Wed, 15 Nov 2023 12:39:44 -0500 -Subject: [PATCH] send-email: remove stray characters from usage - -A few stray single quotes crept into the usage string in a2ce608244 -(send-email docs: add format-patch options, 2021-10-25). Remove them. - -Signed-off-by: Todd Zullinger <tmz@pobox.com> -Signed-off-by: Junio C Hamano <gitster@pobox.com> ---- - git-send-email.perl | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/git-send-email.perl b/git-send-email.perl -index 897cea6564fb50..85544a18bfe37e 100755 ---- a/git-send-email.perl -+++ b/git-send-email.perl -@@ -28,8 +28,8 @@ - - sub usage { - print <<EOT; --git send-email' [<options>] <file|directory> --git send-email' [<options>] <format-patch options> -+git send-email [<options>] <file|directory> -+git send-email [<options>] <format-patch options> - git send-email --dump-aliases - - Composing: -From d13a73e383076636534e10ba799af0c9a2b85357 Mon Sep 17 00:00:00 2001 -From: Todd Zullinger <tmz@pobox.com> -Date: Thu, 16 Nov 2023 14:30:10 -0500 -Subject: [PATCH] perl: bump the required Perl version to 5.8.1 from 5.8.0 - -The following commit will make use of a Getopt::Long feature which is -only present in Perl >= 5.8.1. Document that as the minimum version we -support. - -Many of our Perl scripts will continue to run with 5.8.0 but this change -allows us to adjust them as needed without breaking any promises to our -users. - -The Perl requirement was last changed in d48b284183 (perl: bump the -required Perl version to 5.8 from 5.6.[21], 2010-09-24). At that time, -5.8.0 was 8 years old. It is now over 21 years old. - -Signed-off-by: Todd Zullinger <tmz@pobox.com> -Signed-off-by: Junio C Hamano <gitster@pobox.com> ---- - Documentation/CodingGuidelines | 2 +- - INSTALL | 2 +- - contrib/diff-highlight/DiffHighlight.pm | 2 +- - contrib/mw-to-git/Git/Mediawiki.pm | 2 +- - git-archimport.perl | 2 +- - git-cvsexportcommit.perl | 2 +- - git-cvsimport.perl | 2 +- - git-cvsserver.perl | 2 +- - git-send-email.perl | 4 ++-- - git-svn.perl | 2 +- - gitweb/INSTALL | 2 +- - gitweb/gitweb.perl | 2 +- - perl/Git.pm | 2 +- - perl/Git/I18N.pm | 2 +- - perl/Git/LoadCPAN.pm | 2 +- - perl/Git/LoadCPAN/Error.pm | 2 +- - perl/Git/LoadCPAN/Mail/Address.pm | 2 +- - perl/Git/Packet.pm | 2 +- - t/t0202/test.pl | 2 +- - t/t5562/invoke-with-content-length.pl | 2 +- - t/t9700/test.pl | 2 +- - t/test-terminal.perl | 2 +- - 22 files changed, 23 insertions(+), 23 deletions(-) - -diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines -index 65af8d82cedd5f..aa0195d6ebab0b 100644 ---- a/Documentation/CodingGuidelines -+++ b/Documentation/CodingGuidelines -@@ -490,7 +490,7 @@ For Perl programs: - - - Most of the C guidelines above apply. - -- - We try to support Perl 5.8 and later ("use Perl 5.008"). -+ - We try to support Perl 5.8.1 and later ("use Perl 5.008001"). - - - use strict and use warnings are strongly preferred. - -diff --git a/INSTALL b/INSTALL -index 4b422888828d0e..06f29a8ae70022 100644 ---- a/INSTALL -+++ b/INSTALL -@@ -119,7 +119,7 @@ Issues of note: - - A POSIX-compliant shell is required to run some scripts needed - for everyday use (e.g. "bisect", "request-pull"). - -- - "Perl" version 5.8 or later is needed to use some of the -+ - "Perl" version 5.8.1 or later is needed to use some of the - features (e.g. sending patches using "git send-email", - interacting with svn repositories with "git svn"). If you can - live without these, use NO_PERL. Note that recent releases of -diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm -index 376f577737591e..636add69680675 100644 ---- a/contrib/diff-highlight/DiffHighlight.pm -+++ b/contrib/diff-highlight/DiffHighlight.pm -@@ -1,6 +1,6 @@ - package DiffHighlight; - --use 5.008; -+use 5.008001; - use warnings FATAL => 'all'; - use strict; - -diff --git a/contrib/mw-to-git/Git/Mediawiki.pm b/contrib/mw-to-git/Git/Mediawiki.pm -index 917d9e2d3222c1..ff7811225ee671 100644 ---- a/contrib/mw-to-git/Git/Mediawiki.pm -+++ b/contrib/mw-to-git/Git/Mediawiki.pm -@@ -1,6 +1,6 @@ - package Git::Mediawiki; - --use 5.008; -+use 5.008001; - use strict; - use POSIX; - use Git; -diff --git a/git-archimport.perl b/git-archimport.perl -index b7c173c345544d..f5a317b89961ce 100755 ---- a/git-archimport.perl -+++ b/git-archimport.perl -@@ -54,7 +54,7 @@ =head1 Devel Notes - - =cut - --use 5.008; -+use 5.008001; - use strict; - use warnings; - use Getopt::Std; -diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl -index 289d4bc684dc26..1e03ba94d1b271 100755 ---- a/git-cvsexportcommit.perl -+++ b/git-cvsexportcommit.perl -@@ -1,6 +1,6 @@ - #!/usr/bin/perl - --use 5.008; -+use 5.008001; - use strict; - use warnings; - use Getopt::Std; -diff --git a/git-cvsimport.perl b/git-cvsimport.perl -index 7bf3c12d678974..07ea3443f7f336 100755 ---- a/git-cvsimport.perl -+++ b/git-cvsimport.perl -@@ -13,7 +13,7 @@ - # The head revision is on branch "origin" by default. - # You can change that with the '-o' option. - --use 5.008; -+use 5.008001; - use strict; - use warnings; - use Getopt::Long; -diff --git a/git-cvsserver.perl b/git-cvsserver.perl -index 7b757360e28c01..124f598bdc0705 100755 ---- a/git-cvsserver.perl -+++ b/git-cvsserver.perl -@@ -15,7 +15,7 @@ - #### - #### - --use 5.008; -+use 5.008001; - use strict; - use warnings; - use bytes; -diff --git a/git-send-email.perl b/git-send-email.perl -index 897cea6564fb50..041db702d46fcc 100755 ---- a/git-send-email.perl -+++ b/git-send-email.perl -@@ -16,7 +16,7 @@ - # and second line is the subject of the message. - # - --use 5.008; -+use 5.008001; - use strict; - use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); - use Getopt::Long; -@@ -228,7 +228,7 @@ sub system_or_msg { - my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code); - if (defined $msg) { - # Quiet the 'redundant' warning category, except we -- # need to support down to Perl 5.8, so we can't do a -+ # need to support down to Perl 5.8.1, so we can't do a - # "no warnings 'redundant'", since that category was - # introduced in perl 5.22, and asking for it will die - # on older perls. -diff --git a/git-svn.perl b/git-svn.perl -index be987e316f92ac..1d1c52f42468b1 100755 ---- a/git-svn.perl -+++ b/git-svn.perl -@@ -1,7 +1,7 @@ - #!/usr/bin/perl - # Copyright (C) 2006, Eric Wong <normalperson@yhbt.net> - # License: GPL v2 or later --use 5.008; -+use 5.008001; - use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); - use strict; - use vars qw/ $AUTHOR $VERSION -diff --git a/gitweb/INSTALL b/gitweb/INSTALL -index a58e6b3c44b0ef..dadc6efa81f035 100644 ---- a/gitweb/INSTALL -+++ b/gitweb/INSTALL -@@ -29,7 +29,7 @@ Requirements - ------------ - - - Core git tools -- - Perl 5.8 -+ - Perl 5.8.1 - - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename. - - web server - -diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl -index e66eb3d9bad7cf..55e7c6567e6c47 100755 ---- a/gitweb/gitweb.perl -+++ b/gitweb/gitweb.perl -@@ -7,7 +7,7 @@ - # - # This program is licensed under the GPLv2 - --use 5.008; -+use 5.008001; - use strict; - use warnings; - # handle ACL in file access tests -diff --git a/perl/Git.pm b/perl/Git.pm -index 117765dc73c4a8..03bf570bf4c852 100644 ---- a/perl/Git.pm -+++ b/perl/Git.pm -@@ -7,7 +7,7 @@ Git - Perl interface to the Git version control system - - package Git; - --use 5.008; -+use 5.008001; - use strict; - use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); - -diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm -index 895e759c57a9d9..5454c3a6d2c433 100644 ---- a/perl/Git/I18N.pm -+++ b/perl/Git/I18N.pm -@@ -1,5 +1,5 @@ - package Git::I18N; --use 5.008; -+use 5.008001; - use strict; - use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); - BEGIN { -diff --git a/perl/Git/LoadCPAN.pm b/perl/Git/LoadCPAN.pm -index 0c360bc7998607..8c7fa805f97390 100644 ---- a/perl/Git/LoadCPAN.pm -+++ b/perl/Git/LoadCPAN.pm -@@ -1,5 +1,5 @@ - package Git::LoadCPAN; --use 5.008; -+use 5.008001; - use strict; - use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); - -diff --git a/perl/Git/LoadCPAN/Error.pm b/perl/Git/LoadCPAN/Error.pm -index 5d84c202884b7c..5cecb0fcd6930a 100644 ---- a/perl/Git/LoadCPAN/Error.pm -+++ b/perl/Git/LoadCPAN/Error.pm -@@ -1,5 +1,5 @@ - package Git::LoadCPAN::Error; --use 5.008; -+use 5.008001; - use strict; - use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); - use Git::LoadCPAN ( -diff --git a/perl/Git/LoadCPAN/Mail/Address.pm b/perl/Git/LoadCPAN/Mail/Address.pm -index 340e88a7a56be3..9f808090a66a16 100644 ---- a/perl/Git/LoadCPAN/Mail/Address.pm -+++ b/perl/Git/LoadCPAN/Mail/Address.pm -@@ -1,5 +1,5 @@ - package Git::LoadCPAN::Mail::Address; --use 5.008; -+use 5.008001; - use strict; - use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); - use Git::LoadCPAN ( -diff --git a/perl/Git/Packet.pm b/perl/Git/Packet.pm -index d144f5168f37ad..d896e6952399b0 100644 ---- a/perl/Git/Packet.pm -+++ b/perl/Git/Packet.pm -@@ -1,5 +1,5 @@ - package Git::Packet; --use 5.008; -+use 5.008001; - use strict; - use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); - BEGIN { -diff --git a/t/t0202/test.pl b/t/t0202/test.pl -index 2cbf7b95907384..47d96a2a13f93b 100755 ---- a/t/t0202/test.pl -+++ b/t/t0202/test.pl -@@ -1,5 +1,5 @@ - #!/usr/bin/perl --use 5.008; -+use 5.008001; - use lib (split(/:/, $ENV{GITPERLLIB})); - use strict; - use warnings; -diff --git a/t/t5562/invoke-with-content-length.pl b/t/t5562/invoke-with-content-length.pl -index 718dd9b49d493e..9babb9a375e5fb 100644 ---- a/t/t5562/invoke-with-content-length.pl -+++ b/t/t5562/invoke-with-content-length.pl -@@ -1,4 +1,4 @@ --use 5.008; -+use 5.008001; - use strict; - use warnings; - -diff --git a/t/t9700/test.pl b/t/t9700/test.pl -index 6d753708d2acb6..d8e85482ab2ba7 100755 ---- a/t/t9700/test.pl -+++ b/t/t9700/test.pl -@@ -1,7 +1,7 @@ - #!/usr/bin/perl - use lib (split(/:/, $ENV{GITPERLLIB})); - --use 5.008; -+use 5.008001; - use warnings; - use strict; - -diff --git a/t/test-terminal.perl b/t/test-terminal.perl -index 1bcf01a9a42a61..3810e9bb431903 100755 ---- a/t/test-terminal.perl -+++ b/t/test-terminal.perl -@@ -1,5 +1,5 @@ - #!/usr/bin/perl --use 5.008; -+use 5.008001; - use strict; - use warnings; - use IO::Pty; -From 6ff658cc78f36baa74c0f25314b0043a8f4b4fc6 Mon Sep 17 00:00:00 2001 -From: Todd Zullinger <tmz@pobox.com> -Date: Thu, 16 Nov 2023 14:30:11 -0500 -Subject: [PATCH] send-email: avoid duplicate specification warnings - -A warning is issued for options which are specified more than once -beginning with perl-Getopt-Long >= 2.55. In addition to causing users -to see warnings, this results in test failures which compare the output. -An example, from t9001-send-email.37: - - | +++ diff -u expect actual - | --- expect 2023-11-14 10:38:23.854346488 +0000 - | +++ actual 2023-11-14 10:38:23.848346466 +0000 - | @@ -1,2 +1,7 @@ - | +Duplicate specification "no-chain-reply-to" for option "no-chain-reply-to" - | +Duplicate specification "to-cover|to-cover!" for option "to-cover" - | +Duplicate specification "cc-cover|cc-cover!" for option "cc-cover" - | +Duplicate specification "no-thread" for option "no-thread" - | +Duplicate specification "no-to-cover" for option "no-to-cover" - | fatal: longline.patch:35 is longer than 998 characters - | warning: no patches were sent - | error: last command exited with $?=1 - | not ok 37 - reject long lines - -Remove the duplicate option specs. These are primarily the explicit -'--no-' prefix opts which were added in f471494303 (git-send-email.perl: -support no- prefix with older GetOptions, 2015-01-30). This was done -specifically to support perl-5.8.0 which includes Getopt::Long 2.32[1]. - -Getopt::Long 2.33 added support for the '--no-' prefix natively by -appending '!' to the option specification string, which was included in -perl-5.8.1 and is not present in perl-5.8.0. The previous commit bumped -the minimum supported Perl version to 5.8.1 so we no longer need to -provide the '--no-' variants for negatable options manually. - -Teach `--git-completion-helper` to output the '--no-' options. They are -not included in the options hash and would otherwise be lost. - -Signed-off-by: Todd Zullinger <tmz@pobox.com> -Signed-off-by: Junio C Hamano <gitster@pobox.com> ---- - git-send-email.perl | 19 ++++++------------- - 1 file changed, 6 insertions(+), 13 deletions(-) - -diff --git a/git-send-email.perl b/git-send-email.perl -index 041db702d46fcc..60afafb375d8f4 100755 ---- a/git-send-email.perl -+++ b/git-send-email.perl -@@ -119,13 +119,16 @@ sub completion_helper { - - foreach my $key (keys %$original_opts) { - unless (exists $not_for_completion{$key}) { -- $key =~ s/!$//; -+ my $negatable = ($key =~ s/!$//); - - if ($key =~ /[:=][si]$/) { - $key =~ s/[:=][si]$//; - push (@send_email_opts, "--$_=") foreach (split (/\|/, $key)); - } else { - push (@send_email_opts, "--$_") foreach (split (/\|/, $key)); -+ if ($negatable) { -+ push (@send_email_opts, "--no-$_") foreach (split (/\|/, $key)); -+ } - } - } - } -@@ -491,7 +494,6 @@ sub config_regexp { - "bcc=s" => \@getopt_bcc, - "no-bcc" => \$no_bcc, - "chain-reply-to!" => \$chain_reply_to, -- "no-chain-reply-to" => sub {$chain_reply_to = 0}, - "sendmail-cmd=s" => \$sendmail_cmd, - "smtp-server=s" => \$smtp_server, - "smtp-server-option=s" => \@smtp_server_options, -@@ -506,34 +508,25 @@ sub config_regexp { - "smtp-auth=s" => \$smtp_auth, - "no-smtp-auth" => sub {$smtp_auth = 'none'}, - "annotate!" => \$annotate, -- "no-annotate" => sub {$annotate = 0}, - "compose" => \$compose, - "quiet" => \$quiet, - "cc-cmd=s" => \$cc_cmd, - "suppress-from!" => \$suppress_from, -- "no-suppress-from" => sub {$suppress_from = 0}, - "suppress-cc=s" => \@suppress_cc, - "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc, -- "no-signed-off-cc|no-signed-off-by-cc" => sub {$signed_off_by_cc = 0}, -- "cc-cover|cc-cover!" => \$cover_cc, -- "no-cc-cover" => sub {$cover_cc = 0}, -- "to-cover|to-cover!" => \$cover_to, -- "no-to-cover" => sub {$cover_to = 0}, -+ "cc-cover!" => \$cover_cc, -+ "to-cover!" => \$cover_to, - "confirm=s" => \$confirm, - "dry-run" => \$dry_run, - "envelope-sender=s" => \$envelope_sender, - "thread!" => \$thread, -- "no-thread" => sub {$thread = 0}, - "validate!" => \$validate, -- "no-validate" => sub {$validate = 0}, - "transfer-encoding=s" => \$target_xfer_encoding, - "format-patch!" => \$format_patch, -- "no-format-patch" => sub {$format_patch = 0}, - "8bit-encoding=s" => \$auto_8bit_encoding, - "compose-encoding=s" => \$compose_encoding, - "force" => \$force, - "xmailer!" => \$use_xmailer, -- "no-xmailer" => sub {$use_xmailer = 0}, - "batch-size=i" => \$batch_size, - "relogin-delay=i" => \$relogin_delay, - "git-completion-helper" => \$git_completion_helper, |