go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/proto/gitiles/gitiles_fake.go (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gitiles
    16  
    17  import (
    18  	context "context"
    19  	"fmt"
    20  
    21  	"go.chromium.org/luci/common/errors"
    22  	git "go.chromium.org/luci/common/proto/git"
    23  	grpc "google.golang.org/grpc"
    24  )
    25  
    26  const defaultPageSize = 100
    27  
    28  // key: ref name, value: list of commits
    29  type fakeRepository struct {
    30  	refs    map[string]string
    31  	commits map[string]*git.Commit
    32  }
    33  
    34  // Fake allows testing of Gitiles API without using actual Gitiles
    35  // server. User can set data using SetRepository method.
    36  type Fake struct {
    37  	// key: repository name, value fakeRepository
    38  	m        map[string]fakeRepository
    39  	callLogs []any
    40  }
    41  
    42  // Log retrieves commit log. Merge commits are supported, but it implements
    43  // simple logic and likely won't return results in the same order as Gitiles.
    44  func (f *Fake) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogResponse, error) {
    45  	f.addCallLog(in)
    46  	repository, ok := f.m[in.GetProject()]
    47  	if !ok {
    48  		return nil, errors.New("Repository not found")
    49  	}
    50  	committish := in.GetCommittish()
    51  	// Check if committish is a ref
    52  	if commitID, ok := repository.refs[committish]; ok {
    53  		committish = commitID
    54  	}
    55  	commit, ok := repository.commits[committish]
    56  	if !ok {
    57  		return nil, fmt.Errorf("Commit %s not found", committish)
    58  	}
    59  	size := int(in.GetPageSize())
    60  	if size == 0 {
    61  		size = defaultPageSize
    62  	}
    63  
    64  	resp := &LogResponse{
    65  		Log: []*git.Commit{},
    66  	}
    67  	startAdding := in.GetPageToken() == ""
    68  	q := []*git.Commit{commit}
    69  	visited := map[string]struct{}{}
    70  	for size > len(resp.Log) && len(q) > 0 {
    71  		commit = q[0]
    72  		q = q[1:]
    73  		if _, ok := visited[commit.GetId()]; ok {
    74  			continue
    75  		}
    76  		visited[commit.GetId()] = struct{}{}
    77  
    78  		if startAdding {
    79  			resp.Log = append(resp.Log, commit)
    80  		} else if commit.GetId() == in.GetPageToken() {
    81  			startAdding = true
    82  		}
    83  
    84  		for _, commitID := range commit.GetParents() {
    85  			if in.GetExcludeAncestorsOf() == commitID {
    86  				break
    87  			}
    88  			c, ok := repository.commits[commitID]
    89  			if !ok {
    90  				panic(fmt.Sprintf(
    91  					"Broken git chain, commit %s has parent %s which doesn't exist",
    92  					commit.GetId(), commitID))
    93  			}
    94  			q = append(q, c)
    95  		}
    96  	}
    97  	if len(resp.Log) == size {
    98  		resp.NextPageToken = commit.GetId()
    99  	}
   100  	return resp, nil
   101  }
   102  
   103  // Refs retrieves repo refs.
   104  func (f *Fake) Refs(ctx context.Context, in *RefsRequest, opts ...grpc.CallOption) (*RefsResponse, error) {
   105  	f.addCallLog(in)
   106  	p, ok := f.m[in.GetProject()]
   107  	if !ok {
   108  		return nil, errors.New("Repository not found")
   109  	}
   110  	resp := &RefsResponse{
   111  		Revisions: p.refs,
   112  	}
   113  	return resp, nil
   114  }
   115  
   116  // Archive retrieves archived contents of the project. This is not implemented.
   117  //
   118  // An archive is a shallow bundle of the contents of a repository.
   119  //
   120  // DEPRECATED: Use DownloadFile to obtain plain text files.
   121  // TODO(pprabhu): Migrate known users to DownloadFile and delete this RPC.
   122  func (f *Fake) Archive(ctx context.Context, in *ArchiveRequest, opts ...grpc.CallOption) (*ArchiveResponse, error) {
   123  	f.addCallLog(in)
   124  	panic("not implemented")
   125  }
   126  
   127  // DownloadFile retrieves a file from the project. This is not implemented.
   128  func (f *Fake) DownloadFile(ctx context.Context, in *DownloadFileRequest, opts ...grpc.CallOption) (*DownloadFileResponse, error) {
   129  	f.addCallLog(in)
   130  	panic("not implemented")
   131  }
   132  
   133  // Projects retrieves list of available Gitiles projects
   134  func (f *Fake) Projects(ctx context.Context, in *ProjectsRequest, opts ...grpc.CallOption) (*ProjectsResponse, error) {
   135  	f.addCallLog(in)
   136  	resp := &ProjectsResponse{
   137  		Projects: make([]string, len(f.m)),
   138  	}
   139  	i := 0
   140  	for projectName := range f.m {
   141  		resp.Projects[i] = projectName
   142  		i++
   143  	}
   144  	return resp, nil
   145  }
   146  
   147  // SetRepository stores provided references and commits to desired repository.
   148  // If repository is previously set, it will override it.
   149  //
   150  // refs keys are references, keys are revisions.
   151  // Example:
   152  // f.SetRepository(
   153  //
   154  //	"foo",
   155  //	[]string{"refs/heads/master", "rev1"},
   156  //	[]*git.Commit{ {Id: "rev1", Parents: []string{"rev0"}}, {Id: "rev0"} }
   157  //
   158  // )
   159  // Represents following repository:
   160  // name: foo
   161  // references:
   162  // * refs/heads/master points to rev1
   163  // commits:
   164  // rev1 --> rev0 (root commit)
   165  func (f *Fake) SetRepository(repository string, refs map[string]string, commits []*git.Commit) {
   166  	if f.m == nil {
   167  		f.m = map[string]fakeRepository{}
   168  	}
   169  	commitMap := make(map[string]*git.Commit, len(commits))
   170  	for _, commit := range commits {
   171  		if _, ok := commitMap[commit.GetId()]; ok {
   172  			panic(fmt.Sprintf("Duplicated commit with commit hash: %s", commit.GetId()))
   173  		}
   174  		commitMap[commit.GetId()] = commit
   175  	}
   176  	// Sanity check
   177  	for refs, rev := range refs {
   178  		if rev == "" {
   179  			// empty repository
   180  			continue
   181  		}
   182  		if _, ok := commitMap[rev]; !ok {
   183  			panic(fmt.Sprintf("Ref %s points to invalid revision %s", refs, rev))
   184  		}
   185  	}
   186  	f.m[repository] = fakeRepository{
   187  		refs:    refs,
   188  		commits: commitMap,
   189  	}
   190  }
   191  
   192  // GetCallLogs returns callLogs.
   193  func (f *Fake) GetCallLogs() []any {
   194  	return f.callLogs
   195  }
   196  
   197  func (f *Fake) addCallLog(in any) {
   198  	f.callLogs = append(f.callLogs, in)
   199  }