blob: 06298a8214e11a991bdfd5185ccec286b9892530 [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gitiles
import (
context "context"
"fmt"
"go.chromium.org/luci/common/errors"
git "go.chromium.org/luci/common/proto/git"
grpc "google.golang.org/grpc"
)
const defaultPageSize = 100
// key: ref name, value: list of commits
type fakeRepository struct {
refs map[string]string
commits map[string]*git.Commit
}
// Fake allows testing of Gitiles API without using actual Gitiles
// server. User can set data using SetRepository method.
type Fake struct {
// key: repository name, value fakeRepository
m map[string]fakeRepository
callLogs []interface{}
}
// Log retrieves commit log. Merge commits are supported, but it implements
// simple logic and likely won't return results in the same order as Gitiles.
func (f *Fake) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error) {
f.addCallLog(in)
repository, ok := f.m[in.GetProject()]
if !ok {
return nil, errors.New("Repository not found")
}
committish := in.GetCommittish()
// Check if committish is a ref
if commitID, ok := repository.refs[committish]; ok {
committish = commitID
}
commit, ok := repository.commits[committish]
if !ok {
return nil, fmt.Errorf("Commit %s not found", committish)
}
size := int(in.GetPageSize())
if size == 0 {
size = defaultPageSize
}
resp := &LogResponse{
Log: []*git.Commit{},
}
startAdding := in.GetPageToken() == ""
q := []*git.Commit{commit}
visited := map[string]struct{}{}
for size > len(resp.Log) && len(q) > 0 {
commit = q[0]
q = q[1:]
if _, ok := visited[commit.GetId()]; ok {
continue
}
visited[commit.GetId()] = struct{}{}
if startAdding {
resp.Log = append(resp.Log, commit)
} else if commit.GetId() == in.GetPageToken() {
startAdding = true
}
for _, commitID := range commit.GetParents() {
if in.GetExcludeAncestorsOf() == commitID {
break
}
c, ok := repository.commits[commitID]
if !ok {
panic(fmt.Sprintf(
"Broken git chain, commit %s has parent %s which doesn't exist",
commit.GetId(), commitID))
}
q = append(q, c)
}
}
if len(resp.Log) == size {
resp.NextPageToken = commit.GetId()
}
return resp, nil
}
// Refs retrieves repo refs.
func (f *Fake) Refs(ctx context.Context, in *RefsRequest, opts ...grpc.CallOption) (*RefsResponse, error) {
f.addCallLog(in)
p, ok := f.m[in.GetProject()]
if !ok {
return nil, errors.New("Repository not found")
}
resp := &RefsResponse{
Revisions: p.refs,
}
return resp, nil
}
// Archive retrieves archived contents of the project. This is not implemented.
//
// An archive is a shallow bundle of the contents of a repository.
//
// DEPRECATED: Use DownloadFile to obtain plain text files.
// TODO(pprabhu): Migrate known users to DownloadFile and delete this RPC.
func (f *Fake) Archive(ctx context.Context, in *ArchiveRequest, opts ...grpc.CallOption) (*ArchiveResponse, error) {
f.addCallLog(in)
panic("not implemented")
}
// DownloadFile retrieves a file from the project. This is not implemented.
func (f *Fake) DownloadFile(ctx context.Context, in *DownloadFileRequest, opts ...grpc.CallOption) (*DownloadFileResponse, error) {
f.addCallLog(in)
panic("not implemented")
}
// Projects retrieves list of available Gitiles projects
func (f *Fake) Projects(ctx context.Context, in *ProjectsRequest, opts ...grpc.CallOption) (*ProjectsResponse, error) {
f.addCallLog(in)
resp := &ProjectsResponse{
Projects: make([]string, len(f.m)),
}
i := 0
for projectName := range f.m {
resp.Projects[i] = projectName
i++
}
return resp, nil
}
// SetRepository stores provided references and commits to desired repository.
// If repository is previously set, it will override it.
//
// refs keys are references, keys are revisions.
// Example:
// f.SetRepository(
// "foo",
// []string{"refs/heads/master", "rev1"},
// []*git.Commit{ {Id: "rev1", Parents: []string{"rev0"}}, {Id: "rev0"} }
// )
// Represents following repository:
// name: foo
// references:
// * refs/heads/master points to rev1
// commits:
// rev1 --> rev0 (root commit)
func (f *Fake) SetRepository(repository string, refs map[string]string, commits []*git.Commit) {
if f.m == nil {
f.m = map[string]fakeRepository{}
}
commitMap := make(map[string]*git.Commit, len(commits))
for _, commit := range commits {
if _, ok := commitMap[commit.GetId()]; ok {
panic(fmt.Sprintf("Duplicated commit with commit hash: %s", commit.GetId()))
}
commitMap[commit.GetId()] = commit
}
// Sanity check
for refs, rev := range refs {
if rev == "" {
// empty repository
continue
}
if _, ok := commitMap[rev]; !ok {
panic(fmt.Sprintf("Ref %s points to invalid revision %s", refs, rev))
}
}
f.m[repository] = fakeRepository{
refs: refs,
commits: commitMap,
}
}
// GetCallLogs returns callLogs.
func (f *Fake) GetCallLogs() []interface{} {
return f.callLogs
}
func (f *Fake) addCallLog(in interface{}) {
f.callLogs = append(f.callLogs, in)
}