Skip to content

Commit

Permalink
Merge pull request #63 from augmentable-dev/git_stats_libgit2
Browse files Browse the repository at this point in the history
Git stats libgit2
  • Loading branch information
Vialeon authored Nov 16, 2020
2 parents 01de50a + fb9e68c commit fbc53d4
Show file tree
Hide file tree
Showing 15 changed files with 770 additions and 73 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"go.buildFlags": [
"-tags='sqlite_vtable'"
],
"go.testFlags": [
"-v"
],
"go.vetFlags": [
"-tags='sqlite_vtable'"
],
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ Similar to `git log`, the `commits` table includes all commits in the history of
| parent_id | TEXT |
| parent_count | INT |
| tree_id | TEXT |
| additions | INT |
| deletions | INT |

#### `files`

Expand Down Expand Up @@ -160,6 +158,15 @@ Use the `commit_id` column to filter for files that belong to the work tree of a
| message | TEXT |
| target_type | TEXT |

#### `stats`

| Column | Type |
|-----------|------|
| commit_id | TEXT |
| file | TEXT |
| additions | INT |
| deletions | INT |

### Example Queries

This will return all commits in the history of the currently checked out branch/commit of the repo.
Expand Down
27 changes: 1 addition & 26 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"fmt"
"io/ioutil"
"os"
"os/user"
"path"
"path/filepath"

"github.com/augmentable-dev/askgit/pkg/gitqlite"
Expand Down Expand Up @@ -86,7 +84,7 @@ var rootCmd = &cobra.Command{
if remote, err := vcsurl.Parse(repo); err == nil { // if it can be parsed
dir, err = ioutil.TempDir("", "repo")
handleError(err)
cloneOptions := CreateAuthenticationCallback(remote)
cloneOptions := gitqlite.CreateAuthenticationCallback(remote)
_, err = git.Clone(repo, dir, cloneOptions)
handleError(err)

Expand Down Expand Up @@ -129,29 +127,6 @@ func Execute() {
}

}
func CreateAuthenticationCallback(remote *vcsurl.VCS) *git.CloneOptions {
cloneOptions := &git.CloneOptions{}

if _, err := remote.Remote(vcsurl.SSH); err == nil { // if SSH, use "default" credentials
// use FetchOptions instead of directly RemoteCallbacks
// https://github.com/libgit2/git2go/commit/36e0a256fe79f87447bb730fda53e5cbc90eb47c
cloneOptions.FetchOptions = &git.FetchOptions{
RemoteCallbacks: git.RemoteCallbacks{
CredentialsCallback: func(url string, username string, allowedTypes git.CredType) (*git.Cred, error) {
usr, _ := user.Current()
publicSSH := path.Join(usr.HomeDir, ".ssh/id_rsa.pub")
privateSSH := path.Join(usr.HomeDir, ".ssh/id_rsa")

cred, ret := git.NewCredSshKey("git", publicSSH, privateSSH, "")
return cred, ret
},
CertificateCheckCallback: func(cert *git.Certificate, valid bool, hostname string) git.ErrorCode {
return git.ErrOk
},
}}
}
return cloneOptions
}

func readStdin() (string, error) {
reader := bufio.NewReader(os.Stdin)
Expand Down
9 changes: 4 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ require (
github.com/gitsight/go-vcsurl v1.0.0
github.com/jroimartin/gocui v0.4.0
github.com/kr/text v0.2.0 // indirect
github.com/libgit2/git2go/v30 v30.0.9
github.com/libgit2/git2go/v30 v30.2.2
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-sqlite3 v1.14.2
github.com/mattn/go-sqlite3 v1.14.4
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
github.com/nsf/termbox-go v0.0.0-20201107200903-9b52a5faed9e // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/cobra v1.1.1
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
Expand Down
197 changes: 173 additions & 24 deletions go.sum

Large diffs are not rendered by default.

37 changes: 35 additions & 2 deletions pkg/gitlog/parse_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package gitlog

import (
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"path"
"testing"

"github.com/gitsight/go-vcsurl"
git "github.com/libgit2/git2go/v30"
)

Expand All @@ -31,11 +35,16 @@ func initFixtureRepo() (func() error, error) {
return nil, err
}

fixtureRepo, err = git.Clone(fixtureRepoCloneURL, dir, &git.CloneOptions{})
remote, err := vcsurl.Parse(fixtureRepoCloneURL)
if err != nil {
return nil, err
}

cloneOptions := CreateAuthenticationCallback(remote)
fixtureRepo, err = git.Clone(fixtureRepoCloneURL, dir, cloneOptions)
if err != nil {
fmt.Println(err)
return nil, err
}
fixtureRepoDir = dir

return func() error {
Expand Down Expand Up @@ -96,3 +105,27 @@ func TestParse(t *testing.T) {
t.Fatalf("incorrect number of commits, expected: %d got: %d", shouldBeCount, count)
}
}

func CreateAuthenticationCallback(remote *vcsurl.VCS) *git.CloneOptions {
cloneOptions := &git.CloneOptions{}

if _, err := remote.Remote(vcsurl.SSH); err == nil { // if SSH, use "default" credentials
// use FetchOptions instead of directly RemoteCallbacks
// https://github.com/libgit2/git2go/commit/36e0a256fe79f87447bb730fda53e5cbc90eb47c
cloneOptions.FetchOptions = &git.FetchOptions{
RemoteCallbacks: git.RemoteCallbacks{
CredentialsCallback: func(url string, username string, allowedTypes git.CredType) (*git.Cred, error) {
usr, _ := user.Current()
publicSSH := path.Join(usr.HomeDir, ".ssh/id_rsa.pub")
privateSSH := path.Join(usr.HomeDir, ".ssh/id_rsa")

cred, ret := git.NewCredSshKey("git", publicSSH, privateSSH, "")
return cred, ret
},
CertificateCheckCallback: func(cert *git.Certificate, valid bool, hostname string) git.ErrorCode {
return git.ErrOk
},
}}
}
return cloneOptions
}
4 changes: 1 addition & 3 deletions pkg/gitqlite/git_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ func (m *gitLogModule) Create(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTa
committer_when DATETIME,
parent_id TEXT,
parent_count INT,
tree_id TEXT,
additions INT,
deletions INT
tree_id TEXT
)`, args[0]))
if err != nil {
return nil, err
Expand Down
9 changes: 1 addition & 8 deletions pkg/gitqlite/git_log_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ func (m *gitLogCLIModule) Create(c *sqlite3.SQLiteConn, args []string) (sqlite3.
committer_when DATETIME,
parent_id TEXT,
parent_count INT,
tree_id TEXT,
additions INT,
deletions INT
tree_id TEXT
)`, args[0]))
if err != nil {
return nil, err
Expand Down Expand Up @@ -154,11 +152,6 @@ func (vc *commitCLICursor) Column(c *sqlite3.SQLiteContext, col int) error {
case 11:
//tree_id
c.ResultText(current.TreeID)
case 12:
c.ResultInt(current.Additions)
case 13:
c.ResultInt(current.Deletions)

}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/gitqlite/git_log_cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestCommitCounts(t *testing.T) {
if err != nil {
t.Fatal(err)
}
expected := 14
expected := 12
if len(columns) != expected {
t.Fatalf("expected %d columns, got: %d", expected, len(columns))
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/gitqlite/git_log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestCommits(t *testing.T) {
t.Fatal(err)
}

expected := 14
expected := 12
if len(columns) != expected {
t.Fatalf("expected %d columns, got: %d", expected, len(columns))
}
Expand Down
150 changes: 150 additions & 0 deletions pkg/gitqlite/git_stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package gitqlite

import (
"fmt"
"io"

git "github.com/libgit2/git2go/v30"
"github.com/mattn/go-sqlite3"
)

type gitStatsModule struct{}

type gitStatsTable struct {
repoPath string
repo *git.Repository
}

func (m *gitStatsModule) Create(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
err := c.DeclareVTab(fmt.Sprintf(`
CREATE TABLE %q (
commit_id TEXT,
file TEXT,
additions INT,
deletions INT
)`, args[0]))
if err != nil {
return nil, err
}

// the repoPath will be enclosed in double quotes "..." since ensureTables uses %q when setting up the table
// we need to pop those off when referring to the actual directory in the fs
repoPath := args[3][1 : len(args[3])-1]
return &gitStatsTable{repoPath: repoPath}, nil
}

func (m *gitStatsModule) Connect(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
return m.Create(c, args)
}

func (m *gitStatsModule) DestroyModule() {}

func (v *gitStatsTable) Open() (sqlite3.VTabCursor, error) {
repo, err := git.OpenRepository(v.repoPath)
if err != nil {
return nil, err
}
v.repo = repo

return &StatsCursor{repo: v.repo}, nil
}

func (v *gitStatsTable) BestIndex(cst []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy) (*sqlite3.IndexResult, error) {
used := make([]bool, len(cst))
// TODO implement an index for file name glob patterns?
// TODO this loop construct won't work well for multiple constraints...
for c, constraint := range cst {
switch {
case constraint.Usable && constraint.Column == 0 && constraint.Op == sqlite3.OpEQ:
used[c] = true
return &sqlite3.IndexResult{Used: used, IdxNum: 1, IdxStr: "stats-by-commit-id", EstimatedCost: 1.0, EstimatedRows: 1}, nil
}
}

return &sqlite3.IndexResult{Used: used, EstimatedCost: 100}, nil
}

func (v *gitStatsTable) Disconnect() error {
v.repo = nil
return nil
}
func (v *gitStatsTable) Destroy() error { return nil }

type StatsCursor struct {
repo *git.Repository
iterator *commitStatsIter
current *commitStat
}

func (vc *StatsCursor) Column(c *sqlite3.SQLiteContext, col int) error {
stat := vc.current
switch col {
case 0:
//commit id
c.ResultText(stat.commitID)
case 1:
c.ResultText(stat.file)
case 2:
c.ResultInt(stat.additions)
case 3:
c.ResultInt(stat.deletions)

}

return nil
}
func (vc *StatsCursor) Filter(idxNum int, idxStr string, vals []interface{}) error {
var opt *commitStatsIterOptions

switch idxNum {
case 0:
opt = &commitStatsIterOptions{}
case 1:
opt = &commitStatsIterOptions{commitID: vals[0].(string)}
}

iter, err := NewCommitStatsIter(vc.repo, opt)
if err != nil {
return err
}

vc.iterator = iter

file, err := vc.iterator.Next()
if err != nil {
if err == io.EOF {
vc.current = nil
return nil
}
return err
}

vc.current = file
return nil
}

func (vc *StatsCursor) Next() error {
file, err := vc.iterator.Next()
if err != nil {
if err == io.EOF {
vc.current = nil
return nil
}
return err
}
vc.current = file
return nil
}

func (vc *StatsCursor) EOF() bool {
return vc.current == nil
}

func (vc *StatsCursor) Rowid() (int64, error) {
return int64(0), nil
}

func (vc *StatsCursor) Close() error {
vc.iterator.Close()
return nil
}
Loading

0 comments on commit fbc53d4

Please sign in to comment.