-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #111 from augmentable-dev/renames-and-blame
Add blame table (for real this time)
- Loading branch information
Showing
20 changed files
with
413 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package gitqlite | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
|
||
git "github.com/libgit2/git2go/v31" | ||
"github.com/mattn/go-sqlite3" | ||
) | ||
|
||
type GitBlameModule struct { | ||
options *GitBlameModuleOptions | ||
} | ||
|
||
type GitBlameModuleOptions struct { | ||
RepoPath string | ||
} | ||
|
||
func NewGitBlameModule(options *GitBlameModuleOptions) *GitBlameModule { | ||
return &GitBlameModule{options} | ||
} | ||
|
||
type gitBlameTable struct { | ||
repoPath string | ||
} | ||
|
||
func (m *GitBlameModule) EponymousOnlyModule() {} | ||
|
||
func (m *GitBlameModule) Create(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) { | ||
err := c.DeclareVTab(fmt.Sprintf(` | ||
CREATE TABLE %q ( | ||
line_no INT, | ||
path TEXT, | ||
commit_id TEXT, | ||
contents TEXT | ||
)`, args[0])) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &gitBlameTable{repoPath: m.options.RepoPath}, nil | ||
} | ||
|
||
func (m *GitBlameModule) Connect(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) { | ||
return m.Create(c, args) | ||
} | ||
|
||
func (m *GitBlameModule) DestroyModule() {} | ||
|
||
func (v *gitBlameTable) Open() (sqlite3.VTabCursor, error) { | ||
repo, err := git.OpenRepository(v.repoPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &blameCursor{repo: repo}, nil | ||
|
||
} | ||
|
||
func (v *gitBlameTable) BestIndex(cst []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy) (*sqlite3.IndexResult, error) { | ||
// TODO this should actually be implemented! | ||
dummy := make([]bool, len(cst)) | ||
return &sqlite3.IndexResult{Used: dummy}, nil | ||
} | ||
|
||
func (v *gitBlameTable) Disconnect() error { | ||
return nil | ||
} | ||
|
||
func (v *gitBlameTable) Destroy() error { return nil } | ||
|
||
type blameCursor struct { | ||
repo *git.Repository | ||
current *BlamedLine | ||
iter *BlameIterator | ||
} | ||
|
||
func (vc *blameCursor) Column(c *sqlite3.SQLiteContext, col int) error { | ||
blamedLine := vc.current | ||
switch col { | ||
case 0: | ||
c.ResultInt(blamedLine.Line) | ||
case 1: | ||
c.ResultText(blamedLine.File) | ||
case 2: | ||
c.ResultText(blamedLine.CommitID) | ||
case 3: | ||
c.ResultText(blamedLine.Content) | ||
} | ||
|
||
return nil | ||
|
||
} | ||
|
||
func (vc *blameCursor) Filter(idxNum int, idxStr string, vals []interface{}) error { | ||
iterator, err := NewBlameIterator(vc.repo) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
blamedLine, err := iterator.Next() | ||
if err != nil { | ||
if err == io.EOF { | ||
vc.current = nil | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
vc.iter = iterator | ||
vc.current = blamedLine | ||
return nil | ||
} | ||
|
||
func (vc *blameCursor) Next() error { | ||
blamedLine, err := vc.iter.Next() | ||
if err != nil { | ||
if err == io.EOF { | ||
vc.current = nil | ||
return nil | ||
} | ||
return err | ||
} | ||
vc.current = blamedLine | ||
return nil | ||
} | ||
|
||
func (vc *blameCursor) EOF() bool { | ||
return vc.current == nil | ||
} | ||
|
||
func (vc *blameCursor) Rowid() (int64, error) { | ||
return int64(0), nil | ||
} | ||
|
||
func (vc *blameCursor) Close() error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package gitqlite | ||
|
||
import ( | ||
"strings" | ||
|
||
git "github.com/libgit2/git2go/v31" | ||
) | ||
|
||
type BlameIterator struct { | ||
repo *git.Repository | ||
fileIter *commitFileIter | ||
currentBlamedLines []*BlamedLine | ||
currentBlamedLineIdx int | ||
} | ||
|
||
type BlamedLine struct { | ||
File string | ||
Line int | ||
CommitID string | ||
Content string | ||
} | ||
|
||
func NewBlameIterator(repo *git.Repository) (*BlameIterator, error) { | ||
head, err := repo.Head() | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer head.Free() | ||
|
||
// get a new iterator from repo and use the head commit | ||
fileIter, err := NewCommitFileIter(repo, &commitFileIterOptions{head.Target().String()}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &BlameIterator{ | ||
repo, | ||
fileIter, | ||
nil, | ||
0, | ||
}, nil | ||
} | ||
|
||
func (iter *BlameIterator) nextFile() error { | ||
iter.currentBlamedLines = make([]*BlamedLine, 0) | ||
|
||
// grab the next file | ||
file, err := iter.fileIter.Next() | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Free() | ||
|
||
// blame the file | ||
opts, err := git.DefaultBlameOptions() | ||
if err != nil { | ||
return err | ||
} | ||
blame, err := iter.repo.BlameFile(file.path+file.Name, &opts) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
err := blame.Free() | ||
if err != nil { | ||
panic(err) | ||
} | ||
}() | ||
|
||
// store the lines of the file, used as we iterate over hunks | ||
fileContents := file.Contents() | ||
lines := strings.Split(string(fileContents), "\n") | ||
|
||
// iterate over the blame hunks | ||
fileLine := 1 | ||
for i := 0; i < blame.HunkCount(); i++ { | ||
hunk, err := blame.HunkByIndex(i) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// within a hunk, iterate over every line in the hunk | ||
// creating and adding a new BlamedLine for each | ||
for hunkLineOffset := 0; hunkLineOffset < int(hunk.LinesInHunk); hunkLineOffset++ { | ||
// for every line of the hunk, create a BlamedLine | ||
blamedLine := &BlamedLine{ | ||
File: file.path + file.Name, | ||
CommitID: hunk.OrigCommitId.String(), | ||
Line: fileLine + hunkLineOffset, | ||
Content: lines[i+hunkLineOffset], | ||
} | ||
// add it to the list for the current file | ||
iter.currentBlamedLines = append(iter.currentBlamedLines, blamedLine) | ||
// increment the file line by 1 | ||
fileLine++ | ||
} | ||
} | ||
iter.currentBlamedLineIdx = 0 | ||
|
||
return nil | ||
} | ||
|
||
func (iter *BlameIterator) Next() (*BlamedLine, error) { | ||
// if there's no currently blamed lines, grab the next file | ||
if iter.currentBlamedLines == nil { | ||
err := iter.nextFile() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
// if we've exceeded the | ||
if iter.currentBlamedLineIdx >= len(iter.currentBlamedLines) { | ||
err := iter.nextFile() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
// if there's no blamed lines | ||
if len(iter.currentBlamedLines) == 0 { | ||
return iter.Next() | ||
} | ||
|
||
blamedLine := iter.currentBlamedLines[iter.currentBlamedLineIdx] | ||
iter.currentBlamedLineIdx++ | ||
|
||
return blamedLine, nil | ||
} |
Oops, something went wrong.