Skip to content

Add parse::format whitespace formatter to gix-config (#2594)#2636

Open
Amey Pawar (ameyypawar) wants to merge 3 commits into
GitoxideLabs:mainfrom
ameyypawar:gix-config-formatter
Open

Add parse::format whitespace formatter to gix-config (#2594)#2636
Amey Pawar (ameyypawar) wants to merge 3 commits into
GitoxideLabs:mainfrom
ameyypawar:gix-config-formatter

Conversation

@ameyypawar

Copy link
Copy Markdown
Contributor

feat(gix-config): add a flat whitespace formatter (parse::format)

Implements the library half of #2594 — a config-file formatter that re-emits a single file
with normalized whitespace, without resolving include/includeIf.

What

A new module gix_config::parse::format with:

pub fn normalize(input: &[u8], options: &Options) -> Result<BString, parse::Error>

plus Options, Indentation (Tab | Spaces(usize) | None) and Newline (Detect | Lf | CrLf).

It works on the public parse::Events stream, so it is flat by construction — parsing at that
layer never resolves includes (only File does), which is exactly what Eir Nym (@eirnym) needed. Values,
comments and section headers are re-emitted verbatim; only insignificant whitespace, the =
separator, and newlines are rewritten.

Defaults (from the discussion on #2594)

  • 2-space indentation (configurable: tabs or None for no indentation, per 93578237)
  • a single space around =
  • newline style detected from the input (overridable to LF / CRLF)
  • ensure a single trailing newline
  • blank lines left as-is (opt-in to collapse runs of them)
  • trailing whitespace always removed (never significant)

Why this shape

parse::Events already separates whitespace into its own events and round-trips losslessly via
Event::write_to, so the formatter only substitutes the whitespace/separator/newline bytes and
passes everything else through unchanged. Line continuations (\) carry their leading whitespace
inside the value chunk, so the whole ValueNotDone … ValueDone span is emitted verbatim and stays
byte-identical — covered by a dedicated test.

Scope

Library API only. The CLI is intentionally left out — Eir Nym (@eirnym) offered to build it on top once the
API lands.

Tests

Added gix-config/tests/config/parse/format.rs (16 tests): default policy, semantic-equivalence
(re-parse and compare section/key/value triples), line continuation untouched, trailing backslash at
EOF, implicit boolean keys (no injected =), comments preserved, quoted subsection/value verbatim,
newline detect + forced LF/CRLF, tab / 2-space / no-indent, blank-line preserve + opt-in collapse,
separator toggle, and idempotency.

Verified locally against main with:

cargo fmt -p gix-config --check
cargo clippy -p gix-config --all-targets -- -D warnings
cargo test -p gix-config

All pass (94 unit + 224 integration + 32 doctests, 0 failures).

Files changed

  • gix-config/src/parse/format.rs (new)
  • gix-config/src/parse/mod.rs (pub mod format;)
  • gix-config/tests/config/parse/format.rs (new)
  • gix-config/tests/config/parse/mod.rs (mod format;)

Part of #2594 — this is the library API; the CLI will follow (per Eir Nym (@eirnym)'s offer). Intentionally
not using a Closes keyword so the issue stays open for the CLI work.


Disclosure: developed with AI assistance, reviewed and tested by me.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3af56a3f15

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment on lines +186 to +187
while out.last() == Some(&b'\n') || out.last() == Some(&b'\r') {
out.pop();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve trailing blank lines when collapse is disabled

With the default max_consecutive_blank_lines: None, callers are told blank lines are left exactly as-is, but this loop strips every trailing \r/\n that was already emitted and then appends a single newline. For an input like [a]\nx = 1\n\n, normalization silently removes the final blank line even though blank-line collapsing was not requested; ensure_trailing_newline should add a newline only when one is missing, or otherwise honor the collapse option explicitly.

Useful? React with 👍 / 👎.

…deLabs#2594)

Adds `gix_config::parse::format::normalize(input, &Options)`, which re-emits a
single config file with sanitized whitespace without resolving includes. Values,
comments and section headers are preserved verbatim; only insignificant
whitespace, the `=` separator and newlines are rewritten.

`Options` controls indentation (two spaces by default; tabs or none also
available), spacing around `=`, the newline sequence (detect/LF/CRLF), the
trailing newline, and optional blank-line collapsing.

This is the library portion; the CLI will follow separately.

Co-authored-by: Claude <ai-agent@example.invalid>
@Byron

Copy link
Copy Markdown
Member

Amey Pawar (@ameyypawar) I think this will be useful!

Could you also expose this to gix in a way? Something that comes to mind is gix config fmt [--in-place] [in-file] [out-file], and if [in-file] isn't given, it's using the repo-local configuration. If [out-file] isn't given, it outputs the formatted text to stdout.

Also, I will wait with the review until #2667 has landed. This one will conflict, but fixing these will certainly not be a huge issue (and I will do it).

Thanks again.

@eirnym

This comment was marked as low quality.

Amey Pawar (ameyypawar) and others added 2 commits July 1, 2026 19:14
Reads the given file (or the repository-local configuration when none is
given), normalizes its whitespace via `gix::config::parse::format`, and
writes the result to stdout, an output file, or back in place.
Expose the gix-config whitespace formatter as
`gix config fmt [--in-place] [in-file] [out-file]`: with no in-file it
formats the repository-local configuration, and with no out-file it writes
to stdout. The repository is only opened when the repository-local
configuration is needed, so formatting an explicit file works outside a
repository too.
@ameyypawar

Copy link
Copy Markdown
Contributor Author

Done — exposed the formatter as gix config fmt [--in-place] [in-file] [out-file], matching your suggestion:

  • no [in-file] → formats the repository-local configuration
  • no [out-file] → writes the result to stdout
  • --in-place → rewrites the input file (and errors if combined with an explicit output file)

The repository is only opened when the repository-local configuration is actually needed, so gix config fmt some.config also works outside of a repository. No new dependencies are pulled in.

One point worth your call: it currently formats with Options::default() (2-space indentation, spaces around =). Since git itself writes tab-indented config, --in-place on an existing .git/config will reindent tabs → spaces. Happy to switch the CLI to Indentation::Tab for less churn, or expose the format options as flags — whichever you'd prefer.

@EliahKagan

Copy link
Copy Markdown
Member

It looks like the CI failure here is a known flake due to #1832, unrelated to changes in this PR. I'm rerunning the CI job.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants