Class: Shipit::Commit

Inherits:
Record
  • Object
show all
Includes:
DeferredTouch
Defined in:
app/models/shipit/commit.rb

Direct Known Subclasses

UndeployedCommit

Constant Summary collapse

RECENT_COMMIT_THRESHOLD =
10.seconds
AmbiguousRevision =
Class.new(StandardError)

Constants included from DeferredTouch

DeferredTouch::CACHE_KEY, DeferredTouch::SET_KEY, DeferredTouch::THROTTLE_TTL, DeferredTouch::TMP_KEY

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DeferredTouch

touch_now!

Methods inherited from Record

serializer_class

Class Method Details

.by_sha(sha) ⇒ Object

Raises:



92
93
94
95
96
97
98
99
# File 'app/models/shipit/commit.rb', line 92

def self.by_sha(sha)
  raise AmbiguousRevision, "Short SHA1 #{sha} is ambiguous (too short)" if sha.to_s.size < 6

  commits = where('sha like ?', "#{sha}%").take(2)
  raise AmbiguousRevision, "Short SHA1 #{sha} is ambiguous (matches multiple commits)" if commits.size > 1

  commits.first
end

.by_sha!(sha) ⇒ Object



101
102
103
# File 'app/models/shipit/commit.rb', line 101

def self.by_sha!(sha)
  by_sha(sha) || raise(ActiveRecord::RecordNotFound, "Couldn't find commit with sha #{sha}")
end

.create_from_github!(commit, extra_attributes = {}) ⇒ Object



138
139
140
141
142
# File 'app/models/shipit/commit.rb', line 138

def self.create_from_github!(commit, extra_attributes = {})
  record = from_github(commit)
  record.update!(extra_attributes)
  record
end

.detach!Object



88
89
90
# File 'app/models/shipit/commit.rb', line 88

def self.detach!
  Commit.where(id: ids).update_all(detached: true)
end

.from_github(commit) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'app/models/shipit/commit.rb', line 105

def self.from_github(commit)
  author = User.find_or_create_author_from_github_commit(commit)
  author ||= Anonymous.new
  committer = User.find_or_create_committer_from_github_commit(commit)
  committer ||= Anonymous.new

  record = new(
    sha: commit.sha,
    message: commit.commit.message,
    author:,
    committer:,
    committed_at: commit.commit.committer.date,
    authored_at: commit.commit.author.date,
    additions: commit.stats&.additions,
    deletions: commit.stats&.deletions
  )

  record.pull_request_head_sha = commit.parents.last.sha if record.pull_request?

  record
end

.lock_all(user) ⇒ Object



345
346
347
348
349
350
# File 'app/models/shipit/commit.rb', line 345

def self.lock_all(user)
  update_all(
    locked: true,
    lock_author_id: user.id
  )
end

.newer_than(commit) ⇒ Object



60
61
62
63
64
# File 'app/models/shipit/commit.rb', line 60

def self.newer_than(commit)
  return all unless commit

  where('id > ?', commit.try(:id) || commit)
end

.older_than(commit) ⇒ Object



66
67
68
69
70
# File 'app/models/shipit/commit.rb', line 66

def self.older_than(commit)
  return all unless commit

  where('id < ?', commit.try(:id) || commit)
end

.since(commit) ⇒ Object



72
73
74
75
76
# File 'app/models/shipit/commit.rb', line 72

def self.since(commit)
  return all unless commit

  where('id >= ?', commit.try(:id) || commit)
end

.successfulObject



84
85
86
# File 'app/models/shipit/commit.rb', line 84

def self.successful
  preload(:statuses).to_a.select(&:success?)
end

.until(commit) ⇒ Object



78
79
80
81
82
# File 'app/models/shipit/commit.rb', line 78

def self.until(commit)
  return all unless commit

  where('id <= ?', commit.try(:id) || commit)
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


221
222
223
224
225
# File 'app/models/shipit/commit.rb', line 221

def active?
  return false unless stack.active_task?

  stack.active_task.includes_commit?(self)
end

#authorObject



31
32
33
# File 'app/models/shipit/commit.rb', line 31

def author
  super || AnonymousUser.new
end

#author=(user) ⇒ Object



35
36
37
# File 'app/models/shipit/commit.rb', line 35

def author=(user)
  super(user.presence)
end

#blocked?Boolean

Returns:

  • (Boolean)


231
232
233
234
235
236
237
# File 'app/models/shipit/commit.rb', line 231

def blocked?
  return false if stack.blocking_statuses.empty?

  # TODO: Perfs might be horrible here if the range is big.
  # We should look at fetching the undeployed commits only once
  stack.commits.reachable.newer_than(stack.last_deployed_commit).older_than(self).any?(&:blocking?)
end

#checksObject



215
216
217
# File 'app/models/shipit/commit.rb', line 215

def checks
  @checks ||= CommitChecks.new(self)
end

#childrenObject



239
240
241
# File 'app/models/shipit/commit.rb', line 239

def children
  self.class.where(stack_id:).newer_than(self)
end

#committerObject



39
40
41
# File 'app/models/shipit/commit.rb', line 39

def committer
  super || AnonymousUser.new
end

#committer=(user) ⇒ Object



43
44
45
# File 'app/models/shipit/commit.rb', line 43

def committer=(user)
  super(user.presence)
end

#create_or_update_check_run_from_github!(github_check_run) ⇒ Object



194
195
196
# File 'app/models/shipit/commit.rb', line 194

def create_or_update_check_run_from_github!(github_check_run)
  check_runs.create_or_update_from_github!(stack_id, github_check_run)
end

#create_release_status!(state, user: nil, target_url: nil, description: nil) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
# File 'app/models/shipit/commit.rb', line 202

def create_release_status!(state, user: nil, target_url: nil, description: nil)
  return unless stack.release_status?

  @last_release_status = nil
  release_statuses.create!(
    stack:,
    user:,
    state:,
    target_url:,
    description:
  )
end

#create_status_from_github!(github_status) ⇒ Object



165
166
167
168
169
# File 'app/models/shipit/commit.rb', line 165

def create_status_from_github!(github_status)
  add_status do
    statuses.replicate_from_github!(stack_id, github_status)
  end
end

#deploy_failed?Boolean

Returns:

  • (Boolean)


312
313
314
# File 'app/models/shipit/commit.rb', line 312

def deploy_failed?
  stack.deploys.unsuccessful.where(until_commit_id: id).any?
end

#deploy_requested_atObject



330
331
332
333
334
335
336
# File 'app/models/shipit/commit.rb', line 330

def deploy_requested_at
  if merge_request&.merged?
    merge_request.merge_requested_at
  else
    created_at
  end
end

#deployable?Boolean

Returns:

  • (Boolean)


227
228
229
# File 'app/models/shipit/commit.rb', line 227

def deployable?
  !locked? && (stack.ignore_ci? || (success? && !blocked?))
end

#deployed?Boolean

Returns:

  • (Boolean)


308
309
310
# File 'app/models/shipit/commit.rb', line 308

def deployed?
  stack.last_deployed_commit.id >= id
end

#detach_children!Object



243
244
245
# File 'app/models/shipit/commit.rb', line 243

def detach_children!
  children.detach!
end

#fetch_stats!Object



297
298
299
300
301
302
# File 'app/models/shipit/commit.rb', line 297

def fetch_stats!
  update!(
    additions: github_commit.stats&.additions,
    deletions: github_commit.stats&.deletions
  )
end

#github_commitObject



289
290
291
# File 'app/models/shipit/commit.rb', line 289

def github_commit
  @github_commit ||= stack.github_api.commit(github_repo_name, sha)
end

#identify_merge_requestObject



316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'app/models/shipit/commit.rb', line 316

def identify_merge_request
  return unless message_parser.pull_request?

  if merge_request = stack.merge_requests.find_by(number: message_parser.pull_request_number)
    self.merge_request = merge_request
    self.pull_request_number = merge_request.number
    self.pull_request_title = merge_request.title
    self.author = merge_request.merge_requested_by if merge_request.merge_requested_by
  end

  self.pull_request_number = message_parser.pull_request_number unless self[:pull_request_number]
  self.pull_request_title = message_parser.pull_request_title unless self[:pull_request_title]
end

#last_release_statusObject



198
199
200
# File 'app/models/shipit/commit.rb', line 198

def last_release_status
  @last_release_status ||= release_statuses.last || Status::Unknown.new(self)
end

#lock(user) ⇒ Object



338
339
340
341
342
343
# File 'app/models/shipit/commit.rb', line 338

def lock(user)
  update!(
    locked: true,
    lock_author_id: user.id
  )
end

#lock_authorObject



47
48
49
# File 'app/models/shipit/commit.rb', line 47

def lock_author
  super || AnonymousUser.new
end

#lock_author=(user) ⇒ Object



51
52
53
# File 'app/models/shipit/commit.rb', line 51

def lock_author=(user)
  super(user.presence)
end

#message=(message) ⇒ Object



127
128
129
130
131
# File 'app/models/shipit/commit.rb', line 127

def message=(message)
  limit = self.class.columns_hash['message'].limit
  message = message.truncate_bytes(limit) if limit && message && message.bytesize > limit
  super(message)
end

#message_headerObject



260
261
262
# File 'app/models/shipit/commit.rb', line 260

def message_header
  message.lines.first.to_s.strip
end

#paginated_check_runs {|response.check_runs| ... } ⇒ Object

Yields:

  • (response.check_runs)


171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'app/models/shipit/commit.rb', line 171

def paginated_check_runs
  response = stack.handle_github_redirections do
    stack.github_api.check_runs(github_repo_name, sha, per_page: 100)
  end

  yield response.check_runs

  until stack.github_api.last_response.rels[:next].nil?
    page = stack.handle_github_redirections do
      stack.github_api.get(stack.github_api.last_response.rels[:next].href)
    end
    yield page.check_runs
  end
end

#pull_request?Boolean

Returns:

  • (Boolean)


247
248
249
# File 'app/models/shipit/commit.rb', line 247

def pull_request?
  pull_request_number.present?
end

#pull_request_numberObject

TODO: remove in a few versions when it is assumed the commits table was backfilled



252
253
254
# File 'app/models/shipit/commit.rb', line 252

def pull_request_number
  super || message_parser.pull_request_number
end

#pull_request_titleObject

TODO: remove in a few versions when it is assumed the commits table was backfilled



265
266
267
# File 'app/models/shipit/commit.rb', line 265

def pull_request_title
  super || message_parser.pull_request_title
end

#recently_pushed?Boolean

Returns:

  • (Boolean)


356
357
358
# File 'app/models/shipit/commit.rb', line 356

def recently_pushed?
  created_at > RECENT_COMMIT_THRESHOLD.ago
end

#refresh_check_runs!Object



186
187
188
189
190
191
192
# File 'app/models/shipit/commit.rb', line 186

def refresh_check_runs!
  paginated_check_runs do |check_runs|
    check_runs.each do |check_run|
      create_or_update_check_run_from_github!(check_run)
    end
  end
end

#refresh_statuses!Object



156
157
158
159
160
161
162
163
# File 'app/models/shipit/commit.rb', line 156

def refresh_statuses!
  github_statuses = stack.handle_github_redirections do
    stack.github_api.statuses(github_repo_name, sha, per_page: 100)
  end
  github_statuses.each do |status|
    create_status_from_github!(status)
  end
end

#reloadObject



133
134
135
136
# File 'app/models/shipit/commit.rb', line 133

def reload(*)
  @status = nil
  super
end

#revert?Boolean

Returns:

  • (Boolean)


269
270
271
# File 'app/models/shipit/commit.rb', line 269

def revert?
  title.start_with?('Revert "') && title.end_with?('"')
end

#revert_of?(commit) ⇒ Boolean

Returns:

  • (Boolean)


273
274
275
# File 'app/models/shipit/commit.rb', line 273

def revert_of?(commit)
  title == %(Revert "#{commit.title}") || title == %(Revert "#{commit.message_header}")
end

#schedule_continuous_deliveryObject



281
282
283
284
285
286
287
# File 'app/models/shipit/commit.rb', line 281

def schedule_continuous_delivery
  return unless deployable? && stack.continuous_deployment? && stack.deployable?

  # This buffer is to allow for statuses and checks to be refreshed before evaluating if the commit is deployable
  # - e.g. if the commit was fast-forwarded with already passing CI.
  ContinuousDeliveryJob.set(wait: RECENT_COMMIT_THRESHOLD).perform_later(stack)
end

#schedule_fetch_stats!Object



293
294
295
# File 'app/models/shipit/commit.rb', line 293

def schedule_fetch_stats!
  FetchCommitStatsJob.perform_later(self)
end

#schedule_refresh_check_runs!Object



152
153
154
# File 'app/models/shipit/commit.rb', line 152

def schedule_refresh_check_runs!
  RefreshCheckRunsJob.perform_later(commit_id: id)
end

#schedule_refresh_statuses!Object



148
149
150
# File 'app/models/shipit/commit.rb', line 148

def schedule_refresh_statuses!
  RefreshStatusesJob.perform_later(commit_id: id)
end

#short_shaObject



277
278
279
# File 'app/models/shipit/commit.rb', line 277

def short_sha
  sha[0..9]
end

#statusObject



304
305
306
# File 'app/models/shipit/commit.rb', line 304

def status
  @status ||= Status::Group.compact(self, statuses_and_check_runs)
end

#statuses_and_check_runsObject



144
145
146
# File 'app/models/shipit/commit.rb', line 144

def statuses_and_check_runs
  statuses + check_runs
end

#titleObject



256
257
258
# File 'app/models/shipit/commit.rb', line 256

def title
  pull_request_title || message_header
end

#unlockObject



352
353
354
# File 'app/models/shipit/commit.rb', line 352

def unlock
  update!(locked: false, lock_author: nil)
end