Skip to content

Allow text operations on any file type and fall back to text diff for non-semantic changes#1292

Merged
minwoox merged 8 commits into
line:mainfrom
minwoox:json5_diff
Apr 15, 2026
Merged

Allow text operations on any file type and fall back to text diff for non-semantic changes#1292
minwoox merged 8 commits into
line:mainfrom
minwoox:json5_diff

Conversation

@minwoox

@minwoox minwoox commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Motivation:

  • When a JSON5 file had text-level changes that were semantically identical
    (e.g., trailing commas), the diff was silently dropped because the parsed
    JSON nodes were equal. The same issue existed for YAML files with comment
    changes.
    Modifications:
  • In GitRepository.toChangeMap(), added a text diff fallback for JSON5
    and YAML files when the JSON patch is empty but the raw text differs.
  • Removed the file type restriction from ofTextUpsert that rejected JSON
    and JSON5 files, and from both ofTextPatch overloads that rejected JSON
    files.
    Result:
  • JSON5 and YAML files now correctly produce text diffs when only
    non-semantic text changes are made (e.g., trailing commas, comments,
    formatting).
  • Text operations (ofTextUpsert, ofTextPatch) now accept any file type.

@minwoox minwoox added the defect label Apr 7, 2026
@minwoox minwoox added this to the 0.82.0 milestone Apr 7, 2026
@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Text-change factories now accept JSON/JSON5/YAML paths and parse text with Jackson; GitRepository caches raw bytes and emits text diffs when structured patches are empty but raw text differs; server & repo plumbing support IDENTITY_TEXT; multiple tests added or adjusted to validate raw-preservation and diff behaviors.

Changes

Cohort / File(s) Summary
Change factories
common/src/main/java/com/linecorp/centraldogma/common/Change.java
Removed extension-based rejection for JSON/JSON5 in text upsert/patch factories; attempt to parse text via Jackson and throw ChangeFormatException on parse failure.
Git diff generation & fallback
server/src/main/java/.../git/GitRepository.java
Cache raw old/new object bytes per MODIFY; reuse cached bytes for parsing; if semantic JSON/YAML patch is empty but raw text differs (including JSON5), emit text diffs using sanitized UTF‑8 bytes.
Structured-change validation
server/src/main/java/.../DefaultChangesApplier.java
When applying text patches, validate resulting text for JSON/YAML compatibility and throw ChangeConflictException on parse errors.
Query types & identity
common/src/main/java/.../IdentityQuery.java, common/src/main/java/.../Query.java, common/src/main/java/.../QueryType.java
IdentityQuery equality/hash now include queryType; Javadocs clarified; IDENTITY_TEXT documented/expanded to return textual representation for JSON/YAML/TEXT.
Query request & repo plumbing
server/src/main/java/.../api/converter/QueryRequestConverter.java, server/src/main/java/.../storage/repository/Repository.java, server/src/main/java/.../storage/repository/RepositoryUtil.java
QueryRequestConverter parses optional queryType parameter; Repository/RepositoryUtil added handling for IDENTITY_TEXT to use contentAsText() and produce text-based Change results.
Client parameter plumbing
client/java-armeria/src/main/java/.../ArmeriaCentralDogma.java
appendJsonPaths now appends queryType=<name> for non-IDENTITY/JSON_PATH query types.
Tests added/updated
it/server/src/test/java/.../GetDiffTest.java, server/src/test/java/.../TextUpsertTest.java, common/src/test/java/.../ChangeTest.java, it/server/src/test/java/.../GetFileTest.java, server/src/test/java/.../Json5CrudTest.java, client/java/src/test/.../ReplicationLagTolerantCentralDogmaTest.java
Added/updated tests for JSON5 trailing-comma and YAML comment diffs; new TextUpsertTest validates raw preservation and queries for .json/.json5/.yaml/.yml; adjusted tests to accept text ops on structured-file extensions and to expect raw text (with trailing newline) from Query.ofText.

Sequence Diagram(s)

sequenceDiagram
    participant Repo as GitRepository
    participant Reader as ObjectReader
    participant Parser as Jackson/Json5/Yaml
    participant ChangeMap as ChangeMap

    Repo->>Reader: open(oldPath).getBytes() / open(newPath).getBytes()
    Reader-->>Repo: rawOldBytes / rawNewBytes
    Repo->>Parser: readTree(path, rawOldBytes) / readTree(path, rawNewBytes)
    Parser-->>Repo: oldNode / newNode
    Repo->>Parser: computePatch(oldNode, newNode)
    Parser-->>Repo: patch
    alt patch non-empty
        Repo->>ChangeMap: putStructuredDiff(patch)
    else patch empty but raw differs (JSON5/YAML)
        Repo->>ChangeMap: sanitizeText(rawOldBytes/rawNewBytes) -> putTextDiff(...)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • trustin
  • jrhee17

Poem

"I nibble bytes both old and new,
I hop on commas, and comments too,
I read the tree and then compare,
If nodes match but text's unfair,
I give you raw lines — neat and true. 🥕"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main objective: allowing text operations on any file type and falling back to text diff for non-semantic changes, which aligns with the core modifications.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the motivation (JSON5/YAML non-semantic change issues), modifications across multiple files, and the expected results.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@common/src/main/java/com/linecorp/centraldogma/common/Change.java`:
- Around line 163-166: ofTextUpsert currently rejects JSON/JSON5 based on
EntryType.guessFromPath(path) while ofTextPatch uses Json5.isJson(path); update
ofTextUpsert (both overloads around lines near methods named ofTextUpsert) to
check Json5.isJson(path) and throw ChangeFormatException with the same message
guidance ("invalid file type: " + path + " (expected: a non-JSON file). Use
Change.ofJsonUpsert() instead") instead of relying on EntryType.guessFromPath,
so PATCH_TO_UPSERT flows from GitRepository.toChangeMap() and putTextDiff() will
accept .json5 files consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bf627bf3-352e-4e9f-a44f-da7554edcced

📥 Commits

Reviewing files that changed from the base of the PR and between 05e4fa3 and 43f1c4b.

📒 Files selected for processing (3)
  • common/src/main/java/com/linecorp/centraldogma/common/Change.java
  • it/server/src/test/java/com/linecorp/centraldogma/it/GetDiffTest.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepository.java

Comment thread common/src/main/java/com/linecorp/centraldogma/common/Change.java Outdated

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

👍 👍

@minwoox minwoox modified the milestones: 0.82.0, 0.83.0 Apr 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
common/src/test/java/com/linecorp/centraldogma/common/ChangeTest.java (1)

123-141: Consider simplifying this test with parameterization/helper extraction.

The current assertions are correct, but the repetition and variable naming make the test harder to scan than necessary.

♻️ Example refactor
-    void shouldAllowJsonFileWith_OfText() {
-        // Text operations should accept any file type including JSON and JSON5.
-        final Change<String> textUpsertJson = Change.ofTextUpsert("/foo.json", "abc");
-        assertThat(textUpsertJson.type()).isEqualTo(ChangeType.UPSERT_TEXT);
-
-        final Change<String> textUpsertJson5 = Change.ofTextUpsert("/foo.json5", "abc");
-        assertThat(textUpsertJson5.type()).isEqualTo(ChangeType.UPSERT_TEXT);
-
-        final Change<String> textPatchJson = Change.ofTextPatch("/foo.json", "abc");
-        assertThat(textPatchJson.type()).isEqualTo(ChangeType.APPLY_TEXT_PATCH);
-
-        final Change<String> textPatchJson5 = Change.ofTextPatch("/foo.json5", "abc");
-        assertThat(textPatchJson5.type()).isEqualTo(ChangeType.APPLY_TEXT_PATCH);
-
-        final Change<String> textPatchJson2 = Change.ofTextPatch("/foo.json", "foo", "bar");
-        assertThat(textPatchJson2.type()).isEqualTo(ChangeType.APPLY_TEXT_PATCH);
-
-        final Change<String> textPatchJson52 = Change.ofTextPatch("/foo.json5", "foo", "bar");
-        assertThat(textPatchJson52.type()).isEqualTo(ChangeType.APPLY_TEXT_PATCH);
-    }
+    void shouldAllowTextOperationsOnJsonAndJson5Files() {
+        assertTextUpsertType("/foo.json");
+        assertTextUpsertType("/foo.json5");
+        assertTextPatchType("/foo.json");
+        assertTextPatchType("/foo.json5");
+    }
+
+    private static void assertTextUpsertType(String path) {
+        assertThat(Change.ofTextUpsert(path, "abc").type()).isEqualTo(ChangeType.UPSERT_TEXT);
+    }
+
+    private static void assertTextPatchType(String path) {
+        assertThat(Change.ofTextPatch(path, "abc").type()).isEqualTo(ChangeType.APPLY_TEXT_PATCH);
+        assertThat(Change.ofTextPatch(path, "foo", "bar").type()).isEqualTo(ChangeType.APPLY_TEXT_PATCH);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/src/test/java/com/linecorp/centraldogma/common/ChangeTest.java` around
lines 123 - 141, The test shouldAllowJsonFileWith_OfText in ChangeTest repeats
the same assertions; refactor by extracting a small helper or parameterizing the
test to iterate filenames ["/foo.json","/foo.json5"] and the two operations
Change.ofTextUpsert and Change.ofTextPatch (and the multi-arg overload of
ofTextPatch), asserting the resulting Change.type() equals the expected
ChangeType (ChangeType.UPSERT_TEXT or ChangeType.APPLY_TEXT_PATCH); update the
test to call that helper or use a `@ParameterizedTest/`@ValueSource to remove
duplicated variables and assertions while keeping the same coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@common/src/test/java/com/linecorp/centraldogma/common/ChangeTest.java`:
- Around line 123-141: The test shouldAllowJsonFileWith_OfText in ChangeTest
repeats the same assertions; refactor by extracting a small helper or
parameterizing the test to iterate filenames ["/foo.json","/foo.json5"] and the
two operations Change.ofTextUpsert and Change.ofTextPatch (and the multi-arg
overload of ofTextPatch), asserting the resulting Change.type() equals the
expected ChangeType (ChangeType.UPSERT_TEXT or ChangeType.APPLY_TEXT_PATCH);
update the test to call that helper or use a `@ParameterizedTest/`@ValueSource to
remove duplicated variables and assertions while keeping the same coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd0e8bd5-c520-446a-9b10-8ca3fd5b45ef

📥 Commits

Reviewing files that changed from the base of the PR and between 43f1c4b and 5267249.

📒 Files selected for processing (5)
  • common/src/main/java/com/linecorp/centraldogma/common/Change.java
  • common/src/test/java/com/linecorp/centraldogma/common/ChangeTest.java
  • it/server/src/test/java/com/linecorp/centraldogma/it/GetDiffTest.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepository.java
  • server/src/test/java/com/linecorp/centraldogma/server/TextUpsertTest.java
💤 Files with no reviewable changes (1)
  • common/src/main/java/com/linecorp/centraldogma/common/Change.java
✅ Files skipped from review due to trivial changes (1)
  • server/src/test/java/com/linecorp/centraldogma/server/TextUpsertTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • it/server/src/test/java/com/linecorp/centraldogma/it/GetDiffTest.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepository.java

static Change<String> ofTextUpsert(String path, String text) {
requireNonNull(text, "text");
validateFilePath(path, "path");
if (EntryType.guessFromPath(path) == EntryType.JSON) {

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.

Shouldn't we validate text if it is a structured data?

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.

Ignore this review if this PR is still being worked on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ignore this review if this PR is still being worked on.

Yeah, I'm still working on it, but I forgot to validate the data. 😓

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@ikhoon This is ready. 😉

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.

New changes look good.

non-semantic changes
Motivation:
- When a JSON5 file had text-level changes that were semantically identical
  (e.g., trailing commas), the diff was silently dropped because the parsed
  JSON nodes were equal. The same issue existed for YAML files with comment
  changes.
Modifications:
- In `GitRepository.toChangeMap()`, added a text diff fallback for JSON5
  and YAML files when the JSON patch is empty but the raw text differs.
- Removed the file type restriction from `ofTextUpsert` that rejected JSON
  and JSON5 files, and from both `ofTextPatch` overloads that rejected JSON
  files.
Result:
- JSON5 and YAML files now correctly produce text diffs when only
  non-semantic text changes are made (e.g., trailing commas, comments,
  formatting).
- Text operations (`ofTextUpsert`, `ofTextPatch`) now accept any file type.
@minwoox minwoox changed the title Fall back to text diff when JSON5/YAML raw text differs but parsed nodes are equal Allow text operations on any file type and fall back to text diff for non-semantic changes Apr 9, 2026

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

👍 👍

@minwoox minwoox merged commit 57c29ca into line:main Apr 15, 2026
12 of 14 checks passed
@minwoox minwoox deleted the json5_diff branch April 15, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants