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