diff --git a/.gitignore b/.gitignore index 28bf3dea..87a3fb29 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.log *~ .node-version +.DS_Store diff --git a/README.md b/README.md index e5529eac..53477e70 100644 --- a/README.md +++ b/README.md @@ -358,3 +358,13 @@ associated with `path`. `path` - A repository-relative string path. Raises an `Error` if the path isn't readable or if another exception occurs. + +### Repository.getBlame(path) + +Gets the blame information of a given path. + +`path` - A repository-relative string path. + +Raises an `Error` if the path isn't readable or if another exception occurs. + +Returns an array of objects with `commitId`, `signature`, `startLineNumber` and `linesInHunk` keys. diff --git a/spec/fixtures/blame.git/HEAD b/spec/fixtures/blame.git/HEAD new file mode 100644 index 00000000..cb089cd8 --- /dev/null +++ b/spec/fixtures/blame.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/spec/fixtures/blame.git/index b/spec/fixtures/blame.git/index new file mode 100644 index 00000000..8df4e25e Binary files /dev/null and b/spec/fixtures/blame.git/index differ diff --git a/spec/fixtures/blame.git/objects/23/54b25984ae1863618b2a37a6f6d327483e34e4 b/spec/fixtures/blame.git/objects/23/54b25984ae1863618b2a37a6f6d327483e34e4 new file mode 100644 index 00000000..d19be9da Binary files /dev/null and b/spec/fixtures/blame.git/objects/23/54b25984ae1863618b2a37a6f6d327483e34e4 differ diff --git a/spec/fixtures/blame.git/objects/7a/4a0f2a8c1168a2bf7800b0a5f64db64e090b27 b/spec/fixtures/blame.git/objects/7a/4a0f2a8c1168a2bf7800b0a5f64db64e090b27 new file mode 100644 index 00000000..291efbd4 Binary files /dev/null and b/spec/fixtures/blame.git/objects/7a/4a0f2a8c1168a2bf7800b0a5f64db64e090b27 differ diff --git a/spec/fixtures/blame.git/objects/9e/2fa05995713a934a99c88accdbb8ec941f001b b/spec/fixtures/blame.git/objects/9e/2fa05995713a934a99c88accdbb8ec941f001b new file mode 100644 index 00000000..b8c27cec Binary files /dev/null and b/spec/fixtures/blame.git/objects/9e/2fa05995713a934a99c88accdbb8ec941f001b differ diff --git a/spec/fixtures/blame.git/objects/a2/e56f1240cf8ee6fb4912b1fcb631a5d8254d46 b/spec/fixtures/blame.git/objects/a2/e56f1240cf8ee6fb4912b1fcb631a5d8254d46 new file mode 100644 index 00000000..b5f49cbf --- /dev/null +++ b/spec/fixtures/blame.git/objects/a2/e56f1240cf8ee6fb4912b1fcb631a5d8254d46 @@ -0,0 +1 @@ +x5 0 3O1= b);"@t[aRotL6U<_U'S$.TPD9\ό)v% \ No newline at end of file diff --git a/spec/fixtures/blame.git/objects/a3/59b0418fb6c52dd26d806a937b8903bdbf8531 b/spec/fixtures/blame.git/objects/a3/59b0418fb6c52dd26d806a937b8903bdbf8531 new file mode 100644 index 00000000..d86fab2f Binary files /dev/null and b/spec/fixtures/blame.git/objects/a3/59b0418fb6c52dd26d806a937b8903bdbf8531 differ diff --git a/spec/fixtures/blame.git/objects/fc/fc9307b3c7864cbb8bbfe457f44d7a33202de4 b/spec/fixtures/blame.git/objects/fc/fc9307b3c7864cbb8bbfe457f44d7a33202de4 new file mode 100644 index 00000000..d5c5dbe6 Binary files /dev/null and b/spec/fixtures/blame.git/objects/fc/fc9307b3c7864cbb8bbfe457f44d7a33202de4 differ diff --git a/spec/fixtures/blame.git/refs/heads/master b/spec/fixtures/blame.git/refs/heads/master new file mode 100644 index 00000000..897f2e6b --- /dev/null +++ b/spec/fixtures/blame.git/refs/heads/master @@ -0,0 +1 @@ +fcfc9307b3c7864cbb8bbfe457f44d7a33202de4 diff --git a/spec/git-spec.coffee b/spec/git-spec.coffee index 88cc5d41..852d910a 100644 --- a/spec/git-spec.coffee +++ b/spec/git-spec.coffee @@ -724,3 +724,24 @@ describe "git", -> it "throws an error if the file doesn't exist", -> expect(-> repo.add('missing.txt')).toThrow() + + describe ".getBlame(path)", -> + beforeEach -> + repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/blame.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + + it "returns the blame for the path", -> + blame = repo.getBlame('a.txt') + + expect(blame.length).toBe 1 + expect(blame[0].commitId).toBe 'fcfc9307b' + expect(blame[0].startLineNumber).toBe 1 + expect(blame[0].linesInHunk).toBe 3 + expect(blame[0].signature.name).toBe 'Gabriel Isenberg' + expect(blame[0].signature.email).toBe 'gisenberg@gmail.com' + expect(blame[0].signature.when.time).toBe 1442122439 + expect(blame[0].signature.when.offset).toBe -420 + + it "throws an error if the file doesn't exist", -> + expect(-> repo.getBlame('missing.txt')).toThrow() diff --git a/src/repository.cc b/src/repository.cc index 7e9d76ae..aaf963ca 100644 --- a/src/repository.cc +++ b/src/repository.cc @@ -61,6 +61,7 @@ void Repository::Init(Local target) { Nan::SetMethod(proto, "getReferences", Repository::GetReferences); Nan::SetMethod(proto, "checkoutRef", Repository::CheckoutReference); Nan::SetMethod(proto, "add", Repository::Add); + Nan::SetMethod(proto, "getBlame", Repository::GetBlame); target->Set(Nan::New("Repository").ToLocalChecked(), newTemplate->GetFunction()); @@ -910,6 +911,65 @@ NAN_METHOD(Repository::Add) { info.GetReturnValue().Set(Nan::New(true)); } +NAN_METHOD(Repository::GetBlame) { + Nan::HandleScope scope; + + git_repository* repository = GetRepository(info); + std::string path(*String::Utf8Value(info[0])); + + git_blame_options blameOpts = GIT_BLAME_OPTIONS_INIT; + git_blame *blame = NULL; + + if (git_blame_file(&blame, repository, path.c_str(), &blameOpts) != GIT_OK) { + const git_error* e = giterr_last(); + if (e != NULL) + return Nan::ThrowError(e->message); + else + return Nan::ThrowError("Unknown error getting blame."); + } + + size_t hunkCount = git_blame_get_hunk_count(blame); + Local v8Ranges = Nan::New(hunkCount); + + for (size_t i = 0; i < hunkCount; i++) { + const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, i); + const git_signature *finalSig = hunk->final_signature; + char commitId[10] = {0}; + git_oid_tostr(commitId, sizeof(commitId), &hunk->final_commit_id); + + Local v8Range = Nan::New(); + Local signature = Nan::New(); + Local when = Nan::New(); + + when->Set(Nan::New("time").ToLocalChecked(), + Nan::New(finalSig->when.time)); + when->Set(Nan::New("offset").ToLocalChecked(), + Nan::New(finalSig->when.offset)); + + signature->Set(Nan::New("name").ToLocalChecked(), + Nan::New(finalSig->name).ToLocalChecked()); + signature->Set(Nan::New("email").ToLocalChecked(), + Nan::New(finalSig->email).ToLocalChecked()); + signature->Set(Nan::New("when").ToLocalChecked(), + when); + + v8Range->Set(Nan::New("commitId").ToLocalChecked(), + Nan::New(commitId).ToLocalChecked()); + v8Range->Set(Nan::New("startLineNumber").ToLocalChecked(), + Nan::New(hunk->final_start_line_number)); + v8Range->Set(Nan::New("linesInHunk").ToLocalChecked(), + Nan::New(hunk->lines_in_hunk)); + v8Range->Set(Nan::New("signature").ToLocalChecked(), + signature); + + v8Ranges->Set(i, v8Range); + } + + git_blame_free(blame); + + info.GetReturnValue().Set(v8Ranges); +} + Repository::Repository(Local path) { Nan::HandleScope scope; diff --git a/src/repository.h b/src/repository.h index 9f78f36b..7e0baea0 100644 --- a/src/repository.h +++ b/src/repository.h @@ -59,6 +59,7 @@ class Repository : public Nan::ObjectWrap { static NAN_METHOD(GetReferences); static NAN_METHOD(CheckoutReference); static NAN_METHOD(Add); + static NAN_METHOD(GetBlame); static int StatusCallback(const char *path, unsigned int status, void *payload);