From 5da98dc9936b6c31776fd365554ff637ae1d52c5 Mon Sep 17 00:00:00 2001 From: Tyler Wanek Date: Tue, 2 Feb 2016 12:27:38 -0700 Subject: [PATCH 1/6] Implement file history walk in revwalk --- generate/input/descriptor.json | 4 + generate/input/libgit2-supplement.json | 33 ++- .../manual/revwalk/file_history_walk.cc | 212 ++++++++++++++++++ generate/templates/templates/class_header.h | 1 + generate/templates/templates/struct_header.h | 1 + 5 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 generate/templates/manual/revwalk/file_history_walk.cc diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index d433cb6b5..f7aef8c27 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -1857,6 +1857,10 @@ "ignore": true }, "revwalk": { + "dependencies": [ + "../include/commit.h", + "../include/functions/copy.h" + ], "functions": { "git_revwalk_add_hide_cb": { "ignore": true diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json index 708b78982..12433bdac 100644 --- a/generate/input/libgit2-supplement.json +++ b/generate/input/libgit2-supplement.json @@ -200,6 +200,36 @@ "isErrorCode": true } }, + "git_revwalk_file_history_walk": { + "args": [ + { + "name": "file_path", + "type": "const char *" + }, + { + "name": "max_count", + "type": "int" + }, + { + "name": "out", + "type": "std::vector< std::pair *> *" + }, + { + "name": "walk", + "type": "git_revwalk *" + } + ], + "type": "function", + "isManual": true, + "cFile": "generate/templates/manual/revwalk/file_history_walk.cc", + "isAsync": true, + "isPrototypeMethod": true, + "group": "revwalk", + "return": { + "type": "int", + "isErrorCode": true + } + }, "git_stash_save": { "type": "function", "file": "stash.h", @@ -263,7 +293,8 @@ [ "revwalk", [ - "git_revwalk_fast_walk" + "git_revwalk_fast_walk", + "git_revwalk_file_history_walk" ] ], [ diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc new file mode 100644 index 000000000..075c20332 --- /dev/null +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -0,0 +1,212 @@ +NAN_METHOD(GitRevwalk::FileHistoryWalk) +{ + if (info.Length() == 0 || !info[0]->IsString()) { + return Nan::ThrowError("File path to get the history is required."); + } + + if (info.Length() == 1 || !info[1]->IsNumber()) { + return Nan::ThrowError("Max count is required and must be a number."); + } + + if (info.Length() == 2 || !info[2]->IsFunction()) { + return Nan::ThrowError("Callback is required and must be a Function."); + } + + FileHistoryWalkBaton* baton = new FileHistoryWalkBaton; + + baton->error_code = GIT_OK; + baton->error = NULL; + String::Utf8Value from_js_file_path(info[0]->ToString()); + baton->file_path = strdup(*from_js_file_path); + baton->max_count = (unsigned int)info[1]->ToNumber()->Value(); + baton->out = new std::vector< std::pair *>; + baton->out->reserve(baton->max_count); + baton->walk = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); + + Nan::Callback *callback = new Nan::Callback(Local::Cast(info[2])); + FileHistoryWalkWorker *worker = new FileHistoryWalkWorker(baton, callback); + worker->SaveToPersistent("fileHistoryWalk", info.This()); + + Nan::AsyncQueueWorker(worker); + return; +} + +void GitRevwalk::FileHistoryWalkWorker::Execute() +{ + git_repository *repo = git_revwalk_repository(baton->walk); + git_oid *nextOid = (git_oid *)malloc(sizeof(git_oid)); + giterr_clear(); + char * trackPath = strdup(baton->file_path); + bool addedFlag = false; + for ( + unsigned int i = 0; + i < baton->max_count && (baton->error_code = git_revwalk_next(nextOid, baton->walk)) == GIT_OK; + ++i + ) { + // check if this commit has the file + git_commit *nextCommit; + + if ((baton->error_code = git_commit_lookup(&nextCommit, repo, nextOid)) != GIT_OK) { + break; + } + + git_tree *thisTree, *parentTree; + if ((baton->error_code = git_commit_tree(&thisTree, nextCommit)) != GIT_OK) { + git_commit_free(nextCommit); + break; + } + + git_diff *diffs; + git_commit *parent; + unsigned int parents = git_commit_parentcount(nextCommit); + if ( + parents >= 1) { + if ((baton->error_code = git_commit_parent(&parent, nextCommit, 0)) != GIT_OK) { + git_commit_free(nextCommit); + break; + } + if ( + (baton->error_code = git_commit_tree(&parentTree, parent)) != GIT_OK || + (baton->error_code = git_diff_tree_to_tree(&diffs, repo, parentTree, thisTree, NULL)) != GIT_OK + ) { + git_commit_free(nextCommit); + git_commit_free(parent); + break; + } + } else { + if ((baton->error_code = git_diff_tree_to_tree(&diffs, repo, NULL, thisTree, NULL)) != GIT_OK) { + git_commit_free(nextCommit); + break; + } + } + + bool flag = false; + unsigned int numDeltas = git_diff_num_deltas(diffs); + for (unsigned int j = 0; j < numDeltas; ++j) { + git_patch *nextPatch; + baton->error_code = git_patch_from_diff(&nextPatch, diffs, j); + + if (baton->error_code < GIT_OK) { + break; + } + + if (nextPatch == NULL) { + continue; + } + + const git_diff_delta *delta = git_patch_get_delta(nextPatch); + bool isEqualOldFile = !strcmp(delta->old_file.path, trackPath); + bool isEqualNewFile = !strcmp(delta->new_file.path, trackPath); + + if (isEqualNewFile) { + baton->out->push_back( + new std::pair( + nextCommit, + strdup(delta->new_file.path) + )); + if (delta->status == GIT_DELTA_ADDED) { + addedFlag = true; + } else if (!isEqualOldFile) { + free(trackPath); + trackPath = strdup(delta->old_file.path); + } + flag = true; + } + + git_patch_free(nextPatch); + + if (flag) { + break; + } + } + + if (!flag && nextCommit != NULL) { + git_commit_free(nextCommit); + } + + if (baton->error_code != GIT_OK) { + break; + } + + if (addedFlag) { + break; + } + } + + free(trackPath); + free(nextOid); + + if (baton->error_code != GIT_OK) { + if (baton->error_code != GIT_ITEROVER) { + baton->error = git_error_dup(giterr_last()); + + while(!baton->out->empty()) + { + std::pair *pairToFree = baton->out->back(); + baton->out->pop_back(); + git_commit_free(pairToFree->first); + free(pairToFree->second); + free(pairToFree); + } + + delete baton->out; + + baton->out = NULL; + } + } else { + baton->error_code = GIT_OK; + } +} + +void GitRevwalk::FileHistoryWalkWorker::HandleOKCallback() +{ + if (baton->out != NULL) { + unsigned int size = baton->out->size(); + Local result = Nan::New(size); + for (unsigned int i = 0; i < size; i++) { + Local historyEntry = Nan::New(); + std::pair *batonResult = baton->out->at(i); + Nan::Set(historyEntry, Nan::New("commit").ToLocalChecked(), GitCommit::New(batonResult->first, true)); + Nan::Set(historyEntry, Nan::New("name").ToLocalChecked(), Nan::New(batonResult->second).ToLocalChecked()); + Nan::Set(result, Nan::New(i), historyEntry); + + free(batonResult->second); + free(batonResult); + } + + Local argv[2] = { + Nan::Null(), + result + }; + callback->Call(2, argv); + + delete baton->out; + return; + } + + 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); + return; + } + + if (baton->error_code < 0) { + Local err = Nan::Error("Method next has thrown an error.")->ToObject(); + err->Set(Nan::New("errno").ToLocalChecked(), Nan::New(baton->error_code)); + Local argv[1] = { + err + }; + callback->Call(1, argv); + return; + } + + callback->Call(0, NULL); +} diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index 74ec20094..a378f11a5 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "async_baton.h" #include "promise_completion.h" diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index e4a3b98d4..c1f30cc4e 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "async_baton.h" From 4bfba56301daf3fbc3bd2f703889ad6f92e9e27f Mon Sep 17 00:00:00 2001 From: Tyler Wanek Date: Wed, 3 Feb 2016 10:57:02 -0700 Subject: [PATCH 2/6] Vanilla git log implementation --- generate/input/libgit2-supplement.json | 2 +- .../manual/revwalk/file_history_walk.cc | 52 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/generate/input/libgit2-supplement.json b/generate/input/libgit2-supplement.json index 12433bdac..f525a6390 100644 --- a/generate/input/libgit2-supplement.json +++ b/generate/input/libgit2-supplement.json @@ -212,7 +212,7 @@ }, { "name": "out", - "type": "std::vector< std::pair *> *" + "type": "std::vector< std::pair > *> *" }, { "name": "walk", diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc index 075c20332..ea779ffb0 100644 --- a/generate/templates/manual/revwalk/file_history_walk.cc +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -19,7 +19,7 @@ NAN_METHOD(GitRevwalk::FileHistoryWalk) String::Utf8Value from_js_file_path(info[0]->ToString()); baton->file_path = strdup(*from_js_file_path); baton->max_count = (unsigned int)info[1]->ToNumber()->Value(); - baton->out = new std::vector< std::pair *>; + baton->out = new std::vector< std::pair > *>; baton->out->reserve(baton->max_count); baton->walk = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); @@ -36,8 +36,6 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() git_repository *repo = git_revwalk_repository(baton->walk); git_oid *nextOid = (git_oid *)malloc(sizeof(git_oid)); giterr_clear(); - char * trackPath = strdup(baton->file_path); - bool addedFlag = false; for ( unsigned int i = 0; i < baton->max_count && (baton->error_code = git_revwalk_next(nextOid, baton->walk)) == GIT_OK; @@ -59,8 +57,10 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() git_diff *diffs; git_commit *parent; unsigned int parents = git_commit_parentcount(nextCommit); - if ( - parents >= 1) { + if (parents > 1) { + git_commit_free(nextCommit); + continue; + } else if (parents == 1) { if ((baton->error_code = git_commit_parent(&parent, nextCommit, 0)) != GIT_OK) { git_commit_free(nextCommit); break; @@ -95,21 +95,23 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() } const git_diff_delta *delta = git_patch_get_delta(nextPatch); - bool isEqualOldFile = !strcmp(delta->old_file.path, trackPath); - bool isEqualNewFile = !strcmp(delta->new_file.path, trackPath); + bool isEqualOldFile = !strcmp(delta->old_file.path, baton->file_path); + bool isEqualNewFile = !strcmp(delta->new_file.path, baton->file_path); if (isEqualNewFile) { - baton->out->push_back( - new std::pair( + std::pair > *historyEntry; + if (!isEqualOldFile) { + historyEntry = new std::pair >( + nextCommit, + std::make_pair(strdup(delta->old_file.path), delta->status) + ); + } else { + historyEntry = new std::pair >( nextCommit, - strdup(delta->new_file.path) - )); - if (delta->status == GIT_DELTA_ADDED) { - addedFlag = true; - } else if (!isEqualOldFile) { - free(trackPath); - trackPath = strdup(delta->old_file.path); + std::make_pair(strdup(delta->new_file.path), delta->status) + ); } + baton->out->push_back(historyEntry); flag = true; } @@ -127,13 +129,8 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() if (baton->error_code != GIT_OK) { break; } - - if (addedFlag) { - break; - } } - free(trackPath); free(nextOid); if (baton->error_code != GIT_OK) { @@ -142,10 +139,10 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() while(!baton->out->empty()) { - std::pair *pairToFree = baton->out->back(); + std::pair > *pairToFree = baton->out->back(); baton->out->pop_back(); git_commit_free(pairToFree->first); - free(pairToFree->second); + free(pairToFree->second.first); free(pairToFree); } @@ -165,12 +162,15 @@ void GitRevwalk::FileHistoryWalkWorker::HandleOKCallback() Local result = Nan::New(size); for (unsigned int i = 0; i < size; i++) { Local historyEntry = Nan::New(); - std::pair *batonResult = baton->out->at(i); + std::pair > *batonResult = baton->out->at(i); Nan::Set(historyEntry, Nan::New("commit").ToLocalChecked(), GitCommit::New(batonResult->first, true)); - Nan::Set(historyEntry, Nan::New("name").ToLocalChecked(), Nan::New(batonResult->second).ToLocalChecked()); + Nan::Set(historyEntry, Nan::New("status").ToLocalChecked(), Nan::New(batonResult->second.second)); + if (batonResult->second.second == GIT_DELTA_RENAMED) { + Nan::Set(historyEntry, Nan::New("oldName").ToLocalChecked(), Nan::New(batonResult->second.first).ToLocalChecked()); + } Nan::Set(result, Nan::New(i), historyEntry); - free(batonResult->second); + free(batonResult->second.first); free(batonResult); } From c667e1abfd1bf7fef264a00817cd97a128decee5 Mon Sep 17 00:00:00 2001 From: Tyler Wanek Date: Wed, 3 Feb 2016 13:41:22 -0700 Subject: [PATCH 3/6] Rename detection for git log + tests --- .../manual/revwalk/file_history_walk.cc | 62 +++++++- test/tests/revwalk.js | 140 +++++++++++++++++- 2 files changed, 199 insertions(+), 3 deletions(-) diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc index ea779ffb0..bf3030018 100644 --- a/generate/templates/manual/revwalk/file_history_walk.cc +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -80,7 +80,14 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() } } + if ((baton->error_code = git_diff_find_similar(diffs, NULL)) != GIT_OK) { + git_commit_free(nextCommit); + git_commit_free(parent); + break; + } + bool flag = false; + bool doRenamedPass = false; unsigned int numDeltas = git_diff_num_deltas(diffs); for (unsigned int j = 0; j < numDeltas; ++j) { git_patch *nextPatch; @@ -99,16 +106,20 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() bool isEqualNewFile = !strcmp(delta->new_file.path, baton->file_path); if (isEqualNewFile) { + if (delta->status == GIT_DELTA_ADDED) { + doRenamedPass = true; + break; + } std::pair > *historyEntry; if (!isEqualOldFile) { historyEntry = new std::pair >( nextCommit, - std::make_pair(strdup(delta->old_file.path), delta->status) + std::pair(strdup(delta->old_file.path), delta->status) ); } else { historyEntry = new std::pair >( nextCommit, - std::make_pair(strdup(delta->new_file.path), delta->status) + std::pair(strdup(delta->new_file.path), delta->status) ); } baton->out->push_back(historyEntry); @@ -122,6 +133,53 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() } } + if ( + doRenamedPass && + (baton->error_code = git_diff_find_similar(diffs, NULL)) == GIT_OK + ) { + flag = false; + numDeltas = git_diff_num_deltas(diffs); + for (unsigned int j = 0; j < numDeltas; ++j) { + git_patch *nextPatch; + baton->error_code = git_patch_from_diff(&nextPatch, diffs, j); + + if (baton->error_code < GIT_OK) { + break; + } + + if (nextPatch == NULL) { + continue; + } + + const git_diff_delta *delta = git_patch_get_delta(nextPatch); + bool isEqualOldFile = !strcmp(delta->old_file.path, baton->file_path); + bool isEqualNewFile = !strcmp(delta->new_file.path, baton->file_path); + + if (isEqualNewFile) { + std::pair > *historyEntry; + if (!isEqualOldFile) { + historyEntry = new std::pair >( + nextCommit, + std::pair(strdup(delta->old_file.path), delta->status) + ); + } else { + historyEntry = new std::pair >( + nextCommit, + std::pair(strdup(delta->new_file.path), delta->status) + ); + } + baton->out->push_back(historyEntry); + flag = true; + } + + git_patch_free(nextPatch); + + if (flag) { + break; + } + } + } + if (!flag && nextCommit != NULL) { git_commit_free(nextCommit); } diff --git a/test/tests/revwalk.js b/test/tests/revwalk.js index 79fc58e01..518dd3cd3 100644 --- a/test/tests/revwalk.js +++ b/test/tests/revwalk.js @@ -1,4 +1,7 @@ var assert = require("assert"); +var RepoUtils = require("../utils/repository_setup"); +var promisify = require("promisify-node"); +var fse = promisify(require("fs-extra")); var path = require("path"); var local = path.join.bind(path, __dirname); @@ -27,6 +30,7 @@ describe("Revwalk", function() { beforeEach(function() { this.walker = this.repository.createRevWalk(); + this.walker.sorting(NodeGit.Revwalk.SORT.TIME); this.walker.push(this.commit.id()); }); @@ -141,7 +145,7 @@ describe("Revwalk", function() { }); }); - it("can get do a fast walk", function() { + it("can do a fast walk", function() { var test = this; var magicSha = "b8a94aefb22d0534cc0e5acf533989c13d8725dc"; @@ -152,6 +156,140 @@ describe("Revwalk", function() { }); }); + it("can get the history of a file", function() { + var test = this; + var magicShas = [ + "6ed3027eda383d417457b99b38c73f88f601c368", + "95cefff6aabd3c1f6138ec289f42fec0921ff610", + "7ad92a7e4d26a1af93f3450aea8b9d9b8069ea8c", + "96f077977eb1ffcb63f9ce766cdf110e9392fdf5", + "694adc5369687c47e02642941906cfc5cb21e6c2", + "eebd0ead15d62eaf0ba276da53af43bbc3ce43ab", + "1273fff13b3c28cfdb13ba7f575d696d2a8902e1" + ]; + + return test.walker.fileHistoryWalk("include/functions/copy.h", 1000) + .then(function(results) { + var shas = results.map(function(result) { + return result.commit.sha(); + }); + assert.equal(magicShas.length, shas.length); + magicShas.forEach(function(sha, i) { + assert.equal(sha, shas[i]); + }); + }); + }); + + it("can get the history of a file while ignoring parallel branches", + function() { + var test = this; + var magicShas = [ + "f80e085e3118bbd6aad49dad7c53bdc37088bf9b", + "907b29d8a3b765570435c922a59cd849836a7b51" + ]; + var shas; + var walker = test.repository.createRevWalk(); + walker.sorting(NodeGit.Revwalk.SORT.TIME); + walker.push("115d114e2c4d5028c7a78428f16a4528c51be7dd"); + + return walker.fileHistoryWalk("README.md", 15) + .then(function(results) { + shas = results.map(function(result) { + return result.commit.sha(); + }); + assert.equal(magicShas.length, shas.length); + magicShas.forEach(function(sha, i) { + assert.equal(sha, shas[i]); + }); + + magicShas = [ + "4a34168b80fe706f52417106821c9cbfec630e47", + "f80e085e3118bbd6aad49dad7c53bdc37088bf9b", + "694b2d703a02501f288269bea7d1a5d643a83cc8", + "907b29d8a3b765570435c922a59cd849836a7b51" + ]; + + walker = test.repository.createRevWalk(); + walker.sorting(NodeGit.Revwalk.SORT.TIME); + walker.push("d46f7da82969ca6620864d79a55b951be0540bda"); + + return walker.fileHistoryWalk("README.md", 50); + }) + .then(function(results) { + shas = results.map(function(result) { + return result.commit.sha(); + }); + assert.equal(magicShas.length, shas.length); + magicShas.forEach(function(sha, i) { + assert.equal(sha, shas[i]); + }); + }); + }); + + it("can yield information about renames in a file history walk", + function() { + var treeOid; + var repo; + var fileNameA = "a.txt"; + var fileNameB = "b.txt"; + var repoPath = local("../repos/renamedFileRepo"); + var signature = NodeGit.Signature.create("Foo bar", + "foo@bar.com", 123456789, 60); + return RepoUtils.createRepository(repoPath) + .then(function(r) { + repo = r; + return RepoUtils.commitFileToRepo( + repo, + fileNameA, + "line1\nline2\nline3\n" + ); + }) + .then(function() { + return fse.move( + path.join(repoPath, fileNameA), + path.join(repoPath, fileNameB) + ); + }) + .then(function() { + return repo.openIndex() + .then(function(index) { + index.read(1); + index.addByPath(fileNameB); + index.removeByPath(fileNameA); + index.write(); + + return index.writeTree(); + }); + }) + .then(function(oidResult) { + treeOid = oidResult; + return NodeGit.Reference.nameToId(repo, "HEAD"); + }) + .then(function(head) { + return repo.getCommit(head); + }) + .then(function(head) { + return repo.createCommit("HEAD", signature, signature, + "renamed commit", treeOid, [head]); + }) + .then(function() { + return NodeGit.Reference.nameToId(repo, "HEAD"); + }) + .then(function(commitOid) { + var walker = repo.createRevWalk(); + walker.sorting(NodeGit.Revwalk.SORT.TIME); + walker.push(commitOid.tostrS()); + return walker.fileHistoryWalk(fileNameB, 5); + }) + .then(function(results) { + assert.equal(results[0].status, NodeGit.Diff.DELTA.RENAMED); + assert.equal(results[0].oldName, fileNameA); + }) + .then(function() { + return fse.remove(repoPath); + }); + }); + // This test requires forcing garbage collection, so mocha needs to be run // via node rather than npm, with a la `node --expose-gc [pathtohmoca] // [testglob]` From b873633aebdde79aaf967c8ce939837e6420d6cc Mon Sep 17 00:00:00 2001 From: Tyler Wanek Date: Wed, 3 Feb 2016 14:50:48 -0700 Subject: [PATCH 4/6] Update example for file history --- examples/walk-history-for-file.js | 83 ++++++++++++++++++------------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/examples/walk-history-for-file.js b/examples/walk-history-for-file.js index 340308abe..f09415455 100644 --- a/examples/walk-history-for-file.js +++ b/examples/walk-history-for-file.js @@ -1,49 +1,62 @@ var nodegit = require("../"), - path = require("path"); + path = require("path"), + historyFile = "generate/input/descriptor.json", + walker, + historyCommits = [], + commit, + repo; // This code walks the history of the master branch and prints results // that look very similar to calling `git log` from the command line +function compileHistory(resultingArrayOfCommits) { + var lastSha; + if (historyCommits.length > 0) { + lastSha = historyCommits[historyCommits.length - 1].commit.sha(); + if ( + resultingArrayOfCommits.length == 1 && + resultingArrayOfCommits[0].commit.sha() == lastSha + ) { + return; + } + } + + resultingArrayOfCommits.forEach(function(entry) { + historyCommits.push(entry); + }); + + lastSha = historyCommits[historyCommits.length - 1].commit.sha(); + + walker = repo.createRevWalk(); + walker.push(lastSha); + walker.sorting(nodegit.Revwalk.SORT.TIME); + + return walker.fileHistoryWalk(historyFile, 500) + .then(compileHistory); +} + nodegit.Repository.open(path.resolve(__dirname, "../.git")) - .then(function(repo) { + .then(function(r) { + repo = r; return repo.getMasterCommit(); }) .then(function(firstCommitOnMaster){ // History returns an event. - var history = firstCommitOnMaster.history(nodegit.Revwalk.SORT.Time); - var commits = []; - - // History emits "commit" event for each commit in the branch's history - history.on("commit", function(commit) { - return commit.getDiff() - .then(function(diffList) { - diffList.map(function(diff) { - diff.patches().then(function(patches) { - patches.map(function(patch) { - var result = - !!~patch.oldFile().path().indexOf("descriptor.json") || - !!~patch.newFile().path().indexOf("descriptor.json"); - - if(result && !~commits.indexOf(commit)) { - commits.push(commit); - } - }); - }); - }); - }); - }); + walker = repo.createRevWalk(); + walker.push(firstCommitOnMaster.sha()); + walker.sorting(nodegit.Revwalk.SORT.Time); - history.on("end", function() { - commits.forEach(function(commit) { - console.log("commit " + commit.sha()); - console.log("Author:", commit.author().name() + - " <" + commit.author().email() + ">"); - console.log("Date:", commit.date()); - console.log("\n " + commit.message()); - }); + return walker.fileHistoryWalk(historyFile, 500); + }) + .then(compileHistory) + .then(function() { + historyCommits.forEach(function(entry) { + commit = entry.commit; + console.log("commit " + commit.sha()); + console.log("Author:", commit.author().name() + + " <" + commit.author().email() + ">"); + console.log("Date:", commit.date()); + console.log("\n " + commit.message()); }); - - // Don't forget to call `start()`! - history.start(); }) .done(); From 9d548eca877a0cfba58d4a19d18463f5560d3dc4 Mon Sep 17 00:00:00 2001 From: Tyler Wanek Date: Wed, 3 Feb 2016 17:37:03 -0700 Subject: [PATCH 5/6] Manage diffs better Only find similar if there is an added entry Free diffs when we are done with them. --- generate/templates/manual/revwalk/file_history_walk.cc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc index bf3030018..2ad9a24ed 100644 --- a/generate/templates/manual/revwalk/file_history_walk.cc +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -80,12 +80,6 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() } } - if ((baton->error_code = git_diff_find_similar(diffs, NULL)) != GIT_OK) { - git_commit_free(nextCommit); - git_commit_free(parent); - break; - } - bool flag = false; bool doRenamedPass = false; unsigned int numDeltas = git_diff_num_deltas(diffs); @@ -106,7 +100,7 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() bool isEqualNewFile = !strcmp(delta->new_file.path, baton->file_path); if (isEqualNewFile) { - if (delta->status == GIT_DELTA_ADDED) { + if (delta->status == GIT_DELTA_ADDED || delta->status == GIT_DELTA_DELETED) { doRenamedPass = true; break; } @@ -180,6 +174,8 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() } } + git_diff_free(diffs); + if (!flag && nextCommit != NULL) { git_commit_free(nextCommit); } From 11ad5aead7dafcf2db79a0123012f0ca9e07c36c Mon Sep 17 00:00:00 2001 From: Tyler Wanek Date: Wed, 3 Feb 2016 17:44:15 -0700 Subject: [PATCH 6/6] JSdocs for filehistorywalk --- lib/revwalk.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/revwalk.js b/lib/revwalk.js index 4d9844cce..d3cfa0456 100644 --- a/lib/revwalk.js +++ b/lib/revwalk.js @@ -123,3 +123,21 @@ Revwalk.prototype.getCommits = function(count) { return Promise.all(promises); }); }; + +/** + * @typedef historyEntry + * @type {Object} + * @property {Commit} commit the commit for this entry + * @property {Number} status the status of the file in the commit + * @property {String} oldName the old name that is provided when status is + * renamed + */ + +var fileHistoryWalk = Revwalk.prototype.fileHistoryWalk; +/** + * @param {String} filePath + * @param {Number} max_count + * @async + * @return {Array} + */ +Revwalk.prototype.fileHistoryWalk = fileHistoryWalk;