Skip to content

✨ Add --format option to diff subcommand - #3243

Merged
ChanTsune merged 1 commit into
mainfrom
cli/diff/format-option
Jul 29, 2026
Merged

✨ Add --format option to diff subcommand#3243
ChanTsune merged 1 commit into
mainfrom
cli/diff/format-option

Conversation

@ChanTsune

@ChanTsune ChanTsune commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

pna experimental diff gains --format <plain|jsonl>. plain (default) keeps the current tar-style text unchanged; jsonl emits one JSON object per difference per line: {"path", "kind", "target"?}, where kind is one of missing, size, content, mode, mtime, uid, gid, type, symlink, hardlink, and target appears only for hardlink. Part of #3228.

Notes

  • The format value vocabulary follows pna list --format (jsonl).
  • kind names are a fixed wire vocabulary decoupled from internal enum names; the schema evolves by adding fields only.
  • mode/mtime/uid/gid records occur only on Unix (metadata comparison is Unix-only).

Summary by CodeRabbit

  • New Features
    • Added an --format option for experimental diff output: plain (default) or jsonl.
    • JSONL now emits newline-delimited records per detected difference, including relevant hardlink target details.
    • jsonl is gated behind the --unstable flag.
  • Bug Fixes
    • Improved and standardized diff reporting so all scenarios produce consistent output while preserving accurate difference counts.
  • Tests
    • Expanded CLI coverage for jsonl and plain, including content changes, missing entries, hardlink breaks, symlink and Unix metadata differences, ordering, and no-difference output.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The diff command adds a --format option supporting plain and JSONL output. Difference kinds are serialized into structured records, comparison branches use shared reporting, and CLI tests cover output formats and comparison scenarios.

Changes

Diff output formats

Layer / File(s) Summary
Format contract and CLI wiring
cli/src/command/diff.rs
DiffCommand parses plain or jsonl, gates JSONL behind --unstable, and passes the selected format through CompareOptions.
Formatted difference reporting
cli/src/command/diff.rs
DiffKind and DiffRecord support JSON serialization, while missing, metadata, content, symlink, hardlink, and type differences use report(...).
Output format validation
cli/tests/cli/diff.rs, cli/tests/cli/diff/option_format.rs
CLI tests validate JSONL records, ordering, gating, empty output for matching archives, and plain-output compatibility.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DiffCommand
  participant compare_entry
  participant report
  participant stdout
  User->>DiffCommand: run diff with --format
  DiffCommand->>compare_entry: compare archive entries with selected Format
  compare_entry->>report: submit DiffKind and path
  report->>stdout: emit plain text or JSONL record
Loading

Possibly related issues

  • ChanTsune/Portable-Network-Archive issue 3228: Covers selectable plain and JSONL diff output with structured records.
  • ChanTsune/Portable-Network-Archive issue 3232: Covers selectable diff output and JSONL records.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a --format option to the diff subcommand.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cli/diff/format-option

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a --format option to the diff command, allowing users to output differences in either the default plain text format or a structured jsonl format. To support this, serde_json was added as a dependency, and the diff reporting logic was refactored to use a unified report function. Comprehensive integration tests were also added to verify the new formatting options. The reviewer feedback suggests using scopeguard::guard in the newly added integration tests to ensure that temporary test directories are reliably cleaned up even if a test fails or panics.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread cli/tests/cli/diff/option_format.rs
Comment thread cli/tests/cli/diff/option_format.rs
Comment thread cli/tests/cli/diff/option_format.rs
Comment thread cli/tests/cli/diff/option_format.rs
Comment thread cli/tests/cli/diff/option_format.rs
@ChanTsune ChanTsune mentioned this pull request Jul 26, 2026
7 tasks
@ChanTsune
ChanTsune force-pushed the cli/diff/format-option branch from 30ccda2 to 0581d88 Compare July 27, 2026 01:19
@github-actions github-actions Bot added dependencies Pull requests that update a dependency file cli This issue is about cli application labels Jul 27, 2026

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
cli/src/command/diff.rs (1)

34-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Format arg design: LGTM, minor DRY nit on the default.

The --format plumbing and Format enum are correct (ValueEnum + rename_all = "lower" correctly maps JsonL"jsonl"). One small nit: Format already declares its default via #[default] Plain (line 45), but diff_archive (line 67) re-specifies Format::Plain in unwrap_or. Using unwrap_or_default() would keep the single source of truth and avoid future drift if the default variant ever changes.

♻️ Suggested tweak
-        format: args.format.unwrap_or(Format::Plain),
+        format: args.format.unwrap_or_default(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/command/diff.rs` around lines 34 - 48, Update the format selection in
diff_archive to use Format::default() via unwrap_or_default() instead of
explicitly specifying Format::Plain, while preserving the existing behavior and
keeping the enum’s #[default] declaration as the single source of truth.
cli/tests/cli/diff/option_format.rs (1)

1-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid coverage for content/missing/hardlink/no-diff/plain; consider adding the Unix-only metadata kinds.

Tests validate kind values content, missing, and hardlink, plus the empty-output and plain-format paths. Per the PR objectives, mode, mtime, uid, and gid are also valid kind values (Unix-only) but aren't exercised here, so the wire_name() mapping for those variants isn't covered by any jsonl test.

Consider adding a #[cfg(unix)] test that changes a file's mode/mtime/uid/gid relative to the archived entry and asserts the corresponding kind records appear.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/tests/cli/diff/option_format.rs` around lines 1 - 217, Add a Unix-only
JSONL integration test alongside the existing diff format tests that archives a
file, changes its metadata, runs experimental diff with --format jsonl, and
parses stdout records. Assert that the output includes records with kind values
mode, mtime, uid, and gid, covering the wire_name() mappings for these metadata
variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/src/command/diff.rs`:
- Around line 34-48: Update the format selection in diff_archive to use
Format::default() via unwrap_or_default() instead of explicitly specifying
Format::Plain, while preserving the existing behavior and keeping the enum’s
#[default] declaration as the single source of truth.

In `@cli/tests/cli/diff/option_format.rs`:
- Around line 1-217: Add a Unix-only JSONL integration test alongside the
existing diff format tests that archives a file, changes its metadata, runs
experimental diff with --format jsonl, and parses stdout records. Assert that
the output includes records with kind values mode, mtime, uid, and gid, covering
the wire_name() mappings for these metadata variants.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 08e180b4-63c5-48b6-8c18-0691fba656a5

📥 Commits

Reviewing files that changed from the base of the PR and between 3b7fb7e and 0581d88.

📒 Files selected for processing (4)
  • cli/Cargo.toml
  • cli/src/command/diff.rs
  • cli/tests/cli/diff.rs
  • cli/tests/cli/diff/option_format.rs

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
cli/tests/cli/diff/option_format.rs (1)

9-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extending JSONL coverage to other kind values.

Current tests cover content, missing, and hardlink (plus multi-diff/no-diff/plain-equivalence), but not mode/mtime/uid/gid/type/symlink, which share the same report()/DiffRecord path but aren't directly exercised in JSONL form.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/tests/cli/diff/option_format.rs` around lines 9 - 39, Extend the JSONL
diff tests around diff_with_format_jsonl_and_content_difference to cover the
remaining DiffRecord kind values: mode, mtime, uid, gid, type, and symlink.
Create fixtures that trigger each difference, invoke the same experimental diff
--format jsonl path, and assert the JSONL output reports the expected path and
kind while preserving existing content, missing, hardlink, multi-diff, no-diff,
and plain-format coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/tests/cli/diff/option_format.rs`:
- Around line 9-39: Extend the JSONL diff tests around
diff_with_format_jsonl_and_content_difference to cover the remaining DiffRecord
kind values: mode, mtime, uid, gid, type, and symlink. Create fixtures that
trigger each difference, invoke the same experimental diff --format jsonl path,
and assert the JSONL output reports the expected path and kind while preserving
existing content, missing, hardlink, multi-diff, no-diff, and plain-format
coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eb78def4-cf40-4546-9106-6790828b495e

📥 Commits

Reviewing files that changed from the base of the PR and between 0581d88 and f5164d2.

📒 Files selected for processing (3)
  • cli/src/command/diff.rs
  • cli/tests/cli/diff.rs
  • cli/tests/cli/diff/option_format.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/tests/cli/diff.rs

@ChanTsune
ChanTsune force-pushed the cli/diff/format-option branch from f5164d2 to 55a20fc Compare July 28, 2026 14:28

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
cli/tests/cli/diff/option_format.rs (1)

9-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared setup boilerplate for the JSONL test suite.

Each of the 9 tests repeats the same pattern: remove/create a unique dir, write fixture files, create an archive with --overwrite, mutate the filesystem, then run diff --format jsonl|plain and assert. A small helper (e.g. fn setup_dir(name: &str) -> String plus a fn create_archive(archive: &str, files: &[&str]) wrapper) would remove most of this duplication and make future test additions cheaper.

♻️ Sketch of a shared helper
fn setup_dir(name: &str) -> String {
    let _ = fs::remove_dir_all(name);
    fs::create_dir_all(name).unwrap();
    name.to_string()
}

fn create_archive(archive: &str, extra_args: &[&str], files: &[&str]) {
    cargo_bin_cmd!("pna")
        .args(["create", "-f", archive, "--overwrite"])
        .args(extra_args)
        .args(files)
        .assert()
        .success();
}

Also applies to: 45-75, 83-116, 122-167, 173-201, 207-246, 253-286, 294-359, 365-404

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/tests/cli/diff/option_format.rs` around lines 9 - 39, Extract the
repeated directory and archive creation setup from the JSONL and plain diff
tests into shared helpers near the test module, such as setup_dir and
create_archive. Update all nine affected tests, including
diff_with_format_jsonl_and_content_difference, to use these helpers while
preserving each test’s unique fixture files, archive arguments, filesystem
mutations, and diff assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/tests/cli/diff/option_format.rs`:
- Around line 9-39: Extract the repeated directory and archive creation setup
from the JSONL and plain diff tests into shared helpers near the test module,
such as setup_dir and create_archive. Update all nine affected tests, including
diff_with_format_jsonl_and_content_difference, to use these helpers while
preserving each test’s unique fixture files, archive arguments, filesystem
mutations, and diff assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b5ee94f9-ce51-4774-911a-44eec18424e9

📥 Commits

Reviewing files that changed from the base of the PR and between f5164d2 and 55a20fc.

📒 Files selected for processing (3)
  • cli/src/command/diff.rs
  • cli/tests/cli/diff.rs
  • cli/tests/cli/diff/option_format.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/tests/cli/diff.rs
  • cli/src/command/diff.rs

jsonl emits one JSON Lines record per difference: {"path", "kind", "target"?}.
@ChanTsune
ChanTsune force-pushed the cli/diff/format-option branch from 55a20fc to 824b91d Compare July 29, 2026 03:12

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
cli/tests/cli/diff/option_format.rs (1)

10-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer structural JSON assertions over exact stdout string matches.

Every JSONL test here asserts the full stdout via a literal format!-built JSON string (e.g. Line 40, 77, 119, 197-201, 242, 308-316). The PR objectives state the schema is meant to evolve by adding fields only, so any future additive field will require touching every one of these exact-match assertions simultaneously, even for kinds unrelated to the new field. Parsing each line into a value and asserting the relevant subset of fields would decouple these tests from unrelated schema growth.

♻️ Example refactor for the content-difference test
-        .stdout(format!(r#"{{"path":"{file_path}","kind":"content"}}"#) + "\n");
+        .success(); // replaced above with .code(1) as before
+
+    let stdout = String::from_utf8(output.get_output().stdout.clone()).unwrap();
+    let lines: Vec<serde_json::Value> = stdout
+        .lines()
+        .map(|l| serde_json::from_str(l).unwrap())
+        .collect();
+    assert_eq!(lines.len(), 1);
+    assert_eq!(lines[0]["path"], file_path);
+    assert_eq!(lines[0]["kind"], "content");
+    assert!(lines[0].get("target").is_none());

Also applies to: 47-78, 86-120, 162-202, 209-243, 251-317

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/tests/cli/diff/option_format.rs` around lines 10 - 41, Update the JSONL
assertions in the tests diff_with_format_jsonl_and_content_difference and the
other affected diff-format tests to parse each stdout line as JSON and assert
only the fields relevant to that scenario, rather than matching exact format!
strings. Preserve checks for the expected path, kind, and any scenario-specific
fields while allowing additional schema fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/tests/cli/diff/option_format.rs`:
- Around line 10-41: Update the JSONL assertions in the tests
diff_with_format_jsonl_and_content_difference and the other affected diff-format
tests to parse each stdout line as JSON and assert only the fields relevant to
that scenario, rather than matching exact format! strings. Preserve checks for
the expected path, kind, and any scenario-specific fields while allowing
additional schema fields.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe9d208-1494-4c25-a60a-65dceb3dda77

📥 Commits

Reviewing files that changed from the base of the PR and between 55a20fc and 824b91d.

📒 Files selected for processing (3)
  • cli/src/command/diff.rs
  • cli/tests/cli/diff.rs
  • cli/tests/cli/diff/option_format.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/tests/cli/diff.rs
  • cli/src/command/diff.rs

@ChanTsune
ChanTsune merged commit 5887f3b into main Jul 29, 2026
109 of 110 checks passed
@ChanTsune
ChanTsune deleted the cli/diff/format-option branch July 29, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli This issue is about cli application dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant