Skip to content

Commit

Permalink
Merge pull request #111 from augmentable-dev/renames-and-blame
Browse files Browse the repository at this point in the history
Add blame table (for real this time)
  • Loading branch information
patrickdevivo authored Jan 18, 2021
2 parents dab4fe4 + da11e3d commit 1763e32
Show file tree
Hide file tree
Showing 20 changed files with 413 additions and 7 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ More in-depth examples and documentation can be found below.
- [Tables](#tables)
- [Local Git Repository](#local-git-repository)
- [`commits`](#commits)
- [`blame`](#blame)
- [`stats`](#stats)
- [`files`](#files)
- [`branches`](#branches)
Expand Down Expand Up @@ -148,6 +149,18 @@ Similar to `git log`, the `commits` table includes all commits in the history of
| parent_id | TEXT |
| parent_count | INT |

##### `blame`

Similar to `git blame`, the `blame` table includes blame information for all files in the current HEAD.

| Column | Type |
|-----------------|----------|
| line_no | INT |
| path | TEXT |
| commit_id | TEXT |
| contents | TEXT |


##### `stats`

| Column | Type |
Expand Down
12 changes: 5 additions & 7 deletions pkg/askgit/askgit.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ func (a *AskGit) loadGitQLiteModules(conn *sqlite3.SQLiteConn) error {
return err
}

err = conn.CreateModule("blame", gitqlite.NewGitBlameModule(&gitqlite.GitBlameModuleOptions{RepoPath: a.RepoPath()}))
if err != nil {
return err
}

return nil
}

Expand Down Expand Up @@ -183,13 +188,6 @@ func (a *AskGit) loadGitHubModules(conn *sqlite3.SQLiteConn) error {
if err != nil {
return err
}
err = conn.CreateModule("github_issues", ghqlite.NewIssuesModule(ghqlite.IssuesModuleOptions{
Token: githubToken,
RateLimiter: rateLimiter,
}))
if err != nil {
return err
}

return nil
}
Expand Down
138 changes: 138 additions & 0 deletions pkg/gitqlite/blame.go
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
}
129 changes: 129 additions & 0 deletions pkg/gitqlite/blame_iter.go
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
}
Loading

0 comments on commit 1763e32

Please sign in to comment.