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