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