##// END OF EJS Templates
scm: add exception of fetching revisions error in repository model (#5357, #2799, #4741, #8030)....
Toshi MARUYAMA -
r5779:fa94e86d43c9
parent child
Show More
@@ -1,320 +1,322
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 class ScmFetchError < Exception; end
19
18 20 class Repository < ActiveRecord::Base
19 21 include Redmine::Ciphering
20 22
21 23 belongs_to :project
22 24 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
23 25 has_many :changes, :through => :changesets
24 26
25 27 serialize :extra_info
26 28
27 29 # Raw SQL to delete changesets and changes in the database
28 30 # has_many :changesets, :dependent => :destroy is too slow for big repositories
29 31 before_destroy :clear_changesets
30 32
31 33 validates_length_of :password, :maximum => 255, :allow_nil => true
32 34 # Checks if the SCM is enabled when creating a repository
33 35 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
34 36
35 37 def self.human_attribute_name(attribute_key_name)
36 38 attr_name = attribute_key_name
37 39 if attr_name == "log_encoding"
38 40 attr_name = "commit_logs_encoding"
39 41 end
40 42 super(attr_name)
41 43 end
42 44
43 45 # Removes leading and trailing whitespace
44 46 def url=(arg)
45 47 write_attribute(:url, arg ? arg.to_s.strip : nil)
46 48 end
47 49
48 50 # Removes leading and trailing whitespace
49 51 def root_url=(arg)
50 52 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
51 53 end
52 54
53 55 def password
54 56 read_ciphered_attribute(:password)
55 57 end
56 58
57 59 def password=(arg)
58 60 write_ciphered_attribute(:password, arg)
59 61 end
60 62
61 63 def scm_adapter
62 64 self.class.scm_adapter_class
63 65 end
64 66
65 67 def scm
66 68 @scm ||= self.scm_adapter.new(url, root_url,
67 69 login, password, path_encoding)
68 70 update_attribute(:root_url, @scm.root_url) if root_url.blank?
69 71 @scm
70 72 end
71 73
72 74 def scm_name
73 75 self.class.scm_name
74 76 end
75 77
76 78 def merge_extra_info(arg)
77 79 h = extra_info || {}
78 80 return h if arg.nil?
79 81 h.merge!(arg)
80 82 write_attribute(:extra_info, h)
81 83 end
82 84
83 85 def report_last_commit
84 86 true
85 87 end
86 88
87 89 def supports_cat?
88 90 scm.supports_cat?
89 91 end
90 92
91 93 def supports_annotate?
92 94 scm.supports_annotate?
93 95 end
94 96
95 97 def supports_all_revisions?
96 98 true
97 99 end
98 100
99 101 def supports_directory_revisions?
100 102 false
101 103 end
102 104
103 105 def entry(path=nil, identifier=nil)
104 106 scm.entry(path, identifier)
105 107 end
106 108
107 109 def entries(path=nil, identifier=nil)
108 110 scm.entries(path, identifier)
109 111 end
110 112
111 113 def branches
112 114 scm.branches
113 115 end
114 116
115 117 def tags
116 118 scm.tags
117 119 end
118 120
119 121 def default_branch
120 122 scm.default_branch
121 123 end
122 124
123 125 def properties(path, identifier=nil)
124 126 scm.properties(path, identifier)
125 127 end
126 128
127 129 def cat(path, identifier=nil)
128 130 scm.cat(path, identifier)
129 131 end
130 132
131 133 def diff(path, rev, rev_to)
132 134 scm.diff(path, rev, rev_to)
133 135 end
134 136
135 137 def diff_format_revisions(cs, cs_to, sep=':')
136 138 text = ""
137 139 text << cs_to.format_identifier + sep if cs_to
138 140 text << cs.format_identifier if cs
139 141 text
140 142 end
141 143
142 144 # Returns a path relative to the url of the repository
143 145 def relative_path(path)
144 146 path
145 147 end
146 148
147 149 # Finds and returns a revision with a number or the beginning of a hash
148 150 def find_changeset_by_name(name)
149 151 return nil if name.blank?
150 152 changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
151 153 ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
152 154 end
153 155
154 156 def latest_changeset
155 157 @latest_changeset ||= changesets.find(:first)
156 158 end
157 159
158 160 # Returns the latest changesets for +path+
159 161 # Default behaviour is to search in cached changesets
160 162 def latest_changesets(path, rev, limit=10)
161 163 if path.blank?
162 164 changesets.find(
163 165 :all,
164 166 :include => :user,
165 167 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
166 168 :limit => limit)
167 169 else
168 170 changes.find(
169 171 :all,
170 172 :include => {:changeset => :user},
171 173 :conditions => ["path = ?", path.with_leading_slash],
172 174 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
173 175 :limit => limit
174 176 ).collect(&:changeset)
175 177 end
176 178 end
177 179
178 180 def scan_changesets_for_issue_ids
179 181 self.changesets.each(&:scan_comment_for_issue_ids)
180 182 end
181 183
182 184 # Returns an array of committers usernames and associated user_id
183 185 def committers
184 186 @committers ||= Changeset.connection.select_rows(
185 187 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
186 188 end
187 189
188 190 # Maps committers username to a user ids
189 191 def committer_ids=(h)
190 192 if h.is_a?(Hash)
191 193 committers.each do |committer, user_id|
192 194 new_user_id = h[committer]
193 195 if new_user_id && (new_user_id.to_i != user_id.to_i)
194 196 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
195 197 Changeset.update_all(
196 198 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
197 199 ["repository_id = ? AND committer = ?", id, committer])
198 200 end
199 201 end
200 202 @committers = nil
201 203 @found_committer_users = nil
202 204 true
203 205 else
204 206 false
205 207 end
206 208 end
207 209
208 210 # Returns the Redmine User corresponding to the given +committer+
209 211 # It will return nil if the committer is not yet mapped and if no User
210 212 # with the same username or email was found
211 213 def find_committer_user(committer)
212 214 unless committer.blank?
213 215 @found_committer_users ||= {}
214 216 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
215 217
216 218 user = nil
217 219 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
218 220 if c && c.user
219 221 user = c.user
220 222 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
221 223 username, email = $1.strip, $3
222 224 u = User.find_by_login(username)
223 225 u ||= User.find_by_mail(email) unless email.blank?
224 226 user = u
225 227 end
226 228 @found_committer_users[committer] = user
227 229 user
228 230 end
229 231 end
230 232
231 233 def repo_log_encoding
232 234 encoding = log_encoding.to_s.strip
233 235 encoding.blank? ? 'UTF-8' : encoding
234 236 end
235 237
236 238 # Fetches new changesets for all repositories of active projects
237 239 # Can be called periodically by an external script
238 240 # eg. ruby script/runner "Repository.fetch_changesets"
239 241 def self.fetch_changesets
240 242 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
241 243 if project.repository
242 244 begin
243 245 project.repository.fetch_changesets
244 246 rescue Redmine::Scm::Adapters::CommandFailed => e
245 247 logger.error "scm: error during fetching changesets: #{e.message}"
246 248 end
247 249 end
248 250 end
249 251 end
250 252
251 253 # scan changeset comments to find related and fixed issues for all repositories
252 254 def self.scan_changesets_for_issue_ids
253 255 find(:all).each(&:scan_changesets_for_issue_ids)
254 256 end
255 257
256 258 def self.scm_name
257 259 'Abstract'
258 260 end
259 261
260 262 def self.available_scm
261 263 subclasses.collect {|klass| [klass.scm_name, klass.name]}
262 264 end
263 265
264 266 def self.factory(klass_name, *args)
265 267 klass = "Repository::#{klass_name}".constantize
266 268 klass.new(*args)
267 269 rescue
268 270 nil
269 271 end
270 272
271 273 def self.scm_adapter_class
272 274 nil
273 275 end
274 276
275 277 def self.scm_command
276 278 ret = ""
277 279 begin
278 280 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
279 281 rescue Redmine::Scm::Adapters::CommandFailed => e
280 282 logger.error "scm: error during get command: #{e.message}"
281 283 end
282 284 ret
283 285 end
284 286
285 287 def self.scm_version_string
286 288 ret = ""
287 289 begin
288 290 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
289 291 rescue Redmine::Scm::Adapters::CommandFailed => e
290 292 logger.error "scm: error during get version string: #{e.message}"
291 293 end
292 294 ret
293 295 end
294 296
295 297 def self.scm_available
296 298 ret = false
297 299 begin
298 300 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
299 301 rescue Redmine::Scm::Adapters::CommandFailed => e
300 302 logger.error "scm: error during get scm available: #{e.message}"
301 303 end
302 304 ret
303 305 end
304 306
305 307 private
306 308
307 309 def before_save
308 310 # Strips url and root_url
309 311 url.strip!
310 312 root_url.strip!
311 313 true
312 314 end
313 315
314 316 def clear_changesets
315 317 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
316 318 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
317 319 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
318 320 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
319 321 end
320 322 end
General Comments 0
You need to be logged in to leave comments. Login now