From 37424d2741e4ab629a12ac7113b6e3cf28aed2d3 Mon Sep 17 00:00:00 2001 From: John Haley Date: Mon, 7 Mar 2016 13:15:10 -0700 Subject: [PATCH 1/3] Updated CHANGELOG.md to add callback speedup for 0.11.7 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a6dee8a..f4b105c6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ [Full Changelog](https://github.com/nodegit/nodegit/compare/v0.11.6...v0.11.7) - Added `Repository#mergeheadForeach` [PR #937](https://github.com/nodegit/nodegit/pull/937) +- Improved speed of all callbacks dramatically [PR #932](https://github.com/nodegit/nodegit/pull/932) - Fixed `Merge.merge` docs to show it takes in an `AnnotatedCommit` and not a `Commit` [PR #935](https://github.com/nodegit/nodegit/pull/935) - Fixed unicode in `Diff.blobToBuffer` getting corrupted [PR #935](https://github.com/nodegit/nodegit/pull/935) - Fixed fetching/pulling to bitbucket in versions > v5.6 of node [PR #942](https://github.com/nodegit/nodegit/pull/942) From a2b870a1ba83222819b55b30912a80c4803c63e1 Mon Sep 17 00:00:00 2001 From: John Haley Date: Thu, 3 Mar 2016 14:10:52 -0700 Subject: [PATCH 2/3] Revert "Merge pull request #932 from srajko/callback-throttle" A non-trivial number of segfaults are seeing this in production. Pulling this back for right now until a later release where we can fix them. --- generate/input/callbacks.json | 12 +- .../templates/manual/include/async_baton.h | 32 ----- .../manual/include/callback_wrapper.h | 55 +-------- .../templates/manual/include/lock_master.h | 2 - .../templates/partials/callback_helpers.cc | 28 +++-- .../templates/partials/field_accessors.cc | 76 +++++------- generate/templates/templates/class_content.cc | 1 + generate/templates/templates/class_header.h | 6 +- .../templates/templates/struct_content.cc | 5 + generate/templates/templates/struct_header.h | 11 +- test/tests/clone.js | 109 ------------------ 11 files changed, 72 insertions(+), 265 deletions(-) diff --git a/generate/input/callbacks.json b/generate/input/callbacks.json index eb6b128a3..d18919768 100644 --- a/generate/input/callbacks.json +++ b/generate/input/callbacks.json @@ -96,8 +96,7 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1, - "throttle": 100 + "error": -1 } }, "git_checkout_perfdata_cb": { @@ -208,8 +207,7 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1, - "throttle": 100 + "error": -1 } }, "git_diff_hunk_cb": { @@ -562,8 +560,7 @@ "type": "int", "noResults":0, "success": 0, - "error": -1, - "throttle": 100 + "error": -1 } }, "git_stash_cb": { @@ -673,8 +670,7 @@ "type": "int", "noResults": 0, "success": 0, - "error": -1, - "throttle": 100 + "error": -1 } }, "git_transport_cb": { diff --git a/generate/templates/manual/include/async_baton.h b/generate/templates/manual/include/async_baton.h index fee87c4c1..a1ce5c380 100644 --- a/generate/templates/manual/include/async_baton.h +++ b/generate/templates/manual/include/async_baton.h @@ -4,9 +4,6 @@ #include #include -#include "lock_master.h" -#include "functions/sleep_for_ms.h" - // Base class for Batons used for callbacks (for example, // JS functions passed as callback parameters, // or field properties of configuration objects whose values are callbacks) @@ -16,33 +13,4 @@ struct AsyncBaton { bool done; }; -template -struct AsyncBatonWithResult : public AsyncBaton { - ResultT result; - ResultT defaultResult; // result returned if the callback doesn't return anything valid - - AsyncBatonWithResult(const ResultT &defaultResult) - : defaultResult(defaultResult) { - } - - ResultT ExecuteAsync(uv_async_cb asyncCallback) { - result = 0; - req.data = this; - done = false; - - uv_async_init(uv_default_loop(), &req, asyncCallback); - { - LockMaster::TemporaryUnlock temporaryUnlock; - - uv_async_send(&req); - - while(!done) { - sleep_for_ms(1); - } - } - - return result; - } -}; - #endif diff --git a/generate/templates/manual/include/callback_wrapper.h b/generate/templates/manual/include/callback_wrapper.h index b23a7bb36..41552de3e 100644 --- a/generate/templates/manual/include/callback_wrapper.h +++ b/generate/templates/manual/include/callback_wrapper.h @@ -1,60 +1,17 @@ #ifndef CALLBACK_WRAPPER_H #define CALLBACK_WRAPPER_H -#include -#include +#include +#include + +#include "nan.h" using namespace v8; using namespace node; -class CallbackWrapper { +struct CallbackWrapper { Nan::Callback* jsCallback; - - // throttling data, used for callbacks that need to be throttled - int throttle; // in milliseconds - if > 0, calls to the JS callback will be throttled - uint64_t lastCallTime; - -public: - CallbackWrapper() { - jsCallback = NULL; - lastCallTime = 0; - throttle = 0; - } - - ~CallbackWrapper() { - SetCallback(NULL); - } - - bool HasCallback() { - return jsCallback != NULL; - } - - Nan::Callback* GetCallback() { - return jsCallback; - } - - void SetCallback(Nan::Callback* callback, int throttle = 0) { - if(jsCallback) { - delete jsCallback; - } - jsCallback = callback; - this->throttle = throttle; - } - - bool WillBeThrottled() { - if(!throttle) { - return false; - } - // throttle if needed - uint64_t now = uv_hrtime(); - if(lastCallTime > 0 && now < lastCallTime + throttle * 1000000) { - // throttled - return true; - } else { - lastCallTime = now; - return false; - } - } + void * payload; }; #endif diff --git a/generate/templates/manual/include/lock_master.h b/generate/templates/manual/include/lock_master.h index 7fd4ee8dc..fd1a09b6f 100644 --- a/generate/templates/manual/include/lock_master.h +++ b/generate/templates/manual/include/lock_master.h @@ -1,8 +1,6 @@ #ifndef LOCK_MASTER_H #define LOCK_MASTER_H -#include - class LockMasterImpl; class LockMaster { diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc index b776c0097..0acd215ae 100644 --- a/generate/templates/partials/callback_helpers.cc +++ b/generate/templates/partials/callback_helpers.cc @@ -6,14 +6,28 @@ {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} {% endeach %} ) { - {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = - new {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton({{ cbFunction.return.noResults }}); + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = new {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton(); {% each cbFunction.args|argsInfo as arg %} baton->{{ arg.name }} = {{ arg.name }}; {% endeach %} - return baton->ExecuteAsync((uv_async_cb) {{ cppFunctionName }}_{{ cbFunction.name }}_async); + baton->result = 0; + baton->req.data = baton; + baton->done = false; + + uv_async_init(uv_default_loop(), &baton->req, (uv_async_cb) {{ cppFunctionName }}_{{ cbFunction.name }}_async); + { + LockMaster::TemporaryUnlock temporaryUnlock; + + uv_async_send(&baton->req); + + while(!baton->done) { + sleep_for_ms(1); + } + } + + return baton->result; } void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_async_t* req, int status) { @@ -79,12 +93,12 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_as baton->result = (int)result->ToNumber()->Value(); } else { - baton->result = baton->defaultResult; + baton->result = {{ cbFunction.return.noResults }}; } {% endif %} } else { - baton->result = baton->defaultResult; + baton->result = {{ cbFunction.return.noResults }}; } {% endeach %} @@ -113,12 +127,12 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_promiseComp baton->result = (int)result->ToNumber()->Value(); } else { - baton->result = baton->defaultResult; + baton->result = {{ cbFunction.return.noResults }}; } {% endif %} } else { - baton->result = baton->defaultResult; + baton->result = {{ cbFunction.return.noResults }}; } {% endeach %} } diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 6a3123095..17b338dc3 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -11,8 +11,8 @@ info.GetReturnValue().Set(Nan::New(wrapper->{{ field.name }})); {% elsif field.isCallbackFunction %} - if (wrapper->{{field.name}}.HasCallback()) { - info.GetReturnValue().Set(wrapper->{{ field.name }}.GetCallback()->GetFunction()); + if (wrapper->{{field.name}} != NULL) { + info.GetReturnValue().Set(wrapper->{{ field.name }}->GetFunction()); } else { info.GetReturnValue().SetUndefined(); } @@ -31,7 +31,6 @@ } NAN_SETTER({{ cppClassName }}::Set{{ field.cppFunctionName }}) { - Nan::HandleScope scope; {{ cppClassName }} *wrapper = Nan::ObjectWrap::Unwrap<{{ cppClassName }}>(info.This()); @@ -48,35 +47,16 @@ wrapper->raw->{{ field.name }} = {% if not field.cType | isPointer %}*{% endif %}{% if field.cppClassName == 'GitStrarray' %}StrArrayConverter::Convert({{ field.name }}->ToObject()){% else %}Nan::ObjectWrap::Unwrap<{{ field.cppClassName }}>({{ field.name }}->ToObject())->GetValue(){% endif %}; {% elsif field.isCallbackFunction %} - Nan::Callback *callback = NULL; - int throttle = {%if field.return.throttle %}{{ field.return.throttle }}{%else%}0{%endif%}; + if (wrapper->{{ field.name }} != NULL) { + delete wrapper->{{ field.name }}; + } if (value->IsFunction()) { - callback = new Nan::Callback(value.As()); - } else if (value->IsObject()) { - Local object = value.As(); - Local callbackKey; - Nan::MaybeLocal maybeObjectCallback = Nan::Get(object, Nan::New("callback").ToLocalChecked()); - if (!maybeObjectCallback.IsEmpty()) { - Local objectCallback = maybeObjectCallback.ToLocalChecked(); - if (objectCallback->IsFunction()) { - callback = new Nan::Callback(objectCallback.As()); - Nan::MaybeLocal maybeObjectThrottle = Nan::Get(object, Nan::New("throttle").ToLocalChecked()); - if(!maybeObjectThrottle.IsEmpty()) { - Local objectThrottle = maybeObjectThrottle.ToLocalChecked(); - if (objectThrottle->IsNumber()) { - throttle = (int)objectThrottle.As()->Value(); - } - } - } - } - } - if (callback) { if (!wrapper->raw->{{ field.name }}) { wrapper->raw->{{ field.name }} = ({{ field.cType }}){{ field.name }}_cppCallback; } - wrapper->{{ field.name }}.SetCallback(callback, throttle); + wrapper->{{ field.name }} = new Nan::Callback(value.As()); } {% elsif field.payloadFor %} @@ -102,42 +82,46 @@ } {% if field.isCallbackFunction %} - {{ cppClassName }}* {{ cppClassName }}::{{ field.name }}_getInstanceFromBaton({{ field.name|titleCase }}Baton* baton) { - return static_cast<{{ cppClassName }}*>(baton->{% each field.args|argsInfo as arg %} - {% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %} - {% endeach %}); - } - {{ field.return.type }} {{ cppClassName }}::{{ field.name }}_cppCallback ( {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} {% endeach %} ) { - {{ field.name|titleCase }}Baton* baton = - new {{ field.name|titleCase }}Baton({{ field.return.noResults }}); + {{ field.name|titleCase }}Baton* baton = new {{ field.name|titleCase }}Baton(); {% each field.args|argsInfo as arg %} baton->{{ arg.name }} = {{ arg.name }}; {% endeach %} - {{ cppClassName }}* instance = {{ field.name }}_getInstanceFromBaton(baton); + baton->result = 0; + baton->req.data = baton; + baton->done = false; + + uv_async_init(uv_default_loop(), &baton->req, (uv_async_cb) {{ field.name }}_async); + { + LockMaster::TemporaryUnlock temporaryUnlock; + + uv_async_send(&baton->req); - if (instance->{{ field.name }}.WillBeThrottled()) { - return baton->defaultResult; + while(!baton->done) { + sleep_for_ms(1); + } } - return baton->ExecuteAsync((uv_async_cb) {{ field.name }}_async); + return baton->result; } void {{ cppClassName }}::{{ field.name }}_async(uv_async_t* req, int status) { Nan::HandleScope scope; {{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(req->data); - {{ cppClassName }}* instance = {{ field.name }}_getInstanceFromBaton(baton); + {{ cppClassName }}* instance = static_cast<{{ cppClassName }}*>(baton->{% each field.args|argsInfo as arg %} + {% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %} + {% endeach %}); - if (instance->{{ field.name }}.GetCallback()->IsEmpty()) { + if (instance->{{ field.name }}->IsEmpty()) { {% if field.return.type == "int" %} - baton->result = baton->defaultResult; // no results acquired + baton->result = {{ field.return.noResults }}; // no results acquired {% endif %} baton->done = true; @@ -179,7 +163,7 @@ }; Nan::TryCatch tryCatch; - Local result = instance->{{ field.name }}.GetCallback()->Call({{ field.args|jsArgsCount }}, argv); + Local result = instance->{{ field.name }}->Call({{ field.args|jsArgsCount }}, argv); uv_close((uv_handle_t*) &baton->req, NULL); @@ -203,12 +187,12 @@ baton->result = (int)result->ToNumber()->Value(); } else { - baton->result = baton->defaultResult; + baton->result = {{ field.return.noResults }}; } {% endif %} } else { - baton->result = baton->defaultResult; + baton->result = {{ field.return.noResults }}; } {% endeach %} baton->done = true; @@ -236,12 +220,12 @@ baton->result = (int)result->ToNumber()->Value(); } else{ - baton->result = baton->defaultResult; + baton->result = {{ field.return.noResults }}; } {% endif %} } else { - baton->result = baton->defaultResult; + baton->result = {{ field.return.noResults }}; } {% endeach %} } diff --git a/generate/templates/templates/class_content.cc b/generate/templates/templates/class_content.cc index 8fead5aa8..c172e1fa5 100644 --- a/generate/templates/templates/class_content.cc +++ b/generate/templates/templates/class_content.cc @@ -11,6 +11,7 @@ extern "C" { #include "../include/lock_master.h" #include "../include/functions/copy.h" #include "../include/{{ filename }}.h" +#include "../include/functions/sleep_for_ms.h" {% each dependencies as dependency %} #include "{{ dependency }}" diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index b18defd0e..84c9b5c4f 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -64,14 +64,12 @@ class {{ cppClassName }} : public Nan::ObjectWrap { static void {{ function.cppFunctionName }}_{{ arg.name }}_async(uv_async_t* req, int status); static void {{ function.cppFunctionName }}_{{ arg.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result); - struct {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton : public AsyncBatonWithResult<{{ arg.return.type }}> { + struct {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton : AsyncBaton { {% each arg.args|argsInfo as cbArg %} {{ cbArg.cType }} {{ cbArg.name }}; {% endeach %} - {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton(const {{ arg.return.type }} &defaultResult) - : AsyncBatonWithResult<{{ arg.return.type }}>(defaultResult) { - } + {{ arg.return.type }} result; }; {% endif %} {% endeach %} diff --git a/generate/templates/templates/struct_content.cc b/generate/templates/templates/struct_content.cc index b54af5510..cab524a80 100644 --- a/generate/templates/templates/struct_content.cc +++ b/generate/templates/templates/struct_content.cc @@ -17,6 +17,7 @@ extern "C" { #include "../include/lock_master.h" #include "../include/functions/copy.h" #include "../include/{{ filename }}.h" +#include "../include/functions/sleep_for_ms.h" {% each dependencies as dependency %} #include "{{ dependency }}" @@ -52,7 +53,10 @@ using namespace std; {% if not field.ignore %} {% if not field.isEnum %} {% if field.isCallbackFunction %} + if (this->{{ field.name }} != NULL) { + delete this->{{ field.name }}; this->raw->{{ fields|payloadFor field.name }} = NULL; + } {% endif %} {% endif %} {% endif %} @@ -80,6 +84,7 @@ void {{ cppClassName }}::ConstructFields() { // the current instance this->raw->{{ field.name }} = NULL; this->raw->{{ fields|payloadFor field.name }} = (void *)this; + this->{{ field.name }} = NULL; {% elsif field.payloadFor %} Local {{ field.name }} = Nan::Undefined(); diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index 87720aa4a..0320fbbf6 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -6,7 +6,6 @@ #include #include "async_baton.h" -#include "callback_wrapper.h" extern "C" { #include @@ -49,17 +48,13 @@ class {{ cppClassName }} : public Nan::ObjectWrap { static void {{ field.name }}_async(uv_async_t* req, int status); static void {{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result); - struct {{ field.name|titleCase }}Baton : public AsyncBatonWithResult<{{ field.return.type }}> { + struct {{ field.name|titleCase }}Baton : public AsyncBaton { {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name}}; {% endeach %} - {{ field.name|titleCase }}Baton(const {{ field.return.type }} &defaultResult) - : AsyncBatonWithResult<{{ field.return.type }}>(defaultResult) { - } + {{ field.return.type }} result; }; - static {{ cppClassName }} * {{ field.name }}_getInstanceFromBaton ( - {{ field.name|titleCase }}Baton *baton); {% endif %} {% endif %} {% endeach %} @@ -78,7 +73,7 @@ class {{ cppClassName }} : public Nan::ObjectWrap { {% if field.isLibgitType %} Nan::Persistent {{ field.name }}; {% elsif field.isCallbackFunction %} - CallbackWrapper {{ field.name }}; + Nan::Callback* {{ field.name }}; {% elsif field.payloadFor %} Nan::Persistent {{ field.name }}; {% endif %} diff --git a/test/tests/clone.js b/test/tests/clone.js index 3e2a86446..46830a889 100644 --- a/test/tests/clone.js +++ b/test/tests/clone.js @@ -3,7 +3,6 @@ var assert = require("assert"); var promisify = require("promisify-node"); var fse = promisify(require("fs-extra")); var local = path.join.bind(path, __dirname); -var _ = require("lodash"); describe("Clone", function() { var NodeGit = require("../../"); @@ -38,114 +37,6 @@ describe("Clone", function() { }); }); - it("can clone twice with http using same config object", function() { - var test = this; - var url = "http://git.tbranyen.com/smart/site-content"; - var progressCount = 0; - var opts = { - fetchOpts: { - callbacks: { - transferProgress: function(progress) { - progressCount++; - } - } - } - }; - - return Clone(url, clonePath, opts) - .then(function(repo) { - assert.ok(repo instanceof Repository); - assert.notEqual(progressCount, 0); - return fse.remove(clonePath); - }) - .then(function() { - progressCount = 0; - return Clone(url, clonePath, opts); - }) - .then(function(repo) { - assert.ok(repo instanceof Repository); - assert.notEqual(progressCount, 0); - test.repository = repo; - }); - }); - - function updateProgressIntervals(progressIntervals, lastInvocation) { - var now = new Date(); - if (lastInvocation) { - progressIntervals.push(now - lastInvocation); - } - return now; - } - - it("can clone with http and default throttled progress", function() { - var test = this; - var url = "http://git.tbranyen.com/smart/site-content"; - var progressCount = 0; - var lastInvocation; - var progressIntervals = []; - var opts = { - fetchOpts: { - callbacks: { - transferProgress: function(progress) { - lastInvocation = updateProgressIntervals(progressIntervals); - progressCount++; - } - } - } - }; - - return Clone(url, clonePath, opts).then(function(repo) { - assert.ok(repo instanceof Repository); - assert.notEqual(progressCount, 0); - var averageProgressInterval = _.sum(progressIntervals) / - progressIntervals.length; - // even though we are specifying a throttle period of 100, - // the throttle is applied on the scheduling side, - // and actual execution is at the mercy of the main js thread - // so the actual throttle intervals could be less than the specified - // throttle period - if (averageProgressInterval < 75) { - assert.fail(averageProgressInterval, 75, - "unexpected average time between callbacks", "<"); - } - test.repository = repo; - }); - }); - - it("can clone with http and explicitly throttled progress", function() { - var test = this; - var url = "http://git.tbranyen.com/smart/site-content"; - var progressCount = 0; - var lastInvocation; - var progressIntervals = []; - var opts = { - fetchOpts: { - callbacks: { - transferProgress: { - throttle: 50, - callback: function(progress) { - lastInvocation = updateProgressIntervals(progressIntervals, - lastInvocation); - progressCount++; - } - } - } - } - }; - - return Clone(url, clonePath, opts).then(function(repo) { - assert.ok(repo instanceof Repository); - assert.notEqual(progressCount, 0); - var averageProgressInterval = _.sum(progressIntervals) / - progressIntervals.length; - if (averageProgressInterval < 35) { - assert.fail(averageProgressInterval, 35, - "unexpected average time between callbacks", "<"); - } - test.repository = repo; - }); - }); - it("can clone with https", function() { var test = this; var url = "https://github.com/nodegit/test.git"; From a421e24c6701731b7b20b2bcd805895065079cd4 Mon Sep 17 00:00:00 2001 From: John Haley Date: Mon, 7 Mar 2016 16:13:43 -0700 Subject: [PATCH 3/3] Bump to 0.11.8 --- CHANGELOG.md | 6 ++++++ README.md | 2 +- package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b105c6b..1097803f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [0.11.8](https://github.com/nodegit/nodegit/releases/tag/v0.11.8) (2016-03-07) + +[Full Changelog](https://github.com/nodegit/nodegit/compare/v0.11.7...v0.11.8) + +- Removed callback throttling due to segmentation faults. Will be implemented later. [PR #943](https://github.com/nodegit/nodegit/pull/943) + ## [0.11.7](https://github.com/nodegit/nodegit/releases/tag/v0.11.7) (2016-03-07) [Full Changelog](https://github.com/nodegit/nodegit/compare/v0.11.6...v0.11.7) diff --git a/README.md b/README.md index 7d84e166e..96d37f420 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ NodeGit -**Stable: 0.11.7** +**Stable: 0.11.8** ## Have a problem? Come chat with us! ## diff --git a/package.json b/package.json index f00d77862..18868d6ab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nodegit", "description": "Node.js libgit2 asynchronous native bindings", - "version": "0.11.7", + "version": "0.11.8", "homepage": "http://nodegit.org", "keywords": [ "libgit2",