##// END OF EJS Templates
Removes the XML declaration that breaks the parser with JRuby....
Jean-Philippe Lang -
r9352:cb16661d365c
parent child
Show More
@@ -1,382 +1,389
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 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
27
28 # raised if scm command exited with error, e.g. unknown revision.
28 # raised if scm command exited with error, e.g. unknown revision.
29 class ScmCommandAborted < CommandFailed; end
29 class ScmCommandAborted < CommandFailed; end
30
30
31 class << self
31 class << self
32 def client_command
32 def client_command
33 ""
33 ""
34 end
34 end
35
35
36 def shell_quote_command
36 def shell_quote_command
37 if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
37 if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
38 client_command
38 client_command
39 else
39 else
40 shell_quote(client_command)
40 shell_quote(client_command)
41 end
41 end
42 end
42 end
43
43
44 # Returns the version of the scm client
44 # Returns the version of the scm client
45 # Eg: [1, 5, 0] or [] if unknown
45 # Eg: [1, 5, 0] or [] if unknown
46 def client_version
46 def client_version
47 []
47 []
48 end
48 end
49
49
50 # Returns the version string of the scm client
50 # Returns the version string of the scm client
51 # Eg: '1.5.0' or 'Unknown version' if unknown
51 # Eg: '1.5.0' or 'Unknown version' if unknown
52 def client_version_string
52 def client_version_string
53 v = client_version || 'Unknown version'
53 v = client_version || 'Unknown version'
54 v.is_a?(Array) ? v.join('.') : v.to_s
54 v.is_a?(Array) ? v.join('.') : v.to_s
55 end
55 end
56
56
57 # Returns true if the current client version is above
57 # Returns true if the current client version is above
58 # or equals the given one
58 # or equals the given one
59 # If option is :unknown is set to true, it will return
59 # If option is :unknown is set to true, it will return
60 # true if the client version is unknown
60 # true if the client version is unknown
61 def client_version_above?(v, options={})
61 def client_version_above?(v, options={})
62 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
62 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
63 end
63 end
64
64
65 def client_available
65 def client_available
66 true
66 true
67 end
67 end
68
68
69 def shell_quote(str)
69 def shell_quote(str)
70 if Redmine::Platform.mswin?
70 if Redmine::Platform.mswin?
71 '"' + str.gsub(/"/, '\\"') + '"'
71 '"' + str.gsub(/"/, '\\"') + '"'
72 else
72 else
73 "'" + str.gsub(/'/, "'\"'\"'") + "'"
73 "'" + str.gsub(/'/, "'\"'\"'") + "'"
74 end
74 end
75 end
75 end
76 end
76 end
77
77
78 def initialize(url, root_url=nil, login=nil, password=nil,
78 def initialize(url, root_url=nil, login=nil, password=nil,
79 path_encoding=nil)
79 path_encoding=nil)
80 @url = url
80 @url = url
81 @login = login if login && !login.empty?
81 @login = login if login && !login.empty?
82 @password = (password || "") if @login
82 @password = (password || "") if @login
83 @root_url = root_url.blank? ? retrieve_root_url : root_url
83 @root_url = root_url.blank? ? retrieve_root_url : root_url
84 end
84 end
85
85
86 def adapter_name
86 def adapter_name
87 'Abstract'
87 'Abstract'
88 end
88 end
89
89
90 def supports_cat?
90 def supports_cat?
91 true
91 true
92 end
92 end
93
93
94 def supports_annotate?
94 def supports_annotate?
95 respond_to?('annotate')
95 respond_to?('annotate')
96 end
96 end
97
97
98 def root_url
98 def root_url
99 @root_url
99 @root_url
100 end
100 end
101
101
102 def url
102 def url
103 @url
103 @url
104 end
104 end
105
105
106 def path_encoding
106 def path_encoding
107 nil
107 nil
108 end
108 end
109
109
110 # get info about the svn repository
110 # get info about the svn repository
111 def info
111 def info
112 return nil
112 return nil
113 end
113 end
114
114
115 # Returns the entry identified by path and revision identifier
115 # Returns the entry identified by path and revision identifier
116 # or nil if entry doesn't exist in the repository
116 # or nil if entry doesn't exist in the repository
117 def entry(path=nil, identifier=nil)
117 def entry(path=nil, identifier=nil)
118 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
118 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
119 search_path = parts[0..-2].join('/')
119 search_path = parts[0..-2].join('/')
120 search_name = parts[-1]
120 search_name = parts[-1]
121 if search_path.blank? && search_name.blank?
121 if search_path.blank? && search_name.blank?
122 # Root entry
122 # Root entry
123 Entry.new(:path => '', :kind => 'dir')
123 Entry.new(:path => '', :kind => 'dir')
124 else
124 else
125 # Search for the entry in the parent directory
125 # Search for the entry in the parent directory
126 es = entries(search_path, identifier)
126 es = entries(search_path, identifier)
127 es ? es.detect {|e| e.name == search_name} : nil
127 es ? es.detect {|e| e.name == search_name} : nil
128 end
128 end
129 end
129 end
130
130
131 # Returns an Entries collection
131 # Returns an Entries collection
132 # or nil if the given path doesn't exist in the repository
132 # or nil if the given path doesn't exist in the repository
133 def entries(path=nil, identifier=nil, options={})
133 def entries(path=nil, identifier=nil, options={})
134 return nil
134 return nil
135 end
135 end
136
136
137 def branches
137 def branches
138 return nil
138 return nil
139 end
139 end
140
140
141 def tags
141 def tags
142 return nil
142 return nil
143 end
143 end
144
144
145 def default_branch
145 def default_branch
146 return nil
146 return nil
147 end
147 end
148
148
149 def properties(path, identifier=nil)
149 def properties(path, identifier=nil)
150 return nil
150 return nil
151 end
151 end
152
152
153 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
153 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
154 return nil
154 return nil
155 end
155 end
156
156
157 def diff(path, identifier_from, identifier_to=nil)
157 def diff(path, identifier_from, identifier_to=nil)
158 return nil
158 return nil
159 end
159 end
160
160
161 def cat(path, identifier=nil)
161 def cat(path, identifier=nil)
162 return nil
162 return nil
163 end
163 end
164
164
165 def with_leading_slash(path)
165 def with_leading_slash(path)
166 path ||= ''
166 path ||= ''
167 (path[0,1]!="/") ? "/#{path}" : path
167 (path[0,1]!="/") ? "/#{path}" : path
168 end
168 end
169
169
170 def with_trailling_slash(path)
170 def with_trailling_slash(path)
171 path ||= ''
171 path ||= ''
172 (path[-1,1] == "/") ? path : "#{path}/"
172 (path[-1,1] == "/") ? path : "#{path}/"
173 end
173 end
174
174
175 def without_leading_slash(path)
175 def without_leading_slash(path)
176 path ||= ''
176 path ||= ''
177 path.gsub(%r{^/+}, '')
177 path.gsub(%r{^/+}, '')
178 end
178 end
179
179
180 def without_trailling_slash(path)
180 def without_trailling_slash(path)
181 path ||= ''
181 path ||= ''
182 (path[-1,1] == "/") ? path[0..-2] : path
182 (path[-1,1] == "/") ? path[0..-2] : path
183 end
183 end
184
184
185 def shell_quote(str)
185 def shell_quote(str)
186 self.class.shell_quote(str)
186 self.class.shell_quote(str)
187 end
187 end
188
188
189 private
189 private
190 def retrieve_root_url
190 def retrieve_root_url
191 info = self.info
191 info = self.info
192 info ? info.root_url : nil
192 info ? info.root_url : nil
193 end
193 end
194
194
195 def target(path, sq=true)
195 def target(path, sq=true)
196 path ||= ''
196 path ||= ''
197 base = path.match(/^\//) ? root_url : url
197 base = path.match(/^\//) ? root_url : url
198 str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
198 str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
199 if sq
199 if sq
200 str = shell_quote(str)
200 str = shell_quote(str)
201 end
201 end
202 str
202 str
203 end
203 end
204
204
205 def logger
205 def logger
206 self.class.logger
206 self.class.logger
207 end
207 end
208
208
209 def shellout(cmd, options = {}, &block)
209 def shellout(cmd, options = {}, &block)
210 self.class.shellout(cmd, options, &block)
210 self.class.shellout(cmd, options, &block)
211 end
211 end
212
212
213 def self.logger
213 def self.logger
214 Rails.logger
214 Rails.logger
215 end
215 end
216
216
217 def self.shellout(cmd, options = {}, &block)
217 def self.shellout(cmd, options = {}, &block)
218 if logger && logger.debug?
218 if logger && logger.debug?
219 logger.debug "Shelling out: #{strip_credential(cmd)}"
219 logger.debug "Shelling out: #{strip_credential(cmd)}"
220 end
220 end
221 if Rails.env == 'development'
221 if Rails.env == 'development'
222 # Capture stderr when running in dev environment
222 # Capture stderr when running in dev environment
223 cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
223 cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
224 end
224 end
225 begin
225 begin
226 mode = "r+"
226 mode = "r+"
227 IO.popen(cmd, mode) do |io|
227 IO.popen(cmd, mode) do |io|
228 io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
228 io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
229 io.close_write unless options[:write_stdin]
229 io.close_write unless options[:write_stdin]
230 block.call(io) if block_given?
230 block.call(io) if block_given?
231 end
231 end
232 ## If scm command does not exist,
232 ## If scm command does not exist,
233 ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
233 ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
234 ## in production environment.
234 ## in production environment.
235 # rescue Errno::ENOENT => e
235 # rescue Errno::ENOENT => e
236 rescue Exception => e
236 rescue Exception => e
237 msg = strip_credential(e.message)
237 msg = strip_credential(e.message)
238 # The command failed, log it and re-raise
238 # The command failed, log it and re-raise
239 logmsg = "SCM command failed, "
239 logmsg = "SCM command failed, "
240 logmsg += "make sure that your SCM command (e.g. svn) is "
240 logmsg += "make sure that your SCM command (e.g. svn) is "
241 logmsg += "in PATH (#{ENV['PATH']})\n"
241 logmsg += "in PATH (#{ENV['PATH']})\n"
242 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
242 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
243 logmsg += "#{strip_credential(cmd)}\n"
243 logmsg += "#{strip_credential(cmd)}\n"
244 logmsg += "with: #{msg}"
244 logmsg += "with: #{msg}"
245 logger.error(logmsg)
245 logger.error(logmsg)
246 raise CommandFailed.new(msg)
246 raise CommandFailed.new(msg)
247 end
247 end
248 end
248 end
249
249
250 # Hides username/password in a given command
250 # Hides username/password in a given command
251 def self.strip_credential(cmd)
251 def self.strip_credential(cmd)
252 q = (Redmine::Platform.mswin? ? '"' : "'")
252 q = (Redmine::Platform.mswin? ? '"' : "'")
253 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
253 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
254 end
254 end
255
255
256 def strip_credential(cmd)
256 def strip_credential(cmd)
257 self.class.strip_credential(cmd)
257 self.class.strip_credential(cmd)
258 end
258 end
259
259
260 def scm_iconv(to, from, str)
260 def scm_iconv(to, from, str)
261 return nil if str.nil?
261 return nil if str.nil?
262 return str if to == from
262 return str if to == from
263 begin
263 begin
264 Iconv.conv(to, from, str)
264 Iconv.conv(to, from, str)
265 rescue Iconv::Failure => err
265 rescue Iconv::Failure => err
266 logger.error("failed to convert from #{from} to #{to}. #{err}")
266 logger.error("failed to convert from #{from} to #{to}. #{err}")
267 nil
267 nil
268 end
268 end
269 end
269 end
270
271 def parse_xml(xml)
272 if RUBY_PLATFORM == 'java'
273 xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
274 end
275 ActiveSupport::XmlMini.parse(xml)
276 end
270 end
277 end
271
278
272 class Entries < Array
279 class Entries < Array
273 def sort_by_name
280 def sort_by_name
274 sort {|x,y|
281 sort {|x,y|
275 if x.kind == y.kind
282 if x.kind == y.kind
276 x.name.to_s <=> y.name.to_s
283 x.name.to_s <=> y.name.to_s
277 else
284 else
278 x.kind <=> y.kind
285 x.kind <=> y.kind
279 end
286 end
280 }
287 }
281 end
288 end
282
289
283 def revisions
290 def revisions
284 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
291 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
285 end
292 end
286 end
293 end
287
294
288 class Info
295 class Info
289 attr_accessor :root_url, :lastrev
296 attr_accessor :root_url, :lastrev
290 def initialize(attributes={})
297 def initialize(attributes={})
291 self.root_url = attributes[:root_url] if attributes[:root_url]
298 self.root_url = attributes[:root_url] if attributes[:root_url]
292 self.lastrev = attributes[:lastrev]
299 self.lastrev = attributes[:lastrev]
293 end
300 end
294 end
301 end
295
302
296 class Entry
303 class Entry
297 attr_accessor :name, :path, :kind, :size, :lastrev
304 attr_accessor :name, :path, :kind, :size, :lastrev
298 def initialize(attributes={})
305 def initialize(attributes={})
299 self.name = attributes[:name] if attributes[:name]
306 self.name = attributes[:name] if attributes[:name]
300 self.path = attributes[:path] if attributes[:path]
307 self.path = attributes[:path] if attributes[:path]
301 self.kind = attributes[:kind] if attributes[:kind]
308 self.kind = attributes[:kind] if attributes[:kind]
302 self.size = attributes[:size].to_i if attributes[:size]
309 self.size = attributes[:size].to_i if attributes[:size]
303 self.lastrev = attributes[:lastrev]
310 self.lastrev = attributes[:lastrev]
304 end
311 end
305
312
306 def is_file?
313 def is_file?
307 'file' == self.kind
314 'file' == self.kind
308 end
315 end
309
316
310 def is_dir?
317 def is_dir?
311 'dir' == self.kind
318 'dir' == self.kind
312 end
319 end
313
320
314 def is_text?
321 def is_text?
315 Redmine::MimeType.is_type?('text', name)
322 Redmine::MimeType.is_type?('text', name)
316 end
323 end
317 end
324 end
318
325
319 class Revisions < Array
326 class Revisions < Array
320 def latest
327 def latest
321 sort {|x,y|
328 sort {|x,y|
322 unless x.time.nil? or y.time.nil?
329 unless x.time.nil? or y.time.nil?
323 x.time <=> y.time
330 x.time <=> y.time
324 else
331 else
325 0
332 0
326 end
333 end
327 }.last
334 }.last
328 end
335 end
329 end
336 end
330
337
331 class Revision
338 class Revision
332 attr_accessor :scmid, :name, :author, :time, :message,
339 attr_accessor :scmid, :name, :author, :time, :message,
333 :paths, :revision, :branch, :identifier,
340 :paths, :revision, :branch, :identifier,
334 :parents
341 :parents
335
342
336 def initialize(attributes={})
343 def initialize(attributes={})
337 self.identifier = attributes[:identifier]
344 self.identifier = attributes[:identifier]
338 self.scmid = attributes[:scmid]
345 self.scmid = attributes[:scmid]
339 self.name = attributes[:name] || self.identifier
346 self.name = attributes[:name] || self.identifier
340 self.author = attributes[:author]
347 self.author = attributes[:author]
341 self.time = attributes[:time]
348 self.time = attributes[:time]
342 self.message = attributes[:message] || ""
349 self.message = attributes[:message] || ""
343 self.paths = attributes[:paths]
350 self.paths = attributes[:paths]
344 self.revision = attributes[:revision]
351 self.revision = attributes[:revision]
345 self.branch = attributes[:branch]
352 self.branch = attributes[:branch]
346 self.parents = attributes[:parents]
353 self.parents = attributes[:parents]
347 end
354 end
348
355
349 # Returns the readable identifier.
356 # Returns the readable identifier.
350 def format_identifier
357 def format_identifier
351 self.identifier.to_s
358 self.identifier.to_s
352 end
359 end
353 end
360 end
354
361
355 class Annotate
362 class Annotate
356 attr_reader :lines, :revisions
363 attr_reader :lines, :revisions
357
364
358 def initialize
365 def initialize
359 @lines = []
366 @lines = []
360 @revisions = []
367 @revisions = []
361 end
368 end
362
369
363 def add_line(line, revision)
370 def add_line(line, revision)
364 @lines << line
371 @lines << line
365 @revisions << revision
372 @revisions << revision
366 end
373 end
367
374
368 def content
375 def content
369 content = lines.join("\n")
376 content = lines.join("\n")
370 end
377 end
371
378
372 def empty?
379 def empty?
373 lines.empty?
380 lines.empty?
374 end
381 end
375 end
382 end
376
383
377 class Branch < String
384 class Branch < String
378 attr_accessor :revision, :scmid
385 attr_accessor :revision, :scmid
379 end
386 end
380 end
387 end
381 end
388 end
382 end
389 end
@@ -1,341 +1,341
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 require 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19 require 'cgi'
19 require 'cgi'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class MercurialAdapter < AbstractAdapter
24 class MercurialAdapter < AbstractAdapter
25
25
26 # Mercurial executable name
26 # Mercurial executable name
27 HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg"
27 HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg"
28 HELPERS_DIR = File.dirname(__FILE__) + "/mercurial"
28 HELPERS_DIR = File.dirname(__FILE__) + "/mercurial"
29 HG_HELPER_EXT = "#{HELPERS_DIR}/redminehelper.py"
29 HG_HELPER_EXT = "#{HELPERS_DIR}/redminehelper.py"
30 TEMPLATE_NAME = "hg-template"
30 TEMPLATE_NAME = "hg-template"
31 TEMPLATE_EXTENSION = "tmpl"
31 TEMPLATE_EXTENSION = "tmpl"
32
32
33 # raised if hg command exited with error, e.g. unknown revision.
33 # raised if hg command exited with error, e.g. unknown revision.
34 class HgCommandAborted < CommandFailed; end
34 class HgCommandAborted < CommandFailed; end
35
35
36 class << self
36 class << self
37 def client_command
37 def client_command
38 @@bin ||= HG_BIN
38 @@bin ||= HG_BIN
39 end
39 end
40
40
41 def sq_bin
41 def sq_bin
42 @@sq_bin ||= shell_quote_command
42 @@sq_bin ||= shell_quote_command
43 end
43 end
44
44
45 def client_version
45 def client_version
46 @@client_version ||= (hgversion || [])
46 @@client_version ||= (hgversion || [])
47 end
47 end
48
48
49 def client_available
49 def client_available
50 client_version_above?([1, 2])
50 client_version_above?([1, 2])
51 end
51 end
52
52
53 def hgversion
53 def hgversion
54 # The hg version is expressed either as a
54 # The hg version is expressed either as a
55 # release number (eg 0.9.5 or 1.0) or as a revision
55 # release number (eg 0.9.5 or 1.0) or as a revision
56 # id composed of 12 hexa characters.
56 # id composed of 12 hexa characters.
57 theversion = hgversion_from_command_line.dup
57 theversion = hgversion_from_command_line.dup
58 if theversion.respond_to?(:force_encoding)
58 if theversion.respond_to?(:force_encoding)
59 theversion.force_encoding('ASCII-8BIT')
59 theversion.force_encoding('ASCII-8BIT')
60 end
60 end
61 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
61 if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
62 m[2].scan(%r{\d+}).collect(&:to_i)
62 m[2].scan(%r{\d+}).collect(&:to_i)
63 end
63 end
64 end
64 end
65
65
66 def hgversion_from_command_line
66 def hgversion_from_command_line
67 shellout("#{sq_bin} --version") { |io| io.read }.to_s
67 shellout("#{sq_bin} --version") { |io| io.read }.to_s
68 end
68 end
69
69
70 def template_path
70 def template_path
71 @@template_path ||= template_path_for(client_version)
71 @@template_path ||= template_path_for(client_version)
72 end
72 end
73
73
74 def template_path_for(version)
74 def template_path_for(version)
75 "#{HELPERS_DIR}/#{TEMPLATE_NAME}-1.0.#{TEMPLATE_EXTENSION}"
75 "#{HELPERS_DIR}/#{TEMPLATE_NAME}-1.0.#{TEMPLATE_EXTENSION}"
76 end
76 end
77 end
77 end
78
78
79 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
79 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
80 super
80 super
81 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
81 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
82 end
82 end
83
83
84 def path_encoding
84 def path_encoding
85 @path_encoding
85 @path_encoding
86 end
86 end
87
87
88 def info
88 def info
89 tip = summary['repository']['tip']
89 tip = summary['repository']['tip']
90 Info.new(:root_url => CGI.unescape(summary['repository']['root']),
90 Info.new(:root_url => CGI.unescape(summary['repository']['root']),
91 :lastrev => Revision.new(:revision => tip['revision'],
91 :lastrev => Revision.new(:revision => tip['revision'],
92 :scmid => tip['node']))
92 :scmid => tip['node']))
93 # rescue HgCommandAborted
93 # rescue HgCommandAborted
94 rescue Exception => e
94 rescue Exception => e
95 logger.error "hg: error during getting info: #{e.message}"
95 logger.error "hg: error during getting info: #{e.message}"
96 nil
96 nil
97 end
97 end
98
98
99 def tags
99 def tags
100 as_ary(summary['repository']['tag']).map { |e| e['name'] }
100 as_ary(summary['repository']['tag']).map { |e| e['name'] }
101 end
101 end
102
102
103 # Returns map of {'tag' => 'nodeid', ...}
103 # Returns map of {'tag' => 'nodeid', ...}
104 def tagmap
104 def tagmap
105 alist = as_ary(summary['repository']['tag']).map do |e|
105 alist = as_ary(summary['repository']['tag']).map do |e|
106 e.values_at('name', 'node')
106 e.values_at('name', 'node')
107 end
107 end
108 Hash[*alist.flatten]
108 Hash[*alist.flatten]
109 end
109 end
110
110
111 def branches
111 def branches
112 brs = []
112 brs = []
113 as_ary(summary['repository']['branch']).each do |e|
113 as_ary(summary['repository']['branch']).each do |e|
114 br = Branch.new(e['name'])
114 br = Branch.new(e['name'])
115 br.revision = e['revision']
115 br.revision = e['revision']
116 br.scmid = e['node']
116 br.scmid = e['node']
117 brs << br
117 brs << br
118 end
118 end
119 brs
119 brs
120 end
120 end
121
121
122 # Returns map of {'branch' => 'nodeid', ...}
122 # Returns map of {'branch' => 'nodeid', ...}
123 def branchmap
123 def branchmap
124 alist = as_ary(summary['repository']['branch']).map do |e|
124 alist = as_ary(summary['repository']['branch']).map do |e|
125 e.values_at('name', 'node')
125 e.values_at('name', 'node')
126 end
126 end
127 Hash[*alist.flatten]
127 Hash[*alist.flatten]
128 end
128 end
129
129
130 def summary
130 def summary
131 return @summary if @summary
131 return @summary if @summary
132 hg 'rhsummary' do |io|
132 hg 'rhsummary' do |io|
133 output = io.read
133 output = io.read
134 if output.respond_to?(:force_encoding)
134 if output.respond_to?(:force_encoding)
135 output.force_encoding('UTF-8')
135 output.force_encoding('UTF-8')
136 end
136 end
137 begin
137 begin
138 @summary = ActiveSupport::XmlMini.parse(output)['rhsummary']
138 @summary = parse_xml(output)['rhsummary']
139 rescue
139 rescue
140 end
140 end
141 end
141 end
142 end
142 end
143 private :summary
143 private :summary
144
144
145 def entries(path=nil, identifier=nil, options={})
145 def entries(path=nil, identifier=nil, options={})
146 p1 = scm_iconv(@path_encoding, 'UTF-8', path)
146 p1 = scm_iconv(@path_encoding, 'UTF-8', path)
147 manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)),
147 manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)),
148 CGI.escape(without_leading_slash(p1.to_s))) do |io|
148 CGI.escape(without_leading_slash(p1.to_s))) do |io|
149 output = io.read
149 output = io.read
150 if output.respond_to?(:force_encoding)
150 if output.respond_to?(:force_encoding)
151 output.force_encoding('UTF-8')
151 output.force_encoding('UTF-8')
152 end
152 end
153 begin
153 begin
154 ActiveSupport::XmlMini.parse(output)['rhmanifest']['repository']['manifest']
154 parse_xml(output)['rhmanifest']['repository']['manifest']
155 rescue
155 rescue
156 end
156 end
157 end
157 end
158 path_prefix = path.blank? ? '' : with_trailling_slash(path)
158 path_prefix = path.blank? ? '' : with_trailling_slash(path)
159
159
160 entries = Entries.new
160 entries = Entries.new
161 as_ary(manifest['dir']).each do |e|
161 as_ary(manifest['dir']).each do |e|
162 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
162 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
163 p = "#{path_prefix}#{n}"
163 p = "#{path_prefix}#{n}"
164 entries << Entry.new(:name => n, :path => p, :kind => 'dir')
164 entries << Entry.new(:name => n, :path => p, :kind => 'dir')
165 end
165 end
166
166
167 as_ary(manifest['file']).each do |e|
167 as_ary(manifest['file']).each do |e|
168 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
168 n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name']))
169 p = "#{path_prefix}#{n}"
169 p = "#{path_prefix}#{n}"
170 lr = Revision.new(:revision => e['revision'], :scmid => e['node'],
170 lr = Revision.new(:revision => e['revision'], :scmid => e['node'],
171 :identifier => e['node'],
171 :identifier => e['node'],
172 :time => Time.at(e['time'].to_i))
172 :time => Time.at(e['time'].to_i))
173 entries << Entry.new(:name => n, :path => p, :kind => 'file',
173 entries << Entry.new(:name => n, :path => p, :kind => 'file',
174 :size => e['size'].to_i, :lastrev => lr)
174 :size => e['size'].to_i, :lastrev => lr)
175 end
175 end
176
176
177 entries
177 entries
178 rescue HgCommandAborted
178 rescue HgCommandAborted
179 nil # means not found
179 nil # means not found
180 end
180 end
181
181
182 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
182 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
183 revs = Revisions.new
183 revs = Revisions.new
184 each_revision(path, identifier_from, identifier_to, options) { |e| revs << e }
184 each_revision(path, identifier_from, identifier_to, options) { |e| revs << e }
185 revs
185 revs
186 end
186 end
187
187
188 # Iterates the revisions by using a template file that
188 # Iterates the revisions by using a template file that
189 # makes Mercurial produce a xml output.
189 # makes Mercurial produce a xml output.
190 def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={})
190 def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={})
191 hg_args = ['log', '--debug', '-C', '--style', self.class.template_path]
191 hg_args = ['log', '--debug', '-C', '--style', self.class.template_path]
192 hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
192 hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
193 hg_args << '--limit' << options[:limit] if options[:limit]
193 hg_args << '--limit' << options[:limit] if options[:limit]
194 hg_args << hgtarget(path) unless path.blank?
194 hg_args << hgtarget(path) unless path.blank?
195 log = hg(*hg_args) do |io|
195 log = hg(*hg_args) do |io|
196 output = io.read
196 output = io.read
197 if output.respond_to?(:force_encoding)
197 if output.respond_to?(:force_encoding)
198 output.force_encoding('UTF-8')
198 output.force_encoding('UTF-8')
199 end
199 end
200 begin
200 begin
201 # Mercurial < 1.5 does not support footer template for '</log>'
201 # Mercurial < 1.5 does not support footer template for '</log>'
202 ActiveSupport::XmlMini.parse("#{output}</log>")['log']
202 parse_xml("#{output}</log>")['log']
203 rescue
203 rescue
204 end
204 end
205 end
205 end
206 as_ary(log['logentry']).each do |le|
206 as_ary(log['logentry']).each do |le|
207 cpalist = as_ary(le['paths']['path-copied']).map do |e|
207 cpalist = as_ary(le['paths']['path-copied']).map do |e|
208 [e['__content__'], e['copyfrom-path']].map do |s|
208 [e['__content__'], e['copyfrom-path']].map do |s|
209 scm_iconv('UTF-8', @path_encoding, CGI.unescape(s))
209 scm_iconv('UTF-8', @path_encoding, CGI.unescape(s))
210 end
210 end
211 end
211 end
212 cpmap = Hash[*cpalist.flatten]
212 cpmap = Hash[*cpalist.flatten]
213 paths = as_ary(le['paths']['path']).map do |e|
213 paths = as_ary(le['paths']['path']).map do |e|
214 p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) )
214 p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) )
215 {:action => e['action'],
215 {:action => e['action'],
216 :path => with_leading_slash(p),
216 :path => with_leading_slash(p),
217 :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil),
217 :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil),
218 :from_revision => (cpmap.member?(p) ? le['node'] : nil)}
218 :from_revision => (cpmap.member?(p) ? le['node'] : nil)}
219 end.sort { |a, b| a[:path] <=> b[:path] }
219 end.sort { |a, b| a[:path] <=> b[:path] }
220 parents_ary = []
220 parents_ary = []
221 as_ary(le['parents']['parent']).map do |par|
221 as_ary(le['parents']['parent']).map do |par|
222 parents_ary << par['__content__'] if par['__content__'] != "000000000000"
222 parents_ary << par['__content__'] if par['__content__'] != "000000000000"
223 end
223 end
224 yield Revision.new(:revision => le['revision'],
224 yield Revision.new(:revision => le['revision'],
225 :scmid => le['node'],
225 :scmid => le['node'],
226 :author => (le['author']['__content__'] rescue ''),
226 :author => (le['author']['__content__'] rescue ''),
227 :time => Time.parse(le['date']['__content__']),
227 :time => Time.parse(le['date']['__content__']),
228 :message => le['msg']['__content__'],
228 :message => le['msg']['__content__'],
229 :paths => paths,
229 :paths => paths,
230 :parents => parents_ary)
230 :parents => parents_ary)
231 end
231 end
232 self
232 self
233 end
233 end
234
234
235 # Returns list of nodes in the specified branch
235 # Returns list of nodes in the specified branch
236 def nodes_in_branch(branch, options={})
236 def nodes_in_branch(branch, options={})
237 hg_args = ['rhlog', '--template', '{node|short}\n', '--rhbranch', CGI.escape(branch)]
237 hg_args = ['rhlog', '--template', '{node|short}\n', '--rhbranch', CGI.escape(branch)]
238 hg_args << '--from' << CGI.escape(branch)
238 hg_args << '--from' << CGI.escape(branch)
239 hg_args << '--to' << '0'
239 hg_args << '--to' << '0'
240 hg_args << '--limit' << options[:limit] if options[:limit]
240 hg_args << '--limit' << options[:limit] if options[:limit]
241 hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } }
241 hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } }
242 end
242 end
243
243
244 def diff(path, identifier_from, identifier_to=nil)
244 def diff(path, identifier_from, identifier_to=nil)
245 hg_args = %w|rhdiff|
245 hg_args = %w|rhdiff|
246 if identifier_to
246 if identifier_to
247 hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
247 hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
248 else
248 else
249 hg_args << '-c' << hgrev(identifier_from)
249 hg_args << '-c' << hgrev(identifier_from)
250 end
250 end
251 unless path.blank?
251 unless path.blank?
252 p = scm_iconv(@path_encoding, 'UTF-8', path)
252 p = scm_iconv(@path_encoding, 'UTF-8', path)
253 hg_args << CGI.escape(hgtarget(p))
253 hg_args << CGI.escape(hgtarget(p))
254 end
254 end
255 diff = []
255 diff = []
256 hg *hg_args do |io|
256 hg *hg_args do |io|
257 io.each_line do |line|
257 io.each_line do |line|
258 diff << line
258 diff << line
259 end
259 end
260 end
260 end
261 diff
261 diff
262 rescue HgCommandAborted
262 rescue HgCommandAborted
263 nil # means not found
263 nil # means not found
264 end
264 end
265
265
266 def cat(path, identifier=nil)
266 def cat(path, identifier=nil)
267 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
267 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
268 hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
268 hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
269 io.binmode
269 io.binmode
270 io.read
270 io.read
271 end
271 end
272 rescue HgCommandAborted
272 rescue HgCommandAborted
273 nil # means not found
273 nil # means not found
274 end
274 end
275
275
276 def annotate(path, identifier=nil)
276 def annotate(path, identifier=nil)
277 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
277 p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
278 blame = Annotate.new
278 blame = Annotate.new
279 hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
279 hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
280 io.each_line do |line|
280 io.each_line do |line|
281 line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding)
281 line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding)
282 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
282 next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
283 r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
283 r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
284 :identifier => $3)
284 :identifier => $3)
285 blame.add_line($4.rstrip, r)
285 blame.add_line($4.rstrip, r)
286 end
286 end
287 end
287 end
288 blame
288 blame
289 rescue HgCommandAborted
289 rescue HgCommandAborted
290 # means not found or cannot be annotated
290 # means not found or cannot be annotated
291 Annotate.new
291 Annotate.new
292 end
292 end
293
293
294 class Revision < Redmine::Scm::Adapters::Revision
294 class Revision < Redmine::Scm::Adapters::Revision
295 # Returns the readable identifier
295 # Returns the readable identifier
296 def format_identifier
296 def format_identifier
297 "#{revision}:#{scmid}"
297 "#{revision}:#{scmid}"
298 end
298 end
299 end
299 end
300
300
301 # Runs 'hg' command with the given args
301 # Runs 'hg' command with the given args
302 def hg(*args, &block)
302 def hg(*args, &block)
303 repo_path = root_url || url
303 repo_path = root_url || url
304 full_args = ['-R', repo_path, '--encoding', 'utf-8']
304 full_args = ['-R', repo_path, '--encoding', 'utf-8']
305 full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}"
305 full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}"
306 full_args << '--config' << 'diff.git=false'
306 full_args << '--config' << 'diff.git=false'
307 full_args += args
307 full_args += args
308 ret = shellout(
308 ret = shellout(
309 self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
309 self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
310 &block
310 &block
311 )
311 )
312 if $? && $?.exitstatus != 0
312 if $? && $?.exitstatus != 0
313 raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}"
313 raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}"
314 end
314 end
315 ret
315 ret
316 end
316 end
317 private :hg
317 private :hg
318
318
319 # Returns correct revision identifier
319 # Returns correct revision identifier
320 def hgrev(identifier, sq=false)
320 def hgrev(identifier, sq=false)
321 rev = identifier.blank? ? 'tip' : identifier.to_s
321 rev = identifier.blank? ? 'tip' : identifier.to_s
322 rev = shell_quote(rev) if sq
322 rev = shell_quote(rev) if sq
323 rev
323 rev
324 end
324 end
325 private :hgrev
325 private :hgrev
326
326
327 def hgtarget(path)
327 def hgtarget(path)
328 path ||= ''
328 path ||= ''
329 root_url + '/' + without_leading_slash(path)
329 root_url + '/' + without_leading_slash(path)
330 end
330 end
331 private :hgtarget
331 private :hgtarget
332
332
333 def as_ary(o)
333 def as_ary(o)
334 return [] unless o
334 return [] unless o
335 o.is_a?(Array) ? o : Array[o]
335 o.is_a?(Array) ? o : Array[o]
336 end
336 end
337 private :as_ary
337 private :as_ary
338 end
338 end
339 end
339 end
340 end
340 end
341 end
341 end
@@ -1,291 +1,291
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 require 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19 require 'uri'
19 require 'uri'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class SubversionAdapter < AbstractAdapter
24 class SubversionAdapter < AbstractAdapter
25
25
26 # SVN executable name
26 # SVN executable name
27 SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
27 SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
28
28
29 class << self
29 class << self
30 def client_command
30 def client_command
31 @@bin ||= SVN_BIN
31 @@bin ||= SVN_BIN
32 end
32 end
33
33
34 def sq_bin
34 def sq_bin
35 @@sq_bin ||= shell_quote_command
35 @@sq_bin ||= shell_quote_command
36 end
36 end
37
37
38 def client_version
38 def client_version
39 @@client_version ||= (svn_binary_version || [])
39 @@client_version ||= (svn_binary_version || [])
40 end
40 end
41
41
42 def client_available
42 def client_available
43 # --xml options are introduced in 1.3.
43 # --xml options are introduced in 1.3.
44 # http://subversion.apache.org/docs/release-notes/1.3.html
44 # http://subversion.apache.org/docs/release-notes/1.3.html
45 client_version_above?([1, 3])
45 client_version_above?([1, 3])
46 end
46 end
47
47
48 def svn_binary_version
48 def svn_binary_version
49 scm_version = scm_version_from_command_line.dup
49 scm_version = scm_version_from_command_line.dup
50 if scm_version.respond_to?(:force_encoding)
50 if scm_version.respond_to?(:force_encoding)
51 scm_version.force_encoding('ASCII-8BIT')
51 scm_version.force_encoding('ASCII-8BIT')
52 end
52 end
53 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
53 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
54 m[2].scan(%r{\d+}).collect(&:to_i)
54 m[2].scan(%r{\d+}).collect(&:to_i)
55 end
55 end
56 end
56 end
57
57
58 def scm_version_from_command_line
58 def scm_version_from_command_line
59 shellout("#{sq_bin} --version") { |io| io.read }.to_s
59 shellout("#{sq_bin} --version") { |io| io.read }.to_s
60 end
60 end
61 end
61 end
62
62
63 # Get info about the svn repository
63 # Get info about the svn repository
64 def info
64 def info
65 cmd = "#{self.class.sq_bin} info --xml #{target}"
65 cmd = "#{self.class.sq_bin} info --xml #{target}"
66 cmd << credentials_string
66 cmd << credentials_string
67 info = nil
67 info = nil
68 shellout(cmd) do |io|
68 shellout(cmd) do |io|
69 output = io.read
69 output = io.read
70 if output.respond_to?(:force_encoding)
70 if output.respond_to?(:force_encoding)
71 output.force_encoding('UTF-8')
71 output.force_encoding('UTF-8')
72 end
72 end
73 begin
73 begin
74 doc = ActiveSupport::XmlMini.parse(output)
74 doc = parse_xml(output)
75 # root_url = doc.elements["info/entry/repository/root"].text
75 # root_url = doc.elements["info/entry/repository/root"].text
76 info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
76 info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
77 :lastrev => Revision.new({
77 :lastrev => Revision.new({
78 :identifier => doc['info']['entry']['commit']['revision'],
78 :identifier => doc['info']['entry']['commit']['revision'],
79 :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
79 :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
80 :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
80 :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
81 })
81 })
82 })
82 })
83 rescue
83 rescue
84 end
84 end
85 end
85 end
86 return nil if $? && $?.exitstatus != 0
86 return nil if $? && $?.exitstatus != 0
87 info
87 info
88 rescue CommandFailed
88 rescue CommandFailed
89 return nil
89 return nil
90 end
90 end
91
91
92 # Returns an Entries collection
92 # Returns an Entries collection
93 # or nil if the given path doesn't exist in the repository
93 # or nil if the given path doesn't exist in the repository
94 def entries(path=nil, identifier=nil, options={})
94 def entries(path=nil, identifier=nil, options={})
95 path ||= ''
95 path ||= ''
96 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
96 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
97 entries = Entries.new
97 entries = Entries.new
98 cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}"
98 cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}"
99 cmd << credentials_string
99 cmd << credentials_string
100 shellout(cmd) do |io|
100 shellout(cmd) do |io|
101 output = io.read
101 output = io.read
102 if output.respond_to?(:force_encoding)
102 if output.respond_to?(:force_encoding)
103 output.force_encoding('UTF-8')
103 output.force_encoding('UTF-8')
104 end
104 end
105 begin
105 begin
106 doc = ActiveSupport::XmlMini.parse(output)
106 doc = parse_xml(output)
107 each_xml_element(doc['lists']['list'], 'entry') do |entry|
107 each_xml_element(doc['lists']['list'], 'entry') do |entry|
108 commit = entry['commit']
108 commit = entry['commit']
109 commit_date = commit['date']
109 commit_date = commit['date']
110 # Skip directory if there is no commit date (usually that
110 # Skip directory if there is no commit date (usually that
111 # means that we don't have read access to it)
111 # means that we don't have read access to it)
112 next if entry['kind'] == 'dir' && commit_date.nil?
112 next if entry['kind'] == 'dir' && commit_date.nil?
113 name = entry['name']['__content__']
113 name = entry['name']['__content__']
114 entries << Entry.new({:name => URI.unescape(name),
114 entries << Entry.new({:name => URI.unescape(name),
115 :path => ((path.empty? ? "" : "#{path}/") + name),
115 :path => ((path.empty? ? "" : "#{path}/") + name),
116 :kind => entry['kind'],
116 :kind => entry['kind'],
117 :size => ((s = entry['size']) ? s['__content__'].to_i : nil),
117 :size => ((s = entry['size']) ? s['__content__'].to_i : nil),
118 :lastrev => Revision.new({
118 :lastrev => Revision.new({
119 :identifier => commit['revision'],
119 :identifier => commit['revision'],
120 :time => Time.parse(commit_date['__content__'].to_s).localtime,
120 :time => Time.parse(commit_date['__content__'].to_s).localtime,
121 :author => ((a = commit['author']) ? a['__content__'] : nil)
121 :author => ((a = commit['author']) ? a['__content__'] : nil)
122 })
122 })
123 })
123 })
124 end
124 end
125 rescue Exception => e
125 rescue Exception => e
126 logger.error("Error parsing svn output: #{e.message}")
126 logger.error("Error parsing svn output: #{e.message}")
127 logger.error("Output was:\n #{output}")
127 logger.error("Output was:\n #{output}")
128 end
128 end
129 end
129 end
130 return nil if $? && $?.exitstatus != 0
130 return nil if $? && $?.exitstatus != 0
131 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
131 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
132 entries.sort_by_name
132 entries.sort_by_name
133 end
133 end
134
134
135 def properties(path, identifier=nil)
135 def properties(path, identifier=nil)
136 # proplist xml output supported in svn 1.5.0 and higher
136 # proplist xml output supported in svn 1.5.0 and higher
137 return nil unless self.class.client_version_above?([1, 5, 0])
137 return nil unless self.class.client_version_above?([1, 5, 0])
138
138
139 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
139 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
140 cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}"
140 cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}"
141 cmd << credentials_string
141 cmd << credentials_string
142 properties = {}
142 properties = {}
143 shellout(cmd) do |io|
143 shellout(cmd) do |io|
144 output = io.read
144 output = io.read
145 if output.respond_to?(:force_encoding)
145 if output.respond_to?(:force_encoding)
146 output.force_encoding('UTF-8')
146 output.force_encoding('UTF-8')
147 end
147 end
148 begin
148 begin
149 doc = ActiveSupport::XmlMini.parse(output)
149 doc = parse_xml(output)
150 each_xml_element(doc['properties']['target'], 'property') do |property|
150 each_xml_element(doc['properties']['target'], 'property') do |property|
151 properties[ property['name'] ] = property['__content__'].to_s
151 properties[ property['name'] ] = property['__content__'].to_s
152 end
152 end
153 rescue
153 rescue
154 end
154 end
155 end
155 end
156 return nil if $? && $?.exitstatus != 0
156 return nil if $? && $?.exitstatus != 0
157 properties
157 properties
158 end
158 end
159
159
160 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
160 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
161 path ||= ''
161 path ||= ''
162 identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
162 identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
163 identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
163 identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
164 revisions = Revisions.new
164 revisions = Revisions.new
165 cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}"
165 cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}"
166 cmd << credentials_string
166 cmd << credentials_string
167 cmd << " --verbose " if options[:with_paths]
167 cmd << " --verbose " if options[:with_paths]
168 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
168 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
169 cmd << ' ' + target(path)
169 cmd << ' ' + target(path)
170 shellout(cmd) do |io|
170 shellout(cmd) do |io|
171 output = io.read
171 output = io.read
172 if output.respond_to?(:force_encoding)
172 if output.respond_to?(:force_encoding)
173 output.force_encoding('UTF-8')
173 output.force_encoding('UTF-8')
174 end
174 end
175 begin
175 begin
176 doc = ActiveSupport::XmlMini.parse(output)
176 doc = parse_xml(output)
177 each_xml_element(doc['log'], 'logentry') do |logentry|
177 each_xml_element(doc['log'], 'logentry') do |logentry|
178 paths = []
178 paths = []
179 each_xml_element(logentry['paths'], 'path') do |path|
179 each_xml_element(logentry['paths'], 'path') do |path|
180 paths << {:action => path['action'],
180 paths << {:action => path['action'],
181 :path => path['__content__'],
181 :path => path['__content__'],
182 :from_path => path['copyfrom-path'],
182 :from_path => path['copyfrom-path'],
183 :from_revision => path['copyfrom-rev']
183 :from_revision => path['copyfrom-rev']
184 }
184 }
185 end if logentry['paths'] && logentry['paths']['path']
185 end if logentry['paths'] && logentry['paths']['path']
186 paths.sort! { |x,y| x[:path] <=> y[:path] }
186 paths.sort! { |x,y| x[:path] <=> y[:path] }
187
187
188 revisions << Revision.new({:identifier => logentry['revision'],
188 revisions << Revision.new({:identifier => logentry['revision'],
189 :author => (logentry['author'] ? logentry['author']['__content__'] : ""),
189 :author => (logentry['author'] ? logentry['author']['__content__'] : ""),
190 :time => Time.parse(logentry['date']['__content__'].to_s).localtime,
190 :time => Time.parse(logentry['date']['__content__'].to_s).localtime,
191 :message => logentry['msg']['__content__'],
191 :message => logentry['msg']['__content__'],
192 :paths => paths
192 :paths => paths
193 })
193 })
194 end
194 end
195 rescue
195 rescue
196 end
196 end
197 end
197 end
198 return nil if $? && $?.exitstatus != 0
198 return nil if $? && $?.exitstatus != 0
199 revisions
199 revisions
200 end
200 end
201
201
202 def diff(path, identifier_from, identifier_to=nil)
202 def diff(path, identifier_from, identifier_to=nil)
203 path ||= ''
203 path ||= ''
204 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
204 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
205
205
206 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
206 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
207
207
208 cmd = "#{self.class.sq_bin} diff -r "
208 cmd = "#{self.class.sq_bin} diff -r "
209 cmd << "#{identifier_to}:"
209 cmd << "#{identifier_to}:"
210 cmd << "#{identifier_from}"
210 cmd << "#{identifier_from}"
211 cmd << " #{target(path)}@#{identifier_from}"
211 cmd << " #{target(path)}@#{identifier_from}"
212 cmd << credentials_string
212 cmd << credentials_string
213 diff = []
213 diff = []
214 shellout(cmd) do |io|
214 shellout(cmd) do |io|
215 io.each_line do |line|
215 io.each_line do |line|
216 diff << line
216 diff << line
217 end
217 end
218 end
218 end
219 return nil if $? && $?.exitstatus != 0
219 return nil if $? && $?.exitstatus != 0
220 diff
220 diff
221 end
221 end
222
222
223 def cat(path, identifier=nil)
223 def cat(path, identifier=nil)
224 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
224 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
225 cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}"
225 cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}"
226 cmd << credentials_string
226 cmd << credentials_string
227 cat = nil
227 cat = nil
228 shellout(cmd) do |io|
228 shellout(cmd) do |io|
229 io.binmode
229 io.binmode
230 cat = io.read
230 cat = io.read
231 end
231 end
232 return nil if $? && $?.exitstatus != 0
232 return nil if $? && $?.exitstatus != 0
233 cat
233 cat
234 end
234 end
235
235
236 def annotate(path, identifier=nil)
236 def annotate(path, identifier=nil)
237 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
237 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
238 cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}"
238 cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}"
239 cmd << credentials_string
239 cmd << credentials_string
240 blame = Annotate.new
240 blame = Annotate.new
241 shellout(cmd) do |io|
241 shellout(cmd) do |io|
242 io.each_line do |line|
242 io.each_line do |line|
243 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
243 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
244 rev = $1
244 rev = $1
245 blame.add_line($3.rstrip,
245 blame.add_line($3.rstrip,
246 Revision.new(
246 Revision.new(
247 :identifier => rev,
247 :identifier => rev,
248 :revision => rev,
248 :revision => rev,
249 :author => $2.strip
249 :author => $2.strip
250 ))
250 ))
251 end
251 end
252 end
252 end
253 return nil if $? && $?.exitstatus != 0
253 return nil if $? && $?.exitstatus != 0
254 blame
254 blame
255 end
255 end
256
256
257 private
257 private
258
258
259 def credentials_string
259 def credentials_string
260 str = ''
260 str = ''
261 str << " --username #{shell_quote(@login)}" unless @login.blank?
261 str << " --username #{shell_quote(@login)}" unless @login.blank?
262 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
262 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
263 str << " --no-auth-cache --non-interactive"
263 str << " --no-auth-cache --non-interactive"
264 str
264 str
265 end
265 end
266
266
267 # Helper that iterates over the child elements of a xml node
267 # Helper that iterates over the child elements of a xml node
268 # MiniXml returns a hash when a single child is found
268 # MiniXml returns a hash when a single child is found
269 # or an array of hashes for multiple children
269 # or an array of hashes for multiple children
270 def each_xml_element(node, name)
270 def each_xml_element(node, name)
271 if node && node[name]
271 if node && node[name]
272 if node[name].is_a?(Hash)
272 if node[name].is_a?(Hash)
273 yield node[name]
273 yield node[name]
274 else
274 else
275 node[name].each do |element|
275 node[name].each do |element|
276 yield element
276 yield element
277 end
277 end
278 end
278 end
279 end
279 end
280 end
280 end
281
281
282 def target(path = '')
282 def target(path = '')
283 base = path.match(/^\//) ? root_url : url
283 base = path.match(/^\//) ? root_url : url
284 uri = "#{base}/#{path}"
284 uri = "#{base}/#{path}"
285 uri = URI.escape(URI.escape(uri), '[]')
285 uri = URI.escape(URI.escape(uri), '[]')
286 shell_quote(uri.gsub(/[?<>\*]/, ''))
286 shell_quote(uri.gsub(/[?<>\*]/, ''))
287 end
287 end
288 end
288 end
289 end
289 end
290 end
290 end
291 end
291 end
General Comments 0
You need to be logged in to leave comments. Login now