Class: Repository

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

Direct Known Subclasses

Bazaar, Cvs, Darcs, Filesystem, Git, Mercurial, Subversion

Defined Under Namespace

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

Constant Summary

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



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

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

.factory(klass_name, *args) ⇒ Object



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

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”



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

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



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

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

.human_attribute_name(attribute_key_name, *args) ⇒ Object



69
70
71
72
73
74
75
# File 'app/models/repository.rb', line 69

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



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

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



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

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

.scm_adapter_classObject



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

def self.scm_adapter_class
  nil
end

.scm_availableObject



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

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

.scm_commandObject



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

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

.scm_nameObject



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

def self.scm_name
  'Abstract'
end

.scm_version_stringObject



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

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

Instance Method Details

#<=>(repository) ⇒ Object



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

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

#branchesObject



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

def branches
  scm.branches
end

#cat(path, identifier = nil) ⇒ Object



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

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

#committer_ids=(h) ⇒ Object

Maps committers username to a user ids



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

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



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

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

#default_branchObject



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

def default_branch
  nil
end

#diff(path, rev, rev_to) ⇒ Object



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

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

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



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

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



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

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

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



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

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 ||{}



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

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



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

def find_changeset_by_name(name)
  return nil if name.blank?
  s = name.to_s
  if s.match(/^\d*$/)
    changesets.where("revision = ?", s).first
  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



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

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



124
125
126
# File 'app/models/repository.rb', line 124

def identifier=(identifier)
  super unless identifier_frozen?
end

#identifier_frozen?Boolean

Returns:

  • (Boolean)


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

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

#identifier_paramObject



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

def identifier_param
  if is_default?
    nil
  elsif identifier.present?
    identifier
  else
    id.to_s
  end
end

#latest_changesetObject



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

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



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

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



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

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

#nameObject



114
115
116
117
118
119
120
121
122
# File 'app/models/repository.rb', line 114

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

#passwordObject



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

def password
  read_ciphered_attribute(:password)
end

#password=(arg) ⇒ Object



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

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

#properties(path, identifier = nil) ⇒ Object



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

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

#relative_path(path) ⇒ Object

Returns a path relative to the url of the repository



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

def relative_path(path)
  path
end

#repo_create_validationObject



63
64
65
66
67
# File 'app/models/repository.rb', line 63

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

#repo_log_encodingObject



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

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

#report_last_commitObject



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

def report_last_commit
  true
end

#root_url=(arg) ⇒ Object

Removes leading and trailing whitespace



83
84
85
# File 'app/models/repository.rb', line 83

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



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

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



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

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

#scmObject



99
100
101
102
103
104
105
106
107
108
# File 'app/models/repository.rb', line 99

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



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

def scm_adapter
  self.class.scm_adapter_class
end

#scm_nameObject



110
111
112
# File 'app/models/repository.rb', line 110

def scm_name
  self.class.scm_name
end

#set_as_default?Boolean

Returns:

  • (Boolean)


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

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



424
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
451
# File 'app/models/repository.rb', line 424

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)


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

def supports_all_revisions?
  true
end

#supports_annotate?Boolean

Returns:

  • (Boolean)


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

def supports_annotate?
  scm.supports_annotate?
end

#supports_cat?Boolean

Returns:

  • (Boolean)


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

def supports_cat?
  scm.supports_cat?
end

#supports_directory_revisions?Boolean

Returns:

  • (Boolean)


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

def supports_directory_revisions?
  false
end

#supports_revision_graph?Boolean

Returns:

  • (Boolean)


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

def supports_revision_graph?
  false
end

#tagsObject



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

def tags
  scm.tags
end

#url=(arg) ⇒ Object

Removes leading and trailing whitespace



78
79
80
# File 'app/models/repository.rb', line 78

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