diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json index 557410cfc..46e0a5d1d 100644 --- a/generate/input/libgit2-supplement.json +++ b/generate/input/libgit2-supplement.json @@ -997,6 +997,36 @@ "isErrorCode": true } }, + "git_revwalk_first_commit": { + "args": [ + { + "name": "timeout", + "type": "int" + }, + { + "name": "found", + "type": "bool" + }, + { + "name": "out", + "type": "git_oid*" + }, + { + "name": "walk", + "type": "git_revwalk *" + } + ], + "type": "function", + "isManual": true, + "cFile": "generate/templates/manual/revwalk/first_commit.cc", + "isAsync": true, + "isPrototypeMethod": true, + "group": "revwalk", + "return": { + "type": "int", + "isErrorCode": true + } + }, "git_status_list_get_perfdata": { "file": "sys/diff.h", "args": [ @@ -1237,7 +1267,8 @@ [ "git_revwalk_commit_walk", "git_revwalk_fast_walk", - "git_revwalk_file_history_walk" + "git_revwalk_file_history_walk", + "git_revwalk_first_commit" ] ], [ diff --git a/generate/templates/manual/revwalk/first_commit.cc b/generate/templates/manual/revwalk/first_commit.cc new file mode 100644 index 000000000..eb7084d93 --- /dev/null +++ b/generate/templates/manual/revwalk/first_commit.cc @@ -0,0 +1,158 @@ +NAN_METHOD(GitRevwalk::FirstCommit) +{ + if (info.Length() == 0 || !info[0]->IsNumber()) { + return Nan::ThrowError("Timeout(ms) is required and must be a number."); + } + + if (!info[info.Length() - 1]->IsFunction()) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + + FirstCommitBaton* baton = new FirstCommitBaton(); + + baton->error_code = GIT_OK; + baton->error = NULL; + baton->timeout = Nan::To(info[0]).FromJust(); + baton->found = false; + baton->out = NULL; + baton->walk = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); + + Nan::Callback *callback = new Nan::Callback(Local::Cast(info[info.Length() - 1])); + std::map> cleanupHandles; + FirstCommitWorker *worker = new FirstCommitWorker(baton, callback, cleanupHandles); + worker->Reference("firstCommit", info.This()); + + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); + return; +} + +nodegit::LockMaster GitRevwalk::FirstCommitWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true); + return lockMaster; +} + +void GitRevwalk::FirstCommitWorker::Execute() +{ + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); + git_oid *nextCommit = (git_oid *)malloc(sizeof(git_oid)); + git_error_clear(); + while(true) { + baton->error_code = git_revwalk_next(nextCommit, baton->walk); + + if (baton->error_code != GIT_OK) + { + // We couldn't get a commit out of the revwalk. It's either in + // an error state or there aren't anymore commits in the revwalk. + if (baton->error_code != GIT_ITEROVER) { + free(nextCommit); + baton->error = git_error_dup(git_error_last()); + baton->out = NULL; + } + else { + baton->error_code = GIT_OK; + baton->out = nextCommit; + baton->found = true; + } + + break; + } + + if(baton->timeout > 0 && + std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count() > baton->timeout) { + baton->error_code = GIT_OK; + baton->out = nextCommit; + baton->found = false; + break; + } + + } +} + +void GitRevwalk::FirstCommitWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} + +void GitRevwalk::FirstCommitWorker::HandleOKCallback() +{ + if (baton->out != NULL) + { + const v8::Local result = Nan::New(); + v8::Local context = Nan::GetCurrentContext(); + if (result->Set(context, Nan::New("commit").ToLocalChecked(), GitOid::New(baton->out, true)).IsJust() == false) { + return Nan::ThrowError("Could not set property commit"); + } + if (result->Set(context, Nan::New("found").ToLocalChecked(), Nan::New(baton->found)).IsJust() == false) { + return Nan::ThrowError("Could not set property found"); + } + + Local argv[2] = { + Nan::Null(), + result + }; + callback->Call(2, argv, async_resource); + } + else + { + if (baton->error) + { + Local err; + if (baton->error->message) { + err = Nan::To(Nan::Error(baton->error->message)).ToLocalChecked(); + } else { + err = Nan::To(Nan::Error("Method firstCommit has thrown an error.")).ToLocalChecked(); + } + Nan::Set(err, Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), Nan::New("Revwalk.firstCommit").ToLocalChecked()); + Local argv[1] = { + err + }; + callback->Call(1, argv, async_resource); + if (baton->error->message) + { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + else if (baton->error_code < 0) + { + bool callbackFired = false; + if (!callbackErrorHandle.IsEmpty()) { + v8::Local maybeError = Nan::New(callbackErrorHandle); + if (!maybeError->IsNull() && !maybeError->IsUndefined()) { + v8::Local argv[1] = { + maybeError + }; + callback->Call(1, argv, async_resource); + callbackFired = true; + } + } + + if (!callbackFired) + { + Local err = Nan::To(Nan::Error("Method next has thrown an error.")).ToLocalChecked(); + Nan::Set(err, Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), Nan::New("Revwalk.firstCommit").ToLocalChecked()); + Local argv[1] = { + err + }; + callback->Call(1, argv, async_resource); + } + } + else + { + callback->Call(0, NULL, async_resource); + } + } + + delete baton; +} diff --git a/test/tests/revwalk.js b/test/tests/revwalk.js index bb1cd06ee..67d8fa929 100644 --- a/test/tests/revwalk.js +++ b/test/tests/revwalk.js @@ -157,6 +157,28 @@ describe("Revwalk", function() { }); }); + it.only("can do a first commit walk (revwalk time sorted)", function() { + var test = this; + var magicSha = "c03723ed33bd62ef2daa3edf04c6c6b0032fbed7"; + + return test.walker.firstCommit(5000) + .then(function(data) { + assert.equal(data.commit.toString(), magicSha); + }); + }); + + it.only("can do a first commit walk (revwalk not sorted)", function() { + var test = this; + var magicSha = "99c88fd2ac9c5e385bd1fe119d89c83dce326219"; + var walker = test.repository.createRevWalk(); // do not sort by time + walker.push(test.commit.id()); + + return walker.firstCommit(5000) + .then(function(data) { + assert.equal(data.commit.toString(), magicSha); + }); + }); + it("can get the history of a file", function() { var test = this; var magicShas = [