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
# This class is not backed by a table in the main database.
# It loads the latest Pipeline for the HEAD of a repository, and caches that
# in Redis.
module Gitlab
module Cache
module Ci
class ProjectPipelineStatus
attr_accessor :sha, :status, :ref, :project, :loaded
delegate :commit, to: :project
def self.load_for_project(project)
new(project).tap do |status|
status.load_status
end
end
def self.load_in_batch_for_projects(projects)
cached_results_for_projects(projects).zip(projects).each do |result, project|
project.pipeline_status = new(project, result)
project.pipeline_status.load_status
end
end
def self.cached_results_for_projects(projects)
result = Gitlab::Redis.with do |redis|
redis.multi do
projects.each do |project|
cache_key = cache_key_for_project(project)
redis.exists(cache_key)
redis.hmget(cache_key, :sha, :status, :ref)
end
end
end
result.each_slice(2).map do |(cache_key_exists, (sha, status, ref))|
pipeline_info = { sha: sha, status: status, ref: ref }
{ loaded_from_cache: cache_key_exists, pipeline_info: pipeline_info }
end
end
def self.cache_key_for_project(project)
"projects/#{project.id}/pipeline_status"
end
def self.update_for_pipeline(pipeline)
pipeline_info = {
sha: pipeline.sha,
status: pipeline.status,
ref: pipeline.ref
}
new(pipeline.project, pipeline_info: pipeline_info).
store_in_cache_if_needed
end
def initialize(project, pipeline_info: {}, loaded_from_cache: nil)
@project = project
@sha = pipeline_info[:sha]
@ref = pipeline_info[:ref]
@status = pipeline_info[:status]
@loaded = loaded_from_cache
end
def has_status?
loaded? && sha.present? && status.present?
end
def load_status
return if loaded?
if has_cache?
load_from_cache
else
load_from_project
store_in_cache
end
self.loaded = true
end
def load_from_project
return unless commit
self.sha = commit.sha
self.status = commit.status
self.ref = project.default_branch
end
# We only cache the status for the HEAD commit of a project
# This status is rendered in project lists
def store_in_cache_if_needed
return delete_from_cache unless commit
return unless sha
return unless ref
if commit.sha == sha && project.default_branch == ref
store_in_cache
end
end
def load_from_cache
Gitlab::Redis.with do |redis|
self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref)
end
end
def store_in_cache
Gitlab::Redis.with do |redis|
redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref })
end
end
def delete_from_cache
Gitlab::Redis.with do |redis|
redis.del(cache_key)
end
end
def has_cache?
return self.loaded unless self.loaded.nil?
Gitlab::Redis.with do |redis|
redis.exists(cache_key)
end
end
def loaded?
self.loaded
end
def cache_key
self.class.cache_key_for_project(project)
end
end
end
end
end