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