comparison app/models/repository.rb @ 514:7eba09d624db live

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