From 612ee77ae94958b7b2626079da3d6a7348fee035 Mon Sep 17 00:00:00 2001 From: Jimmy Koppel Date: Tue, 10 Mar 2026 20:30:02 +0500 Subject: [PATCH 1/3] Adding support for mempack backend Co-Authored-By: Claude Opus 4.6 --- generate/input/descriptor.json | 46 ++++++- generate/input/libgit2-supplement.json | 74 +++++++++++ .../templates/manual/include/odb_backend.h | 5 + generate/templates/manual/mempack/create.cc | 92 ++++++++++++++ generate/templates/manual/mempack/reset.cc | 57 +++++++++ generate/templates/manual/odb/add_backend.cc | 113 +++++++++++++++++ lib/mempack.js | 15 +++ lib/odb.js | 6 + test/tests/mempack.js | 118 ++++++++++++++++++ 9 files changed, 524 insertions(+), 2 deletions(-) create mode 100644 generate/templates/manual/include/odb_backend.h create mode 100644 generate/templates/manual/mempack/create.cc create mode 100644 generate/templates/manual/mempack/reset.cc create mode 100644 generate/templates/manual/odb/add_backend.cc create mode 100644 lib/mempack.js create mode 100644 lib/odb.js create mode 100644 test/tests/mempack.js diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index 99837534d..58700858c 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -1914,7 +1914,10 @@ } }, "git_index_new": { - "ignore": true + "isAsync": true, + "return": { + "isErrorCode": true + } }, "git_index_open": { "isAsync": true, @@ -2264,7 +2267,36 @@ } }, "mempack": { - "ignore": true + "cType": "git_odb_backend", + "cppClassName": "GitMempack", + "jsClassName": "Mempack", + "selfFreeing": false, + "cDependencies": [ + "git2/sys/mempack.h" + ], + "functions": { + "git_mempack_new": { + "ignore": true + }, + "git_mempack_dump": { + "ignore": true + }, + "git_mempack_reset": { + "args": { + "backend": { + "ignore": false, + "isSelf": true + } + } + }, + "git_mempack_create": { + "args": { + "out": { + "ignore": false + } + } + } + } }, "merge": { "functions": { @@ -2529,6 +2561,9 @@ }, "odb": { "selfFreeing": true, + "dependencies": [ + "../include/mempack.h" + ], "functions": { "git_odb_add_alternate": { "ignore": true @@ -2536,6 +2571,13 @@ "git_odb_add_backend": { "ignore": true }, + "git_odb_add_mempack_backend": { + "args": { + "backend": { + "ignore": false + } + } + }, "git_odb_add_disk_alternate": { "isAsync": true, "return": { diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json index 12ee8945b..dfaa75bf9 100644 --- a/generate/input/libgit2-supplement.json +++ b/generate/input/libgit2-supplement.json @@ -1508,6 +1508,70 @@ "type": "int", "isErrorCode": true } + }, + "git_mempack_create": { + "_wraps": "git_mempack_new (renamed to 'create' for JS convention)", + "args": [ + { + "name": "out", + "type": "git_odb_backend **" + } + ], + "type": "function", + "isManual": true, + "cFile": "generate/templates/manual/mempack/create.cc", + "isAsync": true, + "isPrototypeMethod": false, + "group": "mempack", + "return": { + "type": "int", + "isErrorCode": true + } + }, + "git_mempack_reset": { + "args": [ + { + "name": "backend", + "type": "git_odb_backend *" + } + ], + "type": "function", + "isManual": true, + "cFile": "generate/templates/manual/mempack/reset.cc", + "isAsync": true, + "isPrototypeMethod": true, + "group": "mempack", + "return": { + "type": "int", + "isErrorCode": true + } + }, + "git_odb_add_mempack_backend": { + "_wraps": "git_odb_add_backend (specialized for Mempack backends only)", + "args": [ + { + "name": "odb", + "type": "git_odb *" + }, + { + "name": "backend", + "type": "git_odb_backend *" + }, + { + "name": "priority", + "type": "int" + } + ], + "type": "function", + "isManual": true, + "cFile": "generate/templates/manual/odb/add_backend.cc", + "isAsync": true, + "isPrototypeMethod": true, + "group": "odb", + "return": { + "type": "int", + "isErrorCode": true + } } }, "groups": [ @@ -1735,6 +1799,13 @@ "git_tree_entry_to_object", "git_tree_entry_type" ] + ], + [ + "mempack", + [ + "git_mempack_create", + "git_mempack_reset" + ] ] ], "types": [ @@ -2682,6 +2753,9 @@ "git_note_next", "git_note_read", "git_note_remove" + ], + "odb": [ + "git_odb_add_mempack_backend" ] } } \ No newline at end of file diff --git a/generate/templates/manual/include/odb_backend.h b/generate/templates/manual/include/odb_backend.h new file mode 100644 index 000000000..d992a0c05 --- /dev/null +++ b/generate/templates/manual/include/odb_backend.h @@ -0,0 +1,5 @@ +// Stub header for git_odb_backend - the type is already defined by git2.h +// This file exists to satisfy auto-generated #include dependencies. +#ifndef GITODB_BACKEND_H +#define GITODB_BACKEND_H +#endif diff --git a/generate/templates/manual/mempack/create.cc b/generate/templates/manual/mempack/create.cc new file mode 100644 index 000000000..c7edbab52 --- /dev/null +++ b/generate/templates/manual/mempack/create.cc @@ -0,0 +1,92 @@ +// Manual binding for git_mempack_new, exposed as Mempack.create() + +NAN_METHOD(GitMempack::Create) { + if (!info[info.Length() - 1]->IsFunction()) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + + CreateBaton *baton = new CreateBaton(); + baton->error_code = GIT_OK; + baton->error = NULL; + baton->out = NULL; + + Nan::Callback *callback = + new Nan::Callback(v8::Local::Cast(info[info.Length() - 1])); + std::map> cleanupHandles; + CreateWorker *worker = new CreateWorker(baton, callback, cleanupHandles); + + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); + return; +} + +nodegit::LockMaster GitMempack::CreateWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true); + return lockMaster; +} + +void GitMempack::CreateWorker::Execute() { + git_error_clear(); + + baton->error_code = git_mempack_new(&baton->out); + + if (baton->error_code != GIT_OK && git_error_last() != NULL && git_error_last()->klass != GIT_ERROR_NONE) { + baton->error = git_error_dup(git_error_last()); + } +} + +void GitMempack::CreateWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} + +void GitMempack::CreateWorker::HandleOKCallback() { + if (baton->error_code == GIT_OK) { + if (baton->out == NULL) { + // This should never happen if error_code == GIT_OK + v8::Local argv[1] = {Nan::Error("Mempack creation returned OK but produced no backend.")}; + callback->Call(1, argv, async_resource); + delete baton; + return; + } + + v8::Local to = GitMempack::New(baton->out, false); + v8::Local argv[2] = {Nan::Null(), to}; + callback->Call(2, argv, async_resource); + } else if (baton->error) { + v8::Local err; + if (baton->error->message) { + err = Nan::To(Nan::Error(baton->error->message)).ToLocalChecked(); + } else { + err = Nan::To(Nan::Error("Method create has thrown an error.")).ToLocalChecked(); + } + Nan::Set(err, Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), + Nan::New("Mempack.create").ToLocalChecked()); + v8::Local argv[1] = {err}; + callback->Call(1, argv, async_resource); + if (baton->error->message) + free((void *)baton->error->message); + free((void *)baton->error); + } else if (baton->error_code < 0) { + v8::Local err = + Nan::To(Nan::Error("Method create has thrown an error.")).ToLocalChecked(); + Nan::Set(err, Nan::New("errno").ToLocalChecked(), + Nan::New(baton->error_code)); + Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), + Nan::New("Mempack.create").ToLocalChecked()); + v8::Local argv[1] = {err}; + callback->Call(1, argv, async_resource); + } else { + callback->Call(0, NULL, async_resource); + } + + delete baton; +} diff --git a/generate/templates/manual/mempack/reset.cc b/generate/templates/manual/mempack/reset.cc new file mode 100644 index 000000000..14490ad2a --- /dev/null +++ b/generate/templates/manual/mempack/reset.cc @@ -0,0 +1,57 @@ +// Manual binding for git_mempack_reset, exposed as Mempack.prototype.reset() + +NAN_METHOD(GitMempack::Reset) { + if (!info[info.Length() - 1]->IsFunction()) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + + ResetBaton *baton = new ResetBaton(); + baton->error_code = GIT_OK; + baton->error = NULL; + baton->backend = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); + + Nan::Callback *callback = + new Nan::Callback(v8::Local::Cast(info[info.Length() - 1])); + std::map> cleanupHandles; + ResetWorker *worker = new ResetWorker(baton, callback, cleanupHandles); + + worker->Reference("backend", info.This()); + + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); + return; +} + +nodegit::LockMaster GitMempack::ResetWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->backend); + return lockMaster; +} + +void GitMempack::ResetWorker::Execute() { + git_error_clear(); + + baton->error_code = git_mempack_reset(baton->backend); + + if (baton->error_code != GIT_OK && git_error_last() != NULL && git_error_last()->klass != GIT_ERROR_NONE) { + baton->error = git_error_dup(git_error_last()); + } +} + +void GitMempack::ResetWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} + +void GitMempack::ResetWorker::HandleOKCallback() { + v8::Local argv[1] = {Nan::Null()}; + callback->Call(1, argv, async_resource); + + delete baton; +} diff --git a/generate/templates/manual/odb/add_backend.cc b/generate/templates/manual/odb/add_backend.cc new file mode 100644 index 000000000..486e279c6 --- /dev/null +++ b/generate/templates/manual/odb/add_backend.cc @@ -0,0 +1,113 @@ +// Manual binding for git_odb_add_backend, exposed as Odb.prototype.addMempackBackend() +// The first argument (backend) must be a Mempack object wrapping a git_odb_backend*. + +NAN_METHOD(GitOdb::AddMempackBackend) { + if (info.Length() < 3) { + return Nan::ThrowError("Backend, priority, and callback arguments are required."); + } + + if (!info[info.Length() - 1]->IsFunction()) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + + AddMempackBackendBaton *baton = new AddMempackBackendBaton(); + baton->error_code = GIT_OK; + baton->error = NULL; + baton->odb = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); + + // Validate and extract git_odb_backend* from the Mempack wrapper + if (!info[0]->IsObject() || info[0]->IsNull() || info[0]->IsUndefined()) { + delete baton; + return Nan::ThrowTypeError("First argument must be a Mempack object."); + } + baton->backend = Nan::ObjectWrap::Unwrap(Nan::To(info[0]).ToLocalChecked())->GetValue(); + + // Priority is required and must be a number + if (!info[1]->IsNumber()) { + delete baton; + return Nan::ThrowTypeError("Second argument (priority) must be a number."); + } + baton->priority = (int)Nan::To(info[1]).FromJust(); + + Nan::Callback *callback = + new Nan::Callback(v8::Local::Cast(info[info.Length() - 1])); + std::map> cleanupHandles; + AddMempackBackendWorker *worker = new AddMempackBackendWorker(baton, callback, cleanupHandles); + + worker->Reference("odb", info.This()); + worker->Reference("backend", info[0]); + + // Store a reference from the backend to the ODB to prevent the ODB from + // being garbage-collected while the backend is alive. This is critical + // because after git_odb_add_backend, libgit2 owns the backend pointer and + // will free it when the ODB is freed. If the ODB is GC'd first, the + // backend's pointer becomes dangling. + Nan::Set(Nan::To(info[0]).ToLocalChecked(), + Nan::New("_parentOdb").ToLocalChecked(), info.This()); + + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); + return; +} + +nodegit::LockMaster GitOdb::AddMempackBackendWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->odb, baton->backend); + return lockMaster; +} + +void GitOdb::AddMempackBackendWorker::Execute() { + git_error_clear(); + + baton->error_code = git_odb_add_backend(baton->odb, baton->backend, baton->priority); + + if (baton->error_code != GIT_OK && git_error_last() != NULL && git_error_last()->klass != GIT_ERROR_NONE) { + baton->error = git_error_dup(git_error_last()); + } +} + +void GitOdb::AddMempackBackendWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} + +void GitOdb::AddMempackBackendWorker::HandleOKCallback() { + if (baton->error_code == GIT_OK) { + v8::Local argv[2] = {Nan::Null(), Nan::New(baton->error_code)}; + callback->Call(2, argv, async_resource); + } else if (baton->error) { + v8::Local err; + if (baton->error->message) { + err = Nan::To(Nan::Error(baton->error->message)).ToLocalChecked(); + } else { + err = Nan::To(Nan::Error("Method addMempackBackend has thrown an error.")).ToLocalChecked(); + } + Nan::Set(err, Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), + Nan::New("Odb.addMempackBackend").ToLocalChecked()); + v8::Local argv[1] = {err}; + callback->Call(1, argv, async_resource); + if (baton->error->message) + free((void *)baton->error->message); + free((void *)baton->error); + } else if (baton->error_code < 0) { + v8::Local err = + Nan::To(Nan::Error("Method addMempackBackend has thrown an error.")).ToLocalChecked(); + Nan::Set(err, Nan::New("errno").ToLocalChecked(), + Nan::New(baton->error_code)); + Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), + Nan::New("Odb.addMempackBackend").ToLocalChecked()); + v8::Local argv[1] = {err}; + callback->Call(1, argv, async_resource); + } else { + callback->Call(0, NULL, async_resource); + } + + delete baton; +} diff --git a/lib/mempack.js b/lib/mempack.js new file mode 100644 index 000000000..5e945d867 --- /dev/null +++ b/lib/mempack.js @@ -0,0 +1,15 @@ +var NodeGit = require("../"); + +var Mempack = NodeGit.Mempack; + +// Mempack.prototype.reset() is directly generated from git_mempack_reset. +// No additional JS-level customizations needed. +// +// Ownership lifecycle: +// 1. Create via Mempack.create() - allocates a git_odb_backend* +// 2. Attach to an ODB via odb.addMempackBackend(backend, priority) +// After this, libgit2 owns the backend pointer. +// 3. Use the ODB normally (write blobs, trees, etc.) +// 4. Call backend.reset() to clear in-memory objects (ODB must still be alive) +// Do NOT call reset() after the ODB/repo has been freed. +// Do NOT add the same backend to multiple ODBs. diff --git a/lib/odb.js b/lib/odb.js new file mode 100644 index 000000000..bc3f8ba48 --- /dev/null +++ b/lib/odb.js @@ -0,0 +1,6 @@ +var NodeGit = require("../"); + +var Odb = NodeGit.Odb; + +// No additional JS-level customizations needed for Odb. +// Use odb.addMempackBackend(backend, priority) to add a mempack backend. diff --git a/test/tests/mempack.js b/test/tests/mempack.js new file mode 100644 index 000000000..1268553f8 --- /dev/null +++ b/test/tests/mempack.js @@ -0,0 +1,118 @@ +var assert = require("assert"); +var RepoUtils = require("../utils/repository_setup"); +var path = require("path"); +var local = path.join.bind(path, __dirname); + +describe("Mempack", function() { + var NodeGit = require("../../"); + + var repoPath = local("../repos/mempackRepo"); + + beforeEach(function() { + var test = this; + + return RepoUtils.createRepository(repoPath) + .then(function(repository) { + test.repository = repository; + }); + }); + + it("can create a mempack backend", function() { + return NodeGit.Mempack.create() + .then(function(backend) { + assert.ok(backend, "Mempack backend should be created"); + }); + }); + + it("can add mempack backend to ODB and write objects in memory", function() { + var test = this; + var mempackBackend; + + return NodeGit.Mempack.create() + .then(function(backend) { + mempackBackend = backend; + return test.repository.odb(); + }) + .then(function(odb) { + test.odb = odb; + return odb.addMempackBackend(mempackBackend, 999); + }) + .then(function() { + var content = "Hello from mempack test!\n"; + return test.odb.write(content, content.length, NodeGit.Object.TYPE.BLOB); + }) + .then(function(blobOid) { + assert.ok(blobOid, "Blob OID should be returned"); + var oidStr = blobOid.tostrS(); + assert.ok( + /^[0-9a-f]{40}$/.test(oidStr), + "Blob OID should be a valid SHA-1 hash, got: " + oidStr + ); + }); + }); + + it("can write a tree via in-memory index without disk writes", function() { + var test = this; + var mempackBackend; + var odb; + + return NodeGit.Mempack.create() + .then(function(backend) { + mempackBackend = backend; + return test.repository.odb(); + }) + .then(function(_odb) { + odb = _odb; + return odb.addMempackBackend(mempackBackend, 999); + }) + .then(function() { + return NodeGit.Index.create(); + }) + .then(function(index) { + test.index = index; + var content = "Hello from mempack test!\n"; + return odb.write(content, content.length, NodeGit.Object.TYPE.BLOB); + }) + .then(function(blobOid) { + var entry = new NodeGit.IndexEntry(); + entry.path = "test-file.txt"; + entry.id = blobOid; + entry.mode = 0o100644; + entry.fileSize = 24; + entry.flags = 0; + entry.flagsExtended = 0; + + return test.index.add(entry); + }) + .then(function() { + return test.index.writeTreeTo(test.repository); + }) + .then(function(treeOid) { + var oidStr = treeOid.tostrS(); + assert.ok( + /^[0-9a-f]{40}$/.test(oidStr), + "Tree OID should be a valid SHA-1 hash, got: " + oidStr + ); + }); + }); + + it("can reset the mempack backend", function() { + var test = this; + var mempackBackend; + + return NodeGit.Mempack.create() + .then(function(backend) { + mempackBackend = backend; + return test.repository.odb(); + }) + .then(function(odb) { + return odb.addMempackBackend(mempackBackend, 999); + }) + .then(function() { + return mempackBackend.reset(); + }) + .then(function() { + assert.ok(true, "Mempack reset completed successfully"); + }); + }); +}); From 85a55c1f06c157ea9bb9081942f35ef2cf78093a Mon Sep 17 00:00:00 2001 From: Jimmy Koppel Date: Tue, 10 Mar 2026 20:30:20 +0500 Subject: [PATCH 2/3] Add Odb.hash and Repository.hashfile bindings for zero-ODB-write hashing Co-Authored-By: Claude Opus 4.6 --- generate/input/descriptor.json | 30 ++++++- test/tests/hash.js | 149 +++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 test/tests/hash.js diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index 58700858c..ad10d9448 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -2620,7 +2620,19 @@ "ignore": true }, "git_odb_hash": { - "ignore": true + "isAsync": true, + "args": { + "oid": { + "isReturn": true + }, + "data": { + "cppClassName": "Wrapper", + "jsClassName": "Buffer" + } + }, + "return": { + "isErrorCode": true + } }, "git_odb_hashfile": { "isAsync": true, @@ -3791,7 +3803,21 @@ "ignore": true }, "git_repository_hashfile": { - "ignore": true + "isAsync": true, + "args": { + "out": { + "isReturn": true + }, + "repo": { + "isSelf": true + }, + "as_path": { + "isOptional": true + } + }, + "return": { + "isErrorCode": true + } }, "git_repository_ident": { "args": { diff --git a/test/tests/hash.js b/test/tests/hash.js new file mode 100644 index 000000000..35fd67a2a --- /dev/null +++ b/test/tests/hash.js @@ -0,0 +1,149 @@ +var assert = require("assert"); +var path = require("path"); +var fs = require("fs"); +var exec = require("child_process").execSync; +var local = path.join.bind(path, __dirname); + +describe("Odb.hash", function() { + var NodeGit = require("../../"); + var Repository = NodeGit.Repository; + var Odb = NodeGit.Odb; + var Oid = NodeGit.Oid; + var Obj = NodeGit.Object; + + var reposPath = local("../repos/workdir"); + + beforeEach(function() { + var test = this; + + return Repository.open(reposPath).then(function(repo) { + test.repo = repo; + + return repo.odb(); + }).then(function(odb) { + test.odb = odb; + }); + }); + + it("can hash a buffer without writing to ODB", function() { + var content = "test content for hashing"; + var odb = this.odb; + + // Get expected OID from git hash-object + var expected = exec( + "printf '" + content + "' | git hash-object --stdin -t blob", + { encoding: "utf8" } + ).trim(); + + return Odb.hash(content, content.length, Obj.TYPE.BLOB) + .then(function(oid) { + assert.ok(oid instanceof Oid); + assert.equal(oid.tostrS(), expected); + }); + }); + + it("does not mutate the ODB when hashing a buffer", function() { + var content = "unique content that should not be written " + Date.now(); + + return Odb.hash(content, content.length, Obj.TYPE.BLOB) + .then(function(oid) { + assert.ok(oid instanceof Oid); + + // Attempting to read the OID from the ODB should fail + // because it was never written + return this.odb.read(oid) + .then(function() { + assert.fail("Should not be able to read a hash-only OID from ODB"); + }) + .catch(function(err) { + // Expected: object not found + assert.ok(err); + }); + }.bind(this)); + }); +}); + +describe("Repository.hashfile", function() { + var NodeGit = require("../../"); + var Repository = NodeGit.Repository; + var Oid = NodeGit.Oid; + var Obj = NodeGit.Object; + + var reposPath = local("../repos/workdir"); + + beforeEach(function() { + var test = this; + + return Repository.open(reposPath).then(function(repo) { + test.repo = repo; + + return repo.odb(); + }).then(function(odb) { + test.odb = odb; + }); + }); + + it("can hash a file with repository filters", function() { + var repo = this.repo; + + // Use an existing file in the test repo + var filePath = path.join(reposPath, "README.md"); + + // Get expected OID from git hash-object (which applies filters) + var expected = exec( + "git -C " + reposPath + " hash-object " + filePath, + { encoding: "utf8" } + ).trim(); + + return repo.hashfile(filePath, Obj.TYPE.BLOB) + .then(function(oid) { + assert.ok(oid instanceof Oid); + assert.equal(oid.tostrS(), expected); + }); + }); + + it("does not mutate the ODB when hashing a file", function() { + var repo = this.repo; + var odb = this.odb; + + // Create a temporary file with unique content + var tmpPath = path.join(reposPath, "tmp_hash_test_" + Date.now() + ".txt"); + var content = "unique file content for hash test " + Date.now(); + fs.writeFileSync(tmpPath, content); + + return repo.hashfile(tmpPath, Obj.TYPE.BLOB) + .then(function(oid) { + assert.ok(oid instanceof Oid); + + // Clean up temp file + fs.unlinkSync(tmpPath); + + // Attempting to read the OID from the ODB should fail + return odb.read(oid) + .then(function() { + assert.fail("Should not be able to read a hash-only OID from ODB"); + }) + .catch(function(err) { + assert.ok(err); + }); + }) + .catch(function(err) { + // Clean up temp file on error + try { fs.unlinkSync(tmpPath); } catch(e) {} + throw err; + }); + }); + + it("can hash a file with as_path filter hint", function() { + var repo = this.repo; + + var filePath = path.join(reposPath, "README.md"); + + return repo.hashfile(filePath, Obj.TYPE.BLOB, "README.md") + .then(function(oid) { + assert.ok(oid instanceof Oid); + // Just verify it returns a valid OID + assert.equal(oid.tostrS().length, 40); + }); + }); +}); From 2b0143f7f11ca97401bbdd9241b1f849f29f2c6a Mon Sep 17 00:00:00 2001 From: Jimmy Koppel Date: Tue, 10 Mar 2026 20:30:48 +0500 Subject: [PATCH 3/3] Add @types/nodegit dev dependency Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++++++++----- package.json | 2 ++ 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93f2f440d..81a61daad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "license": "MIT", "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", + "@types/nodegit": "^0.26.7", "fs-extra": "^7.0.0", "got": "^14.4.7", "json5": "^2.1.0", @@ -79,7 +80,6 @@ "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -625,6 +625,24 @@ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", + "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/nodegit": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/nodegit/-/nodegit-0.26.7.tgz", + "integrity": "sha512-qVwQupq4To/wkRtUnaiwbNhuRiVJShOG9CYIWiGdKQRkvWijtHmEnrC1KJHOVrzK04sMdHRClyecrfmN17VMFQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -894,7 +912,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -1755,6 +1772,16 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -4515,7 +4542,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5416,6 +5442,12 @@ "node": ">=0.10.0" } }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, "node_modules/unique-filename": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", @@ -5984,7 +6016,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", "dev": true, - "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -6362,6 +6393,22 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, + "@types/node": { + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", + "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", + "requires": { + "undici-types": "~7.18.0" + } + }, + "@types/nodegit": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/nodegit/-/nodegit-0.26.7.tgz", + "integrity": "sha512-qVwQupq4To/wkRtUnaiwbNhuRiVJShOG9CYIWiGdKQRkvWijtHmEnrC1KJHOVrzK04sMdHRClyecrfmN17VMFQ==", + "requires": { + "@types/node": "*" + } + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -6534,7 +6581,6 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, - "peer": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -7188,6 +7234,15 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -7707,7 +7762,8 @@ } }, "iconv-lite": { - "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "optional": true, "requires": { @@ -9183,8 +9239,7 @@ "picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "peer": true + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==" }, "pkg-dir": { "version": "4.2.0", @@ -9833,6 +9888,11 @@ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", "dev": true }, + "undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==" + }, "unique-filename": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", diff --git a/package.json b/package.json index 41ccc32dc..b4f2825d4 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ } ], "main": "lib/nodegit.js", + "types": "lib/nodegit.d.ts", "repository": { "type": "git", "url": "git://github.com/nodegit/nodegit.git" @@ -39,6 +40,7 @@ }, "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", + "@types/nodegit": "0.26.7", "fs-extra": "^7.0.0", "got": "^14.4.7", "json5": "^2.1.0",