Skip to content

Commit 0938569

Browse files
committed
functions: COMMIT_STATS, function implementation
Signed-off-by: Máximo Cuadros <[email protected]>
1 parent ead93a3 commit 0938569

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

internal/function/commit_stats.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package function
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/src-d/gitbase"
7+
"github.com/src-d/gitbase/internal/commitstats"
8+
9+
"gopkg.in/src-d/go-git.v4/plumbing"
10+
"gopkg.in/src-d/go-git.v4/plumbing/object"
11+
"gopkg.in/src-d/go-mysql-server.v0/sql"
12+
)
13+
14+
// CommitStats calculates the diff stats for a given commit.
15+
type CommitStats struct {
16+
Repository sql.Expression
17+
From sql.Expression
18+
To sql.Expression
19+
}
20+
21+
// NewCommitStats creates a new COMMIT_STATS function.
22+
func NewCommitStats(args ...sql.Expression) (sql.Expression, error) {
23+
f := &CommitStats{}
24+
switch len(args) {
25+
case 2:
26+
f.Repository, f.To = args[0], args[1]
27+
case 3:
28+
f.Repository, f.From, f.To = args[0], args[1], args[2]
29+
default:
30+
return nil, sql.ErrInvalidArgumentNumber.New("COMMIT_STATS", "2 or 3", len(args))
31+
}
32+
33+
return f, nil
34+
}
35+
36+
func (f *CommitStats) String() string {
37+
if f.From == nil {
38+
return fmt.Sprintf("commit_stats(%s, %s)", f.Repository, f.To)
39+
}
40+
41+
return fmt.Sprintf("commit_stats(%s, %s, %s)", f.Repository, f.From, f.To)
42+
}
43+
44+
// Type implements the Expression interface.
45+
func (CommitStats) Type() sql.Type {
46+
return sql.JSON
47+
}
48+
49+
// TransformUp implements the Expression interface.
50+
func (f *CommitStats) TransformUp(fn sql.TransformExprFunc) (sql.Expression, error) {
51+
repo, err := f.Repository.TransformUp(fn)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
to, err := f.To.TransformUp(fn)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
if f.From == nil {
62+
return fn(&CommitStats{Repository: repo, To: to})
63+
}
64+
65+
from, err := f.From.TransformUp(fn)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
return fn(&CommitStats{Repository: repo, From: from, To: to})
71+
}
72+
73+
// Children implements the Expression interface.
74+
func (f *CommitStats) Children() []sql.Expression {
75+
if f.From == nil {
76+
return []sql.Expression{f.Repository, f.To}
77+
}
78+
79+
return []sql.Expression{f.Repository, f.From, f.To}
80+
}
81+
82+
// IsNullable implements the Expression interface.
83+
func (*CommitStats) IsNullable() bool {
84+
return false
85+
}
86+
87+
// Resolved implements the Expression interface.
88+
func (f *CommitStats) Resolved() bool {
89+
return f.To.Resolved() && (f.From == nil || f.From.Resolved())
90+
}
91+
92+
// Eval implements the Expression interface.
93+
func (f *CommitStats) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
94+
span, ctx := ctx.Span("gitbase.CommitStats")
95+
defer span.Finish()
96+
97+
r, err := f.resolveRepo(ctx, row)
98+
if err != nil {
99+
return nil, err
100+
}
101+
102+
to, err := f.resolveCommit(ctx, r, row, f.To)
103+
if err != nil {
104+
return nil, err
105+
}
106+
107+
from, err := f.resolveCommit(ctx, r, row, f.From)
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
return commitstats.Calculate(r.Repository, from, to)
113+
}
114+
115+
func (f *CommitStats) resolveRepo(ctx *sql.Context, r sql.Row) (*gitbase.Repository, error) {
116+
repoID, err := exprToString(ctx, f.Repository, r)
117+
if err != nil {
118+
return nil, err
119+
}
120+
s, ok := ctx.Session.(*gitbase.Session)
121+
if !ok {
122+
return nil, gitbase.ErrInvalidGitbaseSession.New(ctx.Session)
123+
}
124+
return s.Pool.GetRepo(repoID)
125+
}
126+
127+
func (f *CommitStats) resolveCommit(
128+
ctx *sql.Context, r *gitbase.Repository, row sql.Row, e sql.Expression,
129+
) (*object.Commit, error) {
130+
131+
str, err := exprToString(ctx, e, row)
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
if str == "" {
137+
return nil, nil
138+
}
139+
140+
commitHash, err := r.ResolveRevision(plumbing.Revision(str))
141+
if err != nil {
142+
h := plumbing.NewHash(str)
143+
commitHash = &h
144+
}
145+
146+
return r.CommitObject(*commitHash)
147+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package function
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/src-d/gitbase"
8+
"github.com/src-d/gitbase/internal/commitstats"
9+
"github.com/stretchr/testify/require"
10+
11+
"gopkg.in/src-d/go-git-fixtures.v3"
12+
"gopkg.in/src-d/go-git.v4/plumbing/cache"
13+
"gopkg.in/src-d/go-mysql-server.v0/sql"
14+
"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
15+
)
16+
17+
func TestCommitStatsEval(t *testing.T) {
18+
require.NoError(t, fixtures.Init())
19+
defer func() {
20+
require.NoError(t, fixtures.Clean())
21+
}()
22+
23+
path := fixtures.ByTag("worktree").One().Worktree().Root()
24+
25+
pool := gitbase.NewRepositoryPool(cache.DefaultMaxSize)
26+
require.NoError(t, pool.AddGitWithID("worktree", path))
27+
28+
session := gitbase.NewSession(pool)
29+
ctx := sql.NewContext(context.TODO(), sql.WithSession(session))
30+
31+
testCases := []struct {
32+
name string
33+
repo sql.Expression
34+
from sql.Expression
35+
to sql.Expression
36+
row sql.Row
37+
expected *commitstats.CommitStats
38+
}{
39+
{
40+
name: "init commit",
41+
repo: expression.NewGetField(0, sql.Text, "repository_id", false),
42+
from: nil,
43+
to: expression.NewGetField(1, sql.Text, "commit_hash", false),
44+
row: sql.NewRow("worktree", "b029517f6300c2da0f4b651b8642506cd6aaf45d"),
45+
expected: &commitstats.CommitStats{
46+
Files: 2,
47+
Other: commitstats.KindStats{Additions: 22, Deletions: 0},
48+
Total: commitstats.KindStats{Additions: 22, Deletions: 0},
49+
},
50+
},
51+
}
52+
53+
for _, tc := range testCases {
54+
t.Run(tc.name, func(t *testing.T) {
55+
diff, err := NewCommitStats(tc.repo, tc.from, tc.to)
56+
require.NoError(t, err)
57+
58+
result, err := diff.Eval(ctx, tc.row)
59+
require.NoError(t, err)
60+
61+
stats, ok := result.(*commitstats.CommitStats)
62+
require.True(t, ok)
63+
64+
require.EqualValues(t, tc.expected, stats)
65+
})
66+
}
67+
}

internal/function/registry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import "gopkg.in/src-d/go-mysql-server.v0/sql"
44

55
// Functions for gitbase queries.
66
var Functions = []sql.Function{
7+
sql.FunctionN{Name: "commit_stats", Fn: NewCommitStats},
78
sql.Function1{Name: "is_tag", Fn: NewIsTag},
89
sql.Function1{Name: "is_remote", Fn: NewIsRemote},
910
sql.FunctionN{Name: "language", Fn: NewLanguage},

0 commit comments

Comments
 (0)