diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57d0616de8..c0576befb8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Change Log
+## Unreleased
+
+- Ship maintained TypeScript declarations with `@readme/nodegit`, including
+ `Repository.free()` and corrections for repository, remote callback, progress,
+ and libgit2 option APIs.
+
## v0.28.0-alpha.35 [(2025-06-03)](https://github.com/nodegit/nodegit/releases/tag/v0.28.0-alpha.35)
[Full Changelog](https://github.com/nodegit/nodegit/compare/v0.28.0-alpha.34...v0.28.0-alpha.35)
diff --git a/README.md b/README.md
index f5928c9ceb..0ac8d78d83 100644
--- a/README.md
+++ b/README.md
@@ -35,9 +35,12 @@ NodeGit will work on most systems out-of-the-box without any native
dependencies.
```bash
-npm install nodegit
+npm install @readme/nodegit
```
+TypeScript declarations ship with `@readme/nodegit`. Import from that package and
+remove `@types/nodegit` and any `tsconfig` path mapping to its declarations.
+
In Ubuntu:
```sh
@@ -85,7 +88,7 @@ instructions.
### Cloning a repository and reading a file:
```javascript
-var Git = require("nodegit");
+var Git = require("@readme/nodegit");
// Clone a given repository into the `./tmp` folder.
Git.Clone("https://github.com/nodegit/nodegit", "./tmp")
@@ -125,7 +128,7 @@ Git.Clone("https://github.com/nodegit/nodegit", "./tmp")
### Emulating git log:
```javascript
-var Git = require("nodegit");
+var Git = require("@readme/nodegit");
// Open the repository directory.
Git.Repository.open("tmp")
diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json
index 99837534db..9ac191569d 100644
--- a/generate/input/descriptor.json
+++ b/generate/input/descriptor.json
@@ -3746,7 +3746,7 @@
}
},
"git_repository_free": {
- "ignore": true
+ "ignore": false
},
"git_repository_hashfile": {
"ignore": true
diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json
index 742216f030..82a02056e4 100644
--- a/generate/input/libgit2-supplement.json
+++ b/generate/input/libgit2-supplement.json
@@ -1210,6 +1210,10 @@
"isErrorCode": true
}
},
+ "git_repository_free": {
+ "isManual": true,
+ "cFile": "generate/templates/manual/repository/free.cc"
+ },
"git_repository__cleanup": {
"type": "function",
"file": "sys/repository.h",
diff --git a/generate/templates/manual/commit/extract_signature.cc b/generate/templates/manual/commit/extract_signature.cc
index 82a2141116..1f8ee37a9d 100644
--- a/generate/templates/manual/commit/extract_signature.cc
+++ b/generate/templates/manual/commit/extract_signature.cc
@@ -3,6 +3,7 @@ NAN_METHOD(GitCommit::ExtractSignature)
if (info.Length() == 0 || !info[0]->IsObject()) {
return Nan::ThrowError("Repository repo is required.");
}
+ if (Nan::ObjectWrap::Unwrap(info[0].As())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");
if (info.Length() == 1 || (!info[1]->IsObject() && !info[1]->IsString())) {
return Nan::ThrowError("Oid commit_id is required.");
diff --git a/generate/templates/manual/filter_list/load.cc b/generate/templates/manual/filter_list/load.cc
index 22e2f1f4f3..309a95539f 100644
--- a/generate/templates/manual/filter_list/load.cc
+++ b/generate/templates/manual/filter_list/load.cc
@@ -22,6 +22,7 @@ NAN_METHOD(GitFilterList::Load) {
if (info.Length() == 0 || !info[0]->IsObject()) {
return Nan::ThrowError("Repository repo is required.");
}
+ if (Nan::ObjectWrap::Unwrap(info[0].As())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");
if (info.Length() == 2 || !info[2]->IsString()) {
return Nan::ThrowError("String path is required.");
diff --git a/generate/templates/manual/include/async_worker.h b/generate/templates/manual/include/async_worker.h
index b8f2909901..c8a0bb2bf9 100644
--- a/generate/templates/manual/include/async_worker.h
+++ b/generate/templates/manual/include/async_worker.h
@@ -7,6 +7,7 @@
#include
#include "lock_master.h"
#include "cleanup_handle.h"
+#include "tracker_wrap.h"
namespace nodegit {
class AsyncWorker : public Nan::AsyncWorker {
@@ -47,8 +48,10 @@ namespace nodegit {
}
auto objectWrapPointer = Nan::ObjectWrap::Unwrap(item.As());
+ auto release = RetainNativeOwner(objectWrapPointer);
objectWrapPointer->Reference();
- RegisterCleanupCall([objectWrapPointer]() {
+ RegisterCleanupCall([objectWrapPointer, release]() {
+ if (release) release();
objectWrapPointer->Unreference();
});
}
@@ -81,6 +84,8 @@ namespace nodegit {
std::map> cleanupHandles;
Nan::Global callbackErrorHandle;
+ static std::function RetainNativeOwner(nodegit::TrackerWrap *item) { return item->RetainNativeOwner(); }
+ static std::function RetainNativeOwner(Nan::ObjectWrap *) { return {}; }
private:
std::vector> cleanupCalls;
bool isCancelled = false;
diff --git a/generate/templates/manual/include/nodegit_wrapper.h b/generate/templates/manual/include/nodegit_wrapper.h
index c72f29027d..ed46696c38 100644
--- a/generate/templates/manual/include/nodegit_wrapper.h
+++ b/generate/templates/manual/include/nodegit_wrapper.h
@@ -45,6 +45,11 @@ class NodeGitWrapper : public nodegit::TrackerWrap {
protected:
cType *raw;
std::vector> childCleanupVector;
+ std::vector> nativeOwnerReleases;
+ // Explicit free clears raw, but queued results can inherit a still-pinned native owner.
+ // Clear this pointer with the last pin so a disposed wrapper cannot resurrect freed memory.
+ cType *retainedRaw = NULL;
+ size_t nativeRetainCount = 0;
// owner of the object, in the memory management sense. only populated
// when using ownedByThis, and the type doesn't have a dupFunction
@@ -80,11 +85,13 @@ class NodeGitWrapper : public nodegit::TrackerWrap {
void Reference();
void Unreference();
+ std::function RetainNativeOwner() override;
void AddReferenceCallbacks(size_t, std::function, std::function);
cType *GetValue();
void ClearValue();
+ void ReleaseValue();
private:
std::unordered_map> referenceCallbacks;
diff --git a/generate/templates/manual/include/tracker_wrap.h b/generate/templates/manual/include/tracker_wrap.h
index 3b15eba424..8e44f977f7 100644
--- a/generate/templates/manual/include/tracker_wrap.h
+++ b/generate/templates/manual/include/tracker_wrap.h
@@ -2,6 +2,7 @@
#define TRACKERWRAP_H
#include
+#include
#include
#include
@@ -15,6 +16,7 @@ namespace nodegit {
public:
TrackerWrap() = default;
virtual ~TrackerWrap() = default;
+ virtual std::function RetainNativeOwner() { return {}; }
TrackerWrap(const TrackerWrap &other) = delete;
TrackerWrap(TrackerWrap &&other) = delete;
TrackerWrap& operator=(const TrackerWrap &other) = delete;
diff --git a/generate/templates/manual/repository/free.cc b/generate/templates/manual/repository/free.cc
new file mode 100644
index 0000000000..d3f3e8fff2
--- /dev/null
+++ b/generate/templates/manual/repository/free.cc
@@ -0,0 +1,6 @@
+NAN_METHOD(GitRepository::Free) {
+ GitRepository *repository = Nan::ObjectWrap::Unwrap(info.Holder());
+ repository->ReleaseValue();
+
+ info.GetReturnValue().Set(Nan::Undefined());
+}
diff --git a/generate/templates/manual/repository/get_references.cc b/generate/templates/manual/repository/get_references.cc
index 56bc12ac34..85cdffede0 100644
--- a/generate/templates/manual/repository/get_references.cc
+++ b/generate/templates/manual/repository/get_references.cc
@@ -1,5 +1,6 @@
NAN_METHOD(GitRepository::GetReferences)
{
+ if (Nan::ObjectWrap::Unwrap(info.Holder())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");
if (!info[info.Length() - 1]->IsFunction()) {
return Nan::ThrowError("Callback is required and must be a Function.");
}
diff --git a/generate/templates/manual/repository/get_remotes.cc b/generate/templates/manual/repository/get_remotes.cc
index a7c316bb0e..68b02a8be5 100644
--- a/generate/templates/manual/repository/get_remotes.cc
+++ b/generate/templates/manual/repository/get_remotes.cc
@@ -1,5 +1,6 @@
NAN_METHOD(GitRepository::GetRemotes)
{
+ if (Nan::ObjectWrap::Unwrap(info.Holder())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");
if (!info[info.Length() - 1]->IsFunction()) {
return Nan::ThrowError("Callback is required and must be a Function.");
}
diff --git a/generate/templates/manual/repository/get_submodules.cc b/generate/templates/manual/repository/get_submodules.cc
index 069f6bdbc8..483fde30f5 100644
--- a/generate/templates/manual/repository/get_submodules.cc
+++ b/generate/templates/manual/repository/get_submodules.cc
@@ -1,5 +1,6 @@
NAN_METHOD(GitRepository::GetSubmodules)
{
+ if (Nan::ObjectWrap::Unwrap(info.Holder())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");
if (!info[info.Length() - 1]->IsFunction()) {
return Nan::ThrowError("Callback is required and must be a Function.");
}
diff --git a/generate/templates/manual/repository/refresh_references.cc b/generate/templates/manual/repository/refresh_references.cc
index 5ddaec57fa..1d7e6d68d9 100644
--- a/generate/templates/manual/repository/refresh_references.cc
+++ b/generate/templates/manual/repository/refresh_references.cc
@@ -389,6 +389,7 @@ class RefreshReferencesData {
NAN_METHOD(GitRepository::RefreshReferences)
{
+ if (Nan::ObjectWrap::Unwrap(info.Holder())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");
v8::Local signatureType;
if (info.Length() == 2) {
if (!info[0]->IsString()) {
diff --git a/generate/templates/manual/repository/statistics.cc b/generate/templates/manual/repository/statistics.cc
index f438bb5f8d..9cc2f3987c 100644
--- a/generate/templates/manual/repository/statistics.cc
+++ b/generate/templates/manual/repository/statistics.cc
@@ -1773,6 +1773,7 @@ v8::Local RepoAnalysis::biggestCheckoutsToJS() const
NAN_METHOD(GitRepository::Statistics)
{
+ if (Nan::ObjectWrap::Unwrap(info.Holder())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");
if (!info[info.Length() - 1]->IsFunction()) {
return Nan::ThrowError("Callback is required and must be a Function.");
}
diff --git a/generate/templates/manual/src/nodegit_wrapper.cc b/generate/templates/manual/src/nodegit_wrapper.cc
index a790d7bc3c..ea764422b1 100644
--- a/generate/templates/manual/src/nodegit_wrapper.cc
+++ b/generate/templates/manual/src/nodegit_wrapper.cc
@@ -22,6 +22,10 @@ NodeGitWrapper::NodeGitWrapper(typename Traits::cType *raw, bool selfFre
selfFreeing = true;
} else {
SetNativeOwners(owner);
+ for (nodegit::TrackerWrap *nativeOwner : *GetTrackerWrapOwners()) {
+ auto release = nativeOwner->RetainNativeOwner();
+ if (release) nativeOwnerReleases.push_back(std::move(release));
+ }
this->owner.Reset(owner);
this->raw = raw;
}
@@ -48,14 +52,13 @@ NodeGitWrapper::NodeGitWrapper(const char *error)
template
NodeGitWrapper::~NodeGitWrapper() {
Unlink();
- if (Traits::isFreeable && selfFreeing) {
- Traits::free(raw);
- SelfFreeingInstanceCount--;
- raw = NULL;
+ if (selfFreeing) {
+ ReleaseValue();
}
- else if (!selfFreeing) {
+ else {
--NonSelfFreeingConstructedCount;
}
+ for (auto &release : nativeOwnerReleases) release();
}
template
@@ -136,6 +139,15 @@ void NodeGitWrapper::ClearValue() {
raw = NULL;
}
+template
+void NodeGitWrapper::ReleaseValue() {
+ if (Traits::isFreeable && selfFreeing && raw != NULL) {
+ Traits::free(raw);
+ SelfFreeingInstanceCount--;
+ raw = NULL;
+ }
+}
+
template
thread_local int NodeGitWrapper::SelfFreeingInstanceCount;
@@ -174,6 +186,20 @@ void NodeGitWrapper::Unreference() {
}
}
+template
+std::function NodeGitWrapper::RetainNativeOwner() {
+ if (!Traits::isSingleton) return {};
+ cType *value = raw != NULL ? raw : retainedRaw;
+ if (value == NULL) return {};
+ ReferenceCounter::incrementCountForPointer((void *)value);
+ retainedRaw = value;
+ ++nativeRetainCount;
+ return [this, value]() {
+ if (--nativeRetainCount == 0) retainedRaw = NULL;
+ Traits::free(value);
+ };
+}
+
template
void NodeGitWrapper::AddReferenceCallbacks(size_t fieldIndex, std::function refCb, std::function unrefCb) {
referenceCallbacks[fieldIndex] = refCb;
diff --git a/generate/templates/manual/src/wrapper.cc b/generate/templates/manual/src/wrapper.cc
index 3bad23c7d4..fd1c6dac13 100644
--- a/generate/templates/manual/src/wrapper.cc
+++ b/generate/templates/manual/src/wrapper.cc
@@ -4,10 +4,8 @@
#include
#include
#include
-#include
#include "../include/wrapper.h"
-#include "node_buffer.h"
using namespace v8;
using namespace node;
@@ -67,14 +65,12 @@ NAN_METHOD(Wrapper::ToBuffer) {
}
int len = Nan::To(info[0]).FromJust();
+ if (len < 0) {
+ return Nan::ThrowRangeError("Length must not be negative.");
+ }
- Local bufferConstructor = Local::Cast(
- Nan::Get(Nan::GetCurrentContext()->Global(), Nan::New("Buffer").ToLocalChecked()).ToLocalChecked());
-
- Local constructorArgs[1] = { Nan::New(len) };
- Local