forked from sourcegraph/sourcegraph-public-snapshot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommit_cache.go
More file actions
255 lines (219 loc) · 8.05 KB
/
Copy pathcommit_cache.go
File metadata and controls
255 lines (219 loc) · 8.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
package codenav
import (
"context"
"fmt"
"strings"
"sync"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/database"
"github.com/sourcegraph/sourcegraph/internal/gitserver"
"github.com/sourcegraph/sourcegraph/internal/gitserver/gitdomain"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
type CommitCache interface {
AreCommitsResolvable(ctx context.Context, commits []RepositoryCommit) ([]bool, error)
ExistsBatch(ctx context.Context, commits []RepositoryCommit) ([]bool, error)
SetResolvableCommit(repositoryID int, commit string)
}
type RepositoryCommit struct {
RepositoryID int
Commit string
}
type commitCache struct {
repoStore database.RepoStore
gitserverClient gitserver.Client
mutex sync.RWMutex
cache map[int]map[string]bool
}
func NewCommitCache(repoStore database.RepoStore, client gitserver.Client) CommitCache {
return &commitCache{
repoStore: repoStore,
gitserverClient: client,
cache: map[int]map[string]bool{},
}
}
// ExistsBatch determines if the given commits are resolvable for the given repositories.
// If we do not know the answer from a previous call to set or existsBatch, we ask gitserver
// to resolve the remaining commits and store the results for subsequent calls. This method
// returns a slice of the same size as the input slice, true indicating that the commit at
// the symmetric index exists.
func (c *commitCache) ExistsBatch(ctx context.Context, commits []RepositoryCommit) ([]bool, error) {
exists := make([]bool, len(commits))
rcIndexMap := make([]int, 0, len(commits))
rcs := make([]RepositoryCommit, 0, len(commits))
for i, rc := range commits {
if e, ok := c.getInternal(rc.RepositoryID, rc.Commit); ok {
exists[i] = e
} else {
rcIndexMap = append(rcIndexMap, i)
rcs = append(rcs, RepositoryCommit{
RepositoryID: rc.RepositoryID,
Commit: rc.Commit,
})
}
}
if len(rcs) == 0 {
return exists, nil
}
// Perform heavy work outside of critical section
e, err := c.commitsExist(ctx, rcs)
if err != nil {
return nil, errors.Wrap(err, "gitserverClient.CommitsExist")
}
if len(e) != len(rcs) {
panic(strings.Join([]string{
fmt.Sprintf("Expected slice returned from CommitsExist to have len %d, but has len %d.", len(rcs), len(e)),
"If this panic occurred during a test, your test is missing a mock definition for CommitsExist.",
"If this is occurred during runtime, please file a bug.",
}, " "))
}
for i, rc := range rcs {
exists[rcIndexMap[i]] = e[i]
c.setInternal(rc.RepositoryID, rc.Commit, e[i])
}
return exists, nil
}
// commitsExist determines if the given commits exists in the given repositories. This method returns a
// slice of the same size as the input slice, true indicating that the commit at the symmetric index exists.
func (c *commitCache) commitsExist(ctx context.Context, commits []RepositoryCommit) (_ []bool, err error) {
repositoryIDMap := map[int]struct{}{}
for _, rc := range commits {
repositoryIDMap[rc.RepositoryID] = struct{}{}
}
repositoryIDs := make([]api.RepoID, 0, len(repositoryIDMap))
for repositoryID := range repositoryIDMap {
repositoryIDs = append(repositoryIDs, api.RepoID(repositoryID))
}
repos, err := c.repoStore.GetReposSetByIDs(ctx, repositoryIDs...)
if err != nil {
return nil, err
}
repositoryNames := make(map[int]api.RepoName, len(repos))
for _, v := range repos {
repositoryNames[int(v.ID)] = v.Name
}
// Build the batch request to send to gitserver. Because we only add repo/commit
// pairs that are resolvable to a repo name, we may end up skipping inputs for an
// unresolvable repo. We also ensure that we only represent each repo/commit pair
// ONCE in the input slice.
repoCommits := make([]repoCommit, 0, len(commits)) // input to CommitsExist
indexMapping := make(map[int]int, len(commits)) // map commits[i] to relevant repoCommits[i]
commitsRepresentedInInput := map[int]map[string]int{} // used to populate index mapping
for i, rc := range commits {
repoName, ok := repositoryNames[rc.RepositoryID]
if !ok {
// insert a sentinel value we explicitly check below for any repositories
// that we're unable to resolve
indexMapping[i] = -1
continue
}
// Ensure our second-level mapping exists
if _, ok := commitsRepresentedInInput[rc.RepositoryID]; !ok {
commitsRepresentedInInput[rc.RepositoryID] = map[string]int{}
}
if n, ok := commitsRepresentedInInput[rc.RepositoryID][rc.Commit]; ok {
// repoCommits[n] already represents this pair
indexMapping[i] = n
} else {
// pair is not yet represented in the input, so we'll stash the index of input
// object we're _about_ to insert
n := len(repoCommits)
indexMapping[i] = n
commitsRepresentedInInput[rc.RepositoryID][rc.Commit] = n
repoCommits = append(repoCommits, repoCommit{
repoName: repoName,
commitID: api.CommitID(rc.Commit),
})
}
}
exists := make([]bool, len(commits))
for i, rc := range repoCommits {
_, err := c.gitserverClient.GetCommit(ctx, rc.repoName, rc.commitID)
if err != nil {
if errors.HasType(err, &gitdomain.RevisionNotFoundError{}) {
exists[i] = false
continue
}
return nil, err
}
exists[i] = true
}
// Spread the response back to the correct indexes the caller is expecting. Each value in the
// response from gitserver belongs to some index in the original commits slice. We re-map these
// values and leave all other values implicitly false (these repo name were not resolvable).
out := make([]bool, len(commits))
for i := range commits {
if indexMapping[i] != -1 {
out[i] = exists[indexMapping[i]]
}
}
return out, nil
}
type repoCommit struct {
repoName api.RepoName
commitID api.CommitID
}
// AreCommitsResolvable determines if the given commits are resolvable for the given repositories.
// If we do not know the answer from a previous call to set or AreCommitsResolvable, we ask gitserver
// to resolve the remaining commits and store the results for subsequent calls. This method
// returns a slice of the same size as the input slice, true indicating that the commit at
// the symmetric index exists.
func (c *commitCache) AreCommitsResolvable(ctx context.Context, commits []RepositoryCommit) ([]bool, error) {
exists := make([]bool, len(commits))
rcIndexMap := make([]int, 0, len(commits))
rcs := make([]RepositoryCommit, 0, len(commits))
for i, rc := range commits {
if e, ok := c.getInternal(rc.RepositoryID, rc.Commit); ok {
exists[i] = e
} else {
rcIndexMap = append(rcIndexMap, i)
rcs = append(rcs, RepositoryCommit{
RepositoryID: rc.RepositoryID,
Commit: rc.Commit,
})
}
}
// if there are no repository commits to fetch, we're done
if len(rcs) == 0 {
return exists, nil
}
// Perform heavy work outside of critical section
e, err := c.commitsExist(ctx, rcs)
if err != nil {
return nil, errors.Wrap(err, "gitserverClient.CommitsExist")
}
if len(e) != len(rcs) {
panic(strings.Join([]string{
fmt.Sprintf("Expected slice returned from CommitsExist to have len %d, but has len %d.", len(rcs), len(e)),
"If this panic occurred during a test, your test is missing a mock definition for CommitsExist.",
"If this is occurred during runtime, please file a bug.",
}, " "))
}
for i, rc := range rcs {
exists[rcIndexMap[i]] = e[i]
c.setInternal(rc.RepositoryID, rc.Commit, e[i])
}
return exists, nil
}
// set marks the given repository and commit as valid and resolvable by gitserver.
func (c *commitCache) SetResolvableCommit(repositoryID int, commit string) {
c.setInternal(repositoryID, commit, true)
}
func (c *commitCache) getInternal(repositoryID int, commit string) (bool, bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
if repositoryMap, ok := c.cache[repositoryID]; ok {
if exists, ok := repositoryMap[commit]; ok {
return exists, true
}
}
return false, false
}
func (c *commitCache) setInternal(repositoryID int, commit string, exists bool) {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.cache[repositoryID]; !ok {
c.cache[repositoryID] = map[string]bool{}
}
c.cache[repositoryID][commit] = exists
}