Class: Repository

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
Redmine::Ciphering, Redmine::SafeAttributes
Defined in:
app/models/repository.rb

Direct Known Subclasses

Bazaar, Cvs, Filesystem, Git, Mercurial, Subversion

Defined Under Namespace

Classes: Bazaar, Cvs, Filesystem, Git, Mercurial, Subversion

Constant Summary collapse

IDENTIFIER_MAX_LENGTH =

Maximum length for repository identifiers

255

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Redmine::SafeAttributes

#delete_unsafe_attributes, included, #safe_attribute?, #safe_attribute_names, #safe_attributes=

Methods included from Redmine::Ciphering

cipher_key, decrypt_text, encrypt_text, included, logger

Class Method Details

.available_scmObject


364
365
366
# File 'app/models/repository.rb', line 364

def self.available_scm
  subclasses.collect {|klass| [klass.scm_name, klass.name]}
end

.factory(klass_name, *args) ⇒ Object


368
369
370
# File 'app/models/repository.rb', line 368

def self.factory(klass_name, *args)
  repository_class(klass_name).new(*args) rescue nil
end

.fetch_changesetsObject

Fetches new changesets for all repositories of active projects Can be called periodically by an external script eg. ruby script/runner “Repository.fetch_changesets”


343
344
345
346
347
348
349
350
351
352
353
# File 'app/models/repository.rb', line 343

def self.fetch_changesets
  Project.active.has_module(:repository).all.each do |project|
    project.repositories.each do |repository|
      begin
        repository.fetch_changesets
      rescue Redmine::Scm::Adapters::CommandFailed => e
        logger.error "scm: error during fetching changesets: #{e.message}"
      end
    end
  end
end

.find_by_identifier_param(param) ⇒ Object


153
154
155
156
157
158
159
# File 'app/models/repository.rb', line 153

def self.find_by_identifier_param(param)
  if /^\d+$/.match?(param.to_s)
    find_by_id(param)
  else
    find_by_identifier(param)
  end
end

.human_attribute_name(attribute_key_name, *args) ⇒ Object


72
73
74
75
76
77
78
# File 'app/models/repository.rb', line 72

def self.human_attribute_name(attribute_key_name, *args)
  attr_name = attribute_key_name.to_s
  if attr_name == "log_encoding"
    attr_name = "commit_logs_encoding"
  end
  super(attr_name, *args)
end

.repository_class(class_name) ⇒ Object


372
373
374
375
376
377
# File 'app/models/repository.rb', line 372

def self.repository_class(class_name)
  class_name = class_name.to_s.camelize
  if Redmine::Scm::Base.all.include?(class_name)
    "Repository::#{class_name}".constantize
  end
end

.scan_changesets_for_issue_idsObject

scan changeset comments to find related and fixed issues for all repositories


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

def self.scan_changesets_for_issue_ids
  all.each(&:scan_changesets_for_issue_ids)
end

.scm_adapter_classObject


379
380
381
# File 'app/models/repository.rb', line 379

def self.scm_adapter_class
  nil
end

.scm_availableObject


403
404
405
406
407
408
409
410
411
# File 'app/models/repository.rb', line 403

def self.scm_available
  ret = false
  begin
    ret = self.scm_adapter_class.client_available if self.scm_adapter_class
  rescue => e
    logger.error "scm: error during get scm available: #{e.message}"
  end
  ret
end

.scm_commandObject


383
384
385
386
387
388
389
390
391
# File 'app/models/repository.rb', line 383

def self.scm_command
  ret = ""
  begin
    ret = self.scm_adapter_class.client_command if self.scm_adapter_class
  rescue => e
    logger.error "scm: error during get command: #{e.message}"
  end
  ret
end

.scm_nameObject


360
361
362
# File 'app/models/repository.rb', line 360

def self.scm_name
  'Abstract'
end

.scm_version_stringObject


393
394
395
396
397
398
399
400
401
# File 'app/models/repository.rb', line 393

def self.scm_version_string
  ret = ""
  begin
    ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
  rescue => e
    logger.error "scm: error during get version string: #{e.message}"
  end
  ret
end

Instance Method Details

#<=>(repository) ⇒ Object


143
144
145
146
147
148
149
150
151
# File 'app/models/repository.rb', line 143

def <=>(repository)
  if is_default?
    -1
  elsif repository.is_default?
    1
  else
    identifier.to_s <=> repository.identifier.to_s
  end
end

#branchesObject


213
214
215
# File 'app/models/repository.rb', line 213

def branches
  scm.branches
end

#cat(path, identifier = nil) ⇒ Object


229
230
231
# File 'app/models/repository.rb', line 229

def cat(path, identifier=nil)
  scm.cat(path, identifier)
end

#committer_ids=(h) ⇒ Object

Maps committers username to a user ids


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'app/models/repository.rb', line 293

def committer_ids=(h)
  if h.is_a?(Hash)
    committers.each do |committer, user_id|
      new_user_id = h[committer]
      if new_user_id && (new_user_id.to_i != user_id.to_i)
        new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
        Changeset.where(["repository_id = ? AND committer = ?", id, committer]).
          update_all("user_id = #{new_user_id.nil? ? 'NULL' : new_user_id}")
      end
    end
    @committers            = nil
    @found_committer_users = nil
    true
  else
    false
  end
end

#committersObject

Returns an array of committers usernames and associated user_id


288
289
290
# File 'app/models/repository.rb', line 288

def committers
  @committers ||= Changeset.where(:repository_id => id).distinct.pluck(:committer, :user_id)
end

#default_branchObject


221
222
223
# File 'app/models/repository.rb', line 221

def default_branch
  nil
end

#diff(path, rev, rev_to) ⇒ Object


233
234
235
# File 'app/models/repository.rb', line 233

def diff(path, rev, rev_to)
  scm.diff(path, rev, rev_to)
end

#diff_format_revisions(cs, cs_to, sep = ':') ⇒ Object


237
238
239
240
241
242
# File 'app/models/repository.rb', line 237

def diff_format_revisions(cs, cs_to, sep=':')
  text = ""
  text += cs_to.format_identifier + sep if cs_to
  text += cs.format_identifier if cs
  text
end

#entries(path = nil, identifier = nil) ⇒ Object


207
208
209
210
211
# File 'app/models/repository.rb', line 207

def entries(path=nil, identifier=nil)
  entries = scm_entries(path, identifier)
  load_entries_changesets(entries)
  entries
end

#entry(path = nil, identifier = nil) ⇒ Object


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

def entry(path=nil, identifier=nil)
  scm.entry(path, identifier)
end

#extra_infoObject

TODO: should return an empty hash instead of nil to avoid many ||{}


162
163
164
165
# File 'app/models/repository.rb', line 162

def extra_info
  h = read_attribute(:extra_info)
  h.is_a?(Hash) ? h : nil
end

#find_changeset_by_name(name) ⇒ Object

Finds and returns a revision with a number or the beginning of a hash


250
251
252
253
254
255
256
257
258
# File 'app/models/repository.rb', line 250

def find_changeset_by_name(name)
  return nil if name.blank?
  s = name.to_s
  if /^\d*$/.match?(s)
    changesets.find_by(:revision => s)
  else
    changesets.where("revision LIKE ?", s + '%').first
  end
end

#find_committer_user(committer) ⇒ Object

Returns the Redmine User corresponding to the given committer It will return nil if the committer is not yet mapped and if no User with the same username or email was found


314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'app/models/repository.rb', line 314

def find_committer_user(committer)
  unless committer.blank?
    @found_committer_users ||= {}
    return @found_committer_users[committer] if @found_committer_users.has_key?(committer)

    user = nil
    c = changesets.where(:committer => committer).
          includes(:user).references(:user).first
    if c && c.user
      user = c.user
    elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
      username, email = $1.strip, $3
      u = User.(username)
      u ||= User.find_by_mail(email) unless email.blank?
      user = u
    end
    @found_committer_users[committer] = user
    user
  end
end

#identifier=(identifier) ⇒ Object


127
128
129
# File 'app/models/repository.rb', line 127

def identifier=(identifier)
  super unless identifier_frozen?
end

#identifier_frozen?Boolean

Returns:

  • (Boolean)

131
132
133
# File 'app/models/repository.rb', line 131

def identifier_frozen?
  errors[:identifier].blank? && !(new_record? || identifier.blank?)
end

#identifier_paramObject


135
136
137
138
139
140
141
# File 'app/models/repository.rb', line 135

def identifier_param
  if identifier.present?
    identifier
  else
    id.to_s
  end
end

#latest_changesetObject


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

def latest_changeset
  @latest_changeset ||= changesets.first
end

#latest_changesets(path, rev, limit = 10) ⇒ Object

Returns the latest changesets for path Default behaviour is to search in cached changesets


266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'app/models/repository.rb', line 266

def latest_changesets(path, rev, limit=10)
  if path.blank?
    changesets.
      reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC").
      limit(limit).
      preload(:user).
      to_a
  else
    filechanges.
      where("path = ?", path.with_leading_slash).
      reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC").
      limit(limit).
      preload(:changeset => :user).
      collect(&:changeset)
  end
end

#merge_extra_info(arg) ⇒ Object


167
168
169
170
171
172
# File 'app/models/repository.rb', line 167

def merge_extra_info(arg)
  h = extra_info || {}
  return h if arg.nil?
  h.merge!(arg)
  write_attribute(:extra_info, h)
end

#nameObject


117
118
119
120
121
122
123
124
125
# File 'app/models/repository.rb', line 117

def name
  if identifier.present?
    identifier
  elsif is_default?
    l(:field_repository_is_default)
  else
    scm_name
  end
end

#passwordObject


90
91
92
# File 'app/models/repository.rb', line 90

def password
  read_ciphered_attribute(:password)
end

#password=(arg) ⇒ Object


94
95
96
# File 'app/models/repository.rb', line 94

def password=(arg)
  write_ciphered_attribute(:password, arg)
end

#properties(path, identifier = nil) ⇒ Object


225
226
227
# File 'app/models/repository.rb', line 225

def properties(path, identifier=nil)
  scm.properties(path, identifier)
end

#relative_path(path) ⇒ Object

Returns a path relative to the url of the repository


245
246
247
# File 'app/models/repository.rb', line 245

def relative_path(path)
  path
end

#repo_create_validationObject


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

def repo_create_validation
  unless Setting.enabled_scm.include?(self.class.name.demodulize)
    errors.add(:type, :invalid)
  end
end

#repo_log_encodingObject


335
336
337
338
# File 'app/models/repository.rb', line 335

def repo_log_encoding
  encoding = log_encoding.to_s.strip
  encoding.blank? ? 'UTF-8' : encoding
end

#report_last_commitObject


174
175
176
# File 'app/models/repository.rb', line 174

def report_last_commit
  true
end

#root_url=(arg) ⇒ Object

Removes leading and trailing whitespace


86
87
88
# File 'app/models/repository.rb', line 86

def root_url=(arg)
  write_attribute(:root_url, arg ? arg.to_s.strip : nil)
end

#same_commits_in_scope(scope, changeset) ⇒ Object

Returns a scope of changesets that come from the same commit as the given changeset in different repositories that point to the same backend


454
455
456
457
458
459
460
461
462
# File 'app/models/repository.rb', line 454

def same_commits_in_scope(scope, changeset)
  scope = scope.joins(:repository).where(:repositories => {:url => url, :root_url => root_url, :type => type})
  if changeset.scmid.present?
    scope = scope.where(:scmid => changeset.scmid)
  else
    scope = scope.where(:revision => changeset.revision)
  end
  scope
end

#scan_changesets_for_issue_idsObject


283
284
285
# File 'app/models/repository.rb', line 283

def scan_changesets_for_issue_ids
  self.changesets.each(&:scan_comment_for_issue_ids)
end

#scmObject


102
103
104
105
106
107
108
109
110
111
# File 'app/models/repository.rb', line 102

def scm
  unless @scm
    @scm = self.scm_adapter.new(url, root_url,
                                , password, path_encoding)
    if root_url.blank? && @scm.root_url.present?
      update_attribute(:root_url, @scm.root_url)
    end
  end
  @scm
end

#scm_adapterObject


98
99
100
# File 'app/models/repository.rb', line 98

def scm_adapter
  self.class.scm_adapter_class
end

#scm_nameObject


113
114
115
# File 'app/models/repository.rb', line 113

def scm_name
  self.class.scm_name
end

#set_as_default?Boolean

Returns:

  • (Boolean)

413
414
415
# File 'app/models/repository.rb', line 413

def set_as_default?
  new_record? && project && Repository.where(:project_id => project.id).empty?
end

#stats_by_authorObject

Returns a hash with statistics by author in the following form: {

"John Smith" => { :commits => 45, :changes => 324 },
"Bob" => { ... }

}

Notes:

  • this hash honnors the users mapping defined for the repository


425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'app/models/repository.rb', line 425

def stats_by_author
  commits = Changeset.where("repository_id = ?", id).
              select("committer, user_id, count(*) as count").group("committer, user_id")
  # TODO: restore ordering ; this line probably never worked
  # commits.to_a.sort! {|x, y| x.last <=> y.last}
  changes = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", id).
              select("committer, user_id, count(*) as count").group("committer, user_id")
  user_ids = changesets.map(&:user_id).compact.uniq
  authors_names = User.where(:id => user_ids).inject({}) do |memo, user|
    memo[user.id] = user.to_s
    memo
  end
  (commits + changes).inject({}) do |hash, element|
    mapped_name = element.committer
    if username = authors_names[element.user_id.to_i]
      mapped_name = username
    end
    hash[mapped_name] ||= { :commits_count => 0, :changes_count => 0 }
    if element.is_a?(Changeset)
      hash[mapped_name][:commits_count] += element.count.to_i
    else
      hash[mapped_name][:changes_count] += element.count.to_i
    end
    hash
  end
end

#supports_all_revisions?Boolean

Returns:

  • (Boolean)

186
187
188
# File 'app/models/repository.rb', line 186

def supports_all_revisions?
  true
end

#supports_annotate?Boolean

Returns:

  • (Boolean)

182
183
184
# File 'app/models/repository.rb', line 182

def supports_annotate?
  scm.supports_annotate?
end

#supports_cat?Boolean

Returns:

  • (Boolean)

178
179
180
# File 'app/models/repository.rb', line 178

def supports_cat?
  scm.supports_cat?
end

#supports_directory_revisions?Boolean

Returns:

  • (Boolean)

190
191
192
# File 'app/models/repository.rb', line 190

def supports_directory_revisions?
  false
end

#supports_revision_graph?Boolean

Returns:

  • (Boolean)

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

def supports_revision_graph?
  false
end

#tagsObject


217
218
219
# File 'app/models/repository.rb', line 217

def tags
  scm.tags
end

#url=(arg) ⇒ Object

Removes leading and trailing whitespace


81
82
83
# File 'app/models/repository.rb', line 81

def url=(arg)
  write_attribute(:url, arg ? arg.to_s.strip : nil)
end