##// END OF EJS Templates
scm: code clean up repository model....
Toshi MARUYAMA -
r5527:86d9ea32dbd2
parent child
Show More
@@ -1,298 +1,302
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
137 changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
138 ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
138 end
139 end
139
140
140 def latest_changeset
141 def latest_changeset
141 @latest_changeset ||= changesets.find(:first)
142 @latest_changeset ||= changesets.find(:first)
142 end
143 end
143
144
144 # Returns the latest changesets for +path+
145 # Returns the latest changesets for +path+
145 # Default behaviour is to search in cached changesets
146 # Default behaviour is to search in cached changesets
146 def latest_changesets(path, rev, limit=10)
147 def latest_changesets(path, rev, limit=10)
147 if path.blank?
148 if path.blank?
148 changesets.find(:all, :include => :user,
149 changesets.find(:all, :include => :user,
149 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
150 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
150 :limit => limit)
151 :limit => limit)
151 else
152 else
152 changes.find(:all, :include => {:changeset => :user},
153 changes.find(:all, :include => {:changeset => :user},
153 :conditions => ["path = ?", path.with_leading_slash],
154 :conditions => ["path = ?", path.with_leading_slash],
154 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
155 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
155 :limit => limit).collect(&:changeset)
156 :limit => limit).collect(&:changeset)
156 end
157 end
157 end
158 end
158
159
159 def scan_changesets_for_issue_ids
160 def scan_changesets_for_issue_ids
160 self.changesets.each(&:scan_comment_for_issue_ids)
161 self.changesets.each(&:scan_comment_for_issue_ids)
161 end
162 end
162
163
163 # Returns an array of committers usernames and associated user_id
164 # Returns an array of committers usernames and associated user_id
164 def committers
165 def committers
165 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
166 @committers ||= Changeset.connection.select_rows(
167 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
166 end
168 end
167
169
168 # Maps committers username to a user ids
170 # Maps committers username to a user ids
169 def committer_ids=(h)
171 def committer_ids=(h)
170 if h.is_a?(Hash)
172 if h.is_a?(Hash)
171 committers.each do |committer, user_id|
173 committers.each do |committer, user_id|
172 new_user_id = h[committer]
174 new_user_id = h[committer]
173 if new_user_id && (new_user_id.to_i != user_id.to_i)
175 if new_user_id && (new_user_id.to_i != user_id.to_i)
174 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
176 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
175 Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
177 Changeset.update_all(
178 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
179 ["repository_id = ? AND committer = ?", id, committer])
176 end
180 end
177 end
181 end
178 @committers = nil
182 @committers = nil
179 @found_committer_users = nil
183 @found_committer_users = nil
180 true
184 true
181 else
185 else
182 false
186 false
183 end
187 end
184 end
188 end
185
189
186 # Returns the Redmine User corresponding to the given +committer+
190 # Returns the Redmine User corresponding to the given +committer+
187 # It will return nil if the committer is not yet mapped and if no User
191 # It will return nil if the committer is not yet mapped and if no User
188 # with the same username or email was found
192 # with the same username or email was found
189 def find_committer_user(committer)
193 def find_committer_user(committer)
190 unless committer.blank?
194 unless committer.blank?
191 @found_committer_users ||= {}
195 @found_committer_users ||= {}
192 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
196 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
193
197
194 user = nil
198 user = nil
195 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
199 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
196 if c && c.user
200 if c && c.user
197 user = c.user
201 user = c.user
198 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
202 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
199 username, email = $1.strip, $3
203 username, email = $1.strip, $3
200 u = User.find_by_login(username)
204 u = User.find_by_login(username)
201 u ||= User.find_by_mail(email) unless email.blank?
205 u ||= User.find_by_mail(email) unless email.blank?
202 user = u
206 user = u
203 end
207 end
204 @found_committer_users[committer] = user
208 @found_committer_users[committer] = user
205 user
209 user
206 end
210 end
207 end
211 end
208
212
209 def repo_log_encoding
213 def repo_log_encoding
210 encoding = log_encoding.to_s.strip
214 encoding = log_encoding.to_s.strip
211 encoding.blank? ? 'UTF-8' : encoding
215 encoding.blank? ? 'UTF-8' : encoding
212 end
216 end
213
217
214 # Fetches new changesets for all repositories of active projects
218 # Fetches new changesets for all repositories of active projects
215 # Can be called periodically by an external script
219 # Can be called periodically by an external script
216 # eg. ruby script/runner "Repository.fetch_changesets"
220 # eg. ruby script/runner "Repository.fetch_changesets"
217 def self.fetch_changesets
221 def self.fetch_changesets
218 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
222 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
219 if project.repository
223 if project.repository
220 begin
224 begin
221 project.repository.fetch_changesets
225 project.repository.fetch_changesets
222 rescue Redmine::Scm::Adapters::CommandFailed => e
226 rescue Redmine::Scm::Adapters::CommandFailed => e
223 logger.error "scm: error during fetching changesets: #{e.message}"
227 logger.error "scm: error during fetching changesets: #{e.message}"
224 end
228 end
225 end
229 end
226 end
230 end
227 end
231 end
228
232
229 # scan changeset comments to find related and fixed issues for all repositories
233 # scan changeset comments to find related and fixed issues for all repositories
230 def self.scan_changesets_for_issue_ids
234 def self.scan_changesets_for_issue_ids
231 find(:all).each(&:scan_changesets_for_issue_ids)
235 find(:all).each(&:scan_changesets_for_issue_ids)
232 end
236 end
233
237
234 def self.scm_name
238 def self.scm_name
235 'Abstract'
239 'Abstract'
236 end
240 end
237
241
238 def self.available_scm
242 def self.available_scm
239 subclasses.collect {|klass| [klass.scm_name, klass.name]}
243 subclasses.collect {|klass| [klass.scm_name, klass.name]}
240 end
244 end
241
245
242 def self.factory(klass_name, *args)
246 def self.factory(klass_name, *args)
243 klass = "Repository::#{klass_name}".constantize
247 klass = "Repository::#{klass_name}".constantize
244 klass.new(*args)
248 klass.new(*args)
245 rescue
249 rescue
246 nil
250 nil
247 end
251 end
248
252
249 def self.scm_adapter_class
253 def self.scm_adapter_class
250 nil
254 nil
251 end
255 end
252
256
253 def self.scm_command
257 def self.scm_command
254 ret = ""
258 ret = ""
255 begin
259 begin
256 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
260 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
257 rescue Redmine::Scm::Adapters::CommandFailed => e
261 rescue Redmine::Scm::Adapters::CommandFailed => e
258 logger.error "scm: error during get command: #{e.message}"
262 logger.error "scm: error during get command: #{e.message}"
259 end
263 end
260 ret
264 ret
261 end
265 end
262
266
263 def self.scm_version_string
267 def self.scm_version_string
264 ret = ""
268 ret = ""
265 begin
269 begin
266 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
270 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
267 rescue Redmine::Scm::Adapters::CommandFailed => e
271 rescue Redmine::Scm::Adapters::CommandFailed => e
268 logger.error "scm: error during get version string: #{e.message}"
272 logger.error "scm: error during get version string: #{e.message}"
269 end
273 end
270 ret
274 ret
271 end
275 end
272
276
273 def self.scm_available
277 def self.scm_available
274 ret = false
278 ret = false
275 begin
279 begin
276 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
280 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
277 rescue Redmine::Scm::Adapters::CommandFailed => e
281 rescue Redmine::Scm::Adapters::CommandFailed => e
278 logger.error "scm: error during get scm available: #{e.message}"
282 logger.error "scm: error during get scm available: #{e.message}"
279 end
283 end
280 ret
284 ret
281 end
285 end
282
286
283 private
287 private
284
288
285 def before_save
289 def before_save
286 # Strips url and root_url
290 # Strips url and root_url
287 url.strip!
291 url.strip!
288 root_url.strip!
292 root_url.strip!
289 true
293 true
290 end
294 end
291
295
292 def clear_changesets
296 def clear_changesets
293 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
297 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
294 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
298 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
295 connection.delete("DELETE FROM #{ci} WHERE #{ci}.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})")
296 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
300 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
297 end
301 end
298 end
302 end
General Comments 0
You need to be logged in to leave comments. Login now