diff --git a/.travis.yml b/.travis.yml index d10faa0fc..55a1c2ad9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ env: matrix: - export NODE_VERSION="0.12" - export NODE_VERSION="4.1" - - export NODE_VERSION="5.0" + - export NODE_VERSION="5.8" matrix: fast_finish: true diff --git a/CHANGELOG.md b/CHANGELOG.md index c3ba6b6d7..494cd73a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Change Log +## [0.12.0](https://github.com/nodegit/nodegit/releases/tag/v0.12.0) (2016-03-28) + +[Full Changelog](https://github.com/nodegit/nodegit/compare/v0.11.9...v0.12.0) + +# API changes +- `Ignore` + - Made `Ignore.pathIsIgnored` async [PR #970](https://github.com/nodegit/nodegit/pull/970) + +# Bug fixes + +- Added an error message when trying to install NodeGit without a required version of libstdc++ [PR #972](https://github.com/nodegit/nodegit/pull/972) +- Fix a crash when grabbing content out of a buffer that has unicode [PR #966](https://github.com/nodegit/nodegit/pull/966) +- Added some plumbing for better memory management [PR #958](https://github.com/nodegit/nodegit/pull/958) +- Fix `checkoutOptions` in `Stash#apply` [PR #956](https://github.com/nodegit/nodegit/pull/956) +- Fixed install when there is a space in the username on windows [PR #951](https://github.com/nodegit/nodegit/pull/951) +- Bump to nan@2.2.0 [PR #952](https://github.com/nodegit/nodegit/pull/952) + ## [0.11.9](https://github.com/nodegit/nodegit/releases/tag/v0.11.9) (2016-03-09) [Full Changelog](https://github.com/nodegit/nodegit/compare/v0.11.8...v0.11.9) diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 000000000..6d136b820 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,13 @@ +NodeGit FAQ +----------- + +Feel free to add common problems with their solutions here, or just anything that wasn't clear at first. + +#### Error: callback returned unsupported credentials type #### + +As seen in nodegit/#959 -- some golang hackers have started to use the following stanza in .gitconfig to allow `go get` to work with private repos: +``` +[url "git@github.com:"] + insteadOf = https://github.com/ +``` +But if you do this, code can call `NodeGit.Clone.clone(url: 'https://foo')` and have the `authentication` callback be asked for **SSH** credentials instead of HTTPS ones, which might not be what your application expected. diff --git a/README.md b/README.md index d743134d5..8fec1521d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ NodeGit -**Stable: 0.11.9** +**Stable: 0.12.0** ## Have a problem? Come chat with us! ## @@ -116,8 +116,8 @@ Git.Clone("https://github.com/nodegit/nodegit", "./tmp") }) // Display information about the blob. .then(function(blob) { - // Show the name, sha, and filesize in bytes. - console.log(blob.entry.name() + blob.entry.sha() + blob.size() + "b"); + // Show the path, sha, and filesize in bytes. + console.log(blob.entry.path() + blob.entry.sha() + blob.rawsize() + "b"); // Show a spacer. console.log(Array(72).join("=") + "\n\n"); diff --git a/appveyor.yml b/appveyor.yml index 41d4f90ff..337d1250f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,7 @@ environment: - nodejs_version: "0.12" # Node.js - nodejs_version: "4.1" - - nodejs_version: "5.0" + - nodejs_version: "5.8" matrix: fast_finish: true diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index f51d219ba..7ab980efa 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -121,6 +121,7 @@ } }, "blob": { + "selfFreeing": true, "functions": { "git_blob_create_frombuffer": { "isAsync": false, @@ -140,6 +141,11 @@ "git_blob_filtered_content": { "ignore": true }, + "git_blob_id": { + "return": { + "shouldDuplicate": true + } + }, "git_blob_rawcontent": { "return": { "cppClassName": "Wrapper", @@ -354,6 +360,7 @@ } }, "commit": { + "selfFreeing": true, "functions": { "git_commit_amend": { "args": { @@ -380,6 +387,16 @@ } } }, + "git_commit_author": { + "return": { + "shouldDuplicate": true + } + }, + "git_commit_committer": { + "return": { + "shouldDuplicate": true + } + }, "git_commit_create": { "args": { "id": { @@ -404,6 +421,21 @@ }, "git_commit_create_from_ids": { "ignore": true + }, + "git_commit_id": { + "return": { + "shouldDuplicate": true + } + }, + "git_commit_parent_id": { + "return": { + "shouldDuplicate": true + } + }, + "git_commit_tree_id": { + "return": { + "shouldDuplicate": true + } } } }, @@ -917,9 +949,11 @@ "git_ignore_path_is_ignored": { "args": { "ignored": { + "shouldAlloc": true, "isReturn": true } - } + }, + "isAsync": true } } }, @@ -1273,6 +1307,8 @@ "ignore": true }, "oid": { + "dupFunction": "git_oid_cpy", + "freeFunctionName": "free", "shouldAlloc": true, "functions": { "git_oid_cpy": { @@ -1877,6 +1913,7 @@ } }, "signature": { + "dupFunction": "git_signature_dup", "functions": { "git_signature_default": { "isAsync": false @@ -2113,6 +2150,7 @@ } }, "tag": { + "selfFreeing": true, "functions": { "git_tag_foreach": { "ignore": true @@ -2131,6 +2169,11 @@ "git_tag_create_frombuffer": { "ignore": true }, + "git_tag_id": { + "return": { + "shouldDuplicate": true + } + }, "git_tag_create_lightweight": { "args": { "oid": { @@ -2166,6 +2209,11 @@ }, "isAsync": true }, + "git_tag_tagger": { + "return": { + "shouldDuplicate": true + } + }, "git_tag_target": { "args": { "target_out": { @@ -2173,6 +2221,11 @@ } } }, + "git_tag_target_id": { + "return": { + "shouldDuplicate": true + } + }, "git_tag_delete": { "return": { "isErrorCode": true @@ -2213,16 +2266,34 @@ ] }, "tree": { + "selfFreeing": true, "functions": { - "git_tree_entry_free": { - "ignore": true + "git_tree_entry_byid": { + "return": { + "shouldDuplicate": true + } }, "git_tree_entry_byindex": { "jsFunctionName": "_entryByIndex" }, + "git_tree_entry_byname": { + "return": { + "shouldDuplicate": true + } + }, + "git_tree_entry_id": { + "return": { + "shouldDuplicate": true + } + }, "git_tree_entrycount": { "jsFunctionName": "entryCount" }, + "git_tree_id": { + "return": { + "shouldDuplicate": true + } + }, "git_tree_walk": { "ignore": true } @@ -2249,6 +2320,10 @@ } } }, + "tree_entry": { + "dupFunction": "git_tree_entry_dup", + "freeFunctionName": "git_tree_entry_free" + }, "writestream": { "cType": "git_writestream", "needsForwardDeclaration": false diff --git a/generate/scripts/generateNativeCode.js b/generate/scripts/generateNativeCode.js index 93867b49c..4c43e0b04 100644 --- a/generate/scripts/generateNativeCode.js +++ b/generate/scripts/generateNativeCode.js @@ -70,6 +70,7 @@ module.exports = function generateNativeCode() { returnsCount: require("../templates/filters/returns_count"), returnsInfo: require("../templates/filters/returns_info"), titleCase: require("../templates/filters/title_case"), + toBool: require('../templates/filters/to_bool'), unPointer: require("../templates/filters/un_pointer"), upper: require("../templates/filters/upper") }; diff --git a/generate/scripts/helpers.js b/generate/scripts/helpers.js index 5383bff58..6e1f33613 100644 --- a/generate/scripts/helpers.js +++ b/generate/scripts/helpers.js @@ -143,6 +143,7 @@ var Helpers = { } }, + // returns the libgittype found in types decorateLibgitType: function(type, types, enums) { var normalizedType = Helpers.normalizeCtype(type.cType); var libgitType = Helpers.getLibgitType(normalizedType, types); @@ -164,6 +165,8 @@ var Helpers = { // we don't want to overwrite the c type of the passed in type _.merge(type, descriptor.types[normalizedType.replace("git_", "")] || {}, { cType: type.cType }); } + + return libgitType; }, decoratePrimaryType: function(typeDef, enums) { @@ -176,6 +179,11 @@ var Helpers = { typeDef.filename = typeDef.typeName; typeDef.isLibgitType = true; typeDef.dependencies = []; + typeDef.selfFreeing = Boolean(typeDefOverrides.selfFreeing); + + if (typeDefOverrides.freeFunctionName) { + typeDef.freeFunctionName = typeDefOverrides.freeFunctionName; + } typeDef.fields = typeDef.fields || []; typeDef.fields.forEach(function (field, index, allFields) { @@ -241,7 +249,7 @@ var Helpers = { arg.cppClassName = Helpers.cTypeToCppName(arg.cType); arg.jsClassName = utils.titleCase(Helpers.cTypeToJsName(arg.cType)); - Helpers.decorateLibgitType(arg, libgit2.types, enums); + var libgitType = Helpers.decorateLibgitType(arg, libgit2.types, enums); // Some arguments can be callbacks if (Helpers.isCallbackFunction(type)) { @@ -271,6 +279,10 @@ var Helpers = { _.every(allArgs, function(_arg) { return !_arg.isSelf; }); } + if (arg.isReturn && libgitType) { + arg.selfFreeing = libgitType.selfFreeing; + } + if (arg.isReturn && fnDef.return && fnDef.return.type === "int") { fnDef.return.isErrorCode = true; fnDef.isAsync = true; diff --git a/generate/templates/filters/to_bool.js b/generate/templates/filters/to_bool.js new file mode 100644 index 000000000..dd6063f28 --- /dev/null +++ b/generate/templates/filters/to_bool.js @@ -0,0 +1,3 @@ +module.exports = function(value) { + return !!value; +}; diff --git a/generate/templates/manual/include/wrapper.h b/generate/templates/manual/include/wrapper.h index cd7c26ff0..c040ea64d 100644 --- a/generate/templates/manual/include/wrapper.h +++ b/generate/templates/manual/include/wrapper.h @@ -20,7 +20,7 @@ class Wrapper : public Nan::ObjectWrap { static void InitializeComponent (Local target); void *GetValue(); - static Local New(void *raw); + static Local New(const void *raw); private: Wrapper(void *raw); diff --git a/generate/templates/manual/src/convenient_patch.cc b/generate/templates/manual/src/convenient_patch.cc index 8638cf0d7..dbe75ba74 100644 --- a/generate/templates/manual/src/convenient_patch.cc +++ b/generate/templates/manual/src/convenient_patch.cc @@ -303,7 +303,7 @@ NAN_METHOD(ConvenientPatch::OldFile) { git_diff_file *old_file = (git_diff_file *)malloc(sizeof(git_diff_file)); *old_file = Nan::ObjectWrap::Unwrap(info.This())->GetOldFile(); - to = GitDiffFile::New((void *)old_file, true); + to = GitDiffFile::New(old_file, true); return info.GetReturnValue().Set(to); } @@ -315,7 +315,7 @@ NAN_METHOD(ConvenientPatch::NewFile) { git_diff_file *new_file = (git_diff_file *)malloc(sizeof(git_diff_file)); *new_file = Nan::ObjectWrap::Unwrap(info.This())->GetNewFile(); if (new_file != NULL) { - to = GitDiffFile::New((void *)new_file, true); + to = GitDiffFile::New(new_file, true); } else { to = Nan::Null(); } diff --git a/generate/templates/manual/src/wrapper.cc b/generate/templates/manual/src/wrapper.cc index 0bcc9a745..3f2ea6121 100644 --- a/generate/templates/manual/src/wrapper.cc +++ b/generate/templates/manual/src/wrapper.cc @@ -42,7 +42,7 @@ NAN_METHOD(Wrapper::JSNewFunction) { info.GetReturnValue().Set(info.This()); } -Local Wrapper::New(void *raw) { +Local Wrapper::New(const void *raw) { Nan::EscapableHandleScope scope; Local argv[1] = { Nan::New((void *)raw) }; diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc index 0acd215ae..b103d24e0 100644 --- a/generate/templates/partials/callback_helpers.cc +++ b/generate/templates/partials/callback_helpers.cc @@ -55,7 +55,7 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_as {% if arg.isEnum %} Nan::New((int)baton->{{ arg.name }}), {% elsif arg.isLibgitType %} - {{ arg.cppClassName }}::New((void *)baton->{{ arg.name }}, false), + {{ arg.cppClassName }}::New(baton->{{ arg.name }}, false), {% elsif arg.cType == "size_t" %} // HACK: NAN should really have an overload for Nan::New to support size_t Nan::New((unsigned int)baton->{{ arg.name }}), diff --git a/generate/templates/partials/convert_from_v8.cc b/generate/templates/partials/convert_from_v8.cc index 659e2fb0e..425e691f4 100644 --- a/generate/templates/partials/convert_from_v8.cc +++ b/generate/templates/partials/convert_from_v8.cc @@ -14,7 +14,14 @@ {%if cppClassName == 'String'%} String::Utf8Value {{ name }}(info[{{ jsArg }}]->ToString()); - from_{{ name }} = ({{ cType }}) strdup(*{{ name }}); + // malloc with one extra byte so we can add the terminating null character C-strings expect: + from_{{ name }} = ({{ cType }}) malloc({{ name }}.length() + 1); + // copy the characters from the nodejs string into our C-string (used instead of strdup or strcpy because nulls in + // the middle of strings are valid coming from nodejs): + memcpy((void *)from_{{ name }}, *{{ name }}, {{ name }}.length()); + // ensure the final byte of our new string is null, extra casts added to ensure compatibility with various C types + // used in the nodejs binding generation: + memset((void *)(((char *)from_{{ name }}) + {{ name }}.length()), 0, 1); {%elsif cppClassName == 'GitStrarray' %} from_{{ name }} = StrArrayConverter::Convert(info[{{ jsArg }}]); @@ -24,7 +31,14 @@ {%elsif cppClassName == 'Wrapper'%} String::Utf8Value {{ name }}(info[{{ jsArg }}]->ToString()); - from_{{ name }} = ({{ cType }}) strdup(*{{ name }}); + // malloc with one extra byte so we can add the terminating null character C-strings expect: + from_{{ name }} = ({{ cType }}) malloc({{ name }}.length() + 1); + // copy the characters from the nodejs string into our C-string (used instead of strdup or strcpy because nulls in + // the middle of strings are valid coming from nodejs): + memcpy((void *)from_{{ name }}, *{{ name }}, {{ name }}.length()); + // ensure the final byte of our new string is null, extra casts added to ensure compatibility with various C types + // used in the nodejs binding generation: + memset((void *)(((char *)from_{{ name }}) + {{ name }}.length()), 0, 1); {%elsif cppClassName == 'Array'%} Array *tmp_{{ name }} = Array::Cast(*info[{{ jsArg }}]); diff --git a/generate/templates/partials/convert_to_v8.cc b/generate/templates/partials/convert_to_v8.cc index bfe40dde6..4c669ac76 100644 --- a/generate/templates/partials/convert_to_v8.cc +++ b/generate/templates/partials/convert_to_v8.cc @@ -59,9 +59,9 @@ if ({{= parsedName =}} != NULL) { // {{= cppClassName }} {{= parsedName }} {% if cppClassName == 'Wrapper' %} - to = {{ cppClassName }}::New((void *){{= parsedName =}}); + to = {{ cppClassName }}::New({{= parsedName =}}); {% else %} - to = {{ cppClassName }}::New((void *){{= parsedName =}}, false); + to = {{ cppClassName }}::New({{= parsedName =}}, {{ selfFreeing|toBool }} {% if shouldDuplicate %}, true{% endif %}); {% endif %} } else { diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 17b338dc3..9261c2fcd 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -149,7 +149,7 @@ {% if arg.isEnum %} Nan::New((int)baton->{{ arg.name }}), {% elsif arg.isLibgitType %} - {{ arg.cppClassName }}::New((void *)baton->{{ arg.name }}, false), + {{ arg.cppClassName }}::New(baton->{{ arg.name }}, false), {% elsif arg.cType == "size_t" %} // HACK: NAN should really have an overload for Nan::New to support size_t Nan::New((unsigned int)baton->{{ arg.name }}), diff --git a/generate/templates/templates/class_content.cc b/generate/templates/templates/class_content.cc index c172e1fa5..312c2ef28 100644 --- a/generate/templates/templates/class_content.cc +++ b/generate/templates/templates/class_content.cc @@ -24,15 +24,34 @@ using namespace v8; using namespace node; {% if cType %} - {{ cppClassName }}::{{ cppClassName }}({{ cType }} *raw, bool selfFreeing) { - this->raw = raw; + {{ cppClassName }}::{{ cppClassName }}({{ cType }} *raw, bool selfFreeing, bool shouldDuplicate) { + if (shouldDuplicate) { + {% if shouldAlloc %} + this->raw = ({{ cType }} *)malloc(sizeof({{ cType }})); + {{ dupFunction }}(this->raw, raw); + {% else %} + {{ dupFunction }}(&this->raw, raw); + {% endif %} + selfFreeing = true; + } else { + this->raw = raw; + } this->selfFreeing = selfFreeing; + + if (selfFreeing) { + SelfFreeingInstanceCount++; + } else { + NonSelfFreeingConstructedCount++; + } + } {{ cppClassName }}::~{{ cppClassName }}() { {% if freeFunctionName %} if (this->selfFreeing) { {{ freeFunctionName }}(this->raw); + SelfFreeingInstanceCount--; + this->raw = NULL; } {% endif %} @@ -77,6 +96,9 @@ using namespace node; {% endif %} {% endeach %} + Nan::SetMethod(tpl, "getSelfFreeingInstanceCount", GetSelfFreeingInstanceCount); + Nan::SetMethod(tpl, "getNonSelfFreeingConstructedCount", GetNonSelfFreeingConstructedCount); + Local _constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); constructor_template.Reset(_constructor_template); Nan::Set(target, Nan::New("{{ jsClassName }}").ToLocalChecked(), _constructor_template); @@ -92,16 +114,28 @@ using namespace node; {% endif %} } - {{ cppClassName }}* object = new {{ cppClassName }}(static_cast<{{ cType }} *>(Local::Cast(info[0])->Value()), Nan::To(info[1]).FromJust()); + {{ cppClassName }}* object = new {{ cppClassName }}(static_cast<{{ cType }} *>( + Local::Cast(info[0])->Value()), + Nan::To(info[1]).FromJust(), + info.Length() >= 3 ? Nan::To(info[2]).FromJust() : false + ); object->Wrap(info.This()); info.GetReturnValue().Set(info.This()); } - Local {{ cppClassName }}::New(void *raw, bool selfFreeing) { + Local {{ cppClassName }}::New(const {{ cType }} *raw, bool selfFreeing, bool shouldDuplicate) { Nan::EscapableHandleScope scope; - Local argv[2] = { Nan::New((void *)raw), Nan::New(selfFreeing) }; - return scope.Escape(Nan::NewInstance(Nan::New({{ cppClassName }}::constructor_template), 2, argv).ToLocalChecked()); + Local argv[3] = { Nan::New((void *)raw), Nan::New(selfFreeing), Nan::New(shouldDuplicate) }; + return scope.Escape(Nan::NewInstance(Nan::New({{ cppClassName }}::constructor_template), 3, argv).ToLocalChecked()); + } + + NAN_METHOD({{ cppClassName }}::GetSelfFreeingInstanceCount) { + info.GetReturnValue().Set(SelfFreeingInstanceCount); + } + + NAN_METHOD({{ cppClassName }}::GetNonSelfFreeingConstructedCount) { + info.GetReturnValue().Set(NonSelfFreeingConstructedCount); } {{ cType }} *{{ cppClassName }}::GetValue() { @@ -147,3 +181,6 @@ using namespace node; {% if not cTypeIsUndefined %} Nan::Persistent {{ cppClassName }}::constructor_template; {% endif %} + +int {{ cppClassName }}::SelfFreeingInstanceCount; +int {{ cppClassName }}::NonSelfFreeingConstructedCount; diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index 84c9b5c4f..cc77f52b3 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -40,12 +40,16 @@ class {{ cppClassName }} : public Nan::ObjectWrap { static Nan::Persistent constructor_template; static void InitializeComponent (Local target); + // diagnostic count of self-freeing object instances + static int SelfFreeingInstanceCount; + // diagnostic count of constructed non-self-freeing object instances + static int NonSelfFreeingConstructedCount; {%if cType%} {{ cType }} *GetValue(); void ClearValue(); - static Local New(void *raw, bool selfFreeing); + static Local New(const {{ cType }} *raw, bool selfFreeing, bool shouldDuplicate = false); {%endif%} bool selfFreeing; @@ -81,7 +85,7 @@ class {{ cppClassName }} : public Nan::ObjectWrap { {%if cType%} - {{ cppClassName }}({{ cType }} *raw, bool selfFreeing); + {{ cppClassName }}({{ cType }} *raw, bool selfFreeing, bool shouldDuplicate = false); ~{{ cppClassName }}(); {%endif%} @@ -96,6 +100,8 @@ class {{ cppClassName }} : public Nan::ObjectWrap { {% endeach %} static NAN_METHOD(JSNewFunction); + static NAN_METHOD(GetSelfFreeingInstanceCount); + static NAN_METHOD(GetNonSelfFreeingConstructedCount); {%each fields as field%} {%if not field.ignore%} diff --git a/generate/templates/templates/struct_content.cc b/generate/templates/templates/struct_content.cc index cab524a80..80b496538 100644 --- a/generate/templates/templates/struct_content.cc +++ b/generate/templates/templates/struct_content.cc @@ -73,7 +73,7 @@ void {{ cppClassName }}::ConstructFields() { {% if not field.isEnum %} {% if field.hasConstructor |or field.isLibgitType %} Local {{ field.name }}Temp = {{ field.cppClassName }}::New( - &this->raw->{{ field.name }}, + {%if not field.cType|isPointer %}&{%endif%}this->raw->{{ field.name }}, false )->ToObject(); this->{{ field.name }}.Reset({{ field.name }}Temp); @@ -131,7 +131,7 @@ NAN_METHOD({{ cppClassName }}::JSNewFunction) { info.GetReturnValue().Set(info.This()); } -Local {{ cppClassName }}::New(void* raw, bool selfFreeing) { +Local {{ cppClassName }}::New(const {{ cType }} * raw, bool selfFreeing) { Nan::EscapableHandleScope scope; Local argv[2] = { Nan::New((void *)raw), Nan::New(selfFreeing) }; diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index 0320fbbf6..ac5a0e236 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -30,7 +30,7 @@ class {{ cppClassName }} : public Nan::ObjectWrap { {{ cType }} *GetValue(); void ClearValue(); - static Local New(void *raw, bool selfFreeing); + static Local New(const {{ cType }} *raw, bool selfFreeing); bool selfFreeing; diff --git a/lib/diff.js b/lib/diff.js index 34c279047..c812c9f9b 100644 --- a/lib/diff.js +++ b/lib/diff.js @@ -58,6 +58,22 @@ Diff.prototype.findSimilar = function(opts) { }; var blobToBuffer = Diff.blobToBuffer; +/** + * Directly run a diff between a blob and a buffer. + * @async + * @param {Blob} old_blob Blob for old side of diff, or NULL for empty blob + * @param {String} old_as_path Treat old blob as if it had this filename; + * can be NULL + * @param {String} buffer Raw data for new side of diff, or NULL for empty + * @param {String} buffer_as_path Treat buffer as if it had this filename; + * can be NULL + * @param {DiffOptions} opts Options for diff, or NULL for default options + * @param {Function} file_cb Callback for "file"; made once if there is a diff; + * can be NULL + * @param {Function} binary_cb Callback for binary files; can be NULL + * @param {Function} hunk_cb Callback for each hunk in diff; can be NULL + * @param {Function} line_cb Callback for each line in diff; can be NULL + */ Diff.blobToBuffer= function( old_blob, old_as_path, @@ -72,7 +88,7 @@ Diff.blobToBuffer= function( var bufferLength; if (buffer instanceof Buffer) { bufferText = buffer.toString("utf8"); - bufferLength = buffer.length; + bufferLength = Buffer.byteLength(buffer, "utf8"); } else { bufferText = buffer; bufferLength = !buffer ? 0 : Buffer.byteLength(buffer, "utf8"); @@ -84,7 +100,7 @@ Diff.blobToBuffer= function( this, old_blob, old_as_path, - buffer, + bufferText, bufferLength, buffer_as_path, opts, diff --git a/lib/stash.js b/lib/stash.js index 2df42ecca..fbc1ca170 100644 --- a/lib/stash.js +++ b/lib/stash.js @@ -32,7 +32,7 @@ Stash.apply = function(repo, index, options) { if (checkoutOptions) { options.checkoutOptions = - normalizeOptions(checkoutOptions, NodeGit.checkoutOptions); + normalizeOptions(checkoutOptions, NodeGit.CheckoutOptions); } return sApply(repo, index, options); diff --git a/lifecycleScripts/install.js b/lifecycleScripts/install.js index 34989f2ac..0302d6743 100644 --- a/lifecycleScripts/install.js +++ b/lifecycleScripts/install.js @@ -36,7 +36,7 @@ return installPrebuilt(); function installPrebuilt() { console.info("[nodegit] Fetching binary from S3."); var npg = pathForTool("node-pre-gyp"); - return exec(npg + " install --fallback-to-build=false") + return exec("\""+ npg + "\" install --fallback-to-build=false") .then( function() { console.info("[nodegit] Completed installation successfully."); diff --git a/package.json b/package.json index 03a750a6d..33719702f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nodegit", "description": "Node.js libgit2 asynchronous native bindings", - "version": "0.11.9", + "version": "0.12.0", "homepage": "http://nodegit.org", "keywords": [ "libgit2", @@ -51,7 +51,7 @@ "lcov-result-merger": "~1.0.2", "lodash": "~3.10.1", "mocha": "~2.3.4", - "nan": "~2.0.9", + "nan": "^2.2.0", "node-gyp": "~3.0.3", "nw-gyp": "~0.12.4" }, @@ -88,6 +88,7 @@ "rebuild": "node generate && node-gyp configure build", "recompileDebug": "node-gyp configure --debug build", "rebuildDebug": "node generate && node-gyp configure --debug build", - "xcodeDebug": "node-gyp configure -- -f xcode" + "xcodeDebug": "node-gyp configure -- -f xcode", + "postinstall": "bash postinstall.sh" } } diff --git a/postinstall.sh b/postinstall.sh new file mode 100755 index 000000000..b3fd35fab --- /dev/null +++ b/postinstall.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +if [ -n "$(node lib/nodegit.js 2>&1 | grep libstdc++)" ]; then + echo "[ERROR] Seems like the latest libstdc++ is missing on your system!" + echo "" + echo "On Ubuntu you can install it using:" + echo "" + echo "$ sudo add-apt-repository ppa:ubuntu-toolchain-r/test" + echo "$ sudo apt-get update" + echo "$ sudo apt-get install libstdc++-4.9-dev" + exit 1 +fi diff --git a/test/tests/clone.js b/test/tests/clone.js index 46830a889..cbb0d1c5d 100644 --- a/test/tests/clone.js +++ b/test/tests/clone.js @@ -27,7 +27,7 @@ describe("Clone", function() { }); }); - it("can clone with http", function() { + it.skip("can clone with http", function() { var test = this; var url = "http://git.tbranyen.com/smart/site-content"; diff --git a/test/tests/commit.js b/test/tests/commit.js index 92f122abd..e770a0c1e 100644 --- a/test/tests/commit.js +++ b/test/tests/commit.js @@ -9,6 +9,25 @@ var exec = promisify(function(command, opts, callback) { return require("child_process").exec(command, opts, callback); }); +// aggressively collects garbage until we fail to improve terminatingIterations +// times. +function garbageCollect() { + var terminatingIterations = 3; + var usedBeforeGC = Number.MAX_VALUE; + var nondecreasingIterations = 0; + for ( ; ; ) { + global.gc(); + var usedAfterGC = process.memoryUsage().heapUsed; + if (usedAfterGC >= usedBeforeGC) { + nondecreasingIterations++; + if (nondecreasingIterations >= terminatingIterations) { + break; + } + } + usedBeforeGC = usedAfterGC; + } +} + describe("Commit", function() { var NodeGit = require("../../"); var Repository = NodeGit.Repository; @@ -624,4 +643,55 @@ describe("Commit", function() { assert.equal(this.committer.email(), "mike@panmedia.co.nz"); }); }); + + it("does not leak", function() { + var test = this; + + garbageCollect(); + var Commit = NodeGit.Commit; + var startSelfFreeingCount = Commit.getSelfFreeingInstanceCount(); + var startNonSelfFreeingCount = Commit.getNonSelfFreeingConstructedCount(); + + var resolve; + var promise = new Promise(function(_resolve) { resolve = _resolve; }); + + NodeGit.Commit.lookup(test.repository, oid) + .then(function() { + // get out of this promise chain to help GC get rid of the commit + setTimeout(resolve, 0); + }); + + return promise + .then(function() { + garbageCollect(); + var endSelfFreeingCount = Commit.getSelfFreeingInstanceCount(); + var endNonSelfFreeingCount = Commit.getNonSelfFreeingConstructedCount(); + // any new self-freeing commits should have been freed + assert.equal(startSelfFreeingCount, endSelfFreeingCount); + // no new non-self-freeing commits should have been constructed + assert.equal(startNonSelfFreeingCount, endNonSelfFreeingCount); + }); + }); + + it("duplicates signature", function() { + garbageCollect(); + var Signature = NodeGit.Signature; + var startSelfFreeingCount = Signature.getSelfFreeingInstanceCount(); + var startNonSelfFreeingCount = + Signature.getNonSelfFreeingConstructedCount(); + var signature = this.commit.author(); + + garbageCollect(); + var endSelfFreeingCount = Signature.getSelfFreeingInstanceCount(); + var endNonSelfFreeingCount = Signature.getNonSelfFreeingConstructedCount(); + // we should get one duplicated, self-freeing signature + assert.equal(startSelfFreeingCount + 1, endSelfFreeingCount); + assert.equal(startNonSelfFreeingCount, endNonSelfFreeingCount); + + signature = null; + garbageCollect(); + endSelfFreeingCount = Signature.getSelfFreeingInstanceCount(); + // the self-freeing signature should get freed + assert.equal(startSelfFreeingCount, endSelfFreeingCount); + }); }); diff --git a/test/tests/ignore.js b/test/tests/ignore.js index 1286de90f..f834b28d0 100644 --- a/test/tests/ignore.js +++ b/test/tests/ignore.js @@ -18,7 +18,16 @@ describe("Ignore", function() { }); it("can determine if a path is ignored", function() { - assert.equal(Ignore.pathIsIgnored(this.repository, ".git"), true); - assert.equal(Ignore.pathIsIgnored(this.repository, "LICENSE"), false); + function expectIgnoreState(repo, fileName, expected) { + return Ignore.pathIsIgnored(repo, fileName) + .then(function(ignored) { + assert.equal(ignored, expected); + }); + } + + return Promise.all([ + expectIgnoreState(this.repository, ".git", true), + expectIgnoreState(this.repository, "LICENSE", false) + ]); }); });