Skip to content

Commit 951c470

Browse files
authored
Merge pull request #280 from erizocosmico/feature/ref_commits
gitbase: implement ref_commits table
2 parents 5db6516 + d2f1e1b commit 951c470

6 files changed

+575
-138
lines changed

database.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const (
1717
RepositoriesTableName = "repositories"
1818
// RemotesTableName is the name of the remotes table.
1919
RemotesTableName = "remotes"
20+
// RefCommitsTableName is the name of the ref commits table.
21+
RefCommitsTableName = "ref_commits"
2022
)
2123

2224
// Database holds all git repository tables
@@ -28,6 +30,7 @@ type Database struct {
2830
blobs sql.Table
2931
repositories sql.Table
3032
remotes sql.Table
33+
refCommits sql.Table
3134
}
3235

3336
// NewDatabase creates a new Database structure and initializes its
@@ -41,6 +44,7 @@ func NewDatabase(name string) sql.Database {
4144
treeEntries: newTreeEntriesTable(),
4245
repositories: newRepositoriesTable(),
4346
remotes: newRemotesTable(),
47+
refCommits: newRefCommitsTable(),
4448
}
4549
}
4650

@@ -58,5 +62,6 @@ func (d *Database) Tables() map[string]sql.Table {
5862
TreeEntriesTableName: d.treeEntries,
5963
RepositoriesTableName: d.repositories,
6064
RemotesTableName: d.remotes,
65+
RefCommitsTableName: d.refCommits,
6166
}
6267
}

database_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func TestDatabase_Tables(t *testing.T) {
3232
sort.Strings(tableNames)
3333
expected := []string{
3434
CommitsTableName,
35+
RefCommitsTableName,
3536
ReferencesTableName,
3637
TreeEntriesTableName,
3738
BlobsTableName,

ref_commits.go

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
package gitbase
2+
3+
import (
4+
"io"
5+
"strings"
6+
7+
git "gopkg.in/src-d/go-git.v4"
8+
"gopkg.in/src-d/go-git.v4/plumbing"
9+
10+
"gopkg.in/src-d/go-git.v4/plumbing/object"
11+
"gopkg.in/src-d/go-git.v4/plumbing/storer"
12+
"gopkg.in/src-d/go-mysql-server.v0/sql"
13+
)
14+
15+
type refCommitsTable struct{}
16+
17+
// RefCommitsSchema is the schema for the ref commits table.
18+
var RefCommitsSchema = sql.Schema{
19+
{Name: "repository_id", Type: sql.Text, Source: RefCommitsTableName},
20+
{Name: "commit_hash", Type: sql.Text, Source: RefCommitsTableName},
21+
{Name: "ref_name", Type: sql.Text, Source: RefCommitsTableName},
22+
{Name: "index", Type: sql.Text, Source: RefCommitsTableName},
23+
}
24+
25+
var _ sql.PushdownProjectionAndFiltersTable = (*refCommitsTable)(nil)
26+
27+
func newRefCommitsTable() sql.Table {
28+
return new(refCommitsTable)
29+
}
30+
31+
func (refCommitsTable) isGitbaseTable() {}
32+
33+
func (refCommitsTable) String() string {
34+
return printTable(RefCommitsTableName, RefCommitsSchema)
35+
}
36+
37+
func (refCommitsTable) Resolved() bool { return true }
38+
39+
func (refCommitsTable) Name() string { return RefCommitsTableName }
40+
41+
func (refCommitsTable) Schema() sql.Schema { return RefCommitsSchema }
42+
43+
func (t *refCommitsTable) TransformUp(f sql.TransformNodeFunc) (sql.Node, error) {
44+
return f(t)
45+
}
46+
47+
func (t *refCommitsTable) TransformExpressionsUp(f sql.TransformExprFunc) (sql.Node, error) {
48+
return t, nil
49+
}
50+
51+
func (refCommitsTable) Children() []sql.Node { return nil }
52+
53+
func (refCommitsTable) RowIter(ctx *sql.Context) (sql.RowIter, error) {
54+
span, ctx := ctx.Span("gitbase.RefCommitsTable")
55+
iter, err := NewRowRepoIter(ctx, &refCommitsIter{ctx: ctx})
56+
if err != nil {
57+
span.Finish()
58+
return nil, err
59+
}
60+
61+
return sql.NewSpanIter(span, iter), nil
62+
}
63+
64+
func (refCommitsTable) HandledFilters(filters []sql.Expression) []sql.Expression {
65+
return handledFilters(RefCommitsTableName, RefCommitsSchema, filters)
66+
}
67+
68+
func (refCommitsTable) WithProjectAndFilters(
69+
ctx *sql.Context,
70+
_, filters []sql.Expression,
71+
) (sql.RowIter, error) {
72+
span, ctx := ctx.Span("gitbase.RefCommitsTable")
73+
iter, err := rowIterWithSelectors(
74+
ctx, RefCommitsSchema, RefCommitsTableName, filters,
75+
[]string{"ref_name", "repository_id"},
76+
func(selectors selectors) (RowRepoIter, error) {
77+
repos, err := selectors.textValues("repository_id")
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
names, err := selectors.textValues("ref_name")
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
for i := range names {
88+
names[i] = strings.ToLower(names[i])
89+
}
90+
91+
return &refCommitsIter{
92+
ctx: ctx,
93+
refNames: names,
94+
repos: repos,
95+
}, nil
96+
},
97+
)
98+
99+
if err != nil {
100+
span.Finish()
101+
return nil, err
102+
}
103+
104+
return sql.NewSpanIter(span, iter), nil
105+
}
106+
107+
type refCommitsIter struct {
108+
ctx *sql.Context
109+
repo *Repository
110+
refs storer.ReferenceIter
111+
head *plumbing.Reference
112+
commits *indexedCommitIter
113+
ref *plumbing.Reference
114+
115+
// selectors for faster filtering
116+
repos []string
117+
refNames []string
118+
}
119+
120+
func (i *refCommitsIter) NewIterator(repo *Repository) (RowRepoIter, error) {
121+
var iter storer.ReferenceIter
122+
var head *plumbing.Reference
123+
if len(i.repos) == 0 || stringContains(i.repos, repo.ID) {
124+
var err error
125+
iter, err = repo.Repo.References()
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
head, err = repo.Repo.Head()
131+
if err != nil && err != plumbing.ErrReferenceNotFound {
132+
return nil, err
133+
}
134+
}
135+
136+
return &refCommitsIter{
137+
ctx: i.ctx,
138+
repo: repo,
139+
refs: iter,
140+
head: head,
141+
repos: i.repos,
142+
refNames: i.refNames,
143+
}, nil
144+
}
145+
146+
func (i *refCommitsIter) shouldVisitRef(ref *plumbing.Reference) bool {
147+
if len(i.refNames) > 0 && !stringContains(i.refNames, strings.ToLower(ref.Name().String())) {
148+
return false
149+
}
150+
151+
return true
152+
}
153+
154+
func (i *refCommitsIter) Next() (sql.Row, error) {
155+
s, ok := i.ctx.Session.(*Session)
156+
if !ok {
157+
return nil, ErrInvalidGitbaseSession.New(i.ctx.Session)
158+
}
159+
160+
for {
161+
if i.refs == nil {
162+
return nil, io.EOF
163+
}
164+
165+
if i.commits == nil {
166+
var ref *plumbing.Reference
167+
if i.head == nil {
168+
var err error
169+
ref, err = i.refs.Next()
170+
if err != nil {
171+
if err == io.EOF {
172+
return nil, io.EOF
173+
}
174+
175+
if s.SkipGitErrors {
176+
continue
177+
}
178+
179+
return nil, err
180+
}
181+
182+
if ref.Type() != plumbing.HashReference {
183+
continue
184+
}
185+
} else {
186+
ref = plumbing.NewHashReference(plumbing.ReferenceName("HEAD"), i.head.Hash())
187+
i.head = nil
188+
}
189+
190+
i.ref = ref
191+
if !i.shouldVisitRef(ref) {
192+
continue
193+
}
194+
195+
commit, err := resolveCommit(i.repo, ref.Hash())
196+
if err != nil {
197+
if s.SkipGitErrors {
198+
continue
199+
}
200+
201+
return nil, err
202+
}
203+
204+
i.commits = newIndexedCommitIter(s.SkipGitErrors, i.repo.Repo, commit)
205+
}
206+
207+
commit, idx, err := i.commits.Next()
208+
if err != nil {
209+
if err == io.EOF {
210+
i.commits = nil
211+
continue
212+
}
213+
return nil, err
214+
}
215+
216+
return sql.NewRow(
217+
i.repo.ID,
218+
commit.Hash.String(),
219+
i.ref.Name().String(),
220+
idx,
221+
), nil
222+
}
223+
}
224+
225+
func (i *refCommitsIter) Close() error {
226+
if i.refs != nil {
227+
i.refs.Close()
228+
}
229+
230+
return nil
231+
}
232+
233+
type indexedCommitIter struct {
234+
skipGitErrors bool
235+
repo *git.Repository
236+
stack []*stackFrame
237+
seen map[plumbing.Hash]struct{}
238+
}
239+
240+
func newIndexedCommitIter(skipGitErrors bool, repo *git.Repository, start *object.Commit) *indexedCommitIter {
241+
return &indexedCommitIter{
242+
skipGitErrors: skipGitErrors,
243+
repo: repo,
244+
stack: []*stackFrame{
245+
{0, 0, []plumbing.Hash{start.Hash}},
246+
},
247+
seen: make(map[plumbing.Hash]struct{}),
248+
}
249+
}
250+
251+
type stackFrame struct {
252+
idx int // idx from the start commit
253+
pos int // pos in the hashes slice
254+
hashes []plumbing.Hash
255+
}
256+
257+
func (i *indexedCommitIter) Next() (*object.Commit, int, error) {
258+
for {
259+
if len(i.stack) == 0 {
260+
return nil, -1, io.EOF
261+
}
262+
263+
frame := i.stack[len(i.stack)-1]
264+
265+
h := frame.hashes[frame.pos]
266+
if _, ok := i.seen[h]; !ok {
267+
i.seen[h] = struct{}{}
268+
}
269+
270+
frame.pos++
271+
if frame.pos >= len(frame.hashes) {
272+
i.stack = i.stack[:len(i.stack)-1]
273+
}
274+
275+
c, err := i.repo.CommitObject(h)
276+
if err != nil {
277+
if i.skipGitErrors {
278+
continue
279+
}
280+
281+
return nil, -1, err
282+
}
283+
284+
if c.NumParents() > 0 {
285+
parents := make([]plumbing.Hash, 0, c.NumParents())
286+
for _, h = range c.ParentHashes {
287+
if _, ok := i.seen[h]; !ok {
288+
parents = append(parents, h)
289+
}
290+
}
291+
292+
if len(parents) > 0 {
293+
i.stack = append(i.stack, &stackFrame{frame.idx + 1, 0, parents})
294+
}
295+
}
296+
297+
return c, frame.idx, nil
298+
}
299+
}

0 commit comments

Comments
 (0)