Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.

## <a name="v0-28-0-alpha.35" href="#v0-28-0-alpha-35">v0.28.0-alpha.35</a> [(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)
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion generate/input/descriptor.json
Original file line number Diff line number Diff line change
Expand Up @@ -3746,7 +3746,7 @@
}
},
"git_repository_free": {
"ignore": true
"ignore": false
},
"git_repository_hashfile": {
"ignore": true
Expand Down
4 changes: 4 additions & 0 deletions generate/input/libgit2-supplement.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions generate/templates/manual/commit/extract_signature.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<GitRepository>(info[0].As<v8::Object>())->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.");
Expand Down
1 change: 1 addition & 0 deletions generate/templates/manual/filter_list/load.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<GitRepository>(info[0].As<v8::Object>())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");

if (info.Length() == 2 || !info[2]->IsString()) {
return Nan::ThrowError("String path is required.");
Expand Down
7 changes: 6 additions & 1 deletion generate/templates/manual/include/async_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <vector>
#include "lock_master.h"
#include "cleanup_handle.h"
#include "tracker_wrap.h"

namespace nodegit {
class AsyncWorker : public Nan::AsyncWorker {
Expand Down Expand Up @@ -47,8 +48,10 @@ namespace nodegit {
}

auto objectWrapPointer = Nan::ObjectWrap::Unwrap<NodeGitWrapperT>(item.As<v8::Object>());
auto release = RetainNativeOwner(objectWrapPointer);
objectWrapPointer->Reference();
RegisterCleanupCall([objectWrapPointer]() {
RegisterCleanupCall([objectWrapPointer, release]() {
if (release) release();
objectWrapPointer->Unreference();
});
}
Expand Down Expand Up @@ -81,6 +84,8 @@ namespace nodegit {
std::map<std::string, std::shared_ptr<nodegit::CleanupHandle>> cleanupHandles;
Nan::Global<v8::Value> callbackErrorHandle;

static std::function<void()> RetainNativeOwner(nodegit::TrackerWrap *item) { return item->RetainNativeOwner(); }
static std::function<void()> RetainNativeOwner(Nan::ObjectWrap *) { return {}; }
private:
std::vector<std::function<void()>> cleanupCalls;
bool isCancelled = false;
Expand Down
7 changes: 7 additions & 0 deletions generate/templates/manual/include/nodegit_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class NodeGitWrapper : public nodegit::TrackerWrap {
protected:
cType *raw;
std::vector<std::shared_ptr<nodegit::CleanupHandle>> childCleanupVector;
std::vector<std::function<void()>> 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
Expand Down Expand Up @@ -80,11 +85,13 @@ class NodeGitWrapper : public nodegit::TrackerWrap {

void Reference();
void Unreference();
std::function<void()> RetainNativeOwner() override;

void AddReferenceCallbacks(size_t, std::function<void()>, std::function<void()>);

cType *GetValue();
void ClearValue();
void ReleaseValue();

private:
std::unordered_map<size_t, std::function<void()>> referenceCallbacks;
Expand Down
2 changes: 2 additions & 0 deletions generate/templates/manual/include/tracker_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define TRACKERWRAP_H

#include <nan.h>
#include <functional>
#include <memory>
#include <vector>

Expand All @@ -15,6 +16,7 @@ namespace nodegit {
public:
TrackerWrap() = default;
virtual ~TrackerWrap() = default;
virtual std::function<void()> RetainNativeOwner() { return {}; }
TrackerWrap(const TrackerWrap &other) = delete;
TrackerWrap(TrackerWrap &&other) = delete;
TrackerWrap& operator=(const TrackerWrap &other) = delete;
Expand Down
6 changes: 6 additions & 0 deletions generate/templates/manual/repository/free.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
NAN_METHOD(GitRepository::Free) {
GitRepository *repository = Nan::ObjectWrap::Unwrap<GitRepository>(info.Holder());
repository->ReleaseValue();

info.GetReturnValue().Set(Nan::Undefined());
}
1 change: 1 addition & 0 deletions generate/templates/manual/repository/get_references.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
NAN_METHOD(GitRepository::GetReferences)
{
if (Nan::ObjectWrap::Unwrap<GitRepository>(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.");
}
Expand Down
1 change: 1 addition & 0 deletions generate/templates/manual/repository/get_remotes.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
NAN_METHOD(GitRepository::GetRemotes)
{
if (Nan::ObjectWrap::Unwrap<GitRepository>(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.");
}
Expand Down
1 change: 1 addition & 0 deletions generate/templates/manual/repository/get_submodules.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
NAN_METHOD(GitRepository::GetSubmodules)
{
if (Nan::ObjectWrap::Unwrap<GitRepository>(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.");
}
Expand Down
1 change: 1 addition & 0 deletions generate/templates/manual/repository/refresh_references.cc
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ class RefreshReferencesData {

NAN_METHOD(GitRepository::RefreshReferences)
{
if (Nan::ObjectWrap::Unwrap<GitRepository>(info.Holder())->GetValue() == NULL) return Nan::ThrowError("Repository has been freed.");
v8::Local<v8::String> signatureType;
if (info.Length() == 2) {
if (!info[0]->IsString()) {
Expand Down
1 change: 1 addition & 0 deletions generate/templates/manual/repository/statistics.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1773,6 +1773,7 @@ v8::Local<v8::Object> RepoAnalysis::biggestCheckoutsToJS() const

NAN_METHOD(GitRepository::Statistics)
{
if (Nan::ObjectWrap::Unwrap<GitRepository>(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.");
}
Expand Down
36 changes: 31 additions & 5 deletions generate/templates/manual/src/nodegit_wrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ NodeGitWrapper<Traits>::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;
}
Expand All @@ -48,14 +52,13 @@ NodeGitWrapper<Traits>::NodeGitWrapper(const char *error)
template<typename Traits>
NodeGitWrapper<Traits>::~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<typename Traits>
Expand Down Expand Up @@ -136,6 +139,15 @@ void NodeGitWrapper<Traits>::ClearValue() {
raw = NULL;
}

template<typename Traits>
void NodeGitWrapper<Traits>::ReleaseValue() {
if (Traits::isFreeable && selfFreeing && raw != NULL) {
Traits::free(raw);
SelfFreeingInstanceCount--;
raw = NULL;
}
}

template<typename Traits>
thread_local int NodeGitWrapper<Traits>::SelfFreeingInstanceCount;

Expand Down Expand Up @@ -174,6 +186,20 @@ void NodeGitWrapper<Traits>::Unreference() {
}
}

template<typename Traits>
std::function<void()> NodeGitWrapper<Traits>::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<typename Traits>
void NodeGitWrapper<Traits>::AddReferenceCallbacks(size_t fieldIndex, std::function<void()> refCb, std::function<void()> unrefCb) {
referenceCallbacks[fieldIndex] = refCb;
Expand Down
14 changes: 5 additions & 9 deletions generate/templates/manual/src/wrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
#include <nan.h>
#include <node.h>
#include <string>
#include <cstring>

#include "../include/wrapper.h"
#include "node_buffer.h"

using namespace v8;
using namespace node;
Expand Down Expand Up @@ -67,14 +65,12 @@ NAN_METHOD(Wrapper::ToBuffer) {
}

int len = Nan::To<int>(info[0]).FromJust();
if (len < 0) {
return Nan::ThrowRangeError("Length must not be negative.");
}

Local<Function> bufferConstructor = Local<Function>::Cast(
Nan::Get(Nan::GetCurrentContext()->Global(), Nan::New("Buffer").ToLocalChecked()).ToLocalChecked());

Local<v8::Value> constructorArgs[1] = { Nan::New(len) };
Local<Object> nodeBuffer = Nan::NewInstance(bufferConstructor, 1, constructorArgs).ToLocalChecked();

std::memcpy(node::Buffer::Data(nodeBuffer), Nan::ObjectWrap::Unwrap<Wrapper>(info.This())->GetValue(), len);
const char *data = static_cast<const char *>(Nan::ObjectWrap::Unwrap<Wrapper>(info.Holder())->GetValue());
Local<Object> nodeBuffer = Nan::CopyBuffer(data, len).ToLocalChecked();

info.GetReturnValue().Set(nodeBuffer);
}
15 changes: 15 additions & 0 deletions generate/templates/partials/guard_arguments.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@

{%each args|argsInfo as arg%}
{%if arg.isSelf |and arg.cppClassName == "GitRepository"%}
if (Nan::ObjectWrap::Unwrap<GitRepository>(info.Holder())->GetValue() == NULL) {
return Nan::ThrowError("Repository has been freed.");
}
{%endif%}
{%if arg.isJsArg |and arg.cppClassName == "GitRepository"%}
if (info.Length() > {{arg.jsArg}} && info[{{arg.jsArg}}]->IsObject()) {
GitRepository *repository = Nan::ObjectWrap::Unwrap<GitRepository>(info[{{arg.jsArg}}].As<v8::Object>());
if (repository == NULL || repository->GetValue() == NULL) {
return Nan::ThrowError("Repository has been freed.");
}
}
{%endif%}
{%endeach%}
{%each args|argsInfo as arg%}
{%if arg.isJsArg%}
{%if not arg.isOptional%}
Expand Down
5 changes: 4 additions & 1 deletion lib/repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,10 @@ Repository.prototype.getHeadCommit = function() {
.then(function(head) {
return repo.getCommit(head);
})
.catch(function() {
.catch(function(error) {
if (error.errno !== NodeGit.Error.CODE.ENOTFOUND) {
throw error;
}
return null;
});
};
Expand Down
Loading