Skip to content

feat: add diffBuffers, TreeBuilder, diffTreeToTree, findBlob and Diff.toPatch - #168

Merged
Brooooooklyn merged 2 commits into
mainfrom
feat/buffer-diff-167
Sep 21, 2026
Merged

Brooooooklyn merged 2 commits into
mainfrom
feat/buffer-diff-167

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

Closes #167

Issue #167 asks for diffing arbitrary strings like git diff --no-index. The reporter suggested TreeBuilder + diffTreeToTree as a workaround after finding git_diff_blobs is callback-based. This PR implements both the direct API and the workaround path.

New API surface

API What it does
diffBuffers(old, oldPath, new, newPath, options?) Repo-free diff of two raw buffers via git2::Patch::from_buffers. Returns { patch, hunks, stats } — unified patch text plus eagerly materialized hunks/lines (the Patch borrows the input buffers, so everything is copied out). null on either side = added/deleted file.
Diff.toPatch() Render any Diff as unified-diff text, including sigils (+/-/space) matching libgit2's diff_print.c.
repo.treebuilder(source?) → TreeBuilder insert(name, oid, filemode) / remove / get / write / clear / len / isEmpty — the reporter's literal request.
repo.diffTreeToTree(oldTree?, newTree?, options?) Tree-to-tree diff; null side = empty tree.
repo.findBlob(oid) Look up a blob by OID (new BlobParent::Repository variant).

Reporter's use case, now possible

import { diffBuffers } from '@napi-rs/simple-git'

const { patch } = diffBuffers(
  Buffer.from(expected), 'expected.txt',
  Buffer.from(actual), 'actual.txt',
)

The synthetic-tree route also works end to end: repo.blob() → treebuilder.insert(..., 0o100644) → write() → diffTreeToTree → toPatch().

Tests

  • __test__/buffer-diff.spec.mjs — identical/modified/null-side buffers, stats, toPatch on a workdir diff
  • __test__/tree-builder.spec.mjs — builder round-trip, findBlob, the issue-167 two-tree diff flow
  • Full suite: 188 tests pass, no regressions

Note

Medium Risk
Additive native surface with one unsafe libgit2 patch call; behavior is well-tested but touches ODB writes and diff output consumers may rely on exact patch bytes.

Overview
Adds in-memory and tree-based diffing (closes #167): compare arbitrary byte buffers like git diff --no-index, build synthetic trees in the ODB, and emit standard unified patches.

diffBuffers is a repo-free entry point that returns unified patch bytes, structured hunks/lines, and stats. It calls git_patch_from_buffers directly so null buffers mean absent files (/dev/null, add/delete) rather than empty content; results are fully copied out for JS.

Diff.toPatch() prints any existing Diff as raw patch bytes, prepending +/-/space sigils to match libgit2’s patch format.

On Repository: treebuilder(source?) + TreeBuilder (insert/write/etc.), diffTreeToTree, and findBlob(oid) (new BlobParent::Repository). Together they support blob → single-entry tree → tree diff without touching the workdir.

Also wires libgit2 init at native module load and adds AVA coverage for buffer diffs, tree builder round-trips, and workdir toPatch.

Reviewed by Cursor Bugbot for commit 497de0c. Bugbot is set up for automated code reviews on this repo. Configure here.

….toPatch

Closes the feature request in #167:

- diffBuffers(old, oldPath, new, newPath, options?) — repo-free diff of two
  raw buffers via git2::Patch::from_buffers, returning unified patch text,
  materialized hunks/lines and stats (git diff --no-index equivalent)
- Diff.toPatch() — render any Diff as unified-diff text
- repo.treebuilder() / TreeBuilder — insert/remove/get/write/clear/len/isEmpty
- repo.diffTreeToTree(oldTree?, newTree?, options?)
- repo.findBlob(oid) — plus BlobParent::Repository variant

Refs #167
@cursor

cursor Bot commented Sep 21, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_e3412d32-647d-422b-baaf-f955940d56ca)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-21T03:01:55.451084Z 497de0c New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fe672424c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

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

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

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread index.js Outdated
Comment thread src/buffer_diff.rs Outdated
Comment thread index.js Outdated
…er compat

- diffBuffers routes through libgit2-sys git_patch_from_buffers so a null
  side passes a real NULL pointer: absent files now produce added/deleted
  deltas with /dev/null and 'new file mode'/'deleted file mode' headers
  (creating an empty file is a change, not a no-op)
- diffBuffers result.patch and Diff.toPatch() return Buffer instead of
  lossy UTF-8 strings, preserving non-UTF-8 content exactly
- index.js keeps main's Node >=10-compatible loader (no node:fs/optional
  chaining, exact-value NAPI_RS_FORCE_WASI check) with only the new exports
- libgit2_sys::init() moved to #[module_init] at module load
- rustfmt clean; regression tests for file creation/deletion and
  non-UTF-8 content
@cursor

cursor Bot commented Sep 21, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_6166e028-a8e3-46b1-a99f-9942cd4cda38)

@Brooooooklyn
Brooooooklyn merged commit 070f312 into main Sep 21, 2026
29 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/buffer-diff-167 branch September 21, 2026 03:57
@samvv

samvv commented Sep 21, 2026

Copy link
Copy Markdown

Thank you!

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.

Feature request: TreeBuilder

2 participants