/*
* Copyright 2011, Tim Branyen @tbranyen
* @author Michael Robinson @codeofinterest
*
* Dual licensed under the MIT and GPL licenses.
*/
#include
#include
#include "cvv8/v8-convert.hpp"
#include "git2.h"
#include "../include/diff_list.h"
#include "../include/error.h"
#include "../include/functions/string.h"
using namespace v8;
using namespace node;
namespace cvv8 {
template
struct NativeToJS : NativeToJS {};
}
void GitDiffList::Initialize(Handle target) {
HandleScope scope;
Local tpl = FunctionTemplate::New(New);
tpl->InstanceTemplate()->SetInternalFieldCount(1);
tpl->SetClassName(String::NewSymbol("DiffList"));
NODE_SET_PROTOTYPE_METHOD(tpl, "treeToTree", TreeToTree);
NODE_SET_PROTOTYPE_METHOD(tpl, "walk", Walk);
NODE_SET_PROTOTYPE_METHOD(tpl, "close", Close);
// Add libgit2 delta types to diff_list object
Local libgit2DeltaTypes = Object::New();
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_UNMODIFIED"), cvv8::CastToJS(GIT_DELTA_UNMODIFIED), ReadOnly);
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_ADDED"), cvv8::CastToJS(GIT_DELTA_ADDED), ReadOnly);
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_DELETED"), cvv8::CastToJS(GIT_DELTA_DELETED), ReadOnly);
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_MODIFIED"), cvv8::CastToJS(GIT_DELTA_MODIFIED), ReadOnly);
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_RENAMED"), cvv8::CastToJS(GIT_DELTA_RENAMED), ReadOnly);
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_COPIED"), cvv8::CastToJS(GIT_DELTA_COPIED), ReadOnly);
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_IGNORED"), cvv8::CastToJS(GIT_DELTA_IGNORED), ReadOnly);
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_UNTRACKED"), cvv8::CastToJS(GIT_DELTA_UNTRACKED), ReadOnly);
libgit2DeltaTypes->Set(String::NewSymbol("GIT_DELTA_TYPECHANGE"), cvv8::CastToJS(GIT_DELTA_TYPECHANGE), ReadOnly);
Local libgit2LineOriginConstants = Object::New();
// @todo refactor this into something sane
char _GIT_DIFF_LINE_CONTEXT[2];
_GIT_DIFF_LINE_CONTEXT[0] = GIT_DIFF_LINE_CONTEXT;
_GIT_DIFF_LINE_CONTEXT[1] = '\0';
libgit2LineOriginConstants->Set(String::NewSymbol("GIT_DIFF_LINE_CONTEXT"), cvv8::CastToJS(_GIT_DIFF_LINE_CONTEXT), ReadOnly);
char _GIT_DIFF_LINE_ADDITION[2];
_GIT_DIFF_LINE_ADDITION[0] = GIT_DIFF_LINE_ADDITION;
_GIT_DIFF_LINE_ADDITION[1] = '\0';
libgit2LineOriginConstants->Set(String::NewSymbol("GIT_DIFF_LINE_ADDITION"), cvv8::CastToJS(_GIT_DIFF_LINE_ADDITION), ReadOnly);
char _GIT_DIFF_LINE_DELETION[2];
_GIT_DIFF_LINE_DELETION[0] = GIT_DIFF_LINE_DELETION;
_GIT_DIFF_LINE_DELETION[1] = '\0';
libgit2LineOriginConstants->Set(String::NewSymbol("GIT_DIFF_LINE_DELETION"), cvv8::CastToJS(_GIT_DIFF_LINE_DELETION), ReadOnly);
char _GIT_DIFF_LINE_ADD_EOFNL[2];
_GIT_DIFF_LINE_ADD_EOFNL[0] = GIT_DIFF_LINE_ADD_EOFNL;
_GIT_DIFF_LINE_ADD_EOFNL[1] = '\0';
libgit2LineOriginConstants->Set(String::NewSymbol("GIT_DIFF_LINE_ADD_EOFNL"), cvv8::CastToJS(_GIT_DIFF_LINE_ADD_EOFNL), ReadOnly);
char _GIT_DIFF_LINE_DEL_EOFNL[2];
_GIT_DIFF_LINE_DEL_EOFNL[0] = GIT_DIFF_LINE_DEL_EOFNL;
_GIT_DIFF_LINE_DEL_EOFNL[1] = '\0';
libgit2LineOriginConstants->Set(String::NewSymbol("GIT_DIFF_LINE_DEL_EOFNL"), cvv8::CastToJS(_GIT_DIFF_LINE_DEL_EOFNL), ReadOnly);
char _GIT_DIFF_LINE_FILE_HDR[2];
_GIT_DIFF_LINE_FILE_HDR[0] = GIT_DIFF_LINE_FILE_HDR;
_GIT_DIFF_LINE_FILE_HDR[1] = '\0';
libgit2LineOriginConstants->Set(String::NewSymbol("GIT_DIFF_LINE_FILE_HDR"), cvv8::CastToJS(_GIT_DIFF_LINE_FILE_HDR), ReadOnly);
char _GIT_DIFF_LINE_HUNK_HDR[2];
_GIT_DIFF_LINE_HUNK_HDR[0] = GIT_DIFF_LINE_HUNK_HDR;
_GIT_DIFF_LINE_HUNK_HDR[1] = '\0';
libgit2LineOriginConstants->Set(String::NewSymbol("GIT_DIFF_LINE_HUNK_HDR"), cvv8::CastToJS(_GIT_DIFF_LINE_HUNK_HDR), ReadOnly);
char _GIT_DIFF_LINE_BINARY[2];
_GIT_DIFF_LINE_BINARY[0] = GIT_DIFF_LINE_BINARY;
_GIT_DIFF_LINE_BINARY[1] = '\0';
libgit2LineOriginConstants->Set(String::NewSymbol("GIT_DIFF_LINE_BINARY"), cvv8::CastToJS(_GIT_DIFF_LINE_BINARY), ReadOnly);
constructor_template = Persistent::New(tpl->GetFunction());
constructor_template->Set(String::NewSymbol("deltaTypes"), libgit2DeltaTypes, ReadOnly);
constructor_template->Set(String::NewSymbol("lineOriginTypes"), libgit2LineOriginConstants, ReadOnly);
target->Set(String::NewSymbol("DiffList"), constructor_template);
}
git_diff_list* GitDiffList::GetValue() {
return this->diffList;
}
void GitDiffList::SetValue(git_diff_list* diffList) {
this->diffList = diffList;
}
Handle GitDiffList::New(const Arguments& args) {
HandleScope scope;
GitDiffList *diffList = new GitDiffList();
diffList->Wrap(args.This());
return scope.Close(args.This());
}
void GitDiffList::Close() {
git_diff_list_free(this->diffList);
this->diffList = NULL;
}
Handle GitDiffList::Close(const Arguments& args) {
HandleScope scope;
GitDiffList *diffList = ObjectWrap::Unwrap(args.This());
diffList->Close();
return scope.Close(Undefined());
}
Handle GitDiffList::TreeToTree(const Arguments& args) {
HandleScope scope;
if(args.Length() == 0 || !(args[0]->IsObject() || args[0]->IsString())) {
return ThrowException(Exception::Error(String::New("Repo is required and must be an Object or String")));
}
if(args.Length() == 1 || !(args[1]->IsObject() || args[1]->IsString())) {
return ThrowException(Exception::Error(String::New("Old Oid/SHA is required and must be an Object or String")));
}
if(args.Length() == 2 || !(args[2]->IsObject() || args[2]->IsString())) {
return ThrowException(Exception::Error(String::New("New Oid/SHA is required and must be an Object or String")));
}
if(args.Length() == 3 || !args[3]->IsFunction()) {
return ThrowException(Exception::Error(String::New("Callback is required and must be a Function.")));
}
TreeToTreeBaton *baton = new TreeToTreeBaton();
baton->request.data = baton;
baton->error = NULL;
baton->diffList = ObjectWrap::Unwrap(args.This());
baton->repo = ObjectWrap::Unwrap(args[0]->ToObject())->GetValue();
if (args[1]->IsObject()) {
baton->oldOid = ObjectWrap::Unwrap(args[1]->ToObject())->GetValue();
} else {
baton->oldSha = stringArgToString(args[1]->ToString());
}
if (args[2]->IsObject()) {
baton->newOid = ObjectWrap::Unwrap(args[2]->ToObject())->GetValue();
} else {
baton->newSha = stringArgToString(args[2]->ToString());
}
baton->callback = Persistent::New(Local::Cast(args[3]));
uv_queue_work(uv_default_loop(), &baton->request, TreeToTreeWork, (uv_after_work_cb)TreeToTreeAfterWork);
return Undefined();
}
void GitDiffList::TreeToTreeWork(uv_work_t *req) {
TreeToTreeBaton *baton = static_cast(req->data);
// Prepare git_oid's
git_oid *oldOid = &baton->oldOid;
if (!baton->oldSha.empty()) {
int returnCode = git_oid_fromstr(oldOid, baton->oldSha.c_str());
if (returnCode != GIT_OK) {
baton->error = giterr_last();
return;
}
}
git_oid *newOid = &baton->newOid;
if (!baton->newSha.empty()) {
int returnCode = git_oid_fromstr(newOid, baton->newSha.c_str());
if (returnCode != GIT_OK) {
baton->error = giterr_last();
return;
}
}
// Get commits
git_commit* oldCommit = NULL;
int returnCode = git_commit_lookup(&oldCommit, baton->repo, oldOid);
if (returnCode != GIT_OK) {
baton->error = giterr_last();
return;
}
git_commit* newCommit = NULL;
returnCode = git_commit_lookup(&newCommit, baton->repo, newOid);
if (returnCode != GIT_OK) {
baton->error = giterr_last();
return;
}
// Prepare trees
git_tree* oldTree = NULL;
returnCode = git_commit_tree(&oldTree, oldCommit);
if (returnCode != GIT_OK) {
baton->error = giterr_last();
return;
}
git_tree* newTree = NULL;
returnCode = git_commit_tree(&newTree, newCommit);
if (returnCode != GIT_OK) {
baton->error = giterr_last();
return;
}
baton->rawDiffList = NULL;
returnCode = git_diff_tree_to_tree(&baton->rawDiffList, baton->repo, oldTree, newTree, NULL);
if (returnCode != GIT_OK) {
baton->error = giterr_last();
}
}
void GitDiffList::TreeToTreeAfterWork(uv_work_t *req) {
HandleScope scope;
TreeToTreeBaton *baton = static_cast(req->data);
if (baton->error) {
Local argv[1] = {
GitError::WrapError(baton->error)
};
TryCatch try_catch;
baton->callback->Call(Context::GetCurrent()->Global(), 1, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
} else {
baton->diffList->SetValue(baton->rawDiffList);
Handle argv[2] = {
Local::New(Null()),
baton->diffList->handle_
};
TryCatch try_catch;
baton->callback->Call(Context::GetCurrent()->Global(), 2, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
delete req;
}
Handle GitDiffList::Walk(const Arguments& args) {
HandleScope scope;
GitDiffList* diffList = ObjectWrap::Unwrap(args.This());
if (diffList->GetValue() == NULL) {
return ThrowException(Exception::Error(String::New("No diff list to Walk.")));
}
if(args.Length() == 0 || !args[0]->IsFunction()) {
return ThrowException(Exception::Error(String::New("Diff callback is required and must be a Function.")));
}
if(args.Length() == 1 || !args[1]->IsFunction()) {
return ThrowException(Exception::Error(String::New("Hunk callback is required and must be a Function.")));
}
if(args.Length() == 2 || !args[2]->IsFunction()) {
return ThrowException(Exception::Error(String::New("Line callback is required and must be a Function.")));
}
if(args.Length() == 3 || !args[3]->IsFunction()) {
return ThrowException(Exception::Error(String::New("End callback is required and must be a Function.")));
}
WalkBaton* baton = new WalkBaton();
uv_async_init(uv_default_loop(), &baton->asyncFile, WalkWorkSendFile);
uv_async_init(uv_default_loop(), &baton->asyncHunk, WalkWorkSendHunk);
uv_async_init(uv_default_loop(), &baton->asyncData, WalkWorkSendData);
uv_async_init(uv_default_loop(), &baton->asyncEnd, WalkWorkSendEnd);
uv_mutex_init(&baton->mutex);
baton->rawDiffList = diffList->GetValue();
diffList->Ref();
baton->fileCallback = Persistent::New(Local::Cast(args[0]));
baton->hunkCallback = Persistent::New(Local::Cast(args[1]));
baton->lineCallback = Persistent::New(Local::Cast(args[2]));
baton->endCallback = Persistent::New(Local::Cast(args[3]));
uv_thread_create(&baton->threadId, WalkWork, baton);
return Undefined();
}
void GitDiffList::WalkWork(void *payload) {
WalkBaton *baton = static_cast(payload);
int returnCode = git_diff_foreach(baton->rawDiffList, WalkWorkFile, WalkWorkHunk, WalkWorkData, payload);
if (returnCode != GIT_OK) {
baton->error = giterr_last();
baton->asyncEnd.data = baton;
uv_async_send(&baton->asyncEnd);
return;
}
baton->asyncFile.data = baton;
uv_async_send(&baton->asyncFile);
baton->asyncEnd.data = baton;
uv_async_send(&baton->asyncEnd);
}
int GitDiffList::WalkWorkFile(const git_diff_delta *delta, float progress,
void *payload) {
WalkBaton *baton = static_cast(payload);
/*
diff_file_delta
git_diff_file old_file
git_oid oid
const char *path
git_off_t size
uint32_t flags
uint16_t mode
git_diff_file new_file
git_delta_t status
uint32_t similarity
uint32_t flags
*/
uv_mutex_lock(&baton->mutex);
GitDiffList::Delta* newDelta = new GitDiffList::Delta();
newDelta->raw = (git_diff_delta*)malloc(sizeof(git_diff_delta));
memcpy(newDelta->raw, delta, sizeof(git_diff_delta));
// @todo use combined OID or another less stupid way to index deltas
std::string key(newDelta->raw->old_file.path);
key.append(newDelta->raw->new_file.path);
baton->fileDeltas[key] = newDelta;
uv_mutex_unlock(&baton->mutex);
if ((unsigned int)baton->fileDeltas.size() == (unsigned int)GitDiffList::WALK_DELTA_THRESHHOLD) {
uv_async_send(&baton->asyncFile);
}
return GIT_OK;
}
int GitDiffList::WalkWorkHunk(const git_diff_delta *delta, const git_diff_range *range, const char *header, size_t header_len, void *payload) {
return GIT_OK;
}
int GitDiffList::WalkWorkData(const git_diff_delta *delta, const git_diff_range *range,
char line_origin, const char *content, size_t content_len,
void *payload) {
WalkBaton *baton = static_cast(payload);
uv_mutex_lock(&baton->mutex);
GitDiffList::DeltaContent *deltaContent = new GitDiffList::DeltaContent;
deltaContent->range = (git_diff_range*)malloc(sizeof(git_diff_range));
memcpy(deltaContent->range, range, sizeof(git_diff_range));
deltaContent->lineOrigin = line_origin;
deltaContent->contentLength = content_len;
deltaContent->content = content;
std::string key(delta->old_file.path);
key.append(delta->new_file.path);
baton->fileDeltas[key]->contents.push_back(deltaContent);
uv_mutex_unlock(&baton->mutex);
return GIT_OK;
}
void GitDiffList::WalkWorkSendFile(uv_async_t *handle, int status /*UNUSED*/) {
HandleScope scope;
WalkBaton *baton = static_cast(handle->data);
if (baton->error) {
Local argv[1] = {
GitError::WrapError(baton->error)
};
TryCatch try_catch;
baton->fileCallback->Call(Context::GetCurrent()->Global(), 1, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
} else {
uv_mutex_lock(&baton->mutex);
std::vector fileDeltasArray;
for(std::map::iterator iterator = baton->fileDeltas.begin(); iterator != baton->fileDeltas.end(); ++iterator) {
Local fileDelta = Object::New();
GitDiffList::Delta* delta = iterator->second;
Local oldFile = Object::New();
oldFile->Set(String::NewSymbol("path"), String::New(delta->raw->old_file.path));
fileDelta->Set(String::NewSymbol("oldFile"), oldFile);
Local newFile = Object::New();
newFile->Set(String::NewSymbol("path"), String::New(delta->raw->new_file.path));
fileDelta->Set(String::NewSymbol("newFile"), newFile);
std::vector deltaContent;
for(std::vector::iterator contentIterator = delta->contents.begin(); contentIterator != delta->contents.end(); ++contentIterator) {
DeltaContent* rawContent = (*contentIterator);
Local content = Object::New();
Local range = Object::New();
/*
int old_start
int old_lines
int new_start
int new_lines
*/
Local oldRange = Object::New();
oldRange->Set(String::New("start"), Integer::New(rawContent->range->old_start));
oldRange->Set(String::New("lines"), Integer::New(rawContent->range->old_lines));
range->Set(String::New("old"), oldRange);
Local newRange = Object::New();
newRange->Set(String::New("start"), Integer::New(rawContent->range->new_start));
newRange->Set(String::New("lines"), Integer::New(rawContent->range->new_lines));
range->Set(String::New("new"), newRange);
content->Set(String::New("range"), range);
content->Set(String::New("content"), String::New(rawContent->content.c_str()));
char lineOrigin[2];
strncpy(lineOrigin, &rawContent->lineOrigin, 1);
content->Set(String::New("lineOrigin"), String::New(lineOrigin));
content->Set(String::New("contentLength"), Integer::New(rawContent->contentLength));
deltaContent.push_back(content);
}
fileDelta->Set(String::NewSymbol("content"), cvv8::CastToJS(deltaContent));
fileDelta->Set(String::NewSymbol("status"), cvv8::CastToJS(delta->raw->status));
fileDeltasArray.push_back(fileDelta);
}
baton->fileDeltas.clear();
uv_mutex_unlock(&baton->mutex);
Handle argv[2] = {
Local::New(Null()),
cvv8::CastToJS(fileDeltasArray)
};
TryCatch try_catch;
baton->fileCallback->Call(Context::GetCurrent()->Global(), 2, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
}
void GitDiffList::WalkWorkSendHunk(uv_async_t *handle, int status /*UNUSED*/) { }
void GitDiffList::WalkWorkSendData(uv_async_t *handle, int status /*UNUSED*/) { }
void GitDiffList::WalkWorkSendEnd(uv_async_t *handle, int status /*UNUSED*/) {
WalkBaton *baton = static_cast(handle->data);
uv_mutex_destroy(&baton->mutex);
Local argv[1];
if (baton->error) {
argv[0] = GitError::WrapError(baton->error);
} else {
argv[0] = Local::New(Null());
}
TryCatch try_catch;
baton->endCallback->Call(Context::GetCurrent()->Global(), 1, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
Persistent GitDiffList::constructor_template;