# [Outreachy] Proposal draft v1: Improve how command arguments and options are scanned and parsed

4 messages from 2026-11-01 to 2026-11-05. Participants: Cynthia Uche, Usman Akinyemi, Priestess.
Thread: https://gitlist.dev/t/66741

## Cynthia Uche, 2026-11-01 21:06

Subject: [Outreachy] Proposal draft v1: Improve how command arguments and options are scanned and parsed
Message-ID: <20261101210623.622-1-herroyalpriestess@gmail.com>
URL: https://gitlist.dev/e/20261101210623.622-1-herroyalpriestess%40gmail.com

```
Hi team,

This is the first draft of my proposal for "Improve how command
arguments and options are scanned and parsed" for the December 2026
Outreachy round. I would appreciate feedback, especially on the
candidate inventory (section 4) and the timeline (section 5).

1. About me and logistics
-------------------------
Name:     Cynthia Uche. I'm also called Priestess.
Pronouns: she/her
Email:    herroyalpriestess@gmail.com
GitHub:   https://github.com/herrpriestess
Blog:     https://medium.com/@thegirlSynth
Location: Abia, Nigeria (WAT, UTC+1)

I have read the Outreachy eligibility rules and I meet all the
requirements.

I am a software engineer, mostly working on Python and backend
projects. C was the first language I learned, so I am comfortable
reading and writing it, although I have not used it much day to day
until now.

I have a merged patch in OpenStack Shared File Systems Service (Manila)
(https://review.opendev.org/c/openstack/manila/+/982431), which went
through Gerrit review, so reworking a change after review comments
is not new to me.

Availability: 30 hours per week

2. My Git work
--------------
Microproject: t2104: avoid suppressing git's exit code
  Thread: https://lore.kernel.org/git/20261021033723.5265-1-herroyalpriestess@gmail.com/
  Topic:  cu/t2104-avoid-pipes
  Status: merged to master (merge commit 84bea4324a)
  Patrick reviewed it and found it good, so it needed no second
  version.

Review of the early-scan series:
  [PATCH v3 4/4] fast-import: use early_scan_options() ...
  https://lore.kernel.org/git/20261031142255.4941-1-herroyalpriestess@gmail.com/
  I ran t9300 (261 tests) and t0040 (118 tests) on the topic as in
  origin/seen (tip dabb68a20d); all pass. I noticed that
  EARLY_SCAN_STOP_AT_NON_OPTION is passed although the real parse
  does not use PARSE_OPT_STOP_AT_NON_OPTION. With that argument set
  to 0 locally, "--depth 5 extra --allow-unsafe-features" reached
  the usage error (exit 129) instead of the "forbidden" error
  (exit 128), so the unsafe feature line had been accepted first.
  I suggested documenting this in the comment at the call site.

Steps I have taken: read the microproject and application pages,
SubmittingPatches and MyFirstContribution; built Git and ran the
relevant tests; sent my microproject by git send-email; read the
early-scan series v1 to v3 with its reviews; reproduced the
git column behaviour (section 4); ran the new fast-import
tests in t9300 and tried two command lines by hand (section 2).

3. Project understanding
------------------------
3.1 The problem: two parses that can disagree

Some commands must look at a few options before they call
parse_options(), because the result changes how they read their input.
fast-import is the example: the "feature" lines at the start of the
stream are handled before the command line is parsed, so
cmd_fast_import() scans argv early for --allow-unsafe-features.

The old scan was a hand-rolled loop. It compared each argument with
the exact option name and stopped at the first argument that did not
start with a dash. parse_options() is more permissive: it accepts
unambiguous abbreviations and values separated from their option by a
space. So the two disagree:

 - "--depth 5 --allow-unsafe-features": the scan stops at "5" and
   never sees the flag, although parse_options() accepts it. Unsafe
   features are refused. That is the safe direction, but still wrong.
 - "--quiet --export-pack --allow-unsafe-features": every argument
   starts with a dash, so the old scan saw the flag and allowed
   unsafe features. But parse_options() takes it as the value of
   --export-pack-edges, which --export-pack abbreviates. That is the
   dangerous direction.

3.2 The API in v3

early_scan_options(argc, argv, option, flags, fn, data) walks argv
using the same struct option array that parse_options() uses. Options
the caller cares about are marked PARSE_OPT_EARLY, and the callback
receives (option, value, unset, pos, data); a non-zero return stops
the scan. It returns the index of the first argument not consumed.
Two helpers make it agree with parse_options():
parse_options_takes_argument() (1/4, which also counts
PARSE_OPT_LASTARG_DEFAULT options as taking a value) and
lookup_long_opt(), extracted from parse_long_opt() (2/4), so negated
forms, abbreviations and aliases are looked up the same way without
side effects. The scan always stops at "--" and "--end-of-options";
EARLY_SCAN_STOP_AT_NON_OPTION is for callers whose parse_options()
call uses PARSE_OPT_STOP_AT_NON_OPTION or subcommands. It should not
be used with PARSE_OPT_KEEP_UNKNOWN_OPT.

3.3 Why "stop when unsure" is safe

The scan can miss an option that parse_options() would accept, but it
never reports one that parse_options() would not. It stops at anything
it cannot reliably skip: short options, unknown options, ambiguous or
disallowed abbreviations, low-level callbacks (which may consume any
number of arguments), a value stuck to a negated or no-argument
option, and a value-taking option given as the last argument. Missing
an option only makes the caller behave as if it was not given (for
fast-import, unsafe features are refused). Mistaking an option's value
for an option is the dangerous failure.

Why not make parse_options() do a dry run? Because it would be a much
larger refactoring. According to the 3/4 commit message, a dry run
would have to avoid writing through opt->value, calling callbacks,
dying on invalid values, handling --help and tracking command mode
conflicts. Junio has said he would prefer this in the long term [2].
The scan is simpler, and it keeps the option array and the
PARSE_OPT_EARLY marks, so it does not block a dry run later.

3.4 History of the series

 - v1 (2 Sep, 6 patches): early_scan_options() with its own option
   table, a bisect fix for "--" detection when a term is "--", a
   rev-parse fix for "--" when it is an option value,
   parse_options_takes_argument(), building the scan from a struct
   option array, and the fast-import conversion.
 - v2 (23 Sep, 3 patches): the scan reads the real struct option
   array and PARSE_OPT_EARLY marks the wanted options. The bisect and
   rev-parse patches are gone.
 - v3 (12 Oct, 4 patches, rebased on Git 2.56, a018953688): adds the
   lookup_long_opt() extraction, handles LASTARG_DEFAULT, stops at
   more cases, adds the "unset" callback argument, and removes the
   NEEDSWORK comment in fast-import.

Why bisect and rev-parse were dropped: Junio said he saw little
practical value in them, since he doubted users would name a bisect
term or a --default value "--", unlike fast-import, where the bug
affects a safety check. Christian removed both patches in v2 and said
that bisect_start() could use the early scan once it has been converted
to parse-options [2], [3].

3.5 Current state and open follow-ups

As of Nov 01, cc/early-scan-options is in seen and not in next
(early_scan_options() is not in origin/next). Junio's Oct 28 "What's
cooking" listed it as "Needs review". Kaartic reviewed v3 on
30-31 Oct and found it in much better shape. His points:
 - consume the lookup_long_opt() result in parse_long_opt() with a
   switch instead of an if chain (2/4);
 - whether the PARSE_OPT_EARLY checks in parse_options_check() should
   also be in early_scan_options(), in case it is called before
   parse_options_start() (3/4);
 - document that the callback must not keep the "option" pointer,
   because it is freed after the scan (3/4).

Not covered by the series: commands that do not use parse-options
at all, short options (the scan stops at them) and low-level
callbacks. Those are what section 4 is about.

4. Candidate inventory and risks
--------------------------------
Method: I counted hand-written option checks per builtin with
  git grep -c 'strcmp(arg' -- builtin/ | sort -t: -k2 -n
  git grep -n 'skip_prefix(arg, "--' -- builtin/
and listed builtins that never call parse_options with
  git grep -L parse_options -- 'builtin/*.c'
The counts include small value converters (am, config, log, revert),
which I treat as out of scope. The densest real parsing is in
rev-parse (43), rev-list (18), index-pack and diff (13 each) and
bisect (12).

 - git column (builtin/column.c, cmd_column())
   It already uses parse_options(). Only the early step is by hand:
   if argv[1] starts with "--command=", config is read with that
   name before parse_options() runs, and a die() follows if the
   parsed and scanned values differ. Observed:
     git column --command=foo               works (exit 0)
     git column --command foo               dies (exit 128)
     git column --width=40 --command=foo    dies (exit 128)
     git column --com=foo                   dies (exit 128)
   The three failures all print "fatal: --command must be the
   first argument", even when "--command foo" really is first. I found
   this rule only in the die() message: searching Documentation/ and
   t/ for "must be the first" finds nothing related, and t9002 does not
   test --command.
   Fits: replacing the scan with early_scan_options() and
   PARSE_OPT_EARLY on --command, once the series is in next. I have not
   yet checked whether any option in column's table would
   make the scan stop.
   Risk: user-visible change (errors become success). Question for
   the list: is the "must be first" rule intentional? The callback
   receives pos, so it could be kept (pos == 0) or dropped.

 - bisect (builtin/bisect.c)
   bisect_start() has two hand-written loops: one looks for "--"
   (the bug of the dropped v1 patch) and one parses --no-checkout,
   --first-parent, --reset-when-found[=...] and the four --term-*
   options, each in a "--opt value" and a "--opt=value" branch.
   bisect_run() checks only argv[0] for --reset-when-found, and
   bisect_terms() compares one string with --term-good/old/bad/new.
   Fits: convert to parse-options first, order: bisect_terms(),
   bisect_run(), then bisect_start() as a stretch.
   Risk: bisect_start() is called from "bisect start", from
   bisect_autostart() and from "bisect replay"; the original
   arguments are written to BISECT_LOG and replayed, so they must be
   kept. Abbreviations and "--no-" forms would start to be accepted.

 - check-ref-format (builtin/check-ref-format.c)
   One loop over leading dash arguments handles --normalize/--print,
   --allow-onelevel, --no-allow-onelevel and --refspec-pattern;
   --branch is special-cased as exactly argc == 3; anything else
   calls usage(). git.c lists it as NO_PARSEOPT.
   Tests: t1402 covers --normalize, --allow-onelevel and
   --refspec-pattern, alone and combined, and --branch (also from
   outside a repository, and with the name -nain). It does not test
   --print, abbreviations or "--". Fien Dora has a patch adding tests
   for the precedence of --allow-onelevel and --no-allow-onelevel [10];
   Junio said he would queue it, and it would guard that part of a
   conversion.
   Observed on origin/master (v2.56.0-307-g50fe5cc63f):
     --norm refs/heads/x           usage text, exit=129
     -- refs/heads/x               usage text, exit=129
     --normalize -- refs/heads/x   usage text, exit=129
     refs/heads/x --normalize      usage text, exit=129
     --branch -nain                "fatal: '-nain' is not a valid
                                   branch name", exit=128
   Fits: parse-options: OPT_BIT for the flags (--no-allow-onelevel
   then comes for free), OPT_ALIAS for --print, and dropping
   NO_PARSEOPT in git.c.
   Risks: commands that fail today would succeed: abbreviations, "--"
   and negated forms such as --no-normalize would be accepted, and
   so would options after <refname> unless I pass
   PARSE_OPT_STOP_AT_NON_OPTION. --branch needs an explicit check
   that it is not combined with other options, and "--branch -nain"
   must keep failing with the invalid branch name error (exit 128),
   not become a usage error from short option parsing.
   Question for the list: is accepting "--" and abbreviations
   welcome, should options after <refname> keep being rejected?

 - unpack-objects (builtin/unpack-objects.c) [if time allows]
   -n, -q, -r, --strict[=<msg-types>], --pack_header=,
   --max-input-size=. Risk: other Git programs run it with fixed
   arguments, so those must keep working; I will list the callers
   before choosing it. index-pack and fetch-pack are larger,
   same issue.

 - Not proposed: rerere (Patrick's ps/rerere-subcommands (v2, 29 Oct),
   would convert it to OPT_SUBCOMMAND [9]), rev-parse (options are
   actions run in order and mixed with revisions, and its main option
   handling is not table-driven), rev-list, diff and setup_revisions()
   in revision.c (Christian thinks it should be converted to struct
   option first).

Common risks: behaviour change for scripts; abbreviations
(GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS); short options and low-level
callbacks that stop the scan; PARSE_OPT_KEEP_UNKNOWN_OPT; test
coverage before the change. parse-options.c is changed by several
experienced contributors, so I will coordinate on the list before
any API change.

5. Plan and timeline
--------------------
Internship dates: 7 Dec 2026 to 8 Mar 2027.

Now to Dec 6:  Follow cc/early-scan-options, help it get to next
               with reviews, and discuss the targets on the list,
               including check-ref-format.
Weeks 1-2:     Agree on the inventory and a first target with the
               mentors. Write tests that show the current
               behaviour. Start the weekly blog.
Weeks 2-5:     First series: bisect_terms() and bisect_run(), tests
               first, then the conversion.
Weeks 4-8:     Second series: check-ref-format if it is still free,
               otherwise bisect_start(), which is then a main item and
               not a stretch.
Weeks 6-10:    unpack-objects, if time allows; index-pack and
               fetch-pack only if time remains.
Weeks 8-11:    column on early_scan_options(), once the series is in
               next and the "must be first" question is settled.
               Tests first, then the change.
Weeks 11-13:   Rerolls, documentation, final report, and a list of
               remaining candidates for the next intern.
Every week:    Weekly blog post.

The ranges overlap on purpose: each series usually waits days for
review, so I can work on the next one meanwhile. If I fall behind
I drop unpack-objects first, then the bisect_start() when it is only a
stretch. If the early-scan series changes shape or stalls, the column
step becomes another conversion from section 4 that does not depend on
it. I have not planned any time off, except possibly 1-3 January, which
I will arrange with the mentors beforehand.

6. Communication
----------------
A weekly blog post (Christian asks for one), linked on the list;
weekly written updates to Siddharth; calls with Christian planned in
advance; questions asked on the list first; replies to review within
1-2 days.

I will publish the blog on Medium and link each post on the list.
I love sharing about what I'm learning, and I trust that I'll be
learning a lot in the next few weeks.

7. After the internship
-----------------------
After my internship I intend to keep reviewing patches, help
newcomers learn the ropes, just like I did, and keep working on the
follow-ups that did not fit the internship. I believe in paying it
forward, so if I ever get the chance to mentor someone else, I will
consider it a privilege.

8. References
-------------
[1] v1 cover:
    https://lore.kernel.org/git/20260902161047.476753-1-christian.couder@gmail.com/
[2] Junio on the v1 cover and Christian's answer:
    https://lore.kernel.org/git/xmqqpkyviizc.fsf@gitster.g/
    https://lore.kernel.org/git/CAP8UFD3qUpjUayhkMumZ41iMut=1=Pcmzx1YYcEV9NMG18OPsw@mail.gmail.com/
[3] Junio on v1 2/6 (bisect) and Christian's answer:
    https://lore.kernel.org/git/xmqqse3rffr4.fsf@gitster.g/
    https://lore.kernel.org/git/CAP8UFD3sh9Ejfgv7CB33LRU_z3i662_+tjdW7JqwRrzexWX_Ow@mail.gmail.com/
[4] v3 cover and 4/4:
    https://lore.kernel.org/git/20261012074413.337183-1-christian.couder@gmail.com/
    https://lore.kernel.org/git/20261012074413.337183-5-christian.couder@gmail.com/
[5] Kaartic on 2/4, 3/4 and the v3 cover:
    https://lore.kernel.org/git/02ba1b9d-b598-454b-897f-cb33ba190b13@gmail.com
    https://lore.kernel.org/git/6782fa63-1e46-4ae9-b129-a157986c2556@gmail.com
    https://lore.kernel.org/git/1e194ea7-2e28-4142-b678-d8cb8bca8e35@gmail.com
[6] "What's cooking" Oct 2026 #10:
    https://lore.kernel.org/git/xmqqo6cd5hnt.fsf@gitster.g/
[7] My microproject thread and merge commit:
    https://lore.kernel.org/git/20261021033723.5265-1-herroyalpriestess@gmail.com/
    84bea4324a
[8] My review of 4/4:
    https://lore.kernel.org/git/20261031142255.4941-1-herroyalpriestess@gmail.com/
[9] Patrick's ps/rerere-subcommands:
    https://lore.kernel.org/git/20261029-pks-rerere-subcommands-v2-0-07d5ba18e882@pks.im/
[10] Fien Dora's t1402 patch (v3) and Junio's reply:
     https://lore.kernel.org/git/20261029121715.3845-1-fiendora5@gmail.com/
     https://lore.kernel.org/git/xmqqqzh81goi.fsf@gitster.g/
[11] Earlier drafts: none, this is v1.

9. Closing remarks
------------------
Git has a VAST codebase, and it can seem quite intimidating at first
to a newcomer, but the support in this community has made it much
easier to start. Resilience is one of my core attributes, and I also
know to ask for help when I need it, so I won't be the first to give
up should things get more demanding than I expect. I would be glad to
be mentored by experienced contributors and to keep contributing after
the internship.

Thanks,
Cynthia Uche


```

## Cynthia Uche, 2026-11-04 02:53

Subject: [Outreachy] Proposal draft v2: Improve how command arguments and options are scanned and parsed
Message-ID: <20261104025340.7799-1-herroyalpriestess@gmail.com>
URL: https://gitlist.dev/e/20261104025340.7799-1-herroyalpriestess%40gmail.com
In-Reply-To: <20261101210623.622-1-herroyalpriestess@gmail.com>

```
Hi team,

This is the second draft of my proposal for "Improve how command
arguments and options are scanned and parsed" for the December 2026
Outreachy round. v1 is here: [11]. I would appreciate
feedback, especially on the candidate inventory (section 4) and the
timeline (section 5).

Changes since v1
================
 - Section 2: Christian replied to my review of 4/4 and v4 of the
   early-scan series includes my suggestion; I also added my other
   two reviews.
 - Sections 3.2 to 3.5: v4 of the early-scan series and where it
   stands now.
 - Section 4: Fien Dora's t1402 tests are in 'next'; the status of
   rerere; the build the observations were made on.
 - Steps I have taken: updated.
 - About me: updated.
 - Communication: updated.

1. About me and logistics
-------------------------
Name:     Cynthia Uche. I'm also called Priestess.
Pronouns: she/her
Email:    herroyalpriestess@gmail.com
IRC:      Priestess
GitHub:   https://github.com/herr-priestess
Blog:     https://herr-priestess.github.io/
LinkedIn: https://linkedin.com/in/thecynthiauche
Location: Nigeria (WAT, UTC+1)

I have read the Outreachy eligibility rules and I meet all the
requirements.

I am a software engineer, mostly working on Python and backend
projects. C was the first language I learned, so I am comfortable
reading and writing it, although I have not used it much day to day
until now.

I have a merged patch in OpenStack Shared File Systems Service (Manila)
(https://review.opendev.org/c/openstack/manila/+/982431), which went
through Gerrit review, so reworking a change after review comments
is not new to me.

Availability: 30 hours per week

2. My Git work
--------------
Microproject: t2104: avoid suppressing git's exit code
  Thread: https://lore.kernel.org/git/20261021033723.5265-1-herroyalpriestess@gmail.com/
  Topic:  cu/t2104-avoid-pipes
  Status: merged to 'next' on 2026-10-24 and graduated to master
          (merge commit 84bea4324a); What's cooking Nov 2026 #01
          [6b] lists it under "Graduated to 'master'".
  Patrick reviewed it and found it good, so it needed no second
  version.

Review of the early-scan series:
  (a) [PATCH v3 4/4] fast-import: use early_scan_options() ... [8]
  I ran t9300 (261 tests) and t0040 (118 tests) on the topic as in
  origin/seen (tip dabb68a20d); all pass. I noticed that
  EARLY_SCAN_STOP_AT_NON_OPTION is passed although the real parse
  does not use PARSE_OPT_STOP_AT_NON_OPTION. With that argument set
  to 0 locally, "--depth 5 extra --allow-unsafe-features" reached
  the usage error (exit 129) instead of the "forbidden" error
  (exit 128), so the unsafe feature line had been accepted first.
  I suggested documenting this in the comment at the call site.
  Outcome: Christian replied on 2 Nov and sent v4 [12]. In v4 the
  call site comment explains why the flag is passed, the header
  comment of early_scan_options() says when callers should pass it
  and when they should not use the scan, and a new t9300 test, "R:
  --allow-unsafe-features after a non-option", covers that command
  line. Kaartic then wrote that v4 looks good to him, and Junio that
  he would mark the topic for 'next' [13].

  I tested v4 (tip c2b4099f84 in origin/seen): t0040 (118 tests) and
  t9300 (262 tests) pass, and the three command lines from my
  review and from the series behave as described.

Other reviews:
  (b) [PATCH v2] test-chmtime: use parse_options() ... [14]
  With the PARSE_OPT_KEEP_UNKNOWN_OPT argument set to 0, the new
  t0082 still passed 3 of 3, while "test-tool chmtime --get -86400"
  failed with "unknown switch `8'". I suggested a test for negative
  offsets and listed three behaviour changes that the commit message
  did not mention: flags after the timespec are now accepted,
  "-g -v" now dies, and usage errors exit with 129 instead of 1.
  Outcome: Shashank answered that all three are intended and that v3
  will add the test and document them. v3 was sent on 2 Nov [14].

  (c) [Outreachy PATCH v3 1/2] parse-options: answer
  --git-completion-helper before command runs [15]
  On master (ebdc087dc9) with the series applied, t9300, t0012 and
  t9902 pass, and "git --list-cmds=parseopt" lists the four builtins
  that the series unmarks. I compared "git <cmd> --git-completion-
  helper" and the "-all" form for every builtin, with builds of
  master and of the series, and the output was identical. The new
  test only covers the plain form: with the "-all" branch changed to
  show_gitcomp(opts, 0) it still passed. I suggested a test for that
  branch.

How these relate to the project: its tasks are to find hand-rolled
argument parsing, move it to parse-options or the early scan, and
discuss the patches with reviewers. The three reviews are in that
code: the early-scan series itself, a test helper moved to
parse_options(), and the parse-options completion code. Each time I
started from running something (a mutation of one argument, a
comparison with a base build, a loop over all builtins) and not from
reading alone. In (a) that led to changes in v4. Junio told another
applicant that topics move forward through positive reviews [16], and
that is how I want to work during the internship: start from the
failing command line, make it a test, then change the code.
(b) is also a real case of the flag that the v4 header tells callers
of the early scan to avoid: test-chmtime keeps PARSE_OPT_KEEP_UNKNOWN_OPT
so that a negative offset like "-86400" is not rejected as an unknown
switch, so a command like that could not use early_scan_options() as
it is today. So before choosing a candidate in section 4, I would
check whether its parse_options() call uses that flag.

Steps I have taken: read the microproject and application pages,
SubmittingPatches and MyFirstContribution; built Git and ran the
relevant tests; sent my microproject by git send-email; read the
early-scan series v1 to v4 with its reviews; reproduced the
git column behaviour (section 4); reviewed 4/4 of v3 and tested v4,
and reviewed two other patches (above).

3. Project understanding
------------------------
3.1 The problem: two parses that can disagree

Some commands must look at a few options before they call
parse_options(), because the result changes how they read their input.
fast-import is the example: the "feature" lines at the start of the
stream are handled before the command line is parsed, so
cmd_fast_import() scans argv early for --allow-unsafe-features.

The old scan was a hand-rolled loop. It compared each argument with
the exact option name and stopped at the first argument that did not
start with a dash. parse_options() is more permissive: it accepts
unambiguous abbreviations and values separated from their option by a
space. So the two disagree:

 - "--depth 5 --allow-unsafe-features": the scan stops at "5" and
   never sees the flag, although parse_options() accepts it. Unsafe
   features are refused. That is the safe direction, but still wrong.
 - "--quiet --export-pack --allow-unsafe-features": every argument
   starts with a dash, so the old scan saw the flag and allowed
   unsafe features. But parse_options() takes it as the value of
   --export-pack-edges, which --export-pack abbreviates. That is the
   dangerous direction. (This example is from the 4/4 commit
   message [4]; I did not run it on an old build.)

3.2 The API in v3 and v4

early_scan_options(argc, argv, option, flags, fn, data) walks argv
using the same struct option array that parse_options() uses. Options
the caller cares about are marked PARSE_OPT_EARLY, and the callback
receives (option, value, unset, pos, data); a non-zero return stops
the scan. It returns the index of the first argument not consumed.
Two helpers make it agree with parse_options():
parse_options_takes_argument() (1/4, which also counts
PARSE_OPT_LASTARG_DEFAULT options as taking a value) and
lookup_long_opt(), extracted from parse_long_opt() (2/4), so negated
forms, abbreviations and aliases are looked up the same way without
side effects. The scan always stops at "--" and "--end-of-options";
EARLY_SCAN_STOP_AT_NON_OPTION is for callers whose parse_options()
call uses PARSE_OPT_STOP_AT_NON_OPTION or subcommands. As of v4 the
header also says that passing it in other cases is safe, and useful
when the caller rejects non-option arguments itself, and that the
scan should not be used when parse_options() is called with
PARSE_OPT_KEEP_UNKNOWN_OPT [12]. Since v4, early_scan_options() also
calls parse_options_check() on the options, so a bad PARSE_OPT_EARLY
mark is caught before the scan relies on it.

3.3 Why "stop when unsure" is safe

The scan can miss an option that parse_options() would accept, but it
never reports one that parse_options() would not. It stops at anything
it cannot reliably skip: short options, unknown options, ambiguous or
disallowed abbreviations, low-level callbacks (which may consume any
number of arguments), a value stuck to a negated or no-argument
option, and a value-taking option given as the last argument. Missing
an option only makes the caller behave as if it was not given (for
fast-import, unsafe features are refused). Mistaking an option's value
for an option is the dangerous failure.

Why not make parse_options() do a dry run? Because it would be a much
larger refactoring. According to the 3/4 commit message, a dry run
would have to avoid writing through opt->value, calling callbacks,
dying on invalid values, handling --help and tracking command mode
conflicts. Junio has said he would prefer this in the long term [2].
The scan is simpler, and it keeps the option array and the
PARSE_OPT_EARLY marks, so it does not block a dry run later.

3.4 History of the series

 - v1 (2 Sep, 6 patches): early_scan_options() with its own option
   table, a bisect fix for "--" detection when a term is "--", a
   rev-parse fix for "--" when it is an option value,
   parse_options_takes_argument(), building the scan from a struct
   option array, and the fast-import conversion.
 - v2 (23 Sep, 3 patches): the scan reads the real struct option
   array and PARSE_OPT_EARLY marks the wanted options. The bisect and
   rev-parse patches are gone.
 - v3 (12 Oct, 4 patches, rebased on Git 2.56, a018953688): adds the
   lookup_long_opt() extraction, handles LASTARG_DEFAULT, stops at
   more cases, adds the "unset" callback argument, and removes the
   NEEDSWORK comment in fast-import.
 - v4 (2 Nov, 4 patches, same base): a switch in parse_long_opt()
   (2/4); parse_options_check() called from early_scan_options() and
   clearer header comments (3/4); a clearer comment at the call site
   and a new t9300 test (4/4).

Why bisect and rev-parse were dropped: Junio said he saw little
practical value in them, since he doubted users would name a bisect
term or a --default value "--", unlike fast-import, where the bug
affects a safety check. Christian removed both patches in v2 and said
that bisect_start() could use the early scan once it has been converted
to parse-options [2], [3].

3.5 Current state and open follow-ups

v4 was sent on 2 Nov and addresses the three points Kaartic made on
v3 (the switch in 2/4, the parse_options_check() call and the comment
about the option pointer in 3/4). Kaartic said v4 looks good [13].
Junio replied that it looks good and that he would mark the topic for
'next' [13]. As of 3 Nov, cc/early-scan-options is still only in seen
(git grep -n early_scan_options origin/next).

Not covered by the series: commands that do not use parse-options
at all, short options (the scan stops at them) and low-level
callbacks. Those are what section 4 is about.

4. Candidate inventory and risks
--------------------------------
Method: I counted hand-written option checks per builtin with
  git grep -c 'strcmp(arg' -- builtin/ | sort -t: -k2 -n
  git grep -n 'skip_prefix(arg, "--' -- builtin/
and listed builtins that never call parse_options with
  git grep -L parse_options -- 'builtin/*.c'
The counts include small value converters (am, config, log, revert),
which I treat as out of scope. The densest real parsing is in
rev-parse (43), rev-list (18), index-pack and diff (13 each) and
bisect (12). Observations in this section were made on origin/master
at v2.56.0-307-g50fe5cc63f, unless stated otherwise. I found no topic
in What's cooking #01 [6b] that touches column, bisect or
unpack-objects.

 - git column (builtin/column.c, cmd_column())
   It already uses parse_options(). Only the early step is by hand:
   if argv[1] starts with "--command=", config is read with that
   name before parse_options() runs, and a die() follows if the
   parsed and scanned values differ. Observed:
     git column --command=foo               works (exit 0)
     git column --command foo               dies (exit 128)
     git column --width=40 --command=foo    dies (exit 128)
     git column --com=foo                   dies (exit 128)
   The three failures all print "fatal: --command must be the
   first argument", even when "--command foo" really is first. I found
   this rule only in the die() message: searching Documentation/ and
   t/ for "must be the first" finds nothing related, and t9002 does not
   test --command.
   Fits: replacing the scan with early_scan_options() and
   PARSE_OPT_EARLY on --command, once the series is in next. I have not
   yet checked whether any option in column's table would
   make the scan stop.
   Risk: user-visible change (errors become success). Question for
   the list: is the "must be first" rule intentional? The callback
   receives pos, so it could be kept (pos == 0) or dropped.

 - bisect (builtin/bisect.c)
   bisect_start() has two hand-written loops: one looks for "--"
   (the bug of the dropped v1 patch) and one parses --no-checkout,
   --first-parent, --reset-when-found[=...] and the four --term-*
   options, each in a "--opt value" and a "--opt=value" branch.
   bisect_run() checks only argv[0] for --reset-when-found, and
   bisect_terms() compares one string with --term-good/old/bad/new.
   Fits: convert to parse-options first, order: bisect_terms(),
   bisect_run(), then bisect_start() as a stretch.
   Risk: bisect_start() is called from "bisect start", from
   bisect_autostart() and from "bisect replay"; the original
   arguments are written to BISECT_LOG and replayed, so they must be
   kept. Abbreviations and "--no-" forms would start to be accepted.

 - check-ref-format (builtin/check-ref-format.c)
   One loop over leading dash arguments handles --normalize/--print,
   --allow-onelevel, --no-allow-onelevel and --refspec-pattern;
   --branch is special-cased as exactly argc == 3; anything else
   calls usage(). git.c lists it as NO_PARSEOPT.
   Tests: t1402 covers --normalize, --allow-onelevel and
   --refspec-pattern, alone and combined, and --branch (also from
   outside a repository, and with the name -nain). It does not test
   --print, abbreviations or "--". Fien Dora's patch adding tests for
   the precedence of --allow-onelevel and --no-allow-onelevel [10]
   was merged to 'next' on 1 Nov and is marked "Will merge to
   'master'" in What's cooking #01 [6b]. Her commit message says she
   plans to explore converting this parser in a separate series [10].
   Observed on origin/master (v2.56.0-307-g50fe5cc63f):
     --norm refs/heads/x           usage text, exit=129
     -- refs/heads/x               usage text, exit=129
     --normalize -- refs/heads/x   usage text, exit=129
     refs/heads/x --normalize      usage text, exit=129
     --branch -nain                "fatal: '-nain' is not a valid
                                   branch name", exit=128
   Fits: parse-options: OPT_BIT for the flags (--no-allow-onelevel
   then comes for free), OPT_ALIAS for --print, and dropping
   NO_PARSEOPT in git.c.
   Risks: commands that fail today would succeed: abbreviations, "--"
   and negated forms such as --no-normalize would be accepted, and
   so would options after <refname> unless I pass
   PARSE_OPT_STOP_AT_NON_OPTION. --branch needs an explicit check
   that it is not combined with other options, and "--branch -nain"
   must keep failing with the invalid branch name error (exit 128),
   not become a usage error from short option parsing.
   Question for the list: is accepting "--" and abbreviations
   welcome, should options after <refname> keep being rejected?

 - unpack-objects (builtin/unpack-objects.c) [if time allows]
   -n, -q, -r, --strict[=<msg-types>], --pack_header=,
   --max-input-size=. Risk: other Git programs run it with fixed
   arguments, so those must keep working; I will list the callers
   before choosing it. index-pack and fetch-pack are larger,
   same issue.

 - Not proposed: rerere (Patrick's ps/rerere-subcommands, v2 of
   29 Oct, would convert it to OPT_SUBCOMMAND [9]; "Needs review" in
   What's cooking #01 [6b]), rev-parse (options are actions run in
   order and mixed with revisions, and its main option handling is
   not table-driven), rev-list, diff and setup_revisions() in
   revision.c (Christian has said it should be converted to struct
   option first).

Common risks: behaviour change for scripts; abbreviations
(GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS); short options and low-level
callbacks that stop the scan; PARSE_OPT_KEEP_UNKNOWN_OPT; test
coverage before the change. parse-options.c is changed by several
experienced contributors, so I will coordinate on the list before
any API change.

5. Plan and timeline
--------------------
Internship dates: 7 Dec 2026 to 8 Mar 2027.

Now to Dec 6:  Follow cc/early-scan-options until it reaches
               master, review related patches, and discuss the
               targets on the list, including check-ref-format.
Weeks 1-2:     Agree on the inventory and a first target with the
               mentors. Write tests that show the current
               behaviour. Start the weekly blog.
Weeks 2-5:     First series: bisect_terms() and bisect_run(), tests
               first, then the conversion.
Weeks 4-8:     Second series: check-ref-format if it is still free,
               otherwise bisect_start(), which is then a main item and
               not a stretch.
Weeks 6-10:    unpack-objects, if time allows; index-pack and
               fetch-pack only if time remains.
Weeks 8-11:    column on early_scan_options(), once the series is in
               next and the "must be first" question is settled.
               Tests first, then the change.
Weeks 11-13:   Rerolls, documentation, final report, and a list of
               remaining candidates for the next intern.
Every week:    Weekly blog post.

The ranges overlap on purpose: each series usually waits days for
review, so I can work on the next one meanwhile. If I fall behind
I drop unpack-objects first, then the bisect_start() when it is only a
stretch. If the early-scan series changes shape or stalls, the column
step becomes another conversion from section 4 that does not depend on
it. I have not planned any time off, except possibly 1-3 January, which
I will arrange with the mentors beforehand.

6. Communication
----------------
A weekly blog post (Christian asks for one), linked on the list;
weekly written updates to Siddharth; calls with Christian planned in
advance; questions asked on the list first; replies to review within
1-2 days.

I will publish the blog on GitHub pages and link each post on the list.
I love sharing about what I'm learning, and I trust that I'll be
learning a lot in the next few weeks.

7. After the internship
-----------------------
After my internship I intend to keep reviewing patches, help
newcomers learn the ropes, just like I did, and keep working on the
follow-ups that did not fit the internship. I believe in paying it
forward, so if I ever get the chance to mentor someone else, I will
consider it a privilege.

8. References
-------------
[1] v1 cover:
    https://lore.kernel.org/git/20260902161047.476753-1-christian.couder@gmail.com/
[2] Junio on the v1 cover and Christian's answer:
    https://lore.kernel.org/git/xmqqpkyviizc.fsf@gitster.g/
    https://lore.kernel.org/git/CAP8UFD3qUpjUayhkMumZ41iMut=1=Pcmzx1YYcEV9NMG18OPsw@mail.gmail.com/
[3] Junio on v1 2/6 (bisect) and Christian's answer:
    https://lore.kernel.org/git/xmqqse3rffr4.fsf@gitster.g/
    https://lore.kernel.org/git/CAP8UFD3sh9Ejfgv7CB33LRU_z3i662_+tjdW7JqwRrzexWX_Ow@mail.gmail.com/
[4] v3 cover and 4/4:
    https://lore.kernel.org/git/20261012074413.337183-1-christian.couder@gmail.com/
    https://lore.kernel.org/git/20261012074413.337183-5-christian.couder@gmail.com/
[5] Kaartic on 2/4, 3/4 and the v3 cover:
    https://lore.kernel.org/git/02ba1b9d-b598-454b-897f-cb33ba190b13@gmail.com
    https://lore.kernel.org/git/6782fa63-1e46-4ae9-b129-a157986c2556@gmail.com
    https://lore.kernel.org/git/1e194ea7-2e28-4142-b678-d8cb8bca8e35@gmail.com
[6] "What's cooking" Oct 2026 #10:
    https://lore.kernel.org/git/xmqqo6cd5hnt.fsf@gitster.g/
[6b] "What's cooking" Nov 2026 #01:
    https://lore.kernel.org/git/xmqqcxsmejd9.fsf@gitster.g/
[7] My microproject thread and merge commit:
    https://lore.kernel.org/git/20261021033723.5265-1-herroyalpriestess@gmail.com/
    84bea4324a
[8] My review of 4/4:
    https://lore.kernel.org/git/20261031142255.4941-1-herroyalpriestess@gmail.com/
[9] Patrick's ps/rerere-subcommands:
    https://lore.kernel.org/git/20261029-pks-rerere-subcommands-v2-0-07d5ba18e882@pks.im/
[10] Fien Dora's t1402 patch (v3) and Junio's reply:
     https://lore.kernel.org/git/20261029121715.3845-1-fiendora5@gmail.com/
     https://lore.kernel.org/git/xmqqqzh81goi.fsf@gitster.g/
[11] Earlier drafts: v1:
     https://lore.kernel.org/git/20261101210623.622-1-herroyalpriestess@gmail.com/
[12] v4 cover and Christian's reply to my review:
     https://lore.kernel.org/git/20261102151447.734680-6-christian.couder@gmail.com/
     https://lore.kernel.org/git/CAP8UFD2m+wpcgqB2SasWLxyekY+5khrYyPkOuLoYwANqZws8fw@mail.gmail.com/
[13] Kaartic on v4 and Junio's reply:
     https://lore.kernel.org/git/faf0b5e6-1c63-4c68-9f23-42a452879854@gmail.com/
     https://lore.kernel.org/git/xmqqv76dal2l.fsf@gitster.g/
[14] My review of test-chmtime v2, Shashank's answer and his v3:
     https://lore.kernel.org/git/20261102024623.24897-1-herroyalpriestess@gmail.com/
     https://lore.kernel.org/git/CANHmUQS15GGDOXm-WZ9M-UyKf2N0K+VixowL_heNKW0vjE0vJw@mail.gmail.com/
     https://lore.kernel.org/git/pull.2280.v3.git.1793639275978.gitgitgadget@gmail.com/
[15] My review of Tanga Joris's v3 1/2:
     https://lore.kernel.org/git/20261103092503.14872-1-herroyalpriestess@gmail.com/
[16] Junio to Tanga Joris, 26 Oct:
     https://lore.kernel.org/git/xmqq33ttvz0q.fsf@gitster.g/

9. Closing remarks
------------------
Git has a VAST codebase, and it can seem quite intimidating at first
to a newcomer, but the support in this community has made it much
easier to start. Resilience is one of my core attributes, and I also
know to ask for help when I need it, so I won't be the first to give
up should things get more demanding than I expect. I would be glad to
be mentored by experienced contributors and to keep contributing after
the internship.

Thanks,
Cynthia Uche


```

## Usman Akinyemi, 2026-11-05 00:08

Subject: Re: [Outreachy] Proposal draft v2: Improve how command arguments and options are scanned and parsed
Message-ID: <CAPSxiM8E3LrJeba_f1gj3mxRVUYapp-nSgj+++pcgJEUJpxeLQ@mail.gmail.com>
URL: https://gitlist.dev/e/CAPSxiM8E3LrJeba_f1gj3mxRVUYapp-nSgj%2B%2B%2BpcgJEUJpxeLQ%40mail.gmail.com
In-Reply-To: <20261104025340.7799-1-herroyalpriestess@gmail.com>

```
On Wed, Nov 4, 2026 at 4:03 AM Cynthia Uche <herroyalpriestess@gmail.com> wrote:
>
> Hi team,
>
Hello,

Thanks for your proposal.

Do not forget to submit the application on the Outreachy page.

Thanks.

>


```

## Priestess, 2026-11-05 00:55

Subject: Re: [Outreachy] Proposal draft v2: Improve how command arguments and options are scanned and parsed
Message-ID: <CAOcE0w+5jVvq9ZJW+3ZjJ3WGAnSHTj5wpNvbx7ZC2a0_aEtyxA@mail.gmail.com>
URL: https://gitlist.dev/e/CAOcE0w%2B5jVvq9ZJW%2B3ZjJ3WGAnSHTj5wpNvbx7ZC2a0_aEtyxA%40mail.gmail.com
In-Reply-To: <CAPSxiM8E3LrJeba_f1gj3mxRVUYapp-nSgj+++pcgJEUJpxeLQ@mail.gmail.com>

```
On Thu, Nov 5, 2026 at 1:08 AM Usman Akinyemi
<usmanakinyemi202@gmail.com> wrote:
>
> Hello,
Hi, Usman

> Thanks for your proposal.
>
> Do not forget to submit the application on the Outreachy page.

Will do. Thank you for reading and reviewing!


```
