comparison app/models/.svn/text-base/repository.rb.svn-base @ 514:7eba09d624db live

Merge
author Chris Cannam
date Thu, 14 Jul 2011 10:50:53 +0100
parents 851510f1b535
children
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}.committed_on DESC, #{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 changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%'])) 149 return nil if name.blank?
98 end 150 changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
99 151 ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
152 end
153
100 def latest_changeset 154 def latest_changeset
101 @latest_changeset ||= changesets.find(:first) 155 @latest_changeset ||= changesets.find(:first)
102 end 156 end
103 157
104 # Returns the latest changesets for +path+ 158 # Returns the latest changesets for +path+
105 # Default behaviour is to search in cached changesets 159 # Default behaviour is to search in cached changesets
106 def latest_changesets(path, rev, limit=10) 160 def latest_changesets(path, rev, limit=10)
107 if path.blank? 161 if path.blank?
108 changesets.find(:all, :include => :user, 162 changesets.find(
109 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", 163 :all,
110 :limit => limit) 164 :include => :user,
165 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
166 :limit => limit)
111 else 167 else
112 changes.find(:all, :include => {:changeset => :user}, 168 changes.find(
113 :conditions => ["path = ?", path.with_leading_slash], 169 :all,
114 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", 170 :include => {:changeset => :user},
115 :limit => limit).collect(&:changeset) 171 :conditions => ["path = ?", path.with_leading_slash],
116 end 172 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
117 end 173 :limit => limit
118 174 ).collect(&:changeset)
175 end
176 end
177
119 def scan_changesets_for_issue_ids 178 def scan_changesets_for_issue_ids
120 self.changesets.each(&:scan_comment_for_issue_ids) 179 self.changesets.each(&:scan_comment_for_issue_ids)
121 end 180 end
122 181
123 # Returns an array of committers usernames and associated user_id 182 # Returns an array of committers usernames and associated user_id
124 def committers 183 def committers
125 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}") 184 @committers ||= Changeset.connection.select_rows(
126 end 185 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
127 186 end
187
128 # Maps committers username to a user ids 188 # Maps committers username to a user ids
129 def committer_ids=(h) 189 def committer_ids=(h)
130 if h.is_a?(Hash) 190 if h.is_a?(Hash)
131 committers.each do |committer, user_id| 191 committers.each do |committer, user_id|
132 new_user_id = h[committer] 192 new_user_id = h[committer]
133 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)
134 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)
135 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])
136 end 198 end
137 end 199 end
138 @committers = nil 200 @committers = nil
139 @found_committer_users = nil 201 @found_committer_users = nil
140 true 202 true
141 else 203 else
142 false 204 false
143 end 205 end
144 end 206 end
145 207
146 # Returns the Redmine User corresponding to the given +committer+ 208 # Returns the Redmine User corresponding to the given +committer+
147 # 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
148 # with the same username or email was found 210 # with the same username or email was found
149 def find_committer_user(committer) 211 def find_committer_user(committer)
150 unless committer.blank? 212 unless committer.blank?
151 @found_committer_users ||= {} 213 @found_committer_users ||= {}
152 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)
153 215
154 user = nil 216 user = nil
155 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user) 217 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
156 if c && c.user 218 if c && c.user
157 user = c.user 219 user = c.user
158 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/ 220 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
163 end 225 end
164 @found_committer_users[committer] = user 226 @found_committer_users[committer] = user
165 user 227 user
166 end 228 end
167 end 229 end
168 230
231 def repo_log_encoding
232 encoding = log_encoding.to_s.strip
233 encoding.blank? ? 'UTF-8' : encoding
234 end
235
169 # Fetches new changesets for all repositories of active projects 236 # Fetches new changesets for all repositories of active projects
170 # Can be called periodically by an external script 237 # Can be called periodically by an external script
171 # eg. ruby script/runner "Repository.fetch_changesets" 238 # eg. ruby script/runner "Repository.fetch_changesets"
172 def self.fetch_changesets 239 def self.fetch_changesets
173 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|
174 if project.repository 241 if project.repository
175 project.repository.fetch_changesets 242 begin
243 project.repository.fetch_changesets
244 rescue Redmine::Scm::Adapters::CommandFailed => e
245 logger.error "scm: error during fetching changesets: #{e.message}"
246 end
176 end 247 end
177 end 248 end
178 end 249 end
179 250
180 # 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
181 def self.scan_changesets_for_issue_ids 252 def self.scan_changesets_for_issue_ids
182 find(:all).each(&:scan_changesets_for_issue_ids) 253 find(:all).each(&:scan_changesets_for_issue_ids)
183 end 254 end
184 255
185 def self.scm_name 256 def self.scm_name
186 'Abstract' 257 'Abstract'
187 end 258 end
188 259
189 def self.available_scm 260 def self.available_scm
190 subclasses.collect {|klass| [klass.scm_name, klass.name]} 261 subclasses.collect {|klass| [klass.scm_name, klass.name]}
191 end 262 end
192 263
193 def self.factory(klass_name, *args) 264 def self.factory(klass_name, *args)
194 klass = "Repository::#{klass_name}".constantize 265 klass = "Repository::#{klass_name}".constantize
195 klass.new(*args) 266 klass.new(*args)
196 rescue 267 rescue
197 nil 268 nil
198 end 269 end
199 270
271 def self.scm_adapter_class
272 nil
273 end
274
275 def self.scm_command
276 ret = ""
277 begin
278 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
279 rescue Exception => e
280 logger.error "scm: error during get command: #{e.message}"
281 end
282 ret
283 end
284
285 def self.scm_version_string
286 ret = ""
287 begin
288 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
289 rescue Exception => e
290 logger.error "scm: error during get version string: #{e.message}"
291 end
292 ret
293 end
294
295 def self.scm_available
296 ret = false
297 begin
298 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
299 rescue Exception => e
300 logger.error "scm: error during get scm available: #{e.message}"
301 end
302 ret
303 end
304
200 private 305 private
201 306
202 def before_save 307 def before_save
203 # Strips url and root_url 308 # Strips url and root_url
204 url.strip! 309 url.strip!
205 root_url.strip! 310 root_url.strip!
206 true 311 true
207 end 312 end
208 313
209 def clear_changesets 314 def clear_changesets
210 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}"
211 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})")
212 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})")
213 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}") 318 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")