commit
0ec2f46659
|
@ -0,0 +1,157 @@
|
|||
package git
|
||||
|
||||
/*
|
||||
#include <git2.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type BlameOptions struct {
|
||||
Flags BlameOptionsFlag
|
||||
MinMatchCharacters uint16
|
||||
NewestCommit *Oid
|
||||
OldestCommit *Oid
|
||||
MinLine uint32
|
||||
MaxLine uint32
|
||||
}
|
||||
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
cpath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cpath))
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
ecode := C.git_blame_file(&blamePtr, v.ptr, cpath, 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 (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
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
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())
|
||||
}
|
||||
|
||||
wantHunk1 := BlameHunk{
|
||||
LinesInHunk: 1,
|
||||
FinalCommitId: commitId1,
|
||||
FinalStartLineNumber: 1,
|
||||
OrigCommitId: commitId1,
|
||||
OrigPath: "README",
|
||||
OrigStartLineNumber: 1,
|
||||
Boundary: true,
|
||||
}
|
||||
wantHunk2 := BlameHunk{
|
||||
LinesInHunk: 2,
|
||||
FinalCommitId: commitId2,
|
||||
FinalStartLineNumber: 2,
|
||||
OrigCommitId: commitId2,
|
||||
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, label string, hunk, want BlameHunk) {
|
||||
hunk.FinalSignature = nil
|
||||
want.FinalSignature = nil
|
||||
hunk.OrigSignature = nil
|
||||
want.OrigSignature = nil
|
||||
if !reflect.DeepEqual(hunk, want) {
|
||||
t.Fatalf("%s: got hunk %+v, want %+v", label, hunk, want)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue