|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* This file is part of Fabrica. |
|
5
|
|
|
* |
|
6
|
|
|
* (c) Alexandre Salomé <[email protected]> |
|
7
|
|
|
* (c) Julien DIDIER <[email protected]> |
|
8
|
|
|
* |
|
9
|
|
|
* This source file is subject to the GPL license that is bundled |
|
10
|
|
|
* with this source code in the file LICENSE. |
|
11
|
|
|
*/ |
|
12
|
|
|
|
|
13
|
|
|
namespace Fabrica\Models\Code; |
|
14
|
|
|
|
|
15
|
|
|
use Pedreiro\Models\Base; |
|
16
|
|
|
|
|
17
|
|
|
class Commit extends Base |
|
18
|
|
|
{ |
|
19
|
|
|
|
|
20
|
|
|
public static $apresentationName = 'Commits'; |
|
21
|
|
|
|
|
22
|
|
|
protected $organizationPerspective = true; |
|
23
|
|
|
|
|
24
|
|
|
protected $table = 'repository_commits'; |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* The attributes that are mass assignable. |
|
28
|
|
|
* |
|
29
|
|
|
* @var array |
|
30
|
|
|
*/ |
|
31
|
|
|
protected $fillable = [ |
|
32
|
|
|
'code', |
|
33
|
|
|
'date', |
|
34
|
|
|
'author', |
|
35
|
|
|
'message', |
|
36
|
|
|
'reference' |
|
37
|
|
|
]; |
|
38
|
|
|
|
|
39
|
|
|
public function stayInBranchOfAmbiente(Ambiente $ambiente) |
|
40
|
|
|
{ |
|
41
|
|
|
// @todo |
|
42
|
|
|
return true; |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
public function getApresentationName() |
|
46
|
|
|
{ |
|
47
|
|
|
return 'Numero do Commit'; |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
public function repository() |
|
51
|
|
|
{ |
|
52
|
|
|
return $this->belongsTo(Repository::class, 'repository_id', 'id'); |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
|
|
56
|
|
|
/*# coding: utf-8 |
|
57
|
|
|
# frozen_string_literal: true |
|
58
|
|
|
|
|
59
|
|
|
class Commit |
|
60
|
|
|
extend ActiveModel::Naming |
|
61
|
|
|
extend Gitlab::Cache::RequestCache |
|
62
|
|
|
|
|
63
|
|
|
include ActiveModel::Conversion |
|
64
|
|
|
include Noteable |
|
65
|
|
|
include Participable |
|
66
|
|
|
include Mentionable |
|
67
|
|
|
include Referable |
|
68
|
|
|
include StaticModel |
|
69
|
|
|
include Presentable |
|
70
|
|
|
include ::Gitlab::Utils::StrongMemoize |
|
71
|
|
|
|
|
72
|
|
|
attr_mentionable :safe_message, pipeline: :single_line |
|
73
|
|
|
|
|
74
|
|
|
participant :author |
|
75
|
|
|
participant :committer |
|
76
|
|
|
participant :notes_with_associations |
|
77
|
|
|
|
|
78
|
|
|
attr_accessor :project, :author |
|
79
|
|
|
attr_accessor :redacted_description_html |
|
80
|
|
|
attr_accessor :redacted_title_html |
|
81
|
|
|
attr_accessor :redacted_full_title_html |
|
82
|
|
|
attr_reader :gpg_commit |
|
83
|
|
|
|
|
84
|
|
|
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] |
|
85
|
|
|
|
|
86
|
|
|
# Commits above this size will not be rendered in HTML |
|
87
|
|
|
DIFF_HARD_LIMIT_FILES = 1000 |
|
88
|
|
|
DIFF_HARD_LIMIT_LINES = 50000 |
|
89
|
|
|
|
|
90
|
|
|
MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH |
|
91
|
|
|
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze |
|
92
|
|
|
# Used by GFM to match and present link extensions on node texts and hrefs. |
|
93
|
|
|
LINK_EXTENSION_PATTERN = /(patch)/.freeze |
|
94
|
|
|
|
|
95
|
|
|
def banzai_render_context(field) |
|
96
|
|
|
pipeline = field == :description ? :commit_description : :single_line |
|
97
|
|
|
context = { pipeline: pipeline, project: self.project } |
|
98
|
|
|
context[:author] = self.author if self.author |
|
99
|
|
|
|
|
100
|
|
|
context |
|
101
|
|
|
end |
|
102
|
|
|
|
|
103
|
|
|
class << self |
|
104
|
|
|
def decorate(commits, project) |
|
105
|
|
|
commits.map do |commit| |
|
106
|
|
|
if commit.is_a?(Commit) |
|
107
|
|
|
commit |
|
108
|
|
|
else |
|
109
|
|
|
self.new(commit, project) |
|
110
|
|
|
end |
|
111
|
|
|
end |
|
112
|
|
|
end |
|
113
|
|
|
|
|
114
|
|
|
# Calculate number of lines to render for diffs |
|
115
|
|
|
def diff_line_count(diffs) |
|
116
|
|
|
diffs.reduce(0) { |sum, d| sum + Gitlab::Git::Util.count_lines(d.diff) } |
|
117
|
|
|
end |
|
118
|
|
|
|
|
119
|
|
|
def order_by(collection:, order_by:, sort:) |
|
120
|
|
|
return collection unless %w[email name commits].include?(order_by) |
|
121
|
|
|
return collection unless %w[asc desc].include?(sort) |
|
122
|
|
|
|
|
123
|
|
|
collection.sort do |a, b| |
|
124
|
|
|
operands = [a, b].tap { |o| o.reverse! if sort == 'desc' } |
|
125
|
|
|
|
|
126
|
|
|
attr1, attr2 = operands.first.public_send(order_by), operands.second.public_send(order_by) # rubocop:disable PublicSend |
|
127
|
|
|
|
|
128
|
|
|
# use case insensitive comparison for string values |
|
129
|
|
|
order_by.in?(%w[email name]) ? attr1.casecmp(attr2) : attr1 <=> attr2 |
|
130
|
|
|
end |
|
131
|
|
|
end |
|
132
|
|
|
|
|
133
|
|
|
# Truncate sha to 8 characters |
|
134
|
|
|
def truncate_sha(sha) |
|
135
|
|
|
sha[0..MIN_SHA_LENGTH] |
|
136
|
|
|
end |
|
137
|
|
|
|
|
138
|
|
|
def max_diff_options |
|
139
|
|
|
{ |
|
140
|
|
|
max_files: DIFF_HARD_LIMIT_FILES, |
|
141
|
|
|
max_lines: DIFF_HARD_LIMIT_LINES |
|
142
|
|
|
} |
|
143
|
|
|
end |
|
144
|
|
|
|
|
145
|
|
|
def from_hash(hash, project) |
|
146
|
|
|
raw_commit = Gitlab::Git::Commit.new(project.repository.raw, hash) |
|
147
|
|
|
new(raw_commit, project) |
|
148
|
|
|
end |
|
149
|
|
|
|
|
150
|
|
|
def valid_hash?(key) |
|
151
|
|
|
!!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key) |
|
152
|
|
|
end |
|
153
|
|
|
|
|
154
|
|
|
def lazy(project, oid) |
|
155
|
|
|
BatchLoader.for({ project: project, oid: oid }).batch do |items, loader| |
|
156
|
|
|
items_by_project = items.group_by { |i| i[:project] } |
|
157
|
|
|
|
|
158
|
|
|
items_by_project.each do |project, commit_ids| |
|
159
|
|
|
oids = commit_ids.map { |i| i[:oid] } |
|
160
|
|
|
|
|
161
|
|
|
project.repository.commits_by(oids: oids).each do |commit| |
|
162
|
|
|
loader.call({ project: commit.project, oid: commit.id }, commit) if commit |
|
163
|
|
|
end |
|
164
|
|
|
end |
|
165
|
|
|
end |
|
166
|
|
|
end |
|
167
|
|
|
|
|
168
|
|
|
def parent_class |
|
169
|
|
|
::Project |
|
170
|
|
|
end |
|
171
|
|
|
end |
|
172
|
|
|
|
|
173
|
|
|
attr_accessor :raw |
|
174
|
|
|
|
|
175
|
|
|
def initialize(raw_commit, project) |
|
176
|
|
|
raise "Nil as raw commit passed" unless raw_commit |
|
177
|
|
|
|
|
178
|
|
|
@raw = raw_commit |
|
179
|
|
|
@project = project |
|
180
|
|
|
@statuses = {} |
|
181
|
|
|
@gpg_commit = Gitlab::Gpg::Commit.new(self) if project |
|
182
|
|
|
end |
|
183
|
|
|
|
|
184
|
|
|
def id |
|
185
|
|
|
raw.id |
|
186
|
|
|
end |
|
187
|
|
|
|
|
188
|
|
|
def project_id |
|
189
|
|
|
project.id |
|
190
|
|
|
end |
|
191
|
|
|
|
|
192
|
|
|
def ==(other) |
|
193
|
|
|
other.is_a?(self.class) && raw == other.raw |
|
194
|
|
|
end |
|
195
|
|
|
|
|
196
|
|
|
def self.reference_prefix |
|
197
|
|
|
'@' |
|
198
|
|
|
end |
|
199
|
|
|
|
|
200
|
|
|
# Pattern used to extract commit references from text |
|
201
|
|
|
# |
|
202
|
|
|
# This pattern supports cross-project references. |
|
203
|
|
|
def self.reference_pattern |
|
204
|
|
|
@reference_pattern ||= %r{ |
|
205
|
|
|
(?:#{Project.reference_pattern}#{reference_prefix})? |
|
206
|
|
|
(?<commit>#{COMMIT_SHA_PATTERN}) |
|
207
|
|
|
}x |
|
208
|
|
|
end |
|
209
|
|
|
|
|
210
|
|
|
def self.link_reference_pattern |
|
211
|
|
|
@link_reference_pattern ||= |
|
212
|
|
|
super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})?(\.(?<extension>#{LINK_EXTENSION_PATTERN}))?/) |
|
213
|
|
|
end |
|
214
|
|
|
|
|
215
|
|
|
def to_reference(from = nil, full: false) |
|
216
|
|
|
commit_reference(from, id, full: full) |
|
217
|
|
|
end |
|
218
|
|
|
|
|
219
|
|
|
def reference_link_text(from = nil, full: false) |
|
220
|
|
|
commit_reference(from, short_id, full: full) |
|
221
|
|
|
end |
|
222
|
|
|
|
|
223
|
|
|
def diff_line_count |
|
224
|
|
|
@diff_line_count ||= Commit.diff_line_count(raw_diffs) |
|
225
|
|
|
@diff_line_count |
|
226
|
|
|
end |
|
227
|
|
|
|
|
228
|
|
|
# Returns the commits title. |
|
229
|
|
|
# |
|
230
|
|
|
# Usually, the commit title is the first line of the commit message. |
|
231
|
|
|
# In case this first line is longer than 100 characters, it is cut off |
|
232
|
|
|
# after 80 characters + `...` |
|
233
|
|
|
def title |
|
234
|
|
|
return full_title if full_title.length < 100 |
|
235
|
|
|
|
|
236
|
|
|
# Use three dots instead of the ellipsis Unicode character because |
|
237
|
|
|
# some clients show the raw Unicode value in the merge commit. |
|
238
|
|
|
full_title.truncate(81, separator: ' ', omission: '...') |
|
239
|
|
|
end |
|
240
|
|
|
|
|
241
|
|
|
# Returns the full commits title |
|
242
|
|
|
def full_title |
|
243
|
|
|
@full_title ||= |
|
244
|
|
|
if safe_message.blank? |
|
245
|
|
|
no_commit_message |
|
246
|
|
|
else |
|
247
|
|
|
safe_message.split(/[\r\n]/, 2).first |
|
248
|
|
|
end |
|
249
|
|
|
end |
|
250
|
|
|
|
|
251
|
|
|
# Returns full commit message if title is truncated (greater than 99 characters) |
|
252
|
|
|
# otherwise returns commit message without first line |
|
253
|
|
|
def description |
|
254
|
|
|
return safe_message if full_title.length >= 100 |
|
255
|
|
|
return no_commit_message if safe_message.blank? |
|
256
|
|
|
|
|
257
|
|
|
safe_message.split("\n", 2)[1].try(:chomp) |
|
258
|
|
|
end |
|
259
|
|
|
|
|
260
|
|
|
def description? |
|
261
|
|
|
description.present? |
|
262
|
|
|
end |
|
263
|
|
|
|
|
264
|
|
|
def hook_attrs(with_changed_files: false) |
|
265
|
|
|
data = { |
|
266
|
|
|
id: id, |
|
267
|
|
|
message: safe_message, |
|
268
|
|
|
timestamp: committed_date.xmlschema, |
|
269
|
|
|
url: Gitlab::UrlBuilder.build(self), |
|
270
|
|
|
author: { |
|
271
|
|
|
name: author_name, |
|
272
|
|
|
email: author_email |
|
273
|
|
|
} |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
if with_changed_files |
|
277
|
|
|
data.merge!(repo_changes) |
|
278
|
|
|
end |
|
279
|
|
|
|
|
280
|
|
|
data |
|
281
|
|
|
end |
|
282
|
|
|
|
|
283
|
|
|
# Discover issues should be closed when this commit is pushed to a project's |
|
284
|
|
|
# default branch. |
|
285
|
|
|
def closes_issues(current_user = self.committer) |
|
286
|
|
|
Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) |
|
287
|
|
|
end |
|
288
|
|
|
|
|
289
|
|
|
def lazy_author |
|
290
|
|
|
BatchLoader.for(author_email.downcase).batch do |emails, loader| |
|
291
|
|
|
users = User.by_any_email(emails).includes(:emails) |
|
292
|
|
|
|
|
293
|
|
|
emails.each do |email| |
|
294
|
|
|
user = users.find { |u| u.any_email?(email) } |
|
295
|
|
|
|
|
296
|
|
|
loader.call(email, user) |
|
297
|
|
|
end |
|
298
|
|
|
end |
|
299
|
|
|
end |
|
300
|
|
|
|
|
301
|
|
|
def author |
|
302
|
|
|
# We use __sync so that we get the actual objects back (including an actual |
|
303
|
|
|
# nil), instead of a wrapper, as returning a wrapped nil breaks a lot of |
|
304
|
|
|
# code. |
|
305
|
|
|
lazy_author.__sync |
|
306
|
|
|
end |
|
307
|
|
|
request_cache(:author) { author_email.downcase } |
|
308
|
|
|
|
|
309
|
|
|
def committer |
|
310
|
|
|
@committer ||= User.find_by_any_email(committer_email) |
|
311
|
|
|
end |
|
312
|
|
|
|
|
313
|
|
|
def parents |
|
314
|
|
|
@parents ||= parent_ids.map { |oid| Commit.lazy(project, oid) } |
|
315
|
|
|
end |
|
316
|
|
|
|
|
317
|
|
|
def parent |
|
318
|
|
|
strong_memoize(:parent) do |
|
319
|
|
|
project.commit_by(oid: self.parent_id) if self.parent_id |
|
320
|
|
|
end |
|
321
|
|
|
end |
|
322
|
|
|
|
|
323
|
|
|
def notes |
|
324
|
|
|
project.notes.for_commit_id(self.id) |
|
325
|
|
|
end |
|
326
|
|
|
|
|
327
|
|
|
def discussion_notes |
|
328
|
|
|
notes.non_diff_notes |
|
329
|
|
|
end |
|
330
|
|
|
|
|
331
|
|
|
def notes_with_associations |
|
332
|
|
|
notes.includes(:author, :award_emoji) |
|
333
|
|
|
end |
|
334
|
|
|
|
|
335
|
|
|
def merge_requests |
|
336
|
|
|
@merge_requests ||= project.merge_requests.by_commit_sha(sha) |
|
337
|
|
|
end |
|
338
|
|
|
|
|
339
|
|
|
def method_missing(method, *args, &block) |
|
340
|
|
|
@raw.__send__(method, *args, &block) # rubocop:disable GitlabSecurity/PublicSend |
|
341
|
|
|
end |
|
342
|
|
|
|
|
343
|
|
|
def respond_to_missing?(method, include_private = false) |
|
344
|
|
|
@raw.respond_to?(method, include_private) || super |
|
345
|
|
|
end |
|
346
|
|
|
|
|
347
|
|
|
def short_id |
|
348
|
|
|
@raw.short_id(MIN_SHA_LENGTH) |
|
349
|
|
|
end |
|
350
|
|
|
|
|
351
|
|
|
def diff_refs |
|
352
|
|
|
Gitlab::Diff::DiffRefs.new( |
|
353
|
|
|
base_sha: self.parent_id || Gitlab::Git::BLANK_SHA, |
|
354
|
|
|
head_sha: self.sha |
|
355
|
|
|
) |
|
356
|
|
|
end |
|
357
|
|
|
|
|
358
|
|
|
def pipelines |
|
359
|
|
|
project.ci_pipelines.where(sha: sha) |
|
360
|
|
|
end |
|
361
|
|
|
|
|
362
|
|
|
def last_pipeline |
|
363
|
|
|
strong_memoize(:last_pipeline) do |
|
364
|
|
|
pipelines.last |
|
365
|
|
|
end |
|
366
|
|
|
end |
|
367
|
|
|
|
|
368
|
|
|
def status(ref = nil) |
|
369
|
|
|
return @statuses[ref] if @statuses.key?(ref) |
|
370
|
|
|
|
|
371
|
|
|
@statuses[ref] = status_for_project(ref, project) |
|
372
|
|
|
end |
|
373
|
|
|
|
|
374
|
|
|
def status_for_project(ref, pipeline_project) |
|
375
|
|
|
pipeline_project.ci_pipelines.latest_status_per_commit(id, ref)[id] |
|
376
|
|
|
end |
|
377
|
|
|
|
|
378
|
|
|
def set_status_for_ref(ref, status) |
|
379
|
|
|
@statuses[ref] = status |
|
380
|
|
|
end |
|
381
|
|
|
|
|
382
|
|
|
def signature |
|
383
|
|
|
return @signature if defined?(@signature) |
|
384
|
|
|
|
|
385
|
|
|
@signature = gpg_commit.signature |
|
386
|
|
|
end |
|
387
|
|
|
|
|
388
|
|
|
delegate :has_signature?, to: :gpg_commit |
|
389
|
|
|
|
|
390
|
|
|
def revert_branch_name |
|
391
|
|
|
"revert-#{short_id}" |
|
392
|
|
|
end |
|
393
|
|
|
|
|
394
|
|
|
def cherry_pick_branch_name |
|
395
|
|
|
project.repository.next_branch("cherry-pick-#{short_id}", mild: true) |
|
396
|
|
|
end |
|
397
|
|
|
|
|
398
|
|
|
def cherry_pick_description(user) |
|
399
|
|
|
message_body = ["(cherry picked from commit #{sha})"] |
|
400
|
|
|
|
|
401
|
|
|
if merged_merge_request?(user) |
|
402
|
|
|
commits_in_merge_request = merged_merge_request(user).commits |
|
403
|
|
|
|
|
404
|
|
|
if commits_in_merge_request.present? |
|
405
|
|
|
message_body << "" |
|
406
|
|
|
|
|
407
|
|
|
commits_in_merge_request.reverse.each do |commit_in_merge| |
|
408
|
|
|
message_body << "#{commit_in_merge.short_id} #{commit_in_merge.title}" |
|
409
|
|
|
end |
|
410
|
|
|
end |
|
411
|
|
|
end |
|
412
|
|
|
|
|
413
|
|
|
message_body.join("\n") |
|
414
|
|
|
end |
|
415
|
|
|
|
|
416
|
|
|
def cherry_pick_message(user) |
|
417
|
|
|
%Q{#{message}\n\n#{cherry_pick_description(user)}} |
|
418
|
|
|
end |
|
419
|
|
|
|
|
420
|
|
|
def revert_description(user) |
|
421
|
|
|
if merged_merge_request?(user) |
|
422
|
|
|
"This reverts merge request #{merged_merge_request(user).to_reference}" |
|
423
|
|
|
else |
|
424
|
|
|
"This reverts commit #{sha}" |
|
425
|
|
|
end |
|
426
|
|
|
end |
|
427
|
|
|
|
|
428
|
|
|
def revert_message(user) |
|
429
|
|
|
%Q{Revert "#{title.strip}"\n\n#{revert_description(user)}} |
|
430
|
|
|
end |
|
431
|
|
|
|
|
432
|
|
|
def reverts_commit?(commit, user) |
|
433
|
|
|
description? && description.include?(commit.revert_description(user)) |
|
434
|
|
|
end |
|
435
|
|
|
|
|
436
|
|
|
def merge_commit? |
|
437
|
|
|
parent_ids.size > 1 |
|
438
|
|
|
end |
|
439
|
|
|
|
|
440
|
|
|
def merged_merge_request(current_user) |
|
441
|
|
|
# Memoize with per-user access check |
|
442
|
|
|
@merged_merge_request_hash ||= Hash.new do |hash, user| |
|
443
|
|
|
hash[user] = merged_merge_request_no_cache(user) |
|
444
|
|
|
end |
|
445
|
|
|
|
|
446
|
|
|
@merged_merge_request_hash[current_user] |
|
447
|
|
|
end |
|
448
|
|
|
|
|
449
|
|
|
def has_been_reverted?(current_user, notes_association = nil) |
|
450
|
|
|
ext = all_references(current_user) |
|
451
|
|
|
notes_association ||= notes_with_associations |
|
452
|
|
|
|
|
453
|
|
|
notes_association.system.each do |note| |
|
454
|
|
|
note.all_references(current_user, extractor: ext) |
|
455
|
|
|
end |
|
456
|
|
|
|
|
457
|
|
|
ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self, current_user) } |
|
458
|
|
|
end |
|
459
|
|
|
|
|
460
|
|
|
def change_type_title(user) |
|
461
|
|
|
merged_merge_request?(user) ? 'merge request' : 'commit' |
|
462
|
|
|
end |
|
463
|
|
|
|
|
464
|
|
|
# Get the URI type of the given path |
|
465
|
|
|
# |
|
466
|
|
|
# Used to build URLs to files in the repository in GFM. |
|
467
|
|
|
# |
|
468
|
|
|
# path - String path to check |
|
469
|
|
|
# |
|
470
|
|
|
# Examples: |
|
471
|
|
|
# |
|
472
|
|
|
# uri_type('doc/README.md') # => :blob |
|
473
|
|
|
# uri_type('doc/logo.png') # => :raw |
|
474
|
|
|
# uri_type('doc/api') # => :tree |
|
475
|
|
|
# uri_type('not/found') # => nil |
|
476
|
|
|
# |
|
477
|
|
|
# Returns a symbol |
|
478
|
|
|
def uri_type(path) |
|
479
|
|
|
entry = @raw.tree_entry(path) |
|
480
|
|
|
return unless entry |
|
481
|
|
|
|
|
482
|
|
|
if entry[:type] == :blob |
|
483
|
|
|
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project) |
|
484
|
|
|
blob.image? || blob.video? ? :raw : :blob |
|
485
|
|
|
else |
|
486
|
|
|
entry[:type] |
|
487
|
|
|
end |
|
488
|
|
|
end |
|
489
|
|
|
|
|
490
|
|
|
def raw_diffs(*args) |
|
491
|
|
|
raw.diffs(*args) |
|
492
|
|
|
end |
|
493
|
|
|
|
|
494
|
|
|
def raw_deltas |
|
495
|
|
|
@deltas ||= raw.deltas |
|
496
|
|
|
end |
|
497
|
|
|
|
|
498
|
|
|
def diffs(diff_options = {}) |
|
499
|
|
|
Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options) |
|
500
|
|
|
end |
|
501
|
|
|
|
|
502
|
|
|
def persisted? |
|
503
|
|
|
true |
|
504
|
|
|
end |
|
505
|
|
|
|
|
506
|
|
|
def to_ability_name |
|
507
|
|
|
model_name.singular |
|
508
|
|
|
end |
|
509
|
|
|
|
|
510
|
|
|
def touch |
|
511
|
|
|
# no-op but needs to be defined since #persisted? is defined |
|
512
|
|
|
end |
|
513
|
|
|
|
|
514
|
|
|
def touch_later |
|
515
|
|
|
# No-op. |
|
516
|
|
|
# This method is called by ActiveRecord. |
|
517
|
|
|
# We don't want to do anything for `Commit` model, so this is empty. |
|
518
|
|
|
end |
|
519
|
|
|
|
|
520
|
|
|
WIP_REGEX = /\A\s*(((?i)(\[WIP\]|WIP:|WIP)\s|WIP$))|(fixup!|squash!)\s/.freeze |
|
521
|
|
|
|
|
522
|
|
|
def work_in_progress? |
|
523
|
|
|
!!(title =~ WIP_REGEX) |
|
524
|
|
|
end |
|
525
|
|
|
|
|
526
|
|
|
def merged_merge_request?(user) |
|
527
|
|
|
!!merged_merge_request(user) |
|
528
|
|
|
end |
|
529
|
|
|
|
|
530
|
|
|
def cache_key |
|
531
|
|
|
"commit:#{sha}" |
|
532
|
|
|
end |
|
533
|
|
|
|
|
534
|
|
|
private |
|
535
|
|
|
|
|
536
|
|
|
def commit_reference(from, referable_commit_id, full: false) |
|
537
|
|
|
reference = project.to_reference(from, full: full) |
|
538
|
|
|
|
|
539
|
|
|
if reference.present? |
|
540
|
|
|
"#{reference}#{self.class.reference_prefix}#{referable_commit_id}" |
|
541
|
|
|
else |
|
542
|
|
|
referable_commit_id |
|
543
|
|
|
end |
|
544
|
|
|
end |
|
545
|
|
|
|
|
546
|
|
|
def repo_changes |
|
547
|
|
|
changes = { added: [], modified: [], removed: [] } |
|
548
|
|
|
|
|
549
|
|
|
raw_deltas.each do |diff| |
|
550
|
|
|
if diff.deleted_file |
|
551
|
|
|
changes[:removed] << diff.old_path |
|
552
|
|
|
elsif diff.renamed_file || diff.new_file |
|
553
|
|
|
changes[:added] << diff.new_path |
|
554
|
|
|
else |
|
555
|
|
|
changes[:modified] << diff.new_path |
|
556
|
|
|
end |
|
557
|
|
|
end |
|
558
|
|
|
|
|
559
|
|
|
changes |
|
560
|
|
|
end |
|
561
|
|
|
|
|
562
|
|
|
def merged_merge_request_no_cache(user) |
|
563
|
|
|
MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit? |
|
564
|
|
|
end |
|
565
|
|
|
end |
|
566
|
|
|
*/ |
|
567
|
|
|
} |
|
568
|
|
|
|