Skip to content

Add diffWithCovers/patchSingleStream APIs, robust loading, full CI matrix#3

Merged
sunnylqm merged 4 commits into
mainfrom
feat/covers-api-and-build-improvements
Jul 5, 2026
Merged

Add diffWithCovers/patchSingleStream APIs, robust loading, full CI matrix#3
sunnylqm merged 4 commits into
mainfrom
feat/covers-api-and-build-improvements

Conversation

@sunnylqm

@sunnylqm sunnylqm commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Three groups of changes:

New native APIs (covers feature)

  • diffWithCovers(old, new, covers, options) — create standard hdiffpatch payloads from caller-provided cover lines, with replace / merge / native-coalesce modes and optional debugCovers introspection
  • patchSingleStream(oldPath, diffPath, outNewPath) — file-level apply for single-compressed diffs produced by diff() / diffWithCovers()
  • HDiffPatch submodule now points at the reactnativecn fork (via https) for the cover-listener capacity change
  • Diff-declared sizes (newDataSize, stepMemSize) are validated against size_t before allocation

Robust module loading & packaging

  • Loader order: local build/Release (dev) → static per-platform prebuild paths (now incl. darwin-x64, win32-x64) → node-gyp-build fallback with a clear error; a missing or ABI-incompatible prebuild no longer crashes require() on fresh clones
  • Prebuilds-only install: dropped the install script (published package ships no sources, so the node-gyp fallback could only fail on unsupported platforms), moved node-addon-api to devDependencies, added engines.node >= 14.17
  • Removed redundant .npmignore; anchored the ^v tag regex in prepublish

CI, tests, CLI

  • New ci.yml: build + test on push/PR across linux x64/arm64, macOS Intel/arm64, Windows — under both Node and Bun runtimes
  • publish.yml: restored macos-15-intel and windows-latest prebuild jobs, tests run before artifacts upload, npm publish --provenance
  • hdp patch auto-detects the diff format via header magic (HDIFF13 / HDIFFSF20), so CLI can now apply diff() output as well
  • Tests: async callback paths for all five APIs, corrupt-diff error propagation (sync + async), CLI coverage for both formats and invalid input

Notes for review

  • Windows and macOS Intel prebuilds are enabled for the first time — watch the first CI run on this PR
  • Behavior change: in development, a local build/Release build now always wins over bundled prebuilds (same convention as node-gyp-build); delete build/ if it goes stale

Test plan

  • node test/test.js — 11/11 pass locally (darwin-arm64)
  • bun ./test/test.js — 11/11 pass under Bun 1.3.14
  • Simulated published-package layout (prebuilds only) loads correctly; unsupported-platform path produces a clear No native build was found for platform=… error
  • CI matrix green on all 5 platforms (first run for win32-x64 / darwin-x64)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added cover-aware diff generation with configurable modes and richer result details.
    • Added single-stream patching support for file-based workflows.
    • Expanded CLI support to detect diff format automatically and choose the right patch behavior.
  • Bug Fixes

    • Improved patch safety checks for large inputs.
    • Added more resilient native module loading across platforms.
  • Documentation

    • Updated setup and usage docs with supported platforms and new API guidance.
  • Tests

    • Expanded test coverage for new diff, patch, and CLI scenarios.

sunnylqm and others added 3 commits July 5, 2026 20:59
- diffWithCovers(old, new, covers, options): create standard hdiffpatch
  payloads from caller-provided cover lines, with replace/merge/
  native-coalesce modes and optional debugCovers introspection
- patchSingleStream: file-level apply for single-compressed diffs
  produced by diff()/diffWithCovers()
- Point HDiffPatch submodule at reactnativecn fork (over https) for the
  cover-listener capacity change; migrate module registration out of
  the namespace and guard diff-declared sizes against size_t truncation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Loader prefers a local build/Release build (dev), then static
  per-platform prebuild paths incl. darwin-x64/win32-x64, and falls
  back to node-gyp-build with a clear error on unsupported platforms;
  a missing or ABI-incompatible prebuild no longer crashes require()
- Drop the install script: the published package has no sources, so
  node-gyp fallback could only ever fail; move node-addon-api to
  devDependencies and declare engines.node >=14.17
- Remove redundant .npmignore (files field wins); anchor the version
  tag regex in prepublish.ts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New ci.yml: build + test on push/PR across linux x64/arm64,
  macOS Intel/arm64, and Windows, under both Node and Bun
- publish.yml: restore macos-15-intel and windows-latest prebuilds,
  run tests before uploading artifacts, publish with --provenance
- hdp patch now sniffs the diff header (HDIFF13 / HDIFFSF20) and
  applies the right patcher, so CLI can apply diff() output too
- Tests: async callback paths for all five APIs, corrupt-diff error
  propagation, CLI coverage for both formats; ci-failure-email.yml
  tracked
- README: platform support, development guide, format notes, licenses

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sunnylqm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b5265a3-ba77-4491-858e-510a29bfc0b3

📥 Commits

Reviewing files that changed from the base of the PR and between 5b99ef2 and f5a0bc5.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .github/workflows/publish.yml
  • README.md
  • src/main.cc
  • test/test.js
📝 Walkthrough

Walkthrough

Adds native cover-aware diffing (diffWithCovers) and file-based single-stream patching (patchSingleStream) across C++ core, N-API bindings, TypeScript types, JS wrapper, and CLI, with new tests and docs. Also updates CI/publish workflows, adds a CI-failure issue-notification workflow, repoints the HDiffPatch submodule, and adjusts packaging scripts/dependencies.

Changes

Cover diffing and single-stream patching feature

Layer / File(s) Summary
Native cover-diff core
src/hdiff.cpp, src/hdiff.h
Adds HdiffCover, HdiffCoverMode, HdiffWithCoversResult types, cover validation/slicing/merging/coalescing logic, a centralized LZMA2 config helper, and the new hdiff_with_covers(...) entry point.
Native single-stream patch core
src/hpatch.cpp, src/hpatch.h
Adds overflow-safety checks using std::numeric_limits and introduces hpatch_single_stream(...) for file-path based single-compressed patching.
N-API bindings
src/main.cc
Adds numeric/cover parsing helpers, cover-to-JS conversion helpers, a PatchSingleStreamAsyncWorker, and exported diffWithCovers/patchSingleStream APIs wired into Init.
JS/TS wrapper and types
index.js, index.d.ts
Reworks loadNative() to prefer a local build then prebuild-per-platform, exports patchSingleStream, and declares HpatchCover, DiffWithCoversResult, DiffWithCoversOptions, and new function/overload types.
CLI auto-detection
bin/hdiffpatch.js
Adds detectDiffFormat(...) and updates patch to auto-dispatch between patchSingleStream and patchStream based on the diff file header.
Tests and docs
test/test.js, README.md
Adds tests for cover modes, single-stream patching, async/CLI flows, and documents diffWithCovers, patchSingleStream, installation, and license compatibility notes.

CI/CD, submodule, and packaging updates

Layer / File(s) Summary
New CI workflows
.github/workflows/ci.yml, .github/workflows/ci-failure-email.yml
Adds a matrix-based Test workflow (Node + Bun) and a CI Failure Issue workflow that creates/updates or closes GitHub issues based on the publish workflow's outcome.
Publish workflow matrix and publish updates
.github/workflows/publish.yml
Expands prebuild targets (macos-15-intel, windows-latest), switches verification to Bun, bumps upload-artifact to v7.0.1, and adds --provenance to npm publish.
Submodule update
.gitmodules, HDiffPatch
Repoints the HDiffPatch submodule URL and updates the pinned commit.
Packaging scripts/dependencies
package.json, .npmignore, scripts/prepublish.ts
Adds a test:bun script, removes the install hook, moves node-addon-api to devDependencies, removes .idea/build from .npmignore, and fixes version-string trimming logic.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant JS as diffWithCovers (JS API)
  participant Native as main.cc bindings
  participant Hdiff as hdiff_with_covers
  participant Listener as CoverLinesListener

  JS->>Native: diffWithCovers(old, new, covers, options)
  Native->>Hdiff: hdiff_with_covers(old, new, covers, mode)
  Hdiff->>Hdiff: validate_covers(covers)
  Hdiff->>Listener: create_single_compressed_diff(...)
  Listener->>Listener: prepare_listener_covers(mode)
  Listener-->>Hdiff: final covers, diff buffer
  Hdiff-->>Native: HdiffWithCoversResult
  Native-->>JS: diff buffer + cover metrics
Loading
sequenceDiagram
  participant Publish as Publish Package workflow
  participant GH as GitHub Actions runner
  participant API as GitHub Issues API

  Publish->>GH: workflow_run completed
  alt conclusion is failure/timed_out/action_required
    GH->>API: search open issues by title prefix
    alt issue exists
      GH->>API: update issue title/body
    else no issue
      GH->>API: create new issue
    end
  else conclusion is success
    GH->>API: search open issues by title prefix
    GH->>API: close matching issues with recovery comment
  end
Loading
🚥 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 changes: new APIs, loader hardening, and expanded CI coverage.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/covers-api-and-build-improvements

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.

@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: 4

🧹 Nitpick comments (3)
bin/hdiffpatch.js (1)

22-36: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

fs.openSync call isn't guarded.

If diffFile doesn't exist or isn't readable, fs.openSync (line 25) throws before the try/finally block, producing a raw Node ENOENT/EACCES stack trace instead of a CLI-friendly error via fail(). This is a new dedicated file-access path introduced ahead of the actual patch call, so it's worth handling consistently with the rest of the CLI's error reporting.

Suggested fix
 function detectDiffFormat(diffFile) {
   const header = Buffer.alloc(9);
-  const fd = fs.openSync(diffFile, 'r');
-  let bytesRead;
+  let fd;
+  let bytesRead;
   try {
+    fd = fs.openSync(diffFile, 'r');
     bytesRead = fs.readSync(fd, header, 0, header.length, 0);
   } finally {
-    fs.closeSync(fd);
+    if (fd !== undefined) fs.closeSync(fd);
   }

This doesn't fully solve the friendliness aspect (the thrown error still propagates), but avoids a partially-opened resource if readSync throws for reasons other than open failure. If the caller (line 71) isn't already wrapped in a top-level try/catch producing a clean CLI error message, consider verifying that.

🤖 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 `@bin/hdiffpatch.js` around lines 22 - 36, The detectDiffFormat() helper opens
the diff file before any error handling, so missing or unreadable inputs throw a
raw fs.openSync() exception instead of a CLI-friendly failure. Wrap the
fs.openSync()/fs.readSync() path in try/catch around detectDiffFormat(), and
convert open/read failures into the existing fail() flow; keep the existing
closeSync cleanup in a finally block and make sure the caller that uses
detectDiffFormat() also surfaces a clean error message.
index.js (1)

1-28: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Local build path bypasses all fallback/error handling.

Lines 7-9 require(localBuild) runs outside the try/catch that guards the prebuild switch below. If a stale or ABI-incompatible artifact exists at build/Release/hdiffpatch.node (e.g., built for a different Node major version), this throws an uncaught exception instead of falling through to the prebuild/node-gyp-build paths that exist specifically to handle such mismatches.

Separately, the blanket catch (err) {} around the prebuild switch (line 25) silently swallows any error, not just "file missing"/ABI mismatch — a corrupted prebuild or unrelated native crash would be hidden and quietly retried via node-gyp-build, making failures harder to diagnose.

Suggested tightening
   const localBuild = path.join(__dirname, 'build/Release/hdiffpatch.node');
   if (fs.existsSync(localBuild)) {
-    return require(localBuild);
+    try {
+      return require(localBuild);
+    } catch (err) {
+      // fall through to prebuild/node-gyp-build resolution
+    }
   }
🤖 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 `@index.js` around lines 1 - 28, The native module loading in loadNative()
bypasses fallback handling for the local build and silently swallows all
prebuild errors. Move the local build require(localBuild) into the same guarded
flow as the prebuild switch so ABI-incompatible or stale artifacts can fall back
to node-gyp-build, and narrow the catch around the prebuild require cases to
only handle expected load failures while preserving unexpected errors for
diagnosis.
.github/workflows/ci.yml (1)

21-23: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false on checkout.

Static analysis flags credential persistence via actions/checkout without persist-credentials: false. Since this workflow runs on pull_request (including forks) and doesn't need push access, disabling credential persistence reduces exposure.

🔒 Suggested fix
       - uses: actions/checkout@v5
         with:
           submodules: recursive
+          persist-credentials: false
🤖 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 @.github/workflows/ci.yml around lines 21 - 23, The checkout step in the CI
workflow should disable stored Git credentials by setting persist-credentials to
false. Update the existing actions/checkout usage in the workflow that runs on
pull_request so it keeps submodules: recursive but does not persist credentials,
since no push access is needed. Refer to the checkout step in the CI workflow
when making the change.

Source: Linters/SAST tools

🤖 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.

Inline comments:
In @.gitmodules:
- Line 3: The HDiffPatch submodule remote is pointing to a repository that does
not contain the pinned commit, so update the .gitmodules entry to a remote that
actually exposes the existing HDiffPatch commit or repin the submodule to a
commit available on reactnativecn/HDiffPatch.git. Make sure the submodule
reference remains valid for fresh clones and CI by keeping the commit and remote
in sync for the HDiffPatch submodule entry.

In `@README.md`:
- Around line 111-115: The README note is outdated and contradicts the CLI’s
auto-detection behavior. Update the text near the hdp patch usage guidance to
say that the CLI can handle both streaming diffs from hdp diff and
single-compressed diffs from diff()/diffWithCovers() because bin/hdiffpatch.js
detects the format and routes to patchSingleStream when needed. Make sure the
wording reflects that patch() / patchSingleStream() are the underlying APIs, not
a limitation of the hdp patch command.

In `@src/hdiff.cpp`:
- Around line 129-170: The native coalescing in coalesce_native_covers is
merging adjacent covers based only on position and delta, which can skip over
bytes that were never verified. Update this logic to perform a byte comparison
for the gap before extending the previous hpatch_TCover, or otherwise restrict
native-coalesce to best-effort behavior only. Use the existing helper and flow
in coalesce_native_covers, cover_new_end, and cover_old_end to locate where the
merge decision is made.

In `@src/main.cc`:
- Around line 43-51: `parseUint64String` currently lets `std::strtoull` accept a
leading minus sign and wrap it into a huge unsigned value. Update this helper to
explicitly reject inputs starting with `-` before calling `std::strtoull`, while
keeping the existing empty/string-end/errno checks, so `oldPos`-style fields
fail fast with a clear invalid-number path instead of being converted to
`UINT64_MAX`.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 21-23: The checkout step in the CI workflow should disable stored
Git credentials by setting persist-credentials to false. Update the existing
actions/checkout usage in the workflow that runs on pull_request so it keeps
submodules: recursive but does not persist credentials, since no push access is
needed. Refer to the checkout step in the CI workflow when making the change.

In `@bin/hdiffpatch.js`:
- Around line 22-36: The detectDiffFormat() helper opens the diff file before
any error handling, so missing or unreadable inputs throw a raw fs.openSync()
exception instead of a CLI-friendly failure. Wrap the
fs.openSync()/fs.readSync() path in try/catch around detectDiffFormat(), and
convert open/read failures into the existing fail() flow; keep the existing
closeSync cleanup in a finally block and make sure the caller that uses
detectDiffFormat() also surfaces a clean error message.

In `@index.js`:
- Around line 1-28: The native module loading in loadNative() bypasses fallback
handling for the local build and silently swallows all prebuild errors. Move the
local build require(localBuild) into the same guarded flow as the prebuild
switch so ABI-incompatible or stale artifacts can fall back to node-gyp-build,
and narrow the catch around the prebuild require cases to only handle expected
load failures while preserving unexpected errors for diagnosis.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7446e453-e76a-4e84-8234-5b5e13062fde

📥 Commits

Reviewing files that changed from the base of the PR and between 6ee6c4c and 5b99ef2.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • .github/workflows/ci-failure-email.yml
  • .github/workflows/ci.yml
  • .github/workflows/publish.yml
  • .gitmodules
  • .npmignore
  • HDiffPatch
  • README.md
  • bin/hdiffpatch.js
  • index.d.ts
  • index.js
  • package.json
  • scripts/prepublish.ts
  • src/hdiff.cpp
  • src/hdiff.h
  • src/hpatch.cpp
  • src/hpatch.h
  • src/main.cc
  • test/test.js
💤 Files with no reviewable changes (1)
  • .npmignore

Comment thread .gitmodules
[submodule "HDiffPatch"]
path = HDiffPatch
url = https://github.com/sisong/HDiffPatch.git
url = https://github.com/reactnativecn/HDiffPatch.git

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sha=$(git submodule status --cached HDiffPatch | awk '{print $1}' | sed 's/^[+-]//')
git ls-remote https://github.com/reactnativecn/HDiffPatch.git "$sha"

Repository: reactnativecn/node-hdiffpatch

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'cached submodule SHA: '
git submodule status --cached -- HDiffPatch | awk '{print $1}' | sed 's/^[+-]//'

sha=$(git submodule status --cached -- HDiffPatch | awk '{print $1}' | sed 's/^[+-]//')

printf 'matching refs on reactnativecn/HDiffPatch:\n'
git ls-remote https://github.com/reactnativecn/HDiffPatch.git "$sha" || true

printf '\nexistence check against the remote commit object:\n'
git ls-remote https://github.com/reactnativecn/HDiffPatch.git | awk -v sha="$sha" '$1 == sha {found=1} END {exit found ? 0 : 1}'
echo "found=$?"

Repository: reactnativecn/node-hdiffpatch

Length of output: 332


Keep the HDiffPatch submodule on a remote that contains the pinned commit. The fork at reactnativecn/HDiffPatch.git does not expose 4a7eb78aee3bd5acbe6fe55621391685648030d5, so fresh clones and CI submodule updates will fail unless the submodule is repinned to a commit that exists on that remote.

🤖 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 @.gitmodules at line 3, The HDiffPatch submodule remote is pointing to a
repository that does not contain the pinned commit, so update the .gitmodules
entry to a remote that actually exposes the existing HDiffPatch commit or repin
the submodule to a commit available on reactnativecn/HDiffPatch.git. Make sure
the submodule reference remains valid for fresh clones and CI by keeping the
commit and remote in sync for the HDiffPatch submodule entry.

Comment thread README.md
Comment thread src/hdiff.cpp
Comment on lines +129 to +170
std::vector<hpatch_TCover> coalesce_native_covers(const hpatch_TCover* nativeCovers,
size_t nativeCoverCount) {
const uint64_t maxGapBytes = 64;
std::vector<hpatch_TCover> coalesced;
coalesced.reserve(nativeCoverCount);

for (size_t i = 0; i < nativeCoverCount; ++i) {
const hpatch_TCover& cover = nativeCovers[i];
if (cover.length == 0) {
continue;
}
if (coalesced.empty()) {
coalesced.push_back(cover);
continue;
}

hpatch_TCover& previous = coalesced.back();
const uint64_t previousNewEnd = cover_new_end(previous);
const uint64_t previousOldEnd = cover_old_end(previous);
const uint64_t coverNewPos = static_cast<uint64_t>(cover.newPos);
const uint64_t coverOldPos = static_cast<uint64_t>(cover.oldPos);
if (coverNewPos < previousNewEnd || coverOldPos < previousOldEnd) {
coalesced.push_back(cover);
continue;
}

const uint64_t newGap = coverNewPos - previousNewEnd;
const uint64_t oldGap = coverOldPos - previousOldEnd;
const int64_t previousDelta =
static_cast<int64_t>(previous.oldPos) - static_cast<int64_t>(previous.newPos);
const int64_t coverDelta =
static_cast<int64_t>(cover.oldPos) - static_cast<int64_t>(cover.newPos);
if (newGap <= maxGapBytes && oldGap == newGap && previousDelta == coverDelta) {
previous.length = static_cast<hpatch_StreamPos_t>(cover_new_end(cover) - previous.newPos);
continue;
}

coalesced.push_back(cover);
}

return coalesced;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

HDiffPatch create_single_compressed_diff ICoverLinesListener cover coalescing semantics

💡 Result:

The HDiffPatch library provides the create_single_compressed_diff function as part of its v4 API to generate a single compressed patch file between two data sets [1][2][3]. Regarding your query about ICoverLinesListener and cover coalescing: ICoverLinesListener is a callback interface in HDiffPatch used during the multi-threaded diffing process to monitor and influence the search for matching data blocks (covers) [4][5]. While the HDiffPatch codebase uses internal mechanisms like tm_collate_covers (often called after multi-threaded search) to merge or manage overlapping/adjacent covers, the term "coalescing" in this context typically refers to the logic for combining smaller, fragmented match regions into more efficient, larger cover blocks [6]. The ICoverLinesListener interface allows external control over this process through methods such as: search_cover_limit: Used to determine if the search for covers should be limited [4]. research_cover: Provides an opportunity to refine or re-evaluate detected covers [4]. insert_cover: Allows for the explicit insertion of covers during the process [4]. search_cover_finish: Called when the cover search phase is complete, providing a final opportunity to process or filter the detected covers before serialization [4]. There is no specific "cover coalescing semantics" property defined in the API; rather, the listener provides hooks that allow you to influence how covers are searched, inserted, and finalized, which effectively dictates the structure and optimization of the resulting diff [4][6]. Coalescing, if desired, is generally handled by the library's internal logic or can be influenced by how the listener modifies the list of covers before they are passed to the serialization phase [6]. If you are encountering issues related to "coalescing" or line coverage in other contexts, note that unrelated software projects also use terms like "coalescing" and "coverage" (e.g., nullish coalescing in JavaScript/SonarQube or line coverage in JaCoCo), which are distinct from the HDiffPatch binary diffing process [7][8][9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## hdiff.cpp outline\n'
ast-grep outline src/hdiff.cpp --view expanded || true

printf '\n## relevant symbols\n'
rg -n "coalesce_native_covers|check_single_compressed_diff|NativeCoalesce|coverLines|prepare_listener_covers|validate_covers|create_single_compressed_diff|ICoverLinesListener" src/hdiff.cpp src -n -A 4 -B 4 || true

printf '\n## hdiff.cpp excerpt\n'
sed -n '1,420p' src/hdiff.cpp

Repository: reactnativecn/node-hdiffpatch

Length of output: 30576


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## diff.h / diff_types.h cover docs\n'
rg -n "hpatch_TCover|ICoverLinesListener|coverLines|search_cover|insert_cover|finish|cover" HDiffPatch/libHDiffPatch/HDiff/diff.h HDiffPatch/libHDiffPatch/HDiff/diff_types.h HDiffPatch -A 8 -B 4 || true

printf '\n## focused header excerpts\n'
sed -n '1,260p' HDiffPatch/libHDiffPatch/HDiff/diff_types.h
printf '\n--- diff.h ---\n'
sed -n '1,260p' HDiffPatch/libHDiffPatch/HDiff/diff.h

Repository: reactnativecn/node-hdiffpatch

Length of output: 481


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## locate diff headers\n'
fd -a 'diff.h|diff_types.h' . || true

printf '\n## locate cover symbols\n'
rg -n "typedef struct.*hpatch_TCover|struct hpatch_TCover|ICoverLinesListener|coverLines|search_cover|insert_cover|search_cover_finish" . || true

Repository: reactnativecn/node-hdiffpatch

Length of output: 462


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## top-level tree\n'
find . -maxdepth 2 -type f | sed 's#^\./##' | sort | head -300

printf '\n## search for HDiffPatch headers by basename\n'
fd -a 'diff.h' .
fd -a 'diff_types.h' .
fd -a 'hdiff*.h' .
fd -a '*diff*types*' .

Repository: reactnativecn/node-hdiffpatch

Length of output: 899


🌐 Web query:

HDiffPatch hpatch_TCover ICoverLinesListener semantics cover must be byte-identical merge adjacent covers

💡 Result:

In the HDiffPatch library, the ICoverLinesListener interface is used for managing and processing sequences of match covers (hpatch_TCover) during the diff generation process [1][2]. Its purpose is to allow custom logic for searching, inserting, and refining these covers, rather than enforcing a specific byte-identical merge requirement at the interface level [1]. The semantics of handling covers, including whether adjacent covers should be merged, are typically determined by the implementation of the ICoverLinesListener or the underlying diff algorithm, not by a hard-coded mandate in the interface itself [1][3]. 1. ICoverLinesListener is primarily an interface for listener hooks, such as search_cover_limit, research_cover, insert_cover, and search_cover_finish [1]. 2. The library's core diff functions manage the collection of covers [2][4], and while optimization processes (such as merging or extending covers) may occur during or after the search phase [2][5], these are handled by the specific logic within the diff generation pipeline (e.g., in diff.cpp or via optimization helpers) [3][4]. 3. If an implementation requires byte-identical merge of adjacent covers to optimize diff size or patch speed, it must be explicitly implemented within the logic of the ICoverLinesListener (e.g., inside an insert_cover call) or post-processing step [1][5]. There is no inherent semantic requirement in the ICoverLinesListener interface definition itself that forces byte-identical merging of adjacent covers [1]. Users are expected to handle cover refinement as dictated by their specific patching strategy or serialization requirements [1][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n src/hdiff.h

Repository: reactnativecn/node-hdiffpatch

Length of output: 1601


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## README mentions\n'
rg -n "NativeCoalesce|native-coalesce|merge|cover" README.md src/main.cc src/hdiff.h src/hdiff.cpp -A 4 -B 4 || true

printf '\n## README excerpt\n'
sed -n '1,260p' README.md

Repository: reactnativecn/node-hdiffpatch

Length of output: 32880


🌐 Web query:

site:github.com/sisong/HDiffPatch diff_types.h ICoverLinesListener hpatch_TCover coverLines callback semantics

💡 Result:

In the HDiffPatch library, ICoverLinesListener is an interface (a C-style struct of function pointers) defined in diff_types.h used to hook into and manage the cover (match) generation process during the diff creation [1]. It does not have a single callback called "coverLines"; rather, it provides a suite of lifecycle and control callbacks to customize how matches are searched, processed, or optimized [1]. The term hpatch_TCover refers to the structure representing a matching block between the old and new data, containing the old position, new position, and the length of the match [1]. Key semantics of the ICoverLinesListener interface include: 1. Search Control and Multithreading: The listener allows integration with multithreaded matching through callbacks like next_search_block_MT, which manages the distribution of work, and begin_search_block, which initializes parallel search sessions [1][2]. 2. Cover Manipulation: Callbacks such as insert_cover are used to intercept, filter, or modify the sequence of discovered covers (hpatch_TCover) before they are serialized into the final diff [1][3]. For example, implementations like TCoversOptim use this to unpack or perform custom processing on blocks [3]. 3. Research and Optimization: Methods like research_cover provide hooks for performing additional matching or refinement on specific data ranges, while search_cover_limit and get_limit_cover_length allow fine-grained control over search parameters to manage performance or limit the size/depth of matches [1][2][4]. 4. Serialization Hooks: The map_streams_befor_serialize callback allows the listener to perform final adjustments or mapping to input streams immediately before the diff serialization process begins [1][5]. The interface is typically passed as an optional argument to high-level diff functions (such as those in bsdiff_wrapper or vcdiff_wrapper) to allow users to inject custom logic into the standard diffing pipeline [6][7][8].

Citations:


native-coalesce needs a byte check before widening covers. Matching newPos/oldPos and delta alone can merge across a gap whose bytes were never compared, so this mode can feed an invalid cover chain into the diff builder and turn the final check into a hard failure. Compare the bridged bytes before merging, or keep this mode explicitly best-effort only.

🤖 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 `@src/hdiff.cpp` around lines 129 - 170, The native coalescing in
coalesce_native_covers is merging adjacent covers based only on position and
delta, which can skip over bytes that were never verified. Update this logic to
perform a byte comparison for the gap before extending the previous
hpatch_TCover, or otherwise restrict native-coalesce to best-effort behavior
only. Use the existing helper and flow in coalesce_native_covers, cover_new_end,
and cover_old_end to locate where the merge decision is made.

Comment thread src/main.cc
- Reject negative numeric strings in cover fields: strtoull wraps
  "-1" to UINT64_MAX (and e.g. "-18446744073709551615" silently to 1);
  add a regression test
- Fix stale README note: hdp patch auto-detects both diff formats
- Install node-gyp globally on Windows runners: bun creates exe shims
  while prebuildify spawns node-gyp.cmd, so the prebuild step failed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sunnylqm sunnylqm merged commit 4fa14e3 into main Jul 5, 2026
6 checks passed
@sunnylqm sunnylqm deleted the feat/covers-api-and-build-improvements branch July 5, 2026 14:05
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.

1 participant