comparison lib/redmine/scm/adapters/.svn/text-base/abstract_adapter.rb.svn-base @ 511:107d36338b70 live

Merge from branch "cannam"
author Chris Cannam
date Thu, 14 Jul 2011 10:43:07 +0100
parents 0c939c159af4
children
comparison
equal deleted inserted replaced
451:a9f6345cb43d 511:107d36338b70
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 require 'cgi' 18 require 'cgi'
19 19
20 module Redmine 20 module Redmine
21 module Scm 21 module Scm
22 module Adapters 22 module Adapters
23 class CommandFailed < StandardError #:nodoc: 23 class CommandFailed < StandardError #:nodoc:
24 end 24 end
25 25
26 class AbstractAdapter #:nodoc: 26 class AbstractAdapter #:nodoc:
27 class << self 27 class << self
28 def client_command
29 ""
30 end
31
28 # Returns the version of the scm client 32 # Returns the version of the scm client
29 # Eg: [1, 5, 0] or [] if unknown 33 # Eg: [1, 5, 0] or [] if unknown
30 def client_version 34 def client_version
31 [] 35 []
32 end 36 end
33 37
34 # Returns the version string of the scm client 38 # Returns the version string of the scm client
35 # Eg: '1.5.0' or 'Unknown version' if unknown 39 # Eg: '1.5.0' or 'Unknown version' if unknown
36 def client_version_string 40 def client_version_string
37 v = client_version || 'Unknown version' 41 v = client_version || 'Unknown version'
38 v.is_a?(Array) ? v.join('.') : v.to_s 42 v.is_a?(Array) ? v.join('.') : v.to_s
39 end 43 end
40 44
41 # Returns true if the current client version is above 45 # Returns true if the current client version is above
42 # or equals the given one 46 # or equals the given one
43 # If option is :unknown is set to true, it will return 47 # If option is :unknown is set to true, it will return
44 # true if the client version is unknown 48 # true if the client version is unknown
45 def client_version_above?(v, options={}) 49 def client_version_above?(v, options={})
46 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) 50 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
47 end 51 end
48 end 52
49 53 def client_available
50 def initialize(url, root_url=nil, login=nil, password=nil) 54 true
55 end
56
57 def shell_quote(str)
58 if Redmine::Platform.mswin?
59 '"' + str.gsub(/"/, '\\"') + '"'
60 else
61 "'" + str.gsub(/'/, "'\"'\"'") + "'"
62 end
63 end
64 end
65
66 def initialize(url, root_url=nil, login=nil, password=nil,
67 path_encoding=nil)
51 @url = url 68 @url = url
52 @login = login if login && !login.empty? 69 @login = login if login && !login.empty?
53 @password = (password || "") if @login 70 @password = (password || "") if @login
54 @root_url = root_url.blank? ? retrieve_root_url : root_url 71 @root_url = root_url.blank? ? retrieve_root_url : root_url
55 end 72 end
56 73
57 def adapter_name 74 def adapter_name
58 'Abstract' 75 'Abstract'
59 end 76 end
60 77
61 def supports_cat? 78 def supports_cat?
62 true 79 true
63 end 80 end
64 81
65 def supports_annotate? 82 def supports_annotate?
66 respond_to?('annotate') 83 respond_to?('annotate')
67 end 84 end
68 85
69 def root_url 86 def root_url
70 @root_url 87 @root_url
71 end 88 end
72 89
73 def url 90 def url
74 @url 91 @url
75 end 92 end
76 93
94 def path_encoding
95 nil
96 end
97
77 # get info about the svn repository 98 # get info about the svn repository
78 def info 99 def info
79 return nil 100 return nil
80 end 101 end
81 102
82 # Returns the entry identified by path and revision identifier 103 # Returns the entry identified by path and revision identifier
83 # or nil if entry doesn't exist in the repository 104 # or nil if entry doesn't exist in the repository
84 def entry(path=nil, identifier=nil) 105 def entry(path=nil, identifier=nil)
85 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} 106 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
86 search_path = parts[0..-2].join('/') 107 search_path = parts[0..-2].join('/')
92 # Search for the entry in the parent directory 113 # Search for the entry in the parent directory
93 es = entries(search_path, identifier) 114 es = entries(search_path, identifier)
94 es ? es.detect {|e| e.name == search_name} : nil 115 es ? es.detect {|e| e.name == search_name} : nil
95 end 116 end
96 end 117 end
97 118
98 # Returns an Entries collection 119 # Returns an Entries collection
99 # or nil if the given path doesn't exist in the repository 120 # or nil if the given path doesn't exist in the repository
100 def entries(path=nil, identifier=nil) 121 def entries(path=nil, identifier=nil, options={})
101 return nil 122 return nil
102 end 123 end
103 124
104 def branches 125 def branches
105 return nil 126 return nil
106 end 127 end
107 128
108 def tags 129 def tags
109 return nil 130 return nil
110 end 131 end
111 132
112 def default_branch 133 def default_branch
113 return nil 134 return nil
114 end 135 end
115 136
116 def properties(path, identifier=nil) 137 def properties(path, identifier=nil)
117 return nil 138 return nil
118 end 139 end
119 140
120 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) 141 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
121 return nil 142 return nil
122 end 143 end
123 144
124 def diff(path, identifier_from, identifier_to=nil) 145 def diff(path, identifier_from, identifier_to=nil)
125 return nil 146 return nil
126 end 147 end
127 148
128 def cat(path, identifier=nil) 149 def cat(path, identifier=nil)
129 return nil 150 return nil
130 end 151 end
131 152
132 def with_leading_slash(path) 153 def with_leading_slash(path)
133 path ||= '' 154 path ||= ''
134 (path[0,1]!="/") ? "/#{path}" : path 155 (path[0,1]!="/") ? "/#{path}" : path
135 end 156 end
136 157
137 def with_trailling_slash(path) 158 def with_trailling_slash(path)
138 path ||= '' 159 path ||= ''
139 (path[-1,1] == "/") ? path : "#{path}/" 160 (path[-1,1] == "/") ? path : "#{path}/"
140 end 161 end
141 162
142 def without_leading_slash(path) 163 def without_leading_slash(path)
143 path ||= '' 164 path ||= ''
144 path.gsub(%r{^/+}, '') 165 path.gsub(%r{^/+}, '')
145 end 166 end
146 167
147 def without_trailling_slash(path) 168 def without_trailling_slash(path)
148 path ||= '' 169 path ||= ''
149 (path[-1,1] == "/") ? path[0..-2] : path 170 (path[-1,1] == "/") ? path[0..-2] : path
150 end 171 end
151 172
152 def shell_quote(str) 173 def shell_quote(str)
153 if Redmine::Platform.mswin? 174 self.class.shell_quote(str)
154 '"' + str.gsub(/"/, '\\"') + '"'
155 else
156 "'" + str.gsub(/'/, "'\"'\"'") + "'"
157 end
158 end 175 end
159 176
160 private 177 private
161 def retrieve_root_url 178 def retrieve_root_url
162 info = self.info 179 info = self.info
163 info ? info.root_url : nil 180 info ? info.root_url : nil
164 end 181 end
165 182
166 def target(path) 183 def target(path)
167 path ||= '' 184 path ||= ''
168 base = path.match(/^\//) ? root_url : url 185 base = path.match(/^\//) ? root_url : url
169 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, '')) 186 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
170 end 187 end
171 188
172 def logger 189 def logger
173 self.class.logger 190 self.class.logger
174 end 191 end
175 192
176 def shellout(cmd, &block) 193 def shellout(cmd, &block)
177 self.class.shellout(cmd, &block) 194 self.class.shellout(cmd, &block)
178 end 195 end
179 196
180 def self.logger 197 def self.logger
181 RAILS_DEFAULT_LOGGER 198 RAILS_DEFAULT_LOGGER
182 end 199 end
183 200
184 def self.shellout(cmd, &block) 201 def self.shellout(cmd, &block)
185 logger.debug "Shelling out: #{strip_credential(cmd)}" if logger && logger.debug? 202 if logger && logger.debug?
203 logger.debug "Shelling out: #{strip_credential(cmd)}"
204 end
186 if Rails.env == 'development' 205 if Rails.env == 'development'
187 # Capture stderr when running in dev environment 206 # Capture stderr when running in dev environment
188 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log" 207 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log"
189 end 208 end
190 begin 209 begin
191 IO.popen(cmd, "r+") do |io| 210 if RUBY_VERSION < '1.9'
211 mode = "r+"
212 else
213 mode = "r+:ASCII-8BIT"
214 end
215 IO.popen(cmd, mode) do |io|
192 io.close_write 216 io.close_write
193 block.call(io) if block_given? 217 block.call(io) if block_given?
194 end 218 end
195 rescue Errno::ENOENT => e 219 rescue Errno::ENOENT => e
196 msg = strip_credential(e.message) 220 msg = strip_credential(e.message)
197 # The command failed, log it and re-raise 221 # The command failed, log it and re-raise
198 logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{strip_credential(cmd)}\n with: #{msg}") 222 logmsg = "SCM command failed, "
223 logmsg += "make sure that your SCM command (e.g. svn) is "
224 logmsg += "in PATH (#{ENV['PATH']})\n"
225 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
226 logmsg += "#{strip_credential(cmd)}\n"
227 logmsg += "with: #{msg}"
228 logger.error(logmsg)
199 raise CommandFailed.new(msg) 229 raise CommandFailed.new(msg)
200 end 230 end
201 end 231 end
202 232
203 # Hides username/password in a given command 233 # Hides username/password in a given command
204 def self.strip_credential(cmd) 234 def self.strip_credential(cmd)
205 q = (Redmine::Platform.mswin? ? '"' : "'") 235 q = (Redmine::Platform.mswin? ? '"' : "'")
206 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx') 236 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
207 end 237 end
208 238
209 def strip_credential(cmd) 239 def strip_credential(cmd)
210 self.class.strip_credential(cmd) 240 self.class.strip_credential(cmd)
211 end 241 end
212 end 242
213 243 def scm_iconv(to, from, str)
244 return nil if str.nil?
245 return str if to == from
246 begin
247 Iconv.conv(to, from, str)
248 rescue Iconv::Failure => err
249 logger.error("failed to convert from #{from} to #{to}. #{err}")
250 nil
251 end
252 end
253 end
254
214 class Entries < Array 255 class Entries < Array
215 def sort_by_name 256 def sort_by_name
216 sort {|x,y| 257 sort {|x,y|
217 if x.kind == y.kind 258 if x.kind == y.kind
218 x.name.to_s <=> y.name.to_s 259 x.name.to_s <=> y.name.to_s
219 else 260 else
220 x.kind <=> y.kind 261 x.kind <=> y.kind
221 end 262 end
222 } 263 }
223 end 264 end
224 265
225 def revisions 266 def revisions
226 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact) 267 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
227 end 268 end
228 end 269 end
229 270
230 class Info 271 class Info
231 attr_accessor :root_url, :lastrev 272 attr_accessor :root_url, :lastrev
232 def initialize(attributes={}) 273 def initialize(attributes={})
233 self.root_url = attributes[:root_url] if attributes[:root_url] 274 self.root_url = attributes[:root_url] if attributes[:root_url]
234 self.lastrev = attributes[:lastrev] 275 self.lastrev = attributes[:lastrev]
235 end 276 end
236 end 277 end
237 278
238 class Entry 279 class Entry
239 attr_accessor :name, :path, :kind, :size, :lastrev 280 attr_accessor :name, :path, :kind, :size, :lastrev
240 def initialize(attributes={}) 281 def initialize(attributes={})
241 self.name = attributes[:name] if attributes[:name] 282 self.name = attributes[:name] if attributes[:name]
242 self.path = attributes[:path] if attributes[:path] 283 self.path = attributes[:path] if attributes[:path]
243 self.kind = attributes[:kind] if attributes[:kind] 284 self.kind = attributes[:kind] if attributes[:kind]
244 self.size = attributes[:size].to_i if attributes[:size] 285 self.size = attributes[:size].to_i if attributes[:size]
245 self.lastrev = attributes[:lastrev] 286 self.lastrev = attributes[:lastrev]
246 end 287 end
247 288
248 def is_file? 289 def is_file?
249 'file' == self.kind 290 'file' == self.kind
250 end 291 end
251 292
252 def is_dir? 293 def is_dir?
253 'dir' == self.kind 294 'dir' == self.kind
254 end 295 end
255 296
256 def is_text? 297 def is_text?
257 Redmine::MimeType.is_type?('text', name) 298 Redmine::MimeType.is_type?('text', name)
258 end 299 end
259 end 300 end
260 301
261 class Revisions < Array 302 class Revisions < Array
262 def latest 303 def latest
263 sort {|x,y| 304 sort {|x,y|
264 unless x.time.nil? or y.time.nil? 305 unless x.time.nil? or y.time.nil?
265 x.time <=> y.time 306 x.time <=> y.time
266 else 307 else
267 0 308 0
268 end 309 end
269 }.last 310 }.last
270 end 311 end
271 end 312 end
272 313
273 class Revision 314 class Revision
274 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch 315 attr_accessor :scmid, :name, :author, :time, :message,
316 :paths, :revision, :branch, :identifier
275 317
276 def initialize(attributes={}) 318 def initialize(attributes={})
277 self.identifier = attributes[:identifier] 319 self.identifier = attributes[:identifier]
278 self.scmid = attributes[:scmid] 320 self.scmid = attributes[:scmid]
279 self.name = attributes[:name] || self.identifier 321 self.name = attributes[:name] || self.identifier
280 self.author = attributes[:author] 322 self.author = attributes[:author]
281 self.time = attributes[:time] 323 self.time = attributes[:time]
282 self.message = attributes[:message] || "" 324 self.message = attributes[:message] || ""
283 self.paths = attributes[:paths] 325 self.paths = attributes[:paths]
284 self.revision = attributes[:revision] 326 self.revision = attributes[:revision]
285 self.branch = attributes[:branch] 327 self.branch = attributes[:branch]
286 end 328 end
287 329
288 def save(repo) 330 # Returns the readable identifier.
289 Changeset.transaction do 331 def format_identifier
290 changeset = Changeset.new( 332 self.identifier.to_s
291 :repository => repo, 333 end
292 :revision => identifier, 334 end
293 :scmid => scmid, 335
294 :committer => author,
295 :committed_on => time,
296 :comments => message)
297
298 if changeset.save
299 paths.each do |file|
300 Change.create(
301 :changeset => changeset,
302 :action => file[:action],
303 :path => file[:path])
304 end
305 end
306 end
307 end
308 end
309
310 class Annotate 336 class Annotate
311 attr_reader :lines, :revisions 337 attr_reader :lines, :revisions
312 338
313 def initialize 339 def initialize
314 @lines = [] 340 @lines = []
315 @revisions = [] 341 @revisions = []
316 end 342 end
317 343
318 def add_line(line, revision) 344 def add_line(line, revision)
319 @lines << line 345 @lines << line
320 @revisions << revision 346 @revisions << revision
321 end 347 end
322 348
323 def content 349 def content
324 content = lines.join("\n") 350 content = lines.join("\n")
325 end 351 end
326 352
327 def empty? 353 def empty?
328 lines.empty? 354 lines.empty?
329 end 355 end
330 end 356 end
331 end 357 end