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