diff --git a/generate/input/callbacks.json b/generate/input/callbacks.json index 999ccd57f..5fe947aa5 100644 --- a/generate/input/callbacks.json +++ b/generate/input/callbacks.json @@ -311,6 +311,102 @@ "error": -1 } }, + "git_filter_apply_fn": { + "args": [ + { + "name": "self", + "cType": "git_filter *" + }, + { + "name": "payload", + "cType": "void **" + }, + { + "name": "to", + "cType": "git_buf *" + }, + { + "name": "from", + "cType": "const git_buf *" + }, + { + "name": "src", + "cType": "const git_filter_source *" + } + ], + "return": { + "type": "int", + "noResults": -30, + "success": 0, + "error": -1 + } + }, + "git_filter_check_fn": { + "args": [ + { + "name": "self", + "cType": "git_filter *" + }, + { + "name": "payload", + "cType": "void **" + }, + { + "name": "src", + "cType": "const git_filter_source *" + }, + { + "name": "attr_values", + "cType": "const char **" + } + ], + "return": { + "type": "int", + "noResults": -30, + "success": 0, + "error": -1 + } + }, + "git_filter_cleanup_fn": { + "args": [ + { + "name": "self", + "cType": "git_filter *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "void" + } + }, + "git_filter_init_fn": { + "args": [ + { + "name": "self", + "cType": "git_filter *" + } + ], + "return": { + "type": "int", + "noResults": 0, + "success": 0, + "error": -1 + } + }, + "git_filter_shutdown_fn": { + "args": [ + { + "name": "self", + "cType": "git_filter *" + } + ], + "return": { + "type": "void" + } + }, "git_index_matched_path_cb": { "args": [ { diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index d33298c30..bf6a67d7e 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -879,6 +879,8 @@ } }, "filter": { + "selfFreeing": false, + "hasConstructor": true, "functions": { "git_filter_list_apply_to_blob": { "ignore": true @@ -920,30 +922,21 @@ "ignore": true } }, + "cDependencies": [ + "git2/sys/filter.h" + ], "fields": { - "initialize": { - "ignore": true - }, - "shutdown": { - "ignore": true - }, - "check": { - "ignore": true - }, - "apply": { - "ignore": true - }, - "cleanup": { + "stream": { "ignore": true } - }, + } + }, + "filter_source": { + "ignore": false, "cDependencies": [ "git2/sys/filter.h" ] }, - "filter_source": { - "ignore": true - }, "graph": { "functions": { "git_graph_ahead_behind": { diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json index 52bb45651..12cfe7b2a 100644 --- a/generate/input/libgit2-supplement.json +++ b/generate/input/libgit2-supplement.json @@ -273,6 +273,17 @@ "git_annotated_commit_lookup" ] ], + [ + "filter_source", + [ + "git_filter_source_repo", + "git_filter_source_path", + "git_filter_source_filemode", + "git_filter_source_id", + "git_filter_source_mode", + "git_filter_source_flags" + ] + ], [ "odb_object", [ @@ -422,33 +433,23 @@ }, { "type": "git_filter_init_fn", - "name": "initialize", - "ignore": true + "name": "initialize" }, { "type": "git_filter_shutdown_fn", - "name": "shutdown", - "ignore": true + "name": "shutdown" }, { "type": "git_filter_check_fn", - "name": "check", - "ignore": true + "name": "check" }, { "type": "git_filter_apply_fn", - "name": "apply", - "ignore": true - }, - { - "type": "git_filter_stream_fn", - "name": "stream", - "ignore": true + "name": "apply" }, { "type": "git_filter_cleanup_fn", - "name": "cleanup", - "ignore": true + "name": "cleanup" } ] } @@ -765,14 +766,26 @@ "git_annotated_commit_lookup" ] }, - "odb": { + "filter": { "functions": [ - "git_odb_object_data", - "git_odb_object_dup", - "git_odb_object_free", - "git_odb_object_id", - "git_odb_object_size", - "git_odb_object_type" + "git_filter_list_apply_to_blob", + "git_filter_list_apply_to_data", + "git_filter_list_apply_to_file", + "git_filter_list_contains", + "git_filter_list_free", + "git_filter_list_length", + "git_filter_list_load", + "git_filter_list_new", + "git_filter_list_push", + "git_filter_list_stream_blob", + "git_filter_list_stream_data", + "git_filter_list_stream_file", + "git_filter_source_filemode", + "git_filter_source_flags", + "git_filter_source_id", + "git_filter_source_mode", + "git_filter_source_path", + "git_filter_source_repo" ] }, "merge": { @@ -784,6 +797,16 @@ "git_merge_head_id" ] }, + "odb": { + "functions": [ + "git_odb_object_data", + "git_odb_object_dup", + "git_odb_object_free", + "git_odb_object_id", + "git_odb_object_size", + "git_odb_object_type" + ] + }, "reflog": { "functions": [ "git_reflog_entry_committer", diff --git a/generate/scripts/generateJson.js b/generate/scripts/generateJson.js index 323247e13..49a2795c2 100644 --- a/generate/scripts/generateJson.js +++ b/generate/scripts/generateJson.js @@ -169,9 +169,18 @@ module.exports = function generateJson() { } }; + var addSelfReferentialField = function(prop){ + if (helpers.isSelfReferential(prop.type)) { + prop.isSelfReferential = true; + def.isExtendedStruct = true; + } + }; + def.fields.forEach(addDependencies); + def.fields.forEach(addSelfReferentialField); def.functions.forEach(addDependencies); + Object.keys(dependencies).forEach(function (dependencyFilename) { def.dependencies.push("../include/" + dependencyFilename + ".h"); }); @@ -183,7 +192,6 @@ module.exports = function generateJson() { fn.cppClassName = def.cppClassName; }); }); - // Process enums _(enums).forEach(function(enumerable) { output.some(function(obj) { diff --git a/generate/scripts/generateNativeCode.js b/generate/scripts/generateNativeCode.js index a82bef4e5..30fc52707 100644 --- a/generate/scripts/generateNativeCode.js +++ b/generate/scripts/generateNativeCode.js @@ -68,9 +68,11 @@ module.exports = function generateNativeCode() { replace: require("../templates/filters/replace"), returnsCount: require("../templates/filters/returns_count"), returnsInfo: require("../templates/filters/returns_info"), + subtract: require("../templates/filters/subtract"), titleCase: require("../templates/filters/title_case"), toBool: require('../templates/filters/to_bool'), unPointer: require("../templates/filters/un_pointer"), + setUnsigned: require("../templates/filters/unsigned"), upper: require("../templates/filters/upper") }; diff --git a/generate/scripts/helpers.js b/generate/scripts/helpers.js index 48b651355..9b7baa2cb 100644 --- a/generate/scripts/helpers.js +++ b/generate/scripts/helpers.js @@ -1,4 +1,5 @@ -var callbackTypePattern = /\s*_cb/; +var callbackTypePattern = /\s*_(cb|fn)/, + selfReferentialTypePattern = /\s*_fn/; var utils = require("./utils"); var _ = require("lodash"); @@ -93,6 +94,10 @@ var Helpers = { && ~cbField.name.indexOf(payloadName.replace("_payload", "")); }, + isSelfReferential: function(cType){ + return selfReferentialTypePattern.test(cType); + }, + getLibgitType: function(normalizedType, types) { var libgitType; diff --git a/generate/templates/filters/args_info.js b/generate/templates/filters/args_info.js index 1382c67d6..0c05c30eb 100644 --- a/generate/templates/filters/args_info.js +++ b/generate/templates/filters/args_info.js @@ -13,12 +13,8 @@ module.exports = function(args) { jsArg++; } - if (cArg === args.length -1) { - arg.lastArg = true; - } - else { - arg.lastArg = false; - } + arg.lastArg = cArg === args.length - 1; + arg.firstArg = !arg.lastArg && cArg === 0; arg.cArg = cArg; arg.isCppClassStringOrArray = ~["String", "Array"].indexOf(arg.cppClassName); diff --git a/generate/templates/filters/subtract.js b/generate/templates/filters/subtract.js new file mode 100644 index 000000000..6329f2e2c --- /dev/null +++ b/generate/templates/filters/subtract.js @@ -0,0 +1,3 @@ +module.exports = function(value, other) { + return value - other; +}; diff --git a/generate/templates/filters/unsigned.js b/generate/templates/filters/unsigned.js new file mode 100644 index 000000000..a46078331 --- /dev/null +++ b/generate/templates/filters/unsigned.js @@ -0,0 +1,3 @@ +module.exports = function(value) { + return value < 0 ? 0 : value; +}; diff --git a/generate/templates/manual/include/async_baton.h b/generate/templates/manual/include/async_baton.h index 5f6874102..f8373cd0d 100644 --- a/generate/templates/manual/include/async_baton.h +++ b/generate/templates/manual/include/async_baton.h @@ -60,4 +60,38 @@ struct AsyncBatonWithResult : public AsyncBaton { } }; +struct AsyncBatonWithNoResult : public AsyncBaton { + void (*onCompletion)(AsyncBaton *); + + void Done() { + if (onCompletion) { + onCompletion(this); + } else { + // signal completion + uv_sem_post(&semaphore); + } + } + + void ExecuteAsync(ThreadPool::Callback asyncCallback, void (*onCompletion)(AsyncBaton *) = NULL) { + this->onCompletion = onCompletion; + if (!onCompletion) { + uv_sem_init(&semaphore, 0); + } + + { + LockMaster::TemporaryUnlock temporaryUnlock; + + libgit2ThreadPool.ExecuteReverseCallback(asyncCallback, this); + + if (!onCompletion) { + // wait for completion + uv_sem_wait(&semaphore); + uv_sem_destroy(&semaphore); + } + } + + return; + } +}; + #endif diff --git a/generate/templates/manual/include/filter_registry.h b/generate/templates/manual/include/filter_registry.h new file mode 100644 index 000000000..b75938218 --- /dev/null +++ b/generate/templates/manual/include/filter_registry.h @@ -0,0 +1,75 @@ +#ifndef GITFILTERREGISTRY_H +#define GITFILTERREGISTRY_H +#include +#include +#include +#include + +#include "async_baton.h" +#include "nodegit_wrapper.h" +#include "promise_completion.h" + +extern "C" { +#include +} + +#include "../include/typedefs.h" + +#include "../include/filter.h" + +using namespace node; +using namespace v8; + + +class GitFilterRegistry : public Nan::ObjectWrap { + public: + static void InitializeComponent(v8::Local target); + + static Nan::Persistent persistentHandle; + + private: + + static NAN_METHOD(GitFilterRegister); + + static NAN_METHOD(GitFilterUnregister); + + struct FilterRegisterBaton { + const git_error *error; + git_filter *filter; + char *filter_name; + int filter_priority; + int error_code; + }; + + struct FilterUnregisterBaton { + const git_error *error; + char *filter_name; + int error_code; + }; + + class RegisterWorker : public Nan::AsyncWorker { + public: + RegisterWorker(FilterRegisterBaton *_baton, Nan::Callback *callback) + : Nan::AsyncWorker(callback), baton(_baton) {}; + ~RegisterWorker() {}; + void Execute(); + void HandleOKCallback(); + + private: + FilterRegisterBaton *baton; + }; + + class UnregisterWorker : public Nan::AsyncWorker { + public: + UnregisterWorker(FilterUnregisterBaton *_baton, Nan::Callback *callback) + : Nan::AsyncWorker(callback), baton(_baton) {}; + ~UnregisterWorker() {}; + void Execute(); + void HandleOKCallback(); + + private: + FilterUnregisterBaton *baton; + }; +}; + +#endif diff --git a/generate/templates/manual/src/filter_registry.cc b/generate/templates/manual/src/filter_registry.cc new file mode 100644 index 000000000..a44e4ae70 --- /dev/null +++ b/generate/templates/manual/src/filter_registry.cc @@ -0,0 +1,215 @@ +#include +#include + +extern "C" { + #include +} + +#include "../include/nodegit.h" +#include "../include/lock_master.h" +#include "../include/functions/copy.h" +#include "../include/filter_registry.h" +#include "nodegit_wrapper.cc" +#include "../include/async_libgit2_queue_worker.h" + +#include "../include/filter.h" + +using namespace std; +using namespace v8; +using namespace node; + +Nan::Persistent GitFilterRegistry::persistentHandle; + +// #pragma unmanaged +void GitFilterRegistry::InitializeComponent(v8::Local target) { + Nan::HandleScope scope; + + v8::Local object = Nan::New(); + + Nan::SetMethod(object, "register", GitFilterRegister); + Nan::SetMethod(object, "unregister", GitFilterUnregister); + + Nan::Set(target, Nan::New("FilterRegistry").ToLocalChecked(), object); + GitFilterRegistry::persistentHandle.Reset(object); +} + +NAN_METHOD(GitFilterRegistry::GitFilterRegister) { + Nan::EscapableHandleScope scope; + + if (info.Length() == 0 || !info[0]->IsString()) { + return Nan::ThrowError("String name is required."); + } + + if (info.Length() == 1 || !info[1]->IsObject()) { + return Nan::ThrowError("Filter filter is required."); + } + + if (info.Length() == 2 || !info[2]->IsNumber()) { + return Nan::ThrowError("Number priority is required."); + } + + if (info.Length() == 3 || !info[3]->IsFunction()) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + + FilterRegisterBaton *baton = new FilterRegisterBaton; + + baton->filter = Nan::ObjectWrap::Unwrap(info[1]->ToObject())->GetValue(); + String::Utf8Value name(info[0]->ToString()); + + baton->filter_name = (char *)malloc(name.length() + 1); + memcpy((void *)baton->filter_name, *name, name.length()); + memset((void *)(((char *)baton->filter_name) + name.length()), 0, 1); + + baton->error_code = GIT_OK; + baton->filter_priority = (int)info[2]->ToNumber()->Value(); + + Nan::New(GitFilterRegistry::persistentHandle)->Set(info[0]->ToString(), info[1]->ToObject()); + + Nan::Callback *callback = new Nan::Callback(Local::Cast(info[3])); + RegisterWorker *worker = new RegisterWorker(baton, callback); + + worker->SaveToPersistent("filter_name", info[0]->ToObject()); + worker->SaveToPersistent("filter_priority", info[2]->ToObject()); + + AsyncLibgit2QueueWorker(worker); + return; +} + +void GitFilterRegistry::RegisterWorker::Execute() { + giterr_clear(); + + { + LockMaster lockMaster(/*asyncAction: */true, baton->filter_name, baton->filter); + int result = git_filter_register(baton->filter_name, baton->filter, baton->filter_priority); + baton->error_code = result; + + if (result != GIT_OK && giterr_last() != NULL) { + baton->error = git_error_dup(giterr_last()); + } + } +} + +void GitFilterRegistry::RegisterWorker::HandleOKCallback() { + if (baton->error_code == GIT_OK) { + v8::Local result = Nan::New(baton->error_code); + v8::Local argv[2] = { + Nan::Null(), + result + }; + callback->Call(2, argv); + } + else if (baton->error) { + v8::Local err; + if (baton->error->message) { + err = Nan::Error(baton->error->message)->ToObject(); + } else { + err = Nan::Error("Method register has thrown an error.")->ToObject(); + } + err->Set(Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + v8::Local argv[1] = { + err + }; + callback->Call(1, argv); + if (baton->error->message) + free((void *)baton->error->message); + free((void *)baton->error); + } + else if (baton->error_code < 0) { + v8::Local err = Nan::Error("Method register has thrown an error.")->ToObject(); + err->Set(Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + v8::Local argv[1] = { + err + }; + callback->Call(1, argv); + } + else { + callback->Call(0, NULL); + } + delete baton; + return; +} + +NAN_METHOD(GitFilterRegistry::GitFilterUnregister) { + Nan::EscapableHandleScope scope; + + if (info.Length() == 0 || !info[0]->IsString()) { + return Nan::ThrowError("String name is required."); + } + + if (info.Length() == 1 || !info[1]->IsFunction()) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + + FilterUnregisterBaton *baton = new FilterUnregisterBaton; + String::Utf8Value name(info[0]->ToString()); + + baton->filter_name = (char *)malloc(name.length() + 1); + memcpy((void *)baton->filter_name, *name, name.length()); + memset((void *)(((char *)baton->filter_name) + name.length()), 0, 1); + + baton->error_code = GIT_OK; + + /* Setting up Async Worker */ + Nan::Callback *callback = new Nan::Callback(Local::Cast(info[1])); + UnregisterWorker *worker = new UnregisterWorker(baton, callback); + + worker->SaveToPersistent("filter_name", info[0]); + + AsyncLibgit2QueueWorker(worker); + return; +} + +void GitFilterRegistry::UnregisterWorker::Execute() { + giterr_clear(); + + { + LockMaster lockMaster(/*asyncAction: */true, baton->filter_name); + int result = git_filter_unregister(baton->filter_name); + baton->error_code = result; + + if (result != GIT_OK && giterr_last() != NULL) { + baton->error = git_error_dup(giterr_last()); + } + } +} + +void GitFilterRegistry::UnregisterWorker::HandleOKCallback() { + if (baton->error_code == GIT_OK) { + v8::Local result = Nan::New(baton->error_code); + v8::Local argv[2] = { + Nan::Null(), + result + }; + callback->Call(2, argv); + } + else if (baton->error) { + v8::Local err; + if (baton->error->message) { + err = Nan::Error(baton->error->message)->ToObject(); + } else { + err = Nan::Error("Method register has thrown an error.")->ToObject(); + } + err->Set(Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + v8::Local argv[1] = { + err + }; + callback->Call(1, argv); + if (baton->error->message) + free((void *)baton->error->message); + free((void *)baton->error); + } + else if (baton->error_code < 0) { + v8::Local err = Nan::Error("Method unregister has thrown an error.")->ToObject(); + err->Set(Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + v8::Local argv[1] = { + err + }; + callback->Call(1, argv); + } + else { + callback->Call(0, NULL); + } + delete baton; + return; +} diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index b8e19ddb0..20813136d 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -16,9 +16,13 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { {{ cppFunctionName }}_globalPayload* globalPayload = new {{ cppFunctionName }}_globalPayload; {%endif%} {%if arg.cppClassName == "GitBuf" %} - baton->{{arg.name}} = ({{ arg.cType }})malloc(sizeof({{ arg.cType|replace '*' '' }}));; - baton->{{arg.name}}->ptr = NULL; - baton->{{arg.name}}->size = baton->{{arg.name}}->asize = 0; + {%if cppFunctionName == "Set"%} + baton->{{arg.name}} = Nan::ObjectWrap::Unwrap<{{ arg.cppClassName }}>(info.This())->GetValue(); + {%else%} + baton->{{arg.name}} = ({{ arg.cType }})malloc(sizeof({{ arg.cType|replace '*' '' }})); + baton->{{arg.name}}->ptr = NULL; + baton->{{arg.name}}->size = baton->{{arg.name}}->asize = 0; + {%endif%} {%endif%} {%endeach%} @@ -57,7 +61,10 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { {%endif%} {%endif%} {%elsif arg.shouldAlloc %} - baton->{{ arg.name }} = ({{ arg.cType }})malloc(sizeof({{ arg.cType|replace '*' '' }})); + {%if arg.cppClassName == "GitBuf" %} + {%else%} + baton->{{ arg.name }} = ({{ arg.cType }})malloc(sizeof({{ arg.cType|replace '*' '' }})); + {%endif%} {%endif%} {%endeach%} @@ -273,8 +280,11 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { delete ({{ cppFunctionName}}_globalPayload*)baton->{{ arg.name }}; {%endif%} {%if arg.cppClassName == "GitBuf" %} - git_buf_free(baton->{{ arg.name }}); - free((void *)baton->{{ arg.name }}); + {%if cppFunctionName == "Set" %} + {%else%} + git_buf_free(baton->{{ arg.name }}); + free((void *)baton->{{ arg.name }}); + {%endif%} {%endif%} {%endeach%} diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index ea5bb5db1..480adc76f 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -109,11 +109,20 @@ {% 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 %}); + {% if isExtendedStruct %} + return static_cast<{{ cppClassName }}*>((({{cType}}_extended *)baton->self)->payload); + {% else %} + return static_cast<{{ cppClassName }}*>(baton-> + {% each field.args|argsInfo as arg %} + {% if arg.payload == true %} + {{arg.name}} + {% elsif arg.lastArg %} + {{arg.name}} + {% endif %} + {% endeach %}); + {% endif %} } - + {{ field.return.type }} {{ cppClassName }}::{{ field.name }}_cppCallback ( {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} @@ -127,21 +136,34 @@ {% endeach %} {{ cppClassName }}* instance = {{ field.name }}_getInstanceFromBaton(baton); - - {{ field.return.type }} result; - - if (instance->{{ field.name }}.WillBeThrottled()) { - result = baton->defaultResult; - delete baton; - } else if (instance->{{ field.name }}.ShouldWaitForResult()) { - result = baton->ExecuteAsync({{ field.name }}_async); - delete baton; - } else { - result = baton->defaultResult; - baton->ExecuteAsync({{ field.name }}_async, deleteBaton); - } - return result; + + {% if field.return.type == "void" %} + if (instance->{{ field.name }}.WillBeThrottled()) { + delete baton; + } else if (instance->{{ field.name }}.ShouldWaitForResult()) { + baton->ExecuteAsync({{ field.name }}_async); + delete baton; + } else { + baton->ExecuteAsync({{ field.name }}_async, deleteBaton); + } + return; + {% else %} + {{ field.return.type }} result; + + if (instance->{{ field.name }}.WillBeThrottled()) { + result = baton->defaultResult; + delete baton; + } else if (instance->{{ field.name }}.ShouldWaitForResult()) { + result = baton->ExecuteAsync({{ field.name }}_async); + delete baton; + } else { + result = baton->defaultResult; + baton->ExecuteAsync({{ field.name }}_async, deleteBaton); + } + return result; + {% endif %} } + void {{ cppClassName }}::{{ field.name }}_async(void *untypedBaton) { Nan::HandleScope scope; @@ -153,7 +175,6 @@ {% if field.return.type == "int" %} baton->result = baton->defaultResult; // no results acquired {% endif %} - baton->Done(); return; } @@ -163,15 +184,53 @@ {%-- Do nothing --%} {% elsif arg.isJsArg %} {% if arg.cType == "const char *" %} - if (baton->{{ arg.name }} == NULL) { - baton->{{ arg.name }} = ""; - } + if (baton->{{ arg.name }} == NULL) { + baton->{{ arg.name }} = ""; + } + {% elsif arg.cppClassName == "String" %} + v8::Local src; + if (baton->{{ arg.name }} == NULL) { + src = Nan::Null(); + } + else { + src = Nan::New(*baton->{{ arg.name }}).ToLocalChecked(); + } {% endif %} {% endif %} {% endeach %} - v8::Local argv[{{ field.args|jsArgsCount }}] = { - {% each field.args|argsInfo as arg %} + {% if field.isSelfReferential %} + {% if field.args|jsArgsCount|subtract 2| setUnsigned == 0 %} + v8::Local *argv = NULL; + {% else %} + v8::Local argv[{{ field.args|jsArgsCount|subtract 2| setUnsigned }}] = { + {% endif %} + {% else %} + v8::Local argv[{{ field.args|jsArgsCount }}] = { + {% endif %} + {% each field.args|argsInfo as arg %} + {% if field.isSelfReferential %} + {% if not arg.firstArg %} + {% if field.args|jsArgsCount|subtract 1|or 0 %} + {% if arg.cppClassName == "String" %} + {%-- src is always the last arg --%} + src + {% elsif arg.isJsArg %} + {% if arg.isEnum %} + Nan::New((int)baton->{{ arg.name }}), + {% elsif arg.isLibgitType %} + {{ arg.cppClassName }}::New(baton->{{ arg.name }}, false), + {% elsif arg.cType == "size_t" %} + Nan::New((unsigned int)baton->{{ arg.name }}), + {% elsif arg.name == "payload" %} + {%-- skip, filters should not have a payload --%} + {% else %} + Nan::New(baton->{{ arg.name }}), + {% endif %} + {% endif %} + {% endif %} + {% endif %} + {% else %} {% if arg.name == "payload" %} {%-- payload is always the last arg --%} Nan::New(instance->{{ fields|payloadFor field.name }}) @@ -183,55 +242,35 @@ {% 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 }}), - {% elsif arg.cppClassName == 'String' %} + {% elsif arg.cppClassName == "String" %} Nan::New(baton->{{ arg.name }}).ToLocalChecked(), {% else %} Nan::New(baton->{{ arg.name }}), {% endif %} {% endif %} - {% endeach %} - }; + {% endif %} + {% endeach %} + {% if not field.isSelfReferential %} + }; + {% elsif field.args|jsArgsCount|subtract 2| setUnsigned > 0 %} + }; + {% endif %} Nan::TryCatch tryCatch; - v8::Local result = instance->{{ field.name }}.GetCallback()->Call({{ field.args|jsArgsCount }}, argv); + + {% if field.isSelfReferential %} + v8::Local result = instance->{{ field.name }}.GetCallback()->Call({{ field.args|jsArgsCount|subtract 2| setUnsigned }}, argv); + {% else %} + v8::Local result = instance->{{ field.name }}.GetCallback()->Call({{ field.args|jsArgsCount }}, argv); + {% endif %} if(PromiseCompletion::ForwardIfPromise(result, baton, {{ cppClassName }}::{{ field.name }}_promiseCompleted)) { return; } - - {% each field|returnsInfo false true as _return %} - if (result.IsEmpty() || result->IsNativeError()) { - baton->result = {{ field.return.error }}; - } - else if (!result->IsNull() && !result->IsUndefined()) { - {% if _return.isOutParam %} - {{ _return.cppClassName }}* wrapper = Nan::ObjectWrap::Unwrap<{{ _return.cppClassName }}>(result->ToObject()); - wrapper->selfFreeing = false; - - *baton->{{ _return.name }} = wrapper->GetValue(); - baton->result = {{ field.return.success }}; - {% else %} - if (result->IsNumber()) { - baton->result = (int)result->ToNumber()->Value(); - } - else { - baton->result = baton->defaultResult; - } - {% endif %} - } - else { - baton->result = baton->defaultResult; - } - {% endeach %} - baton->Done(); - } - - void {{ cppClassName }}::{{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result) { - Nan::HandleScope scope; - - {{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(_baton); - - if (isFulfilled) { + + {% if field.return.type == "void" %} + baton->Done(); + {% else %} {% each field|returnsInfo false true as _return %} if (result.IsEmpty() || result->IsNativeError()) { baton->result = {{ field.return.error }}; @@ -247,7 +286,7 @@ if (result->IsNumber()) { baton->result = (int)result->ToNumber()->Value(); } - else{ + else { baton->result = baton->defaultResult; } {% endif %} @@ -256,18 +295,59 @@ baton->result = baton->defaultResult; } {% endeach %} - } - else { - // promise was rejected - {{ 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 %}); - v8::Local parent = instance->handle(); - SetPrivate(parent, Nan::New("NodeGitPromiseError").ToLocalChecked(), result); + baton->Done(); + {% endif %} + } - baton->result = {{ field.return.error }}; - } - baton->Done(); + void {{ cppClassName }}::{{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result) { + Nan::HandleScope scope; + + {{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(_baton); + {% if field.return.type == "void" %} + baton->Done(); + {% else %} + if (isFulfilled) { + {% each field|returnsInfo false true as _return %} + if (result.IsEmpty() || result->IsNativeError()) { + baton->result = {{ field.return.error }}; + } + else if (!result->IsNull() && !result->IsUndefined()) { + {% if _return.isOutParam %} + {{ _return.cppClassName }}* wrapper = Nan::ObjectWrap::Unwrap<{{ _return.cppClassName }}>(result->ToObject()); + wrapper->selfFreeing = false; + + *baton->{{ _return.name }} = wrapper->GetValue(); + baton->result = {{ field.return.success }}; + {% else %} + if (result->IsNumber()) { + baton->result = (int)result->ToNumber()->Value(); + } + else{ + baton->result = baton->defaultResult; + } + {% endif %} + } + else { + baton->result = baton->defaultResult; + } + {% endeach %} + } + else { + // promise was rejected + {% if isExtendedStruct %} + {{ cppClassName }}* instance = static_cast<{{ cppClassName }}*>((({{cType}}_extended *)baton->self)->payload); + {% else %} + {{ 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 %}); + {% endif %} + v8::Local parent = instance->handle(); + SetPrivate(parent, Nan::New("NodeGitPromiseError").ToLocalChecked(), result); + + baton->result = {{ field.return.error }}; + } + baton->Done(); + {% endif %} } {% endif %} {% endif %} diff --git a/generate/templates/templates/binding.gyp b/generate/templates/templates/binding.gyp index 69e2042ad..1be9be490 100644 --- a/generate/templates/templates/binding.gyp +++ b/generate/templates/templates/binding.gyp @@ -10,7 +10,6 @@ "variables": { "coverage%": 0 }, - "sources": [ "src/async_baton.cc", "src/lock_master.cc", @@ -22,6 +21,7 @@ "src/functions/free.cc", "src/convenient_patch.cc", "src/convenient_hunk.cc", + "src/filter_registry.cc", "src/str_array_converter.cc", "src/thread_pool.cc", {% each %} diff --git a/generate/templates/templates/nodegit.cc b/generate/templates/templates/nodegit.cc index 9f392cabc..e81bbb85f 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -21,6 +21,7 @@ {% endeach %} #include "../include/convenient_patch.h" #include "../include/convenient_hunk.h" +#include "../include/filter_registry.h" #if (NODE_MODULE_VERSION > 48) v8::Local GetPrivate(v8::Local object, @@ -140,6 +141,7 @@ extern "C" void init(v8::Local target) { ConvenientHunk::InitializeComponent(target); ConvenientPatch::InitializeComponent(target); + GitFilterRegistry::InitializeComponent(target); NODE_SET_METHOD(target, "enableThreadSafety", LockMasterEnable); NODE_SET_METHOD(target, "setThreadSafetyStatus", LockMasterSetStatus); diff --git a/generate/templates/templates/nodegit.js b/generate/templates/templates/nodegit.js index 84bc558b8..246f5b26b 100644 --- a/generate/templates/templates/nodegit.js +++ b/generate/templates/templates/nodegit.js @@ -58,6 +58,14 @@ _ConvenientPatch.prototype.hunks = promisify(_ConvenientPatch_hunks); var _ConvenientHunk = rawApi.ConvenientHunk; var _ConvenientHunk_lines = _ConvenientHunk.prototype.lines; _ConvenientHunk.prototype.lines = promisify(_ConvenientHunk_lines); + +var _FilterRegistry = rawApi.FilterRegistry; +var _FilterRegistry_register = _FilterRegistry.register; +_FilterRegistry.register = promisify(_FilterRegistry_register); + +var _FilterRegistry_unregister = _FilterRegistry.unregister; +_FilterRegistry.unregister = promisify(_FilterRegistry_unregister); + /* jshint ignore:end */ // Set the exports prototype to the raw API. @@ -86,6 +94,8 @@ require("./status_file"); require("./enums.js"); // Import extensions +// [Manual] extensions +importExtension("filter_registry"); {% each %} {% if type != "enum" %} importExtension("{{ filename }}"); diff --git a/generate/templates/templates/struct_content.cc b/generate/templates/templates/struct_content.cc index c6f9e5b91..9840cfe4f 100644 --- a/generate/templates/templates/struct_content.cc +++ b/generate/templates/templates/struct_content.cc @@ -35,9 +35,15 @@ using namespace std; {% if ignoreInit == true %} this->raw = new {{ cType }}; {% else %} - {{ cType }} wrappedValue = {{ cType|upper }}_INIT; - this->raw = ({{ cType }}*) malloc(sizeof({{ cType }})); - memcpy(this->raw, &wrappedValue, sizeof({{ cType }})); + {% if isExtendedStruct %} + {{ cType }}_extended wrappedValue = {{ cType|upper }}_INIT; + this->raw = ({{ cType }}*) malloc(sizeof({{ cType }}_extended)); + memcpy(this->raw, &wrappedValue, sizeof({{ cType }}_extended)); + {% else %} + {{ cType }} wrappedValue = {{ cType|upper }}_INIT; + this->raw = ({{ cType }}*) malloc(sizeof({{ cType }})); + memcpy(this->raw, &wrappedValue, sizeof({{ cType }})); + {% endif %} {% endif %} this->ConstructFields(); @@ -54,9 +60,13 @@ using namespace std; {% if not field.ignore %} {% if not field.isEnum %} {% if field.isCallbackFunction %} - if (this->{{ field.name }}.HasCallback()) { - this->raw->{{ fields|payloadFor field.name }} = NULL; - } + if (this->{{ field.name }}.HasCallback()) { + {% if isExtendedStruct %} + (({{ cType }}_extended *)this->raw)->payload = NULL; + {% else %} + this->raw->{{ fields|payloadFor field.name }} = NULL; + {% endif %} + } {% endif %} {% endif %} {% endif %} @@ -79,7 +89,11 @@ void {{ cppClassName }}::ConstructFields() { // Set the static method call and set the payload for this function to be // the current instance this->raw->{{ field.name }} = NULL; - this->raw->{{ fields|payloadFor field.name }} = (void *)this; + {% if isExtendedStruct %} + (({{ cType }}_extended *)this->raw)->payload = (void *)this; + {% else %} + this->raw->{{ fields|payloadFor field.name }} = (void *)this; + {% endif %} {% elsif field.payloadFor %} v8::Local {{ field.name }} = Nan::Undefined(); diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index 94fd3bc0f..4d2ec2af5 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -24,7 +24,12 @@ using namespace node; using namespace v8; {%partial traits .%} - +{% if isExtendedStruct %} +struct {{ cType }}_extended { + {{ cType }} raw; + void* payload; +}; +{% endif %} class {{ cppClassName }} : public NodeGitWrapper<{{ cppClassName }}Traits> { // grant full access to base class friend class NodeGitWrapper<{{ cppClassName }}Traits>; @@ -46,15 +51,27 @@ class {{ cppClassName }} : public NodeGitWrapper<{{ cppClassName }}Traits> { static void {{ field.name }}_async(void *baton); static void {{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result); - struct {{ field.name|titleCase }}Baton : public AsyncBatonWithResult<{{ field.return.type }}> { - {% each field.args|argsInfo as arg %} - {{ arg.cType }} {{ arg.name}}; - {% endeach %} + {% if field.return.type == 'void' %} + struct {{ field.name|titleCase }}Baton : public AsyncBatonWithNoResult { + {% each field.args|argsInfo as arg %} + {{ arg.cType }} {{ arg.name }}; + {% endeach %} + + {{ field.name|titleCase }}Baton() + : AsyncBatonWithNoResult() { + } + }; + {% else %} + struct {{ field.name|titleCase }}Baton : public AsyncBatonWithResult<{{ field.return.type }}> { + {% 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.name|titleCase }}Baton(const {{ field.return.type }} &defaultResult) + : AsyncBatonWithResult<{{ field.return.type }}>(defaultResult) { + } + }; + {% endif %} static {{ cppClassName }} * {{ field.name }}_getInstanceFromBaton ( {{ field.name|titleCase }}Baton *baton); {% endif %} diff --git a/lib/filter_registry.js b/lib/filter_registry.js new file mode 100644 index 000000000..76dfba573 --- /dev/null +++ b/lib/filter_registry.js @@ -0,0 +1,42 @@ +var NodeGit = require("../"); +var normalizeOptions = NodeGit.Utils.normalizeOptions; + +var FilterRegistry = NodeGit.FilterRegistry; + +var _register = FilterRegistry.register; +var _unregister = FilterRegistry.unregister; + +// register should add filter by name to dict and return +// Override FilterRegistry.register to normalize Filter +FilterRegistry.register = function(name, filter, priority, callback) { + // setting default value of attributes + if (filter.attributes === undefined) { + filter.attributes = ""; + } + + filter = normalizeOptions(filter, NodeGit.Filter); + + if (!filter.check || !filter.apply) { + return callback(new Error( + "ERROR: please provide check and apply callbacks for filter" + )); + } + + return _register(name, filter, priority) + .then(function(result) { + if (typeof callback === "function") { + callback(null, result); + } + return result; + }, callback); +}; + +FilterRegistry.unregister = function(name, callback) { + return _unregister(name) + .then(function(result) { + if (typeof callback === "function") { + callback(null, result); + } + return result; + }, callback); +}; diff --git a/test/tests/filter.js b/test/tests/filter.js new file mode 100644 index 000000000..1db1c9ecb --- /dev/null +++ b/test/tests/filter.js @@ -0,0 +1,852 @@ +var assert = require("assert"); +var promisify = require("promisify-node"); +var fse = promisify(require("fs-extra")); +var path = require("path"); +var local = path.join.bind(path, __dirname); + +describe("Filter", function() { + var NodeGit = require("../../"); + + var emptyRepoPath = local("../repos/empty"); + var filterName = "psuedo_filter"; + var Registry = NodeGit.FilterRegistry; + var Checkout = NodeGit.Checkout; + var Repository = NodeGit.Repository; + var reposPath = local("../repos/workdir"); + + var packageJsonPath = path.join(reposPath, "package.json"); + var readmePath = path.join(reposPath, "README.md"); + + var mockFilter = { + apply: function() {}, + check: function() {} + }; + + function commitFile(repo, fileName, fileContent, commitMessage) { + let index; + let treeOid; + let parent; + + return fse.writeFile(path.join(repo.workdir(), fileName), fileContent) + .then(function() { + return repo.refreshIndex(); + }) + .then(function(indexResult) { + index = indexResult; + }) + .then(function() { + return index.addByPath(fileName); + }) + .then(function() { + return index.write(); + }) + .then(function() { + return index.writeTree(); + }) + .then(function(oidResult) { + treeOid = oidResult; + return NodeGit.Reference.nameToId(repo, "HEAD"); + }) + .then(function(head) { + return repo.getCommit(head); + }) + .then(function(parentResult) { + parent = parentResult; + return Promise.all([ + NodeGit.Signature.create("Foo Bar", "foo@bar.com", 123456789, 60), + NodeGit.Signature.create("Foo A Bar", "foo@bar.com", 987654321, 90) + ]); + }) + .then(function(signatures) { + let author = signatures[0]; + let committer = signatures[1]; + + return repo.createCommit( + "HEAD", + author, + committer, + commitMessage, + treeOid, + [parent] + ); + }); + } + + beforeEach(function() { + var test = this; + + return Repository.open(reposPath) + .then(function(repository) { + test.repository = repository; + return Repository.open(emptyRepoPath); + }) + .then(function(emptyRepo) { + test.emptyRepo = emptyRepo; + return fse.writeFile( + path.join(reposPath, ".gitattributes"), + "*.md filter=" + filterName + " -text", + { encoding: "utf-8" } + ); + }); + }); + + afterEach(function() { + return Registry.unregister(filterName) + .catch(function(error) { + if (error === NodeGit.Error.CODE.ERROR) { + throw new Error("Cannot unregister filter"); + } + }); + }); + + describe("Register", function() { + var secondFilter = "hellofilter"; + + after(function(done) { + return Registry.unregister(secondFilter) + .then(function() { + done(); + }); + }); + + it("can register a filter", function() { + return Registry.register(filterName, mockFilter, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }); + }); + + it("can register multiple filters", function() { + return Registry.register(filterName, mockFilter, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + return Registry.register(secondFilter, mockFilter, 1); + }) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }); + }); + + it("cannot register the same filter twice", function() { + return Registry.register(filterName, mockFilter, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + return Registry.register(filterName, mockFilter, 0); + }) + .catch(function(error) { + assert.strictEqual(error.errno, NodeGit.Error.CODE.EEXISTS); + }); + }); + }); + + describe("Unregister", function() { + beforeEach(function() { + return Registry.register(filterName, mockFilter, 0); + }); + + it("can unregister the filter", function() { + return Registry.unregister(filterName) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }); + }); + + it("cannot unregister the filter twice", function() { + return Registry.unregister(filterName) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + return Registry.unregister(filterName); + }) + .then(function(result) { + assert.fail("Should not have unregistered successfully"); + }) + .catch(function(error) { + assert.strictEqual(error.errno, NodeGit.Error.CODE.ENOTFOUND); + }); + }); + }); + + describe("Initialize", function(){ + it("initializes successfully", function() { + var test = this; + var initialized = false; + return Registry.register(filterName, { + initialize: function() { + initialized = true; + return NodeGit.Error.CODE.OK; + }, + apply: function() {}, + check: function() { + return NodeGit.Error.CODE.PASSTHROUGH; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout" + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + assert.strictEqual(initialized, true); + }); + }); + + it("initializes successfully even on garbage collect", function() { + var test = this; + var initialized = false; + return Registry.register(filterName, { + initialize: function() { + initialized = true; + return NodeGit.Error.CODE.OK; + }, + apply: function() {}, + check: function() { + return NodeGit.Error.CODE.PASSTHROUGH; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + global.gc(); + + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout" + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + assert.strictEqual(initialized, true); + }); + }); + + it("does not initialize successfully", function() { + var test = this; + var initialized = false; + return Registry.register(filterName, { + initialize: function() { + initialized = true; + return NodeGit.Error.CODE.ERROR; + }, + apply: function() {}, + check: function() { + return NodeGit.Error.CODE.PASSTHROUGH; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout" + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function(head) { + assert.fail(head, undefined, "Should not have actually checked out"); + }) + .catch(function(error) { + assert.strictEqual(initialized, true); + }); + }); + }); + + describe("Shutdown", function() { + it("filter successfully shuts down", function() { + var test = this; + var shutdown = false; + return Registry.register(filterName, { + apply: function() {}, + check: function(){ + return NodeGit.Error.CODE.PASSTHROUGH; + }, + shutdown: function(){ + shutdown = true; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout", + { encoding: "utf-8" } + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + return Registry.unregister(filterName); + }) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + assert.strictEqual(shutdown, true); + }); + }); + + it("filter successfully shuts down on garbage collect", function() { + var test = this; + var shutdown = false; + return Registry.register(filterName, { + apply: function() {}, + check: function(){ + return NodeGit.Error.CODE.PASSTHROUGH; + }, + shutdown: function(){ + shutdown = true; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout", + { encoding: "utf-8" } + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + global.gc(); + return Registry.unregister(filterName); + }) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + assert.strictEqual(shutdown, true); + }); + }); + + it("shutdown completes even if there is an error", function() { + var test = this; + var shutdown = false; + return Registry.register(filterName, { + apply: function() {}, + check: function(){ + return NodeGit.Error.CODE.PASSTHROUGH; + }, + shutdown: function(){ + shutdown = true; + throw new Error("I failed"); + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout", + { encoding: "utf-8" } + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + return Registry.unregister(filterName); + }) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + assert.strictEqual(shutdown, true); + }) + .catch(function(error) { + assert.fail(error, null, "The operation should not have failed"); + }); + }); + }); + + describe("Apply", function() { + var message = "some new fancy filter"; + var length = message.length; + var tempBuffer = new Buffer(message, "utf-8"); + + it("should not apply when check returns GIT_PASSTHROUGH", function(){ + var test = this; + var applied = false; + + return Registry.register(filterName, { + apply: function() { + applied = true; + }, + check: function() { + return NodeGit.Error.CODE.PASSTHROUGH; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout", + { encoding: "utf-8" } + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + assert.notStrictEqual(applied, true); + }); + }); + + it("should apply filter when check succeeds", function() { + var test = this; + var applied = true; + + return Registry.register(filterName, { + apply: function() { + applied = true; + }, + check: function() { + return NodeGit.Error.CODE.OK; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout", + { encoding: "utf-8" } + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + assert.strictEqual(applied, true); + }); + }); + + it("does not apply when GIT_PASSTHROUGH is returned", function() { + var test = this; + + return Registry.register(filterName, { + apply: function(to, from, source) { + return to.set(tempBuffer, length) + .then(function() { + return NodeGit.Error.CODE.PASSTHROUGH; + }); + }, + check: function() { + return NodeGit.Error.CODE.OK; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + var readmeContent = fse.readFileSync( + packageJsonPath, + "utf-8" + ); + assert.notStrictEqual(readmeContent, message); + + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout" + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + var postInitializeReadmeContents = fse.readFileSync( + readmePath, + "utf-8" + ); + + assert.notStrictEqual(postInitializeReadmeContents, message); + }); + }); + + it("applies the filter data on checkout", function() { + var test = this; + + return Registry.register(filterName, { + apply: function(to, from, source) { + return to.set(tempBuffer, length) + .then(function(buf) { + return NodeGit.Error.CODE.OK; + }); + }, + check: function(src, attr) { + return NodeGit.Error.CODE.OK; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, 0); + }) + .then(function() { + var readmeContent = fse.readFileSync( + readmePath, + "utf-8" + ); + assert.notStrictEqual(readmeContent, message); + fse.writeFileSync(readmePath, "whoa", "utf8"); + + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: ["README.md"] + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + var postInitializeReadmeContents = fse.readFileSync( + readmePath, + "utf-8" + ); + + assert.strictEqual(postInitializeReadmeContents, message); + }); + }); + + it("applies the filter data on checkout with gc", function() { + var test = this; + + return Registry.register(filterName, { + apply: function(to, from, source) { + return to.set(tempBuffer, length) + .then(function(buf) { + return NodeGit.Error.CODE.OK; + }); + }, + check: function(src, attr) { + return NodeGit.Error.CODE.OK; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + var readmeContent = fse.readFileSync( + readmePath, + "utf-8" + ); + assert.notStrictEqual(readmeContent, message); + fse.writeFileSync(readmePath, "whoa", "utf8"); + global.gc(); + + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: ["README.md"] + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + var postInitializeReadmeContents = fse.readFileSync( + readmePath, + "utf-8" + ); + + assert.strictEqual(postInitializeReadmeContents, message); + }); + }); + + it("applies the filter data on commit", function() { + var test = this; + + return Registry.register(filterName, { + apply: function(to, from, source) { + return to.set(tempBuffer, length) + .then(function(buf) { + return NodeGit.Error.CODE.OK; + }); + }, + check: function(src, attr) { + return src.path() === "README.md" ? + 0 : NodeGit.Error.CODE.PASSTHROUGH; + }, + cleanup: function() {} + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + var readmeContent = fse.readFileSync( + readmePath, + "utf-8" + ); + assert.notStrictEqual(readmeContent, "testing commit contents"); + }) + .then(function() { + return commitFile(test.repository, "README.md", + "testing commit contents", + "test commit" + ); + }) + .then(function(oid) { + return test.repository.getHeadCommit(); + }) + .then(function(commit) { + var postInitializeReadmeContents = fse.readFileSync( + readmePath, + "utf-8" + ); + + assert.strictEqual( + postInitializeReadmeContents, "testing commit contents" + ); + assert.strictEqual(commit.message(), "test commit"); + + return commit.getEntry("README.md"); + }) + .then(function(entry) { + assert.strictEqual(entry.isBlob(), true); + return entry.getBlob(); + }) + .then(function(blob) { + assert.strictEqual(blob.toString(), message); + }); + }); + + it("applies the filter data on commit with gc", function() { + var test = this; + + return Registry.register(filterName, { + apply: function(to, from, source) { + return to.set(tempBuffer, length) + .then(function(buf) { + return NodeGit.Error.CODE.OK; + }); + }, + check: function(src, attr) { + return src.path() === "README.md" ? + 0 : NodeGit.Error.CODE.PASSTHROUGH; + }, + cleanup: function() {} + }, 0) + .then(function(result) { + global.gc(); + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + var readmeContent = fse.readFileSync( + readmePath, + "utf-8" + ); + assert.notStrictEqual(readmeContent, "testing commit contents"); + }) + .then(function() { + return commitFile(test.repository, "README.md", + "testing commit contents", + "test commit" + ); + }) + .then(function(oid) { + global.gc(); + return test.repository.getHeadCommit(); + }) + .then(function(commit) { + var postInitializeReadmeContents = fse.readFileSync( + readmePath, + "utf-8" + ); + + assert.strictEqual( + postInitializeReadmeContents, "testing commit contents" + ); + assert.strictEqual(commit.message(), "test commit"); + global.gc(); + + return commit.getEntry("README.md"); + }) + .then(function(entry) { + assert.strictEqual(entry.isBlob(), true); + return entry.getBlob(); + }) + .then(function(blob) { + assert.strictEqual(blob.toString(), message); + }); + }); + }); + + describe("Cleanup", function() { + it("is called successfully", function() { + var test = this; + var cleaned = false; + return Registry.register(filterName, { + initialize: function() { + return NodeGit.Error.CODE.OK; + }, + apply: function() { + return NodeGit.Error.CODE.OK; + }, + check: function() { + return NodeGit.Error.CODE.OK; + }, + cleanup: function() { + cleaned = true; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + var packageContent = fse.readFileSync( + packageJsonPath, + "utf-8" + ); + assert.notEqual(packageContent, ""); + + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout", + { encoding: "utf-8" } + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + assert.strictEqual(cleaned, true); + }); + }); + + it("is called successfully with gc", function() { + var test = this; + var cleaned = false; + return Registry.register(filterName, { + initialize: function() { + return NodeGit.Error.CODE.OK; + }, + apply: function() { + return NodeGit.Error.CODE.OK; + }, + check: function() { + return NodeGit.Error.CODE.OK; + }, + cleanup: function() { + cleaned = true; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + var packageContent = fse.readFileSync( + packageJsonPath, + "utf-8" + ); + assert.notEqual(packageContent, ""); + + global.gc(); + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout", + { encoding: "utf-8" } + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "package.json" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + assert.strictEqual(cleaned, true); + }); + }); + + it("is not called when check returns GIT_PASSTHROUGH", function() { + var test = this; + var cleaned = false; + + return Registry.register(filterName, { + initialize: function() { + return NodeGit.Error.CODE.OK; + }, + apply: function() { + return NodeGit.Error.CODE.OK; + }, + check: function() { + return NodeGit.Error.CODE.PASSTHROUGH; + }, + cleanup: function() { + cleaned = true; + } + }, 0) + .then(function(result) { + assert.strictEqual(result, NodeGit.Error.CODE.OK); + }) + .then(function() { + var packageContent = fse.readFileSync( + packageJsonPath, + "utf-8" + ); + var readmeContent = fse.readFileSync( + readmePath, + "utf-8" + ); + + assert.notEqual(packageContent, ""); + assert.notEqual(readmeContent, "Initialized"); + }) + .then(function() { + return fse.writeFile( + packageJsonPath, + "Changing content to trigger checkout", + { encoding: "utf-8" } + ); + }) + .then(function() { + var opts = { + checkoutStrategy: Checkout.STRATEGY.FORCE, + paths: "README.md" + }; + return Checkout.head(test.repository, opts); + }) + .then(function() { + assert.notStrictEqual(cleaned, true); + }); + }); + }); +});