From 65ff78deadeeea5e801ccbaa428f9721c1f3177b Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Mon, 17 Nov 2014 19:44:21 -0800 Subject: [PATCH 1/5] Add BlameFile func and options for git-blaming files --- blame.go | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ blame_test.go | 63 +++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 blame.go create mode 100644 blame_test.go diff --git a/blame.go b/blame.go new file mode 100644 index 0000000..713f83e --- /dev/null +++ b/blame.go @@ -0,0 +1,138 @@ +package git + +/* +#include +#include +*/ +import "C" +import "runtime" + +type BlameOptions struct { + Flags BlameOptionsFlag + MinMatchCharacters uint16 + NewestCommit *Oid + OldestCommit *Oid + MinLine uint32 + MaxLine uint32 +} + +func DefaultBlameOptions() (BlameOptions, error) { + opts := C.git_blame_options{} + ecode := C.git_blame_init_options(&opts, C.GIT_BLAME_OPTIONS_VERSION) + if ecode < 0 { + return BlameOptions{}, MakeGitError(ecode) + } + + return BlameOptions{ + Flags: BlameOptionsFlag(opts.flags), + MinMatchCharacters: uint16(opts.min_match_characters), + NewestCommit: newOidFromC(&opts.newest_commit), + OldestCommit: newOidFromC(&opts.oldest_commit), + MinLine: uint32(opts.min_line), + MaxLine: uint32(opts.max_line), + }, nil +} + +type BlameOptionsFlag uint32 + +const ( + BlameNormal BlameOptionsFlag = C.GIT_BLAME_NORMAL + BlameTrackCopiesSameFile BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_FILE + BlameTrackCopiesSameCommitMoves BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES + BlameTrackCopiesSameCommitCopies BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES + BlameTrackCopiesAnyCommitCopies BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES + BlameFirstParent BlameOptionsFlag = C.GIT_BLAME_FIRST_PARENT +) + +func (v *Repository) BlameFile(path string, opts *BlameOptions) (*Blame, error) { + var blamePtr *C.git_blame + + var copts *C.git_blame_options + if opts != nil { + copts = &C.git_blame_options{ + version: C.GIT_BLAME_OPTIONS_VERSION, + flags: C.uint32_t(opts.Flags), + min_match_characters: C.uint16_t(opts.MinMatchCharacters), + min_line: C.uint32_t(opts.MinLine), + max_line: C.uint32_t(opts.MaxLine), + } + if opts.NewestCommit != nil { + copts.newest_commit = *opts.NewestCommit.toC() + } + if opts.OldestCommit != nil { + copts.oldest_commit = *opts.OldestCommit.toC() + } + } + + ecode := C.git_blame_file(&blamePtr, v.ptr, C.CString(path), copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newBlameFromC(blamePtr), nil +} + +type Blame struct { + ptr *C.git_blame +} + +func (blame *Blame) HunkCount() int { + return int(C.git_blame_get_hunk_count(blame.ptr)) +} + +func (blame *Blame) HunkByIndex(index int) (BlameHunk, error) { + ptr := C.git_blame_get_hunk_byindex(blame.ptr, C.uint32_t(index)) + if ptr == nil { + return BlameHunk{}, ErrInvalid + } + return blameHunkFromC(ptr), nil +} + +func newBlameFromC(ptr *C.git_blame) *Blame { + if ptr == nil { + return nil + } + + blame := &Blame{ + ptr: ptr, + } + + runtime.SetFinalizer(blame, (*Blame).Free) + return blame +} + +func (blame *Blame) Free() error { + if blame.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(blame, nil) + C.git_blame_free(blame.ptr) + blame.ptr = nil + return nil +} + +type BlameHunk struct { + LinesInHunk uint16 + FinalCommitId *Oid + FinalStartLineNumber uint16 + FinalSignature *Signature + OrigCommitId *Oid + OrigPath string + OrigStartLineNumber uint16 + OrigSignature *Signature + Boundary bool +} + +func blameHunkFromC(hunk *C.git_blame_hunk) BlameHunk { + return BlameHunk{ + LinesInHunk: uint16(hunk.lines_in_hunk), + FinalCommitId: newOidFromC(&hunk.final_commit_id), + FinalStartLineNumber: uint16(hunk.final_start_line_number), + FinalSignature: newSignatureFromC(hunk.final_signature), + OrigCommitId: newOidFromC(&hunk.orig_commit_id), + OrigPath: C.GoString(hunk.orig_path), + OrigStartLineNumber: uint16(hunk.orig_start_line_number), + OrigSignature: newSignatureFromC(hunk.orig_signature), + Boundary: hunk.boundary == 1, + } +} diff --git a/blame_test.go b/blame_test.go new file mode 100644 index 0000000..7c730a0 --- /dev/null +++ b/blame_test.go @@ -0,0 +1,63 @@ +package git + +import ( + "os" + "reflect" + "testing" +) + +func TestBlame(t *testing.T) { + repo := createTestRepo(t) + defer repo.Free() + defer os.RemoveAll(repo.Workdir()) + + commitId1, _ := seedTestRepo(t, repo) + commitId2, _ := updateReadme(t, repo, "foo\nbar\nbaz\n") + + opts := BlameOptions{ + NewestCommit: commitId2, + OldestCommit: nil, + MinLine: 1, + MaxLine: 3, + } + blame, err := repo.BlameFile("README", &opts) + checkFatal(t, err) + defer blame.Free() + if blame.HunkCount() != 2 { + t.Errorf("got hunk count %d, want 2", blame.HunkCount()) + } + + hunk1, err := blame.HunkByIndex(0) + checkFatal(t, err) + checkHunk(t, hunk1, BlameHunk{ + LinesInHunk: 1, + FinalCommitId: commitId1, + FinalStartLineNumber: 1, + OrigCommitId: commitId1, + OrigPath: "README", + OrigStartLineNumber: 1, + Boundary: true, + }) + + hunk2, err := blame.HunkByIndex(1) + checkFatal(t, err) + checkHunk(t, hunk2, BlameHunk{ + LinesInHunk: 2, + FinalCommitId: commitId2, + FinalStartLineNumber: 2, + OrigCommitId: commitId2, + OrigPath: "README", + OrigStartLineNumber: 2, + Boundary: false, + }) +} + +func checkHunk(t *testing.T, hunk, want BlameHunk) { + hunk.FinalSignature = nil + want.FinalSignature = nil + hunk.OrigSignature = nil + want.OrigSignature = nil + if !reflect.DeepEqual(hunk, want) { + t.Fatalf("got hunk %+v, want %+v", hunk, want) + } +} -- 2.45.2 From 609a9a3cdf9e99d7d11e271a8838f9db6b40573a Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Tue, 18 Nov 2014 04:55:10 -0800 Subject: [PATCH 2/5] omit unnecessary #include --- blame.go | 1 - 1 file changed, 1 deletion(-) diff --git a/blame.go b/blame.go index 713f83e..5384038 100644 --- a/blame.go +++ b/blame.go @@ -2,7 +2,6 @@ package git /* #include -#include */ import "C" import "runtime" -- 2.45.2 From 8b39eb795340dff00f16f93d6e9d154fabc98b14 Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Tue, 18 Nov 2014 04:57:26 -0800 Subject: [PATCH 3/5] lock OS thread when MakeGitError might be called --- blame.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/blame.go b/blame.go index 5384038..55100a4 100644 --- a/blame.go +++ b/blame.go @@ -16,6 +16,9 @@ type BlameOptions struct { } func DefaultBlameOptions() (BlameOptions, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + opts := C.git_blame_options{} ecode := C.git_blame_init_options(&opts, C.GIT_BLAME_OPTIONS_VERSION) if ecode < 0 { @@ -63,6 +66,9 @@ func (v *Repository) BlameFile(path string, opts *BlameOptions) (*Blame, error) } } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_blame_file(&blamePtr, v.ptr, C.CString(path), copts) if ecode < 0 { return nil, MakeGitError(ecode) -- 2.45.2 From ebb657ce2f8fdeac32dda6fb88d3a20548e034a7 Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Tue, 18 Nov 2014 04:58:23 -0800 Subject: [PATCH 4/5] free C string --- blame.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/blame.go b/blame.go index 55100a4..cd5665a 100644 --- a/blame.go +++ b/blame.go @@ -4,7 +4,10 @@ package git #include */ import "C" -import "runtime" +import ( + "runtime" + "unsafe" +) type BlameOptions struct { Flags BlameOptionsFlag @@ -66,10 +69,13 @@ func (v *Repository) BlameFile(path string, opts *BlameOptions) (*Blame, error) } } + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + runtime.LockOSThread() defer runtime.UnlockOSThread() - ecode := C.git_blame_file(&blamePtr, v.ptr, C.CString(path), copts) + ecode := C.git_blame_file(&blamePtr, v.ptr, cpath, copts) if ecode < 0 { return nil, MakeGitError(ecode) } -- 2.45.2 From 3268bdbeb2314d696ea608b439c3c98927a9db1a Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Tue, 18 Nov 2014 05:06:03 -0800 Subject: [PATCH 5/5] add (*Blame).HunkByLine (git_blame_get_hunk_byline) and test --- blame.go | 8 ++++++++ blame_test.go | 33 ++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/blame.go b/blame.go index cd5665a..c24c934 100644 --- a/blame.go +++ b/blame.go @@ -99,6 +99,14 @@ func (blame *Blame) HunkByIndex(index int) (BlameHunk, error) { return blameHunkFromC(ptr), nil } +func (blame *Blame) HunkByLine(lineno int) (BlameHunk, error) { + ptr := C.git_blame_get_hunk_byline(blame.ptr, C.uint32_t(lineno)) + if ptr == nil { + return BlameHunk{}, ErrInvalid + } + return blameHunkFromC(ptr), nil +} + func newBlameFromC(ptr *C.git_blame) *Blame { if ptr == nil { return nil diff --git a/blame_test.go b/blame_test.go index 7c730a0..1785042 100644 --- a/blame_test.go +++ b/blame_test.go @@ -27,9 +27,7 @@ func TestBlame(t *testing.T) { t.Errorf("got hunk count %d, want 2", blame.HunkCount()) } - hunk1, err := blame.HunkByIndex(0) - checkFatal(t, err) - checkHunk(t, hunk1, BlameHunk{ + wantHunk1 := BlameHunk{ LinesInHunk: 1, FinalCommitId: commitId1, FinalStartLineNumber: 1, @@ -37,11 +35,8 @@ func TestBlame(t *testing.T) { OrigPath: "README", OrigStartLineNumber: 1, Boundary: true, - }) - - hunk2, err := blame.HunkByIndex(1) - checkFatal(t, err) - checkHunk(t, hunk2, BlameHunk{ + } + wantHunk2 := BlameHunk{ LinesInHunk: 2, FinalCommitId: commitId2, FinalStartLineNumber: 2, @@ -49,15 +44,31 @@ func TestBlame(t *testing.T) { OrigPath: "README", OrigStartLineNumber: 2, Boundary: false, - }) + } + + hunk1, err := blame.HunkByIndex(0) + checkFatal(t, err) + checkHunk(t, "index 0", hunk1, wantHunk1) + + hunk2, err := blame.HunkByIndex(1) + checkFatal(t, err) + checkHunk(t, "index 1", hunk2, wantHunk2) + + hunkLine1, err := blame.HunkByLine(1) + checkFatal(t, err) + checkHunk(t, "line 1", hunkLine1, wantHunk1) + + hunkLine2, err := blame.HunkByLine(3) + checkFatal(t, err) + checkHunk(t, "line 2", hunkLine2, wantHunk2) } -func checkHunk(t *testing.T, hunk, want BlameHunk) { +func checkHunk(t *testing.T, label string, hunk, want BlameHunk) { hunk.FinalSignature = nil want.FinalSignature = nil hunk.OrigSignature = nil want.OrigSignature = nil if !reflect.DeepEqual(hunk, want) { - t.Fatalf("got hunk %+v, want %+v", hunk, want) + t.Fatalf("%s: got hunk %+v, want %+v", label, hunk, want) } } -- 2.45.2