To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / app / models / repository.rb @ 954:42e2437766c2

History | View | Annotate | Download (8.78 KB)

1
# Redmine - project management software
2
# Copyright (C) 2006-2011  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

    
18
class ScmFetchError < Exception; end
19

    
20
class Repository < ActiveRecord::Base
21
  include Redmine::Ciphering
22

    
23
  belongs_to :project
24
  has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
25
  has_many :changes, :through => :changesets
26

    
27
  serialize :extra_info
28

    
29
  # Raw SQL to delete changesets and changes in the database
30
  # has_many :changesets, :dependent => :destroy is too slow for big repositories
31
  before_destroy :clear_changesets
32

    
33
  validates_length_of :password, :maximum => 255, :allow_nil => true
34
  # Checks if the SCM is enabled when creating a repository
35
  validate :repo_create_validation, :on => :create
36

    
37
  def repo_create_validation
38
    unless Setting.enabled_scm.include?(self.class.name.demodulize)
39
      errors.add(:type, :invalid)
40
    end
41
  end
42

    
43
  def self.human_attribute_name(attribute_key_name)
44
    attr_name = attribute_key_name
45
    if attr_name == "log_encoding"
46
      attr_name = "commit_logs_encoding"
47
    end
48
    super(attr_name)
49
  end
50

    
51
  # Removes leading and trailing whitespace
52
  def url=(arg)
53
    write_attribute(:url, arg ? arg.to_s.strip : nil)
54
  end
55

    
56
  # Removes leading and trailing whitespace
57
  def root_url=(arg)
58
    write_attribute(:root_url, arg ? arg.to_s.strip : nil)
59
  end
60

    
61
  def password
62
    read_ciphered_attribute(:password)
63
  end
64

    
65
  def password=(arg)
66
    write_ciphered_attribute(:password, arg)
67
  end
68

    
69
  def scm_adapter
70
    self.class.scm_adapter_class
71
  end
72

    
73
  def scm
74
    @scm ||= self.scm_adapter.new(url, root_url,
75
                                  login, password, path_encoding)
76
    update_attribute(:root_url, @scm.root_url) if root_url.blank?
77
    @scm
78
  end
79

    
80
  def scm_name
81
    self.class.scm_name
82
  end
83

    
84
  def merge_extra_info(arg)
85
    h = extra_info || {}
86
    return h if arg.nil?
87
    h.merge!(arg)
88
    write_attribute(:extra_info, h)
89
  end
90

    
91
  def report_last_commit
92
    true
93
  end
94

    
95
  def supports_cat?
96
    scm.supports_cat?
97
  end
98

    
99
  def supports_annotate?
100
    scm.supports_annotate?
101
  end
102

    
103
  def supports_all_revisions?
104
    true
105
  end
106

    
107
  def supports_directory_revisions?
108
    false
109
  end
110

    
111
  def supports_revision_graph?
112
    false
113
  end
114

    
115
  def entry(path=nil, identifier=nil)
116
    scm.entry(path, identifier)
117
  end
118

    
119
  def entries(path=nil, identifier=nil)
120
    scm.entries(path, identifier)
121
  end
122

    
123
  def branches
124
    scm.branches
125
  end
126

    
127
  def tags
128
    scm.tags
129
  end
130

    
131
  def default_branch
132
    nil
133
  end
134

    
135
  def properties(path, identifier=nil)
136
    scm.properties(path, identifier)
137
  end
138

    
139
  def cat(path, identifier=nil)
140
    scm.cat(path, identifier)
141
  end
142

    
143
  def diff(path, rev, rev_to)
144
    scm.diff(path, rev, rev_to)
145
  end
146

    
147
  def diff_format_revisions(cs, cs_to, sep=':')
148
    text = ""
149
    text << cs_to.format_identifier + sep if cs_to
150
    text << cs.format_identifier if cs
151
    text
152
  end
153

    
154
  # Returns a path relative to the url of the repository
155
  def relative_path(path)
156
    path
157
  end
158

    
159
  # Finds and returns a revision with a number or the beginning of a hash
160
  def find_changeset_by_name(name)
161
    return nil if name.blank?
162
    changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
163
          ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
164
  end
165

    
166
  def latest_changeset
167
    @latest_changeset ||= changesets.find(:first)
168
  end
169

    
170
  # Returns the latest changesets for +path+
171
  # Default behaviour is to search in cached changesets
172
  def latest_changesets(path, rev, limit=10)
173
    if path.blank?
174
      changesets.find(
175
         :all,
176
         :include => :user,
177
         :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
178
         :limit => limit)
179
    else
180
      changes.find(
181
         :all,
182
         :include => {:changeset => :user},
183
         :conditions => ["path = ?", path.with_leading_slash],
184
         :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
185
         :limit => limit
186
       ).collect(&:changeset)
187
    end
188
  end
189

    
190
  def scan_changesets_for_issue_ids
191
    self.changesets.each(&:scan_comment_for_issue_ids)
192
  end
193

    
194
  # Returns an array of committers usernames and associated user_id
195
  def committers
196
    @committers ||= Changeset.connection.select_rows(
197
         "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
198
  end
199

    
200
  # Maps committers username to a user ids
201
  def committer_ids=(h)
202
    if h.is_a?(Hash)
203
      committers.each do |committer, user_id|
204
        new_user_id = h[committer]
205
        if new_user_id && (new_user_id.to_i != user_id.to_i)
206
          new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
207
          Changeset.update_all(
208
               "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
209
               ["repository_id = ? AND committer = ?", id, committer])
210
        end
211
      end
212
      @committers            = nil
213
      @found_committer_users = nil
214
      true
215
    else
216
      false
217
    end
218
  end
219

    
220
  # Returns the Redmine User corresponding to the given +committer+
221
  # It will return nil if the committer is not yet mapped and if no User
222
  # with the same username or email was found
223
  def find_committer_user(committer)
224
    unless committer.blank?
225
      @found_committer_users ||= {}
226
      return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
227

    
228
      user = nil
229
      c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
230
      if c && c.user
231
        user = c.user
232
      elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
233
        username, email = $1.strip, $3
234
        u = User.find_by_login(username)
235
        u ||= User.find_by_mail(email) unless email.blank?
236
        user = u
237
      end
238
      @found_committer_users[committer] = user
239
      user
240
    end
241
  end
242

    
243
  def repo_log_encoding
244
    encoding = log_encoding.to_s.strip
245
    encoding.blank? ? 'UTF-8' : encoding
246
  end
247

    
248
  # Fetches new changesets for all repositories of active projects
249
  # Can be called periodically by an external script
250
  # eg. ruby script/runner "Repository.fetch_changesets"
251
  def self.fetch_changesets
252
    Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
253
      if project.repository
254
        begin
255
          project.repository.fetch_changesets
256
        rescue Redmine::Scm::Adapters::CommandFailed => e
257
          logger.error "scm: error during fetching changesets: #{e.message}"
258
        end
259
      end
260
    end
261
  end
262

    
263
  # scan changeset comments to find related and fixed issues for all repositories
264
  def self.scan_changesets_for_issue_ids
265
    find(:all).each(&:scan_changesets_for_issue_ids)
266
  end
267

    
268
  def self.scm_name
269
    'Abstract'
270
  end
271

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

    
276
  def self.factory(klass_name, *args)
277
    klass = "Repository::#{klass_name}".constantize
278
    klass.new(*args)
279
  rescue
280
    nil
281
  end
282

    
283
  def clear_cache
284
    clear_changesets
285
  end
286
    
287
  def self.scm_adapter_class
288
    nil
289
  end
290

    
291
  def self.scm_command
292
    ret = ""
293
    begin
294
      ret = self.scm_adapter_class.client_command if self.scm_adapter_class
295
    rescue Exception => e
296
      logger.error "scm: error during get command: #{e.message}"
297
    end
298
    ret
299
  end
300

    
301
  def self.scm_version_string
302
    ret = ""
303
    begin
304
      ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
305
    rescue Exception => e
306
      logger.error "scm: error during get version string: #{e.message}"
307
    end
308
    ret
309
  end
310

    
311
  def self.scm_available
312
    ret = false
313
    begin
314
      ret = self.scm_adapter_class.client_available if self.scm_adapter_class
315
    rescue Exception => e
316
      logger.error "scm: error during get scm available: #{e.message}"
317
    end
318
    ret
319
  end
320

    
321
  private
322

    
323
  def clear_changesets
324
    cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
325
    connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
326
    connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
327
    connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
328
  end
329
end