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