diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index 54d611ac0..b90b0290b 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -523,7 +523,7 @@ "ignore": true }, "git_commit_extract_signature": { - "ignore": true + "isAsync": true }, "git_commit_id": { "return": { diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json index 3e0d24fd7..a31e54f2b 100644 --- a/generate/input/libgit2-supplement.json +++ b/generate/input/libgit2-supplement.json @@ -86,6 +86,40 @@ }, "new" : { "functions": { + "git_commit_extract_signature": { + "args": [ + { + "name": "signature", + "type": "git_buf" + }, + { + "name": "signed_data", + "type": "git_buf" + }, + { + "name": "repo", + "type": "git_repository *" + }, + { + "name": "commit_id", + "type": "git_oid *" + }, + { + "name": "field", + "type": "char *" + } + ], + "type": "function", + "isManual": true, + "cFile": "generate/templates/manual/commit/extract_signature.cc", + "isAsync": true, + "isPrototypeMethod": false, + "group": "commit", + "return": { + "type": "int", + "isErrorCode": true + } + }, "git_patch_convenient_from_diff": { "args": [ { diff --git a/generate/templates/manual/commit/extract_signature.cc b/generate/templates/manual/commit/extract_signature.cc new file mode 100644 index 000000000..581eef6b8 --- /dev/null +++ b/generate/templates/manual/commit/extract_signature.cc @@ -0,0 +1,156 @@ +NAN_METHOD(GitCommit::ExtractSignature) +{ + if (info.Length() == 0 || !info[0]->IsObject()) { + return Nan::ThrowError("Repository repo is required."); + } + + if (info.Length() == 1 || (!info[1]->IsObject() && !info[1]->IsString())) { + return Nan::ThrowError("Oid commit_id is required."); + } + + if (info.Length() == 2 || (info.Length() == 3 && !info[2]->IsFunction())) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + + if (info.Length() >= 4) { + if (!info[2]->IsString() && !info[2]->IsUndefined() && !info[2]->IsNull()) { + return Nan::ThrowError("String signature_field must be a string or undefined/null."); + } + + if (!info[3]->IsFunction()) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + } + + ExtractSignatureBaton* baton = new ExtractSignatureBaton; + + baton->error_code = GIT_OK; + baton->error = NULL; + baton->signature = GIT_BUF_INIT_CONST(NULL, 0); + baton->signed_data = GIT_BUF_INIT_CONST(NULL, 0); + baton->repo = Nan::ObjectWrap::Unwrap(info[0]->ToObject())->GetValue(); + + // baton->commit_id + if (info[1]->IsString()) { + String::Utf8Value oidString(info[1]->ToString()); + baton->commit_id = (git_oid *)malloc(sizeof(git_oid)); + if (git_oid_fromstr(baton->commit_id, (const char *)strdup(*oidString)) != GIT_OK) { + free(baton->commit_id); + + if (giterr_last()) { + return Nan::ThrowError(giterr_last()->message); + } else { + return Nan::ThrowError("Unknown Error"); + } + } + } else { + baton->commit_id = Nan::ObjectWrap::Unwrap(info[1]->ToObject())->GetValue(); + } + + // baton->field + if (info[2]->IsString()) { + String::Utf8Value field(info[2]->ToString()); + baton->field = (char *)malloc(field.length() + 1); + memcpy((void *)baton->field, *field, field.length()); + baton->field[field.length()] = 0; + } else { + baton->field = NULL; + } + + Nan::Callback *callback; + if (info[2]->IsFunction()) { + callback = new Nan::Callback(Local::Cast(info[2])); + } else { + callback = new Nan::Callback(Local::Cast(info[3])); + } + + ExtractSignatureWorker *worker = new ExtractSignatureWorker(baton, callback); + worker->SaveToPersistent("repo", info[0]->ToObject()); + worker->SaveToPersistent("commit_id", info[1]->ToObject()); + Nan::AsyncQueueWorker(worker); + return; +} + +void GitCommit::ExtractSignatureWorker::Execute() +{ + giterr_clear(); + + { + LockMaster lockMaster( + /*asyncAction: */true, + baton->repo + ); + + baton->error_code = git_commit_extract_signature( + &baton->signature, + &baton->signed_data, + baton->repo, + baton->commit_id, + (const char *)baton->field + ); + + if (baton->error_code != GIT_OK && giterr_last() != NULL) { + baton->error = git_error_dup(giterr_last()); + } + } +} + +void GitCommit::ExtractSignatureWorker::HandleOKCallback() +{ + if (baton->error_code == GIT_OK) + { + Local result = Nan::New(); + Nan::Set( + result, + Nan::New("signature").ToLocalChecked(), + Nan::New(baton->signature.ptr, baton->signature.size).ToLocalChecked() + ); + Nan::Set( + result, + Nan::New("signedData").ToLocalChecked(), + Nan::New(baton->signed_data.ptr, baton->signed_data.size).ToLocalChecked() + ); + + Local argv[2] = { + Nan::Null(), + result + }; + callback->Call(2, argv); + } + else if (baton->error) + { + Local argv[1] = { + Nan::Error(baton->error->message) + }; + callback->Call(1, argv); + if (baton->error->message) + { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + else if (baton->error_code < 0) + { + Local err = Nan::Error("Extract Signature has thrown an error.")->ToObject(); + err->Set(Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + err->Set(Nan::New("errorFunction").ToLocalChecked(), Nan::New("Commit.extractSignature").ToLocalChecked()); + Local argv[1] = { + err + }; + callback->Call(1, argv); + } + else + { + callback->Call(0, NULL); + } + + git_buf_free(&baton->signature); + git_buf_free(&baton->signed_data); + + if (baton->field != NULL) { + free((void *)baton->field); + } + + delete baton; +} diff --git a/lib/commit.js b/lib/commit.js index 73d3f62b4..232d771b0 100644 --- a/lib/commit.js +++ b/lib/commit.js @@ -4,6 +4,7 @@ var Commit = NodeGit.Commit; var LookupWrapper = NodeGit.Utils.lookupWrapper; var _amend = Commit.prototype.amend; + /** * Retrieves the commit pointed to by the oid * @async @@ -162,6 +163,23 @@ Commit.prototype.getParents = function(limit, callback) { }, callback); }; +/** + * @typedef extractedSignature + * @type {Object} + * @property {String} signature the signature of the commit + * @property {String} signedData the extracted signed data + */ + +/** + * Retrieve the signature and signed data for a commit. + * @param {String} field Optional field to get from the signature, + * defaults to gpgsig + * @return {extractedSignature} + */ +Commit.prototype.getSignature = function(field) { + return Commit.extractSignature(this.repo, this.id(), field); +}; + /** * Get the tree associated with this commit. * diff --git a/test/tests/commit.js b/test/tests/commit.js index 94d357a0a..63f8d2b17 100644 --- a/test/tests/commit.js +++ b/test/tests/commit.js @@ -718,4 +718,43 @@ describe("Commit", function() { // the self-freeing signature should get freed assert.equal(startSelfFreeingCount, endSelfFreeingCount); }); + + describe("Commit's Signature", function() { + it.only("Can retrieve the gpg signature from a commit", function() { + var expectedSignedData = + "tree f4661419a6fbbe865f78644fec722c023ce4b65f\n" + + "parent 32789a79e71fbc9e04d3eff7425e1771eb595150\n" + + "author Tyler Ang-Wanek 1521227848 -0700\n" + + "committer Tyler Ang-Wanek 1521227848 -0700\n\n" + + "GPG Signed commit\n"; + + var expectedSignature = + "-----BEGIN PGP SIGNATURE-----\n\n" + + "iQEcBAABCAAGBQJarBhIAAoJEE8pfTd/81lKQA4IAL8Mu5kc4B/MX9s4XB26Ahap\n" + + "n06kCx3RQ1KHMZIRomAjCnb48WieNVuy1y+Ut0RgfCxxrJ1ZnzFG3kF2bIKwIxNI\n" + + "tYIC76iWny+mrVnb2mjKYjn/3F4c4VJGENq9ITiV1WeE4yJ8dHw2ox2D+hACzTvQ\n" + + "KVroedk8BDFJxS6DFb20To35xbAVhwBnAGRcII4Wi5PPMFpqAhGLfq3Czv95ddSz\n" + + "BHlyp27+YWSpV0Og0dqOEhsdDYaPrOBGRcoRiqjue+l5tgK/QerLFZ4aovZzpuEP\n" + + "Xx1yZfqXIiy4Bo40qScSrdnmnp/kMq/NQGR3jYU+SleFHVKNFsya9UwurMaezY0=\n" + + "=eZzi\n-----END PGP SIGNATURE-----"; + + return NodeGit.Commit.lookup( + this.repository, + "cc1401eaac4e9e77190e98a9353b305f0c6313d8" + ) + .then(function(commit) { + return commit.getSignature(); + }) + .then(function(extractSignature) { + assert.equal( + extractSignature.signature, + expectedSignature + ); + assert.equal( + extractSignature.signedData, + expectedSignedData + ); + }); + }); + }); });