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 }