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