diff --git a/examples/status.js b/examples/status.js new file mode 100644 index 000000000..9a04bae10 --- /dev/null +++ b/examples/status.js @@ -0,0 +1,24 @@ +var nodegit = require("../"), + path = require("path"); + +// This code shows working directory changes similar to git status + +nodegit.Repository.open(path.resolve(__dirname, "../.git")) + .then(function(repo) { + repo.getStatus().then(function(statuses) { + function statusToText(status) { + var words = []; + if (status.isNew()) { words.push("NEW"); } + if (status.isModified()) { words.push("MODIFIED"); } + if (status.isTypechange()) { words.push("TYPECHANGE"); } + if (status.isRenamed()) { words.push("RENAMED"); } + if (status.isIgnored()) { words.push("IGNORED"); } + + return words.join(" "); + } + + statuses.forEach(function(file) { + console.log(file.path() + " " + statusToText(file)); + }); + }); +}); diff --git a/generate/combyne/partials/callback_helpers.cc b/generate/combyne/partials/callback_helpers.cc new file mode 100644 index 000000000..e3e250f92 --- /dev/null +++ b/generate/combyne/partials/callback_helpers.cc @@ -0,0 +1,148 @@ +{%each args as cbFunction %} + {%if cbFunction.isCallbackFunction %} + +{{ cbFunction.return.type }} {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_cppCallback ( + {% each cbFunction.args|argsInfo as arg %} + {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} + {% endeach %} +) { + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = new {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton(); + + {% each cbFunction.args|argsInfo as arg %} + baton->{{ arg.name }} = {{ arg.name }}; + {% endeach %} + + baton->req.data = baton; + baton->done = false; + + uv_queue_work(uv_default_loop(), &baton->req, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncWork, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncAfter); + + while(!baton->done) { + this_thread::sleep_for(chrono::milliseconds(1)); + } + + {% each cbFunction|returnsInfo true false as _return %} + *{{ _return.name }} = *baton->{{ _return.name }}; + {% endeach %} + + return baton->result; +} + +void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncWork(uv_work_t* req) { + // We aren't doing any work on a seperate thread, just need to + // access the main node thread in the async after method. + // However, this worker method is still needed +} + +void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncAfter(uv_work_t* req, int status) { + NanScope(); + + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(req->data); + + NanCallback* callback = (NanCallback *)baton->payload; + + Local argv[{{ cbFunction.args|jsArgsCount }}] = { + {% each cbFunction.args|argsInfo as arg %} + {% if arg.name == "payload" %} + {%-- payload is always the last arg --%} + // payload is null because we can use closure scope in javascript + NanUndefined() + {% elsif arg.isJsArg %} + {% if arg.isEnum %} + NanNew((int)baton->{{ arg.name }}), + {% elsif arg.isLibgitType %} + NanNew({{ arg.cppClassName }}::New(&baton->{{ arg.name }}, false)), + {% elsif arg.cType == "size_t" %} + // HACK: NAN should really have an overload for NanNew to support size_t + NanNew((unsigned int)baton->{{ arg.name }}), + {% else %} + NanNew(baton->{{ arg.name }}), + {% endif %} + {% endif %} + {% endeach %} + }; + + TryCatch tryCatch; + Handle result = callback->Call({{ cbFunction.args|jsArgsCount }}, argv); + + if (result->IsObject() && result->ToObject()->Has(NanNew("then"))) { + Handle thenProp = result->ToObject()->Get(NanNew("then")); + + if (thenProp->IsFunction()) { + // we can be reasonbly certain that the result is a promise + Local promise = result->ToObject(); + + NanAssignPersistent(baton->promise, promise); + + uv_queue_work(uv_default_loop(), &baton->req, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncWork, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling); + return; + } + } + + {{ cbFunction.return.type }} resultStatus; + + {% each cbFunction|returnsInfo true false as _return %} + if (result.IsEmpty() || result->IsNativeError()) { + baton->result = {{ cbFunction.return.error }}; + } + else if (!result->IsNull() && !result->IsUndefined()) { + {{ _return.cppClassName }}* wrapper = ObjectWrap::Unwrap<{{ _return.cppClassName }}>(result->ToObject()); + wrapper->selfFreeing = false; + + baton->{{ _return.name }} = wrapper->GetRefValue(); + baton->result = {{ cbFunction.return.success }}; + } + else { + baton->result = {{ cbFunction.return.noResults }}; + } + {% endeach %} + baton->done = true; +} + +void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling(uv_work_t* req, int status) { + NanScope(); + + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(req->data); + Local promise = NanNew(baton->promise); + NanCallback* isPendingFn = new NanCallback(promise->Get(NanNew("isPending")).As()); + Local argv[1]; // MSBUILD won't assign an array of length 0 + Local isPending = isPendingFn->Call(0, argv)->ToBoolean(); + + if (isPending->Value()) { + uv_queue_work(uv_default_loop(), &baton->req, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncWork, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling); + return; + } + + NanCallback* isFulfilledFn = new NanCallback(promise->Get(NanNew("isFulfilled")).As()); + Local isFulfilled = isFulfilledFn->Call(0, argv)->ToBoolean(); + + if (isFulfilled->Value()) { + NanCallback* resultFn = new NanCallback(promise->Get(NanNew("value")).As()); + Handle result = resultFn->Call(0, argv); + {{ cbFunction.return.type }} resultStatus; + + {% each cbFunction|returnsInfo true false as _return %} + if (result.IsEmpty() || result->IsNativeError()) { + baton->result = {{ cbFunction.return.error }}; + } + else if (!result->IsNull() && !result->IsUndefined()) { + {{ _return.cppClassName }}* wrapper = ObjectWrap::Unwrap<{{ _return.cppClassName }}>(result->ToObject()); + wrapper->selfFreeing = false; + + baton->{{ _return.name }} = wrapper->GetRefValue(); + baton->result = {{ cbFunction.return.success }}; + } + else { + baton->result = {{ cbFunction.return.noResults }}; + } + {% endeach %} + baton->done = true; + } + else { + // promise was rejected + baton->result = {{ cbFunction.return.error }}; + baton->done = false; + } +} + {%endif%} +{%endeach%} diff --git a/generate/input/callbacks.json b/generate/input/callbacks.json index 3ca35e475..157bb3be8 100644 --- a/generate/input/callbacks.json +++ b/generate/input/callbacks.json @@ -26,10 +26,12 @@ "cType": "void *" } ], - "returnType": "int", - "returnNoResults": 1, - "returnSuccess": 0, - "returnError": -1 + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } }, "git_checkout_progress_cb": { "args": [ @@ -50,10 +52,12 @@ "cType": "void *" } ], - "returnType": "int", - "returnNoResults": 1, - "returnSuccess": 0, - "returnError": -1 + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } }, "git_cred_acquire_cb": { "args": [ @@ -79,10 +83,12 @@ "cType": "void *" } ], - "returnType": "int", - "returnNoResults": 1, - "returnSuccess": 0, - "returnError": -1 + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } }, "git_diff_notify_cb": { "args": [ @@ -103,10 +109,12 @@ "cType": "void *" } ], - "returnType": "int", - "returnNoResults": 1, - "returnSuccess": 0, - "returnError": -1 + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } }, "git_diff_file_cb": { "args": [ @@ -123,9 +131,523 @@ "cType": "void *" } ], - "returnType": "int", - "returnNoResults": 1, - "returnSuccess": 0, - "returnError": -1 + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_diff_line_cb": { + "args": [ + { + "name": "delta", + "cType": "const git_diff_delta *" + }, + { + "name": "hunk", + "cType": "const git_diff_hunk *" + }, + { + "name": "line", + "cType": "const git_diff_line *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_status_cb": { + "args": [ + { + "name": "path", + "cType": "const char *" + }, + { + "name": "status_flags", + "cType": "unsigned int" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_blob_chunk_cb": { + "args": [ + { + "name": "content", + "cType": "const char *" + }, + { + "name": "max_length", + "cType": "size_t" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_blob_chunk_cb": { + "args": [ + { + "name": "entry", + "cType": "const git_config_entry *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_diff_hunk_cb": { + "args": [ + { + "name": "delta", + "cType": "const git_diff_delta *" + }, + { + "name": "hunk", + "cType": "const git_diff_hunk *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_diff_hunk_cb": { + "args": [ + { + "name": "delta", + "cType": "const git_diff_delta *" + }, + { + "name": "hunk", + "cType": "const git_diff_hunk *" + }, + { + "name": "line", + "cType": "const git_diff_line *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_index_matched_path_cb": { + "args": [ + { + "name": "path", + "cType": "const char *" + }, + { + "name": "matched_pathspec", + "cType": "const char *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_note_foreach_cb": { + "args": [ + { + "name": "blob_id", + "cType": "const git_oid *" + }, + { + "name": "annotated_object_id", + "cType": "const git_oid *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_note_foreach_cb": { + "args": [ + { + "name": "id", + "cType": "const git_oid *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_transfer_progress_cb": { + "args": [ + { + "name": "stats", + "cType": "const git_transfer_progress *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_packbuilder_foreach_cb": { + "args": [ + { + "name": "buf", + "cType": "void *" + }, + { + "name": "size", + "cType": "size_t" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_reference_foreach_cb": { + "args": [ + { + "name": "refname", + "cType": "const char *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_reference_foreach_name_cb": { + "args": [ + { + "name": "name", + "cType": "const char *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_transport_message_cb": { + "args": [ + { + "name": "str", + "cType": "const char *" + }, + { + "name": "len", + "cType": "int" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_repository_fetchhead_foreach_cb": { + "args": [ + { + "name": "refname", + "cType": "const char *" + }, + { + "name": "remote_url", + "cType": "const char *" + }, + { + "name": "oid", + "cType": "const git_oid *" + }, + { + "name": "is_merge", + "cType": "unsigned int" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_repository_mergehead_foreach_cb": { + "args": [ + { + "name": "oid", + "cType": "const git_oid *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_revwalk_hide_cb": { + "args": [ + { + "name": "commit_id", + "cType": "const git_oid *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_tag_foreach_cb": { + "args": [ + { + "name": "name", + "cType": "const char *" + }, + { + "name": "oid", + "cType": "const git_oid *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_treewalk_cb": { + "args": [ + { + "name": "root", + "cType": "const char *" + }, + { + "name": "entry", + "cType": "const git_tree_entry *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_treebuilder_filter_cb": { + "args": [ + { + "name": "entry", + "cType": "const git_tree_entry *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_attr_foreach_cb": { + "args": [ + { + "name": "name", + "cType": "const char *" + }, + { + "name": "value", + "cType": "const char *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_stash_cb": { + "args": [ + { + "name": "index", + "cType": "size_t" + }, + { + "name": "message", + "cType": "const char *" + }, + { + "name": "stash_id", + "cType": "const int *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_transport_cb": { + "args": [ + { + "name": "out", + "cType": "const git_transport *" + }, + { + "name": "owner", + "cType": "const git_remote *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } + }, + "git_config_foreach_cb": { + "args": [ + { + "name": "entry", + "cType": "const git_config_entry *" + }, + { + "name": "payload", + "cType": "void *" + } + ], + "return": { + "type": "int", + "noResults": 1, + "success": 0, + "error": -1 + } } } diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index 77e245df4..7d542b8e3 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -1288,16 +1288,34 @@ "ignore": true }, "git_status_foreach": { - "ignore": true + "isAsync": true, + "return": { + "isErrorCode": true + } }, "git_status_foreach_ext": { - "ignore": true + "isAsync": true, + "return": { + "isErrorCode": true + } }, "git_status_init_options": { "ignore": true - }, + } + } + }, + "status_list": { + "functions": { "git_status_list_new": { - "ignore": true + "isAsync": false, + "args": { + "opts": { + "isOptional": true + } + }, + "return": { + "isErrorCode": true + } } } }, diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json index 693b2d865..f6ad7029f 100644 --- a/generate/input/libgit2-supplement.json +++ b/generate/input/libgit2-supplement.json @@ -248,6 +248,37 @@ { "type": "enum" } + ], + [ + "git_status_options", + { + "type": "struct", + "fields": [ + { + "type": "unsigned int", + "name": "version" + }, + { + "type": "git_status_show_t", + "name": "show" + }, + { + "type": "git_status_opt_t", + "name": "flags" + }, + { + "type": "git_strarray", + "name": "pathspec" + } + ], + "used": { + "needs": [ + "git_status_init_options", + "git_status_foreach_ext", + "git_status_list_new" + ] + } + } ] ], "groups": [ @@ -271,6 +302,15 @@ "git_merge_head_from_ref", "git_merge_head_id" ] + ], + [ + "status_list", + [ + "git_status_list_entrycount", + "git_status_list_free", + "git_status_list_get_perfdata", + "git_status_list_new" + ] ] ] }, @@ -293,6 +333,14 @@ "git_merge_head_from_ref", "git_merge_head_id" ] + }, + "status": { + "functions": [ + "git_status_list_entrycount", + "git_status_list_free", + "git_status_list_get_perfdata", + "git_status_list_new" + ] } } } diff --git a/generate/scripts/generateNativeCode.js b/generate/scripts/generateNativeCode.js index 3f4465066..c3be8298a 100644 --- a/generate/scripts/generateNativeCode.js +++ b/generate/scripts/generateNativeCode.js @@ -28,6 +28,7 @@ module.exports = function generateNativeCode() { var partials = { asyncFunction: utils.readFile("templates/partials/async_function.cc"), + callbackHelpers: utils.readFile("templates/partials/callback_helpers.cc"), convertFromV8: utils.readFile("templates/partials/convert_from_v8.cc"), convertToV8: utils.readFile("templates/partials/convert_to_v8.cc"), doc: utils.readFile("templates/partials/doc.cc"), diff --git a/generate/scripts/helpers.js b/generate/scripts/helpers.js index 2386461bd..d7a7e681c 100644 --- a/generate/scripts/helpers.js +++ b/generate/scripts/helpers.js @@ -87,7 +87,7 @@ var Helpers = { }, isPayloadFor: function(cbField, payloadName) { - return ~payloadName.indexOf("_payload") + return payloadName && ~payloadName.indexOf("_payload") && Helpers.isCallbackFunction(cbField.cType) && ~cbField.name.indexOf(payloadName.replace("_payload", "")); }, @@ -121,6 +121,7 @@ var Helpers = { processPayload: function(field, allFields) { if (field.name === "payload") { field.payloadFor = "*"; + field.isOptional = true; } else { var cbFieldName; @@ -134,6 +135,7 @@ var Helpers = { if (cbFieldName) { field.payloadFor = cbFieldName; + field.isOptional = true; } } }, @@ -211,7 +213,7 @@ var Helpers = { var argOverrides = fieldOverrides.args || {}; field.args = field.args || []; field.args.forEach(function (arg) { - Helpers.decorateArg(arg, null, null, argOverrides[arg.name] || {}, enums); + Helpers.decorateArg(arg, field.args, null, null, argOverrides[arg.name] || {}, enums); }); } else { @@ -226,7 +228,7 @@ var Helpers = { _.merge(field, fieldOverrides); }, - decorateArg: function(arg, typeDef, fnDef, argOverrides, enums) { + decorateArg: function(arg, allArgs, typeDef, fnDef, argOverrides, enums) { var type = arg.cType || arg.type; var normalizedType = Helpers.normalizeCtype(type); @@ -236,7 +238,22 @@ var Helpers = { Helpers.decorateLibgitType(arg, libgit2.types, enums); - if (typeDef && fnDef) { + // Some arguments can be callbacks + if (Helpers.isCallbackFunction(type)) { + Helpers.processCallback(arg); + + var argOverrides = argOverrides.args || {}; + arg.args = arg.args || []; + arg.args.forEach(function (argForCallback) { + Helpers.decorateArg(argForCallback, arg.args, null, null, argOverrides[argForCallback.name] || {}, enums); + }); + } + else if (typeDef && fnDef) { + Helpers.processPayload(arg, allArgs); + if(arg.payloadFor) { + return; + } + // Mark all of the args that are either returns or are the object // itself and determine if this function goes on the prototype // or is a constructor method. @@ -282,11 +299,11 @@ var Helpers = { var argOverrides = fnOverrides.args || {}; fnDef.args.forEach(function(arg) { - Helpers.decorateArg(arg, typeDef, fnDef, argOverrides[arg.name] || {}, enums); + Helpers.decorateArg(arg, fnDef.args, typeDef, fnDef, argOverrides[arg.name] || {}, enums); }); if (fnDef.return) { - Helpers.decorateArg(fnDef.return, typeDef, fnDef, fnOverrides.return || {}, enums); + Helpers.decorateArg(fnDef.return, fnDef.args, typeDef, fnDef, fnOverrides.return || {}, enums); } _(collisionMappings).forEach(function(newName, collidingName) { diff --git a/generate/templates/filters/args_info.js b/generate/templates/filters/args_info.js index b2fb1aa5b..e2fe799d7 100644 --- a/generate/templates/filters/args_info.js +++ b/generate/templates/filters/args_info.js @@ -6,7 +6,7 @@ module.exports = function(args) { for(cArg = 0, jsArg = 0; cArg < args.length; cArg++) { var arg = args[cArg]; - if (!arg.isReturn && !arg.isSelf && !arg.isPayload) { + if (!arg.isReturn && !arg.isSelf) { arg.isJsArg = true; arg.jsArg = jsArg; @@ -23,6 +23,17 @@ module.exports = function(args) { arg.cArg = cArg; arg.isCppClassStringOrArray = ~["String", "Array"].indexOf(arg.cppClassName); + // if we have a callback then we also need the corresponding payload for that callback + if (arg.isCallbackFunction) { + var payload = args.filter(function(payload) { + return payload.payloadFor == arg.name || payload.payloadFor == '*'; + })[0]; + + if (payload) { + arg.payload = payload; + } + } + result.push(arg); } diff --git a/generate/templates/filters/returns_info.js b/generate/templates/filters/returns_info.js index 1c2856901..c1a315aee 100644 --- a/generate/templates/filters/returns_info.js +++ b/generate/templates/filters/returns_info.js @@ -19,6 +19,7 @@ module.exports = function(fn, argReturnsOnly, isAsync) { }); if (!result.length + && !fn.isCallbackFunction && !argReturnsOnly && fn.return && !fn.return.isErrorCode diff --git a/generate/templates/manual/include/callbackWrapper.h b/generate/templates/manual/include/callbackWrapper.h new file mode 100644 index 000000000..e02182f09 --- /dev/null +++ b/generate/templates/manual/include/callbackWrapper.h @@ -0,0 +1,17 @@ +#ifndef CALLBACK_WRAPPER_H +#define CALLBACK_WRAPPER_H + +#include +#include + +#include "nan.h" + +using namespace v8; +using namespace node; + +struct CallbackWrapper { + NanCallback* jsCallback; + void * payload; +}; + +#endif diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index ac0e0d1ee..d1973ace0 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -16,9 +16,14 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { {%if not arg.isReturn %} {%if arg.isSelf %} baton->{{ arg.name }} = ObjectWrap::Unwrap<{{ arg.cppClassName }}>(args.This())->GetValue(); + {%elsif arg.isCallbackFunction %} + baton->{{ arg.name}} = {{ cppFunctionName }}_{{ arg.name }}_cppCallback; + baton->{{ arg.payload.name }} = new NanCallback(args[{{ arg.jsArg }}].As()); + {%elsif arg.payloadFor %} + {%-- payloads are ignored --%} {%elsif arg.name %} {%partial convertFromV8 arg%} - {%if not arg.isPayload %} + {%if not arg.payloadFor %} baton->{{ arg.name }} = from_{{ arg.name }}; {%if arg | isOid %} baton->{{ arg.name }}NeedsFree = args[{{ arg.jsArg }}]->IsString(); @@ -36,7 +41,7 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { {%if not arg.isReturn %} {%if arg.isSelf %} worker->SaveToPersistent("{{ arg.name }}", args.This()); - {%else%} + {%elsif not arg.isCallbackFunction %} if (!args[{{ arg.jsArg }}]->IsUndefined() && !args[{{ arg.jsArg }}]->IsNull()) worker->SaveToPersistent("{{ arg.name }}", args[{{ arg.jsArg }}]->ToObject()); {%endif%} @@ -122,6 +127,8 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { baton->{{ arg.name}}NeedsFree = false; free((void*)baton->{{ arg.name }}); } + {%elsif arg.isCallbackFunction %} + delete baton->{{ arg.payload.name }}; {%else%} free((void*)baton->{{ arg.name }}); {%endif%} @@ -145,8 +152,12 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { baton->{{ arg.name}}NeedsFree = false; free((void *)baton->{{ arg.name }}); } + {%elsif arg.isCallbackFunction %} + delete (NanCallback *)baton->{{ arg.payload.name }}; {%endif%} {%endeach%} delete baton; } + +{%partial callbackHelpers .%} diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc new file mode 100644 index 000000000..e3e250f92 --- /dev/null +++ b/generate/templates/partials/callback_helpers.cc @@ -0,0 +1,148 @@ +{%each args as cbFunction %} + {%if cbFunction.isCallbackFunction %} + +{{ cbFunction.return.type }} {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_cppCallback ( + {% each cbFunction.args|argsInfo as arg %} + {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} + {% endeach %} +) { + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = new {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton(); + + {% each cbFunction.args|argsInfo as arg %} + baton->{{ arg.name }} = {{ arg.name }}; + {% endeach %} + + baton->req.data = baton; + baton->done = false; + + uv_queue_work(uv_default_loop(), &baton->req, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncWork, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncAfter); + + while(!baton->done) { + this_thread::sleep_for(chrono::milliseconds(1)); + } + + {% each cbFunction|returnsInfo true false as _return %} + *{{ _return.name }} = *baton->{{ _return.name }}; + {% endeach %} + + return baton->result; +} + +void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncWork(uv_work_t* req) { + // We aren't doing any work on a seperate thread, just need to + // access the main node thread in the async after method. + // However, this worker method is still needed +} + +void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncAfter(uv_work_t* req, int status) { + NanScope(); + + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(req->data); + + NanCallback* callback = (NanCallback *)baton->payload; + + Local argv[{{ cbFunction.args|jsArgsCount }}] = { + {% each cbFunction.args|argsInfo as arg %} + {% if arg.name == "payload" %} + {%-- payload is always the last arg --%} + // payload is null because we can use closure scope in javascript + NanUndefined() + {% elsif arg.isJsArg %} + {% if arg.isEnum %} + NanNew((int)baton->{{ arg.name }}), + {% elsif arg.isLibgitType %} + NanNew({{ arg.cppClassName }}::New(&baton->{{ arg.name }}, false)), + {% elsif arg.cType == "size_t" %} + // HACK: NAN should really have an overload for NanNew to support size_t + NanNew((unsigned int)baton->{{ arg.name }}), + {% else %} + NanNew(baton->{{ arg.name }}), + {% endif %} + {% endif %} + {% endeach %} + }; + + TryCatch tryCatch; + Handle result = callback->Call({{ cbFunction.args|jsArgsCount }}, argv); + + if (result->IsObject() && result->ToObject()->Has(NanNew("then"))) { + Handle thenProp = result->ToObject()->Get(NanNew("then")); + + if (thenProp->IsFunction()) { + // we can be reasonbly certain that the result is a promise + Local promise = result->ToObject(); + + NanAssignPersistent(baton->promise, promise); + + uv_queue_work(uv_default_loop(), &baton->req, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncWork, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling); + return; + } + } + + {{ cbFunction.return.type }} resultStatus; + + {% each cbFunction|returnsInfo true false as _return %} + if (result.IsEmpty() || result->IsNativeError()) { + baton->result = {{ cbFunction.return.error }}; + } + else if (!result->IsNull() && !result->IsUndefined()) { + {{ _return.cppClassName }}* wrapper = ObjectWrap::Unwrap<{{ _return.cppClassName }}>(result->ToObject()); + wrapper->selfFreeing = false; + + baton->{{ _return.name }} = wrapper->GetRefValue(); + baton->result = {{ cbFunction.return.success }}; + } + else { + baton->result = {{ cbFunction.return.noResults }}; + } + {% endeach %} + baton->done = true; +} + +void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling(uv_work_t* req, int status) { + NanScope(); + + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(req->data); + Local promise = NanNew(baton->promise); + NanCallback* isPendingFn = new NanCallback(promise->Get(NanNew("isPending")).As()); + Local argv[1]; // MSBUILD won't assign an array of length 0 + Local isPending = isPendingFn->Call(0, argv)->ToBoolean(); + + if (isPending->Value()) { + uv_queue_work(uv_default_loop(), &baton->req, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncWork, {{ cppFunctionName }}_{{ cbFunction.name }}_asyncPromisePolling); + return; + } + + NanCallback* isFulfilledFn = new NanCallback(promise->Get(NanNew("isFulfilled")).As()); + Local isFulfilled = isFulfilledFn->Call(0, argv)->ToBoolean(); + + if (isFulfilled->Value()) { + NanCallback* resultFn = new NanCallback(promise->Get(NanNew("value")).As()); + Handle result = resultFn->Call(0, argv); + {{ cbFunction.return.type }} resultStatus; + + {% each cbFunction|returnsInfo true false as _return %} + if (result.IsEmpty() || result->IsNativeError()) { + baton->result = {{ cbFunction.return.error }}; + } + else if (!result->IsNull() && !result->IsUndefined()) { + {{ _return.cppClassName }}* wrapper = ObjectWrap::Unwrap<{{ _return.cppClassName }}>(result->ToObject()); + wrapper->selfFreeing = false; + + baton->{{ _return.name }} = wrapper->GetRefValue(); + baton->result = {{ cbFunction.return.success }}; + } + else { + baton->result = {{ cbFunction.return.noResults }}; + } + {% endeach %} + baton->done = true; + } + else { + // promise was rejected + baton->result = {{ cbFunction.return.error }}; + baton->done = false; + } +} + {%endif%} +{%endeach%} diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index a980f4b22..c166e147f 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -69,7 +69,7 @@ } {% if field.isCallbackFunction %} - {{ field.returnType }} {{ cppClassName }}::{{ field.name }}_cppCallback ( + {{ field.return.type }} {{ cppClassName }}::{{ field.name }}_cppCallback ( {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} {% endeach %} @@ -109,8 +109,8 @@ {{ cppClassName }}* instance = static_cast<{{ cppClassName }}*>(baton->payload); if (instance->{{ field.name }}->IsEmpty()) { - {% if field.returnType == "int" %} - baton->result = {{ field.returnNoResults }}; // no results acquired + {% if field.return.type == "int" %} + baton->result = {{ field.return.noResults }}; // no results acquired {% endif %} baton->done = true; @@ -154,21 +154,21 @@ } } - {{ field.returnType }} resultStatus; + {{ field.return.type }} resultStatus; {% each field|returnsInfo true false as _return %} if (result.IsEmpty() || result->IsNativeError()) { - baton->result = {{ field.returnError }}; + baton->result = {{ field.return.error }}; } else if (!result->IsNull() && !result->IsUndefined()) { {{ _return.cppClassName }}* wrapper = ObjectWrap::Unwrap<{{ _return.cppClassName }}>(result->ToObject()); wrapper->selfFreeing = false; baton->{{ _return.name }} = wrapper->GetRefValue(); - baton->result = {{ field.returnSuccess }}; + baton->result = {{ field.return.success }}; } else { - baton->result = {{ field.returnNoResults }}; + baton->result = {{ field.return.noResults }}; } {% endeach %} baton->done = true; @@ -194,28 +194,28 @@ if (isFulfilled->Value()) { NanCallback* resultFn = new NanCallback(promise->Get(NanNew("value")).As()); Handle result = resultFn->Call(0, argv); - {{ field.returnType }} resultStatus; + {{ field.return.type }} resultStatus; {% each field|returnsInfo true false as _return %} if (result.IsEmpty() || result->IsNativeError()) { - baton->result = {{ field.returnError }}; + baton->result = {{ field.return.error }}; } else if (!result->IsNull() && !result->IsUndefined()) { {{ _return.cppClassName }}* wrapper = ObjectWrap::Unwrap<{{ _return.cppClassName }}>(result->ToObject()); wrapper->selfFreeing = false; baton->{{ _return.name }} = wrapper->GetRefValue(); - baton->result = {{ field.returnSuccess }}; + baton->result = {{ field.return.success }}; } else { - baton->result = {{ field.returnNoResults }}; + baton->result = {{ field.return.noResults }}; } {% endeach %} baton->done = true; } else { // promise was rejected - baton->result = {{ field.returnError }}; + baton->result = {{ field.return.error }}; baton->done = false; } } diff --git a/generate/templates/partials/guard_arguments.cc b/generate/templates/partials/guard_arguments.cc index 5179ce908..29f905a76 100644 --- a/generate/templates/partials/guard_arguments.cc +++ b/generate/templates/partials/guard_arguments.cc @@ -2,17 +2,22 @@ {%each args|argsInfo as arg%} {%if arg.isJsArg%} {%if not arg.isOptional%} - {%if arg | isOid %} + {%if not arg.payloadFor %} + {%if arg | isOid %} if (args.Length() == {{arg.jsArg}} || (!args[{{arg.jsArg}}]->IsObject() && !args[{{arg.jsArg}}]->IsString())) { return NanThrowError("{{arg.jsClassName}} {{arg.name}} is required."); } - - {%else%} + {%elsif arg.isCallbackFunction %} + if (args.Length() == {{arg.jsArg}} || !args[{{arg.jsArg}}]->IsFunction()) { + return NanThrowError("{{arg.jsClassName}} {{arg.name}} is required."); + } + {%else%} if (args.Length() == {{arg.jsArg}} || !args[{{arg.jsArg}}]->Is{{arg.cppClassName|cppToV8}}()) { return NanThrowError("{{arg.jsClassName}} {{arg.name}} is required."); } + {%endif%} {%endif%} {%endif%} {%endif%} diff --git a/generate/templates/partials/sync_function.cc b/generate/templates/partials/sync_function.cc index 243c1654e..d41d94eef 100644 --- a/generate/templates/partials/sync_function.cc +++ b/generate/templates/partials/sync_function.cc @@ -15,11 +15,20 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { {%each args|argsInfo as arg %} {%if not arg.isSelf %} {%if not arg.isReturn %} - {%partial convertFromV8 arg %} + {%if not arg.isCallbackFunction %} + {%if not arg.payloadFor %} + {%partial convertFromV8 arg %} + {%endif%} + {%endif%} {%endif%} {%endif%} {%endeach%} +{%each args|argsInfo as arg %} + {%if arg.isCallbackFunction %} +NanCallback* {{ arg.name }}_callback = new NanCallback(args[{{ arg.jsArg }}].As()); + {%endif%} +{%endeach%} {%if .|hasReturns %} {{ return.cType }} result = {%endif%}{{ cFunctionName }}( {%each args|argsInfo as arg %} @@ -30,12 +39,24 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { ObjectWrap::Unwrap<{{ arg.cppClassName }}>(args.This())->GetValue() {%elsif arg.isReturn %} {{ arg.name }} + {%elsif arg.isCallbackFunction %} +{{ cppFunctionName }}_{{ arg.name }}_cppCallback, +{{ arg.name }}_callback + {%elsif arg.payloadFor %} +{%-- payloads are handled inside of the callback condition --%} {%else%} from_{{ arg.name }} {%endif%} {%if not arg.lastArg %},{%endif%} {%endeach%} ); + +{%each args|argsInfo as arg %} + {%if arg.isCallbackFunction %} +delete {{ arg.name }}_callback; + {%endif%} +{%endeach%} + {%if return.isErrorCode %} if (result != GIT_OK) { {%each args|argsInfo as arg %} @@ -91,3 +112,5 @@ from_{{ arg.name }} {%endif%} {%endif%} } + +{%partial callbackHelpers .%} diff --git a/generate/templates/templates/class_content.cc b/generate/templates/templates/class_content.cc index b534ea4c2..8df6c0399 100644 --- a/generate/templates/templates/class_content.cc +++ b/generate/templates/templates/class_content.cc @@ -1,6 +1,8 @@ // This is a generated file, modify: generate/templates/class_content.cc. #include #include +#include +#include extern "C" { #include @@ -9,7 +11,6 @@ extern "C" { {% endeach %} } - #include "../include/functions/copy.h" #include "../include/macros.h" #include "../include/{{ filename }}.h" diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index 41d9156c8..1f9845562 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -43,6 +43,36 @@ class {{ cppClassName }} : public ObjectWrap { {%endif%} bool selfFreeing; + {% each functions as function %} + {% if not function.ignore %} + {%each function.args as arg %} + {%if arg.isCallbackFunction %} + static {{ arg.return.type }} {{ function.cppFunctionName }}_{{ arg.name }}_cppCallback ( + {% each arg.args|argsInfo as cbArg %} + {{ cbArg.cType }} {{ cbArg.name }} + {% if not cbArg.lastArg %} + , + {% endif %} + {% endeach %} + ); + + static void {{ function.cppFunctionName }}_{{ arg.name }}_asyncWork(uv_work_t* req); + static void {{ function.cppFunctionName }}_{{ arg.name }}_asyncAfter(uv_work_t* req, int status); + static void {{ function.cppFunctionName }}_{{ arg.name }}_asyncPromisePolling(uv_work_t* req, int status); + struct {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton { + {% each arg.args|argsInfo as cbArg %} + {{ cbArg.cType }} {{ cbArg.name }}; + {% endeach %} + + uv_work_t req; + {{ arg.return.type }} result; + Persistent promise; + bool done; + }; + {% endif %} + {% endeach %} + {% endif %} + {% endeach %} private: {%if cType%} {{ cppClassName }}({{ cType }} *raw, bool selfFreeing); @@ -91,6 +121,13 @@ class {{ cppClassName }} : public ObjectWrap { }; {%endif%} + {%each function.args as arg %} + {%if arg.payloadFor %} + + Persistent {{ function.cppFunctionName }}_{{ arg.name }}; + {%endif%} + {%endeach%} + static NAN_METHOD({{ function.cppFunctionName }}); {%endif%} {%endeach%} diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index 319d86136..9efe1ecd7 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -34,7 +34,7 @@ class {{ cppClassName }} : public ObjectWrap { {% each fields as field %} {% if not field.ignore %} {% if field.isCallbackFunction %} - static {{ field.returnType }} {{ field.name }}_cppCallback ( + static {{ field.return.type }} {{ field.name }}_cppCallback ( {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name}} {% if not arg.lastArg %} @@ -52,7 +52,7 @@ class {{ cppClassName }} : public ObjectWrap { {% endeach %} uv_work_t req; - {{ field.returnType }} result; + {{ field.return.type }} result; Persistent promise; bool done; }; diff --git a/lib/repository.js b/lib/repository.js index 60a8d6561..23fd2488e 100644 --- a/lib/repository.js +++ b/lib/repository.js @@ -8,6 +8,8 @@ var Commit = require("./commit"); var Remote = require("./remote"); var Promise = require("nodegit-promise"); var normalizeOptions = require("./util/normalize_options"); +var Status = require("./status"); +var StatusFile = require("./status_file"); var TreeBuilder = NodeGit.Treebuilder; var Repository = NodeGit.Repository; @@ -648,5 +650,28 @@ Repository.initExt = function(repo_path, opts) { return initExt(repo_path, opts); }; +/** + * Get the status of a repo to it's working directory + * + * @param {obj} opts + * @return {Object} Promise object. + */ +Repository.prototype.getStatus = function(opts) { + var statuses = []; + var statusCallback = function(path, status) { + statuses.push(new StatusFile(path, status)); + }; + + if (!opts) { + opts = { + flags: Status.OPT.INCLUDE_UNTRACKED + + Status.OPT.RECURSE_UNTRACKED_DIRS + }; + } + + return Status.foreachExt(this, opts, statusCallback).then(function() { + return statuses; + }); +}; module.exports = Repository; diff --git a/lib/status.js b/lib/status.js index a6f930a13..286f8bbaf 100644 --- a/lib/status.js +++ b/lib/status.js @@ -1,5 +1,19 @@ var NodeGit = require("../"); +var normalizeOptions = require("./util/normalize_options"); var Status = NodeGit.Status; +// Override Status.foreach to eliminate the need to pass null payload +var foreach = Status.foreach; +Status.foreach = function(repo, callback) { + return foreach(repo, callback, null); +}; + +// Override Status.foreachExt to normalize opts +var foreachExt = Status.foreachExt; +Status.foreachExt = function(repo, opts, callback) { + opts = normalizeOptions(opts, NodeGit.StatusOptions); + return foreachExt(repo, opts, callback, null); +}; + module.exports = Status; diff --git a/lib/status_file.js b/lib/status_file.js new file mode 100644 index 000000000..38ec9e967 --- /dev/null +++ b/lib/status_file.js @@ -0,0 +1,63 @@ +var NodeGit = require("../"); + +var Status = NodeGit.Status; + +var StatusFile = function(path, status) { + + var codes = Status.STATUS; + + var getStatus = function() { + var fileStatuses = []; + + for(var key in Status.STATUS) { + if (status & Status.STATUS[key]) { + fileStatuses.push(key); + } + } + + return fileStatuses; + }; + + var data = { + path: path, + statusBit: status, + statuses: getStatus() + }; + + return { + status: function() { + return data.statuses; + }, + statusBit: function() { + return data.statusBit; + }, + path: function() { + return data.path; + }, + isNew: function() { + return status & codes.WT_NEW || + status & codes.INDEX_NEW; + }, + isModified: function() { + return status & codes.WT_MODIFIED || + status & codes.INDEX_MODIFIED; + }, + isDeleted: function() { + return status & codes.WT_DELETED || + status & codes.INDEX_DELETED; + }, + isTypechange: function() { + return status & codes.WT_TYPECHANGE || + status & codes.INDEX_TYPECHANGE; + }, + isRenamed: function() { + return status & codes.WT_RENAMED || + status & codes.INDEX_RENAMED; + }, + isIgnored: function() { + return data.statusBit & codes.IGNORED; + } + }; +}; + +module.exports = StatusFile; diff --git a/test/tests/repository.js b/test/tests/repository.js index 1f9b87af5..0995726dd 100644 --- a/test/tests/repository.js +++ b/test/tests/repository.js @@ -75,4 +75,21 @@ describe("Repository", function() { assert(sig instanceof Signature); }); + + it("gets statuses with StatusFile", function() { + var fileName = "my-new-file-that-shouldnt-exist"; + var fileContent = "new file"; + var repo = this.repository; + var filePath = path.join(repo.workdir(), fileName); + + return fse.writeFile(filePath, fileContent) + .then(function() { + return repo.getStatus().then(function(statuses) { + assert.equal(statuses.length, 1); + assert.equal(statuses[0].path(), fileName); + assert.ok(statuses[0].isNew()); + return fse.unlink(filePath); + }); + }); + }); }); diff --git a/test/tests/status.js b/test/tests/status.js new file mode 100644 index 000000000..bb394fd89 --- /dev/null +++ b/test/tests/status.js @@ -0,0 +1,81 @@ +var assert = require("assert"); +var path = require("path"); +var promisify = require("promisify-node"); +var fse = promisify(require("fs-extra")); + +describe("Status", function() { + var reposPath = path.resolve("test/repos/workdir"); + + var Status = require("../../lib/status"); + var Repository = require("../../lib/repository"); + + before(function(done) { + var test = this; + return Repository.open(reposPath).then(function(repository) { + test.repository = repository; + done(); + }); + }); + + it("gets no statuses on clean working directory", function() { + var statuses = []; + var statusCallback = function(path, status) { + statuses.push({path: path, status: status}); + }; + return Status.foreach(this.repository, statusCallback).then(function() { + assert.equal(statuses.length, 0); + }); + }); + + it("gets a status on changing file directory", function() { + var fileName = "README.md"; + var fileContent = "Cha-cha-cha-chaaaaaangessssss"; + var repo = this.repository; + var oldContent; + var filePath = path.join(repo.workdir(), fileName); + + return fse.readFile(filePath).then(function(content) { + oldContent = content; + return fse.writeFile(filePath, fileContent) + .then(function() { + var statuses = []; + var statusCallback = function(path, status) { + statuses.push({path: path, status: status}); + }; + return Status.foreach(repo, statusCallback).then(function() { + assert.equal(statuses.length, 1); + assert.equal(statuses[0].path, fileName); + assert.equal(statuses[0].status, 256); + return fse.writeFile(filePath, oldContent); + }); + }); + }); + }); + + it("gets status with options", function() { + var fileName = "my-new-file-that-shouldnt-exist"; + var fileContent = "new file"; + var repo = this.repository; + var filePath = path.join(repo.workdir(), fileName); + + return fse.writeFile(filePath, fileContent) + .then(function() { + var statuses = []; + var statusCallback = function(path, status) { + statuses.push({path: path, status: status}); + }; + + var opts = { + flags: Status.OPT.INCLUDE_UNTRACKED + + Status.OPT.RECURSE_UNTRACKED_DIRS + }; + + return Status.foreachExt(repo, opts, statusCallback).then(function() { + assert.equal(statuses.length, 1); + assert.equal(statuses[0].path, fileName); + assert.equal(statuses[0].status, 128); + return fse.unlink(filePath); + }); + }); + }); +}); diff --git a/test/tests/status_file.js b/test/tests/status_file.js new file mode 100644 index 000000000..b72f887ed --- /dev/null +++ b/test/tests/status_file.js @@ -0,0 +1,19 @@ +var assert = require("assert"); + +describe("StatusFile", function() { + var Status = require("../../lib/status"); + var StatusFile = require("../../lib/status_file"); + var path = "README.md"; + var statusCode = Status.STATUS.WT_NEW; + + var status = new StatusFile(path, statusCode); + + it("passes the path to the working function", function() { + assert.equal(status.path(), path); + }); + + it("identifies the proper statuses", function() { + assert.ok(status.isNew()); + assert.ok(!status.isModified()); + }); +});