codeintel: Infer go module version via git tags during auto indexing (#11084)

This commit is contained in:
Eric Fritz 2020-05-29 10:22:34 -05:00 committed by GitHub
parent 6596c95fd2
commit 384e74be0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 185 additions and 1 deletions

1
.github/CODEOWNERS vendored
View File

@ -231,6 +231,7 @@ Dockerfile @sourcegraph/distribution
# Precise code intel
/cmd/precise-code-intel-bundle-manager/ @sourcegraph/code-intel
/cmd/precise-code-intel-worker/ @sourcegraph/code-intel
/cmd/precise-code-intel-indexer/ @sourcegraph/code-intel
/internal/cmd/precise-code-intel-test @sourcegraph/code-intel
/internal/codeintel @sourcegraph/code-intel
/enterprise/internal/codeintel @sourcegraph/code-intel

View File

@ -41,9 +41,17 @@ func (p *processor) Process(ctx context.Context, index db.Index) error {
}
func (p *processor) index(ctx context.Context, repoDir string, index db.Index) error {
tag, exact, err := p.gitserverClient.Tags(ctx, p.db, index.RepositoryID, index.Commit)
if err != nil {
return err
}
if !exact {
tag = fmt.Sprintf("%s-%s", tag, index.Commit[:12])
}
args := []string{
"--repositoryRoot=.",
"--moduleVersion=NONE", // TODO - find git tags on this commit to support module version
fmt.Sprintf("--moduleVersion=%s", tag),
}
return command(repoDir, "lsif-go", args...)

View File

@ -27,6 +27,11 @@ type Client interface {
// FileExists determines whether a file exists in a particular commit of a repository.
FileExists(ctx context.Context, db db.DB, repositoryID int, commit, file string) (bool, error)
// Tags returns the git tags associated with the given commit along with a boolean indicating whether
// or not the tag was attached directly to the commit. If no tags exist at or before this commit, the
// tag is an empty string.
Tags(ctx context.Context, db db.DB, repositoryID int, commit string) (string, bool, error)
}
type defaultClient struct{}
@ -52,3 +57,7 @@ func (c *defaultClient) Archive(ctx context.Context, db db.DB, repositoryID int,
func (c *defaultClient) FileExists(ctx context.Context, db db.DB, repositoryID int, commit, file string) (bool, error) {
return FileExists(ctx, db, repositoryID, commit, file)
}
func (c *defaultClient) Tags(ctx context.Context, db db.DB, repositoryID int, commit string) (string, bool, error) {
return Tags(ctx, db, repositoryID, commit)
}

View File

@ -29,6 +29,9 @@ type MockClient struct {
// HeadFunc is an instance of a mock function object controlling the
// behavior of the method Head.
HeadFunc *ClientHeadFunc
// TagsFunc is an instance of a mock function object controlling the
// behavior of the method Tags.
TagsFunc *ClientTagsFunc
}
// NewMockClient creates a new mock of the Client interface. All methods
@ -60,6 +63,11 @@ func NewMockClient() *MockClient {
return "", nil
},
},
TagsFunc: &ClientTagsFunc{
defaultHook: func(context.Context, db.DB, int, string) (string, bool, error) {
return "", false, nil
},
},
}
}
@ -82,6 +90,9 @@ func NewMockClientFrom(i gitserver.Client) *MockClient {
HeadFunc: &ClientHeadFunc{
defaultHook: i.Head,
},
TagsFunc: &ClientTagsFunc{
defaultHook: i.Tags,
},
}
}
@ -658,3 +669,120 @@ func (c ClientHeadFuncCall) Args() []interface{} {
func (c ClientHeadFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// ClientTagsFunc describes the behavior when the Tags method of the parent
// MockClient instance is invoked.
type ClientTagsFunc struct {
defaultHook func(context.Context, db.DB, int, string) (string, bool, error)
hooks []func(context.Context, db.DB, int, string) (string, bool, error)
history []ClientTagsFuncCall
mutex sync.Mutex
}
// Tags delegates to the next hook function in the queue and stores the
// parameter and result values of this invocation.
func (m *MockClient) Tags(v0 context.Context, v1 db.DB, v2 int, v3 string) (string, bool, error) {
r0, r1, r2 := m.TagsFunc.nextHook()(v0, v1, v2, v3)
m.TagsFunc.appendCall(ClientTagsFuncCall{v0, v1, v2, v3, r0, r1, r2})
return r0, r1, r2
}
// SetDefaultHook sets function that is called when the Tags method of the
// parent MockClient instance is invoked and the hook queue is empty.
func (f *ClientTagsFunc) SetDefaultHook(hook func(context.Context, db.DB, int, string) (string, bool, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// Tags method of the parent MockClient instance inovkes the hook at the
// front of the queue and discards it. After the queue is empty, the default
// hook function is invoked for any future action.
func (f *ClientTagsFunc) PushHook(hook func(context.Context, db.DB, int, string) (string, bool, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultDefaultHook with a function that returns
// the given values.
func (f *ClientTagsFunc) SetDefaultReturn(r0 string, r1 bool, r2 error) {
f.SetDefaultHook(func(context.Context, db.DB, int, string) (string, bool, error) {
return r0, r1, r2
})
}
// PushReturn calls PushDefaultHook with a function that returns the given
// values.
func (f *ClientTagsFunc) PushReturn(r0 string, r1 bool, r2 error) {
f.PushHook(func(context.Context, db.DB, int, string) (string, bool, error) {
return r0, r1, r2
})
}
func (f *ClientTagsFunc) nextHook() func(context.Context, db.DB, int, string) (string, bool, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *ClientTagsFunc) appendCall(r0 ClientTagsFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of ClientTagsFuncCall objects describing the
// invocations of this function.
func (f *ClientTagsFunc) History() []ClientTagsFuncCall {
f.mutex.Lock()
history := make([]ClientTagsFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// ClientTagsFuncCall is an object that describes an invocation of method
// Tags on an instance of MockClient.
type ClientTagsFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 db.DB
// Arg2 is the value of the 3rd argument passed to this method
// invocation.
Arg2 int
// Arg3 is the value of the 4th argument passed to this method
// invocation.
Arg3 string
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 string
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 bool
// Result2 is the value of the 3rd result returned from this method
// invocation.
Result2 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c ClientTagsFuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1, c.Arg2, c.Arg3}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c ClientTagsFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1, c.Result2}
}

View File

@ -0,0 +1,38 @@
package gitserver
import (
"context"
"github.com/sourcegraph/sourcegraph/internal/codeintel/db"
)
// Tags returns the git tags associated with the given commit along with a boolean indicating whether
// or not the tag was attached directly to the commit. If no tags exist at or before this commit, the
// tag is an empty string.
func Tags(ctx context.Context, db db.DB, repositoryID int, commit string) (string, bool, error) {
tag, err := execGitCommand(ctx, db, repositoryID, "tag", "-l", "--points-at", commit)
if err != nil {
return "", false, err
}
if tag != "" {
return tag, true, nil
}
// git describe --tags will exit with status 128 (fatal: No names found, cannot describe anything)
// when there are no tags known to the given repo. In order to prevent a gitserver error from
// occurring, we first check to see if there are any tags and early-exit.
tags, err := execGitCommand(ctx, db, repositoryID, "tag", commit)
if err != nil {
return "", false, err
}
if tags == "" {
return "", false, nil
}
tag, err = execGitCommand(ctx, db, repositoryID, "describe", "--tags", "--abbrev=0", commit)
if err != nil {
return "", false, err
}
return tag, false, nil
}