##// END OF EJS Templates
SCM AbstractAdapter use shell_quote to more properly escape path (closes #838 by John Goerzen)....
Jean-Philippe Lang -
r1224:2c1e63720ec9
parent child
Show More
@@ -1,386 +1,386
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 def initialize(url, root_url=nil, login=nil, password=nil)
27 def initialize(url, root_url=nil, login=nil, password=nil)
28 @url = url
28 @url = url
29 @login = login if login && !login.empty?
29 @login = login if login && !login.empty?
30 @password = (password || "") if @login
30 @password = (password || "") if @login
31 @root_url = root_url.blank? ? retrieve_root_url : root_url
31 @root_url = root_url.blank? ? retrieve_root_url : root_url
32 end
32 end
33
33
34 def adapter_name
34 def adapter_name
35 'Abstract'
35 'Abstract'
36 end
36 end
37
37
38 def supports_cat?
38 def supports_cat?
39 true
39 true
40 end
40 end
41
41
42 def supports_annotate?
42 def supports_annotate?
43 respond_to?('annotate')
43 respond_to?('annotate')
44 end
44 end
45
45
46 def root_url
46 def root_url
47 @root_url
47 @root_url
48 end
48 end
49
49
50 def url
50 def url
51 @url
51 @url
52 end
52 end
53
53
54 # get info about the svn repository
54 # get info about the svn repository
55 def info
55 def info
56 return nil
56 return nil
57 end
57 end
58
58
59 # Returns the entry identified by path and revision identifier
59 # Returns the entry identified by path and revision identifier
60 # or nil if entry doesn't exist in the repository
60 # or nil if entry doesn't exist in the repository
61 def entry(path=nil, identifier=nil)
61 def entry(path=nil, identifier=nil)
62 e = entries(path, identifier)
62 e = entries(path, identifier)
63 e ? e.first : nil
63 e ? e.first : nil
64 end
64 end
65
65
66 # Returns an Entries collection
66 # Returns an Entries collection
67 # or nil if the given path doesn't exist in the repository
67 # or nil if the given path doesn't exist in the repository
68 def entries(path=nil, identifier=nil)
68 def entries(path=nil, identifier=nil)
69 return nil
69 return nil
70 end
70 end
71
71
72 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
72 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
73 return nil
73 return nil
74 end
74 end
75
75
76 def diff(path, identifier_from, identifier_to=nil, type="inline")
76 def diff(path, identifier_from, identifier_to=nil, type="inline")
77 return nil
77 return nil
78 end
78 end
79
79
80 def cat(path, identifier=nil)
80 def cat(path, identifier=nil)
81 return nil
81 return nil
82 end
82 end
83
83
84 def with_leading_slash(path)
84 def with_leading_slash(path)
85 path ||= ''
85 path ||= ''
86 (path[0,1]!="/") ? "/#{path}" : path
86 (path[0,1]!="/") ? "/#{path}" : path
87 end
87 end
88
88
89 def shell_quote(str)
89 def shell_quote(str)
90 if RUBY_PLATFORM =~ /mswin/
90 if RUBY_PLATFORM =~ /mswin/
91 '"' + str.gsub(/"/, '\\"') + '"'
91 '"' + str.gsub(/"/, '\\"') + '"'
92 else
92 else
93 "'" + str.gsub(/'/, "'\"'\"'") + "'"
93 "'" + str.gsub(/'/, "'\"'\"'") + "'"
94 end
94 end
95 end
95 end
96
96
97 private
97 private
98 def retrieve_root_url
98 def retrieve_root_url
99 info = self.info
99 info = self.info
100 info ? info.root_url : nil
100 info ? info.root_url : nil
101 end
101 end
102
102
103 def target(path)
103 def target(path)
104 path ||= ""
104 path ||= ''
105 base = path.match(/^\//) ? root_url : url
105 base = path.match(/^\//) ? root_url : url
106 " \"" << "#{base}/#{path}".gsub(/["?<>\*]/, '') << "\""
106 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
107 end
107 end
108
108
109 def logger
109 def logger
110 RAILS_DEFAULT_LOGGER
110 RAILS_DEFAULT_LOGGER
111 end
111 end
112
112
113 def shellout(cmd, &block)
113 def shellout(cmd, &block)
114 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
114 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
115 begin
115 begin
116 IO.popen(cmd, "r+") do |io|
116 IO.popen(cmd, "r+") do |io|
117 io.close_write
117 io.close_write
118 block.call(io) if block_given?
118 block.call(io) if block_given?
119 end
119 end
120 rescue Errno::ENOENT => e
120 rescue Errno::ENOENT => e
121 # The command failed, log it and re-raise
121 # The command failed, log it and re-raise
122 logger.error("SCM command failed: #{cmd}\n with: #{e.message}")
122 logger.error("SCM command failed: #{cmd}\n with: #{e.message}")
123 raise CommandFailed.new(e.message)
123 raise CommandFailed.new(e.message)
124 end
124 end
125 end
125 end
126 end
126 end
127
127
128 class Entries < Array
128 class Entries < Array
129 def sort_by_name
129 def sort_by_name
130 sort {|x,y|
130 sort {|x,y|
131 if x.kind == y.kind
131 if x.kind == y.kind
132 x.name <=> y.name
132 x.name <=> y.name
133 else
133 else
134 x.kind <=> y.kind
134 x.kind <=> y.kind
135 end
135 end
136 }
136 }
137 end
137 end
138
138
139 def revisions
139 def revisions
140 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
140 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
141 end
141 end
142 end
142 end
143
143
144 class Info
144 class Info
145 attr_accessor :root_url, :lastrev
145 attr_accessor :root_url, :lastrev
146 def initialize(attributes={})
146 def initialize(attributes={})
147 self.root_url = attributes[:root_url] if attributes[:root_url]
147 self.root_url = attributes[:root_url] if attributes[:root_url]
148 self.lastrev = attributes[:lastrev]
148 self.lastrev = attributes[:lastrev]
149 end
149 end
150 end
150 end
151
151
152 class Entry
152 class Entry
153 attr_accessor :name, :path, :kind, :size, :lastrev
153 attr_accessor :name, :path, :kind, :size, :lastrev
154 def initialize(attributes={})
154 def initialize(attributes={})
155 self.name = attributes[:name] if attributes[:name]
155 self.name = attributes[:name] if attributes[:name]
156 self.path = attributes[:path] if attributes[:path]
156 self.path = attributes[:path] if attributes[:path]
157 self.kind = attributes[:kind] if attributes[:kind]
157 self.kind = attributes[:kind] if attributes[:kind]
158 self.size = attributes[:size].to_i if attributes[:size]
158 self.size = attributes[:size].to_i if attributes[:size]
159 self.lastrev = attributes[:lastrev]
159 self.lastrev = attributes[:lastrev]
160 end
160 end
161
161
162 def is_file?
162 def is_file?
163 'file' == self.kind
163 'file' == self.kind
164 end
164 end
165
165
166 def is_dir?
166 def is_dir?
167 'dir' == self.kind
167 'dir' == self.kind
168 end
168 end
169
169
170 def is_text?
170 def is_text?
171 Redmine::MimeType.is_type?('text', name)
171 Redmine::MimeType.is_type?('text', name)
172 end
172 end
173 end
173 end
174
174
175 class Revisions < Array
175 class Revisions < Array
176 def latest
176 def latest
177 sort {|x,y|
177 sort {|x,y|
178 unless x.time.nil? or y.time.nil?
178 unless x.time.nil? or y.time.nil?
179 x.time <=> y.time
179 x.time <=> y.time
180 else
180 else
181 0
181 0
182 end
182 end
183 }.last
183 }.last
184 end
184 end
185 end
185 end
186
186
187 class Revision
187 class Revision
188 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
188 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
189 def initialize(attributes={})
189 def initialize(attributes={})
190 self.identifier = attributes[:identifier]
190 self.identifier = attributes[:identifier]
191 self.scmid = attributes[:scmid]
191 self.scmid = attributes[:scmid]
192 self.name = attributes[:name] || self.identifier
192 self.name = attributes[:name] || self.identifier
193 self.author = attributes[:author]
193 self.author = attributes[:author]
194 self.time = attributes[:time]
194 self.time = attributes[:time]
195 self.message = attributes[:message] || ""
195 self.message = attributes[:message] || ""
196 self.paths = attributes[:paths]
196 self.paths = attributes[:paths]
197 self.revision = attributes[:revision]
197 self.revision = attributes[:revision]
198 self.branch = attributes[:branch]
198 self.branch = attributes[:branch]
199 end
199 end
200
200
201 end
201 end
202
202
203 # A line of Diff
203 # A line of Diff
204 class Diff
204 class Diff
205 attr_accessor :nb_line_left
205 attr_accessor :nb_line_left
206 attr_accessor :line_left
206 attr_accessor :line_left
207 attr_accessor :nb_line_right
207 attr_accessor :nb_line_right
208 attr_accessor :line_right
208 attr_accessor :line_right
209 attr_accessor :type_diff_right
209 attr_accessor :type_diff_right
210 attr_accessor :type_diff_left
210 attr_accessor :type_diff_left
211
211
212 def initialize ()
212 def initialize ()
213 self.nb_line_left = ''
213 self.nb_line_left = ''
214 self.nb_line_right = ''
214 self.nb_line_right = ''
215 self.line_left = ''
215 self.line_left = ''
216 self.line_right = ''
216 self.line_right = ''
217 self.type_diff_right = ''
217 self.type_diff_right = ''
218 self.type_diff_left = ''
218 self.type_diff_left = ''
219 end
219 end
220
220
221 def inspect
221 def inspect
222 puts '### Start Line Diff ###'
222 puts '### Start Line Diff ###'
223 puts self.nb_line_left
223 puts self.nb_line_left
224 puts self.line_left
224 puts self.line_left
225 puts self.nb_line_right
225 puts self.nb_line_right
226 puts self.line_right
226 puts self.line_right
227 end
227 end
228 end
228 end
229
229
230 class DiffTableList < Array
230 class DiffTableList < Array
231 def initialize (diff, type="inline")
231 def initialize (diff, type="inline")
232 diff_table = DiffTable.new type
232 diff_table = DiffTable.new type
233 diff.each do |line|
233 diff.each do |line|
234 if line =~ /^(---|\+\+\+) (.*)$/
234 if line =~ /^(---|\+\+\+) (.*)$/
235 self << diff_table if diff_table.length > 1
235 self << diff_table if diff_table.length > 1
236 diff_table = DiffTable.new type
236 diff_table = DiffTable.new type
237 end
237 end
238 a = diff_table.add_line line
238 a = diff_table.add_line line
239 end
239 end
240 self << diff_table unless diff_table.empty?
240 self << diff_table unless diff_table.empty?
241 self
241 self
242 end
242 end
243 end
243 end
244
244
245 # Class for create a Diff
245 # Class for create a Diff
246 class DiffTable < Hash
246 class DiffTable < Hash
247 attr_reader :file_name, :line_num_l, :line_num_r
247 attr_reader :file_name, :line_num_l, :line_num_r
248
248
249 # Initialize with a Diff file and the type of Diff View
249 # Initialize with a Diff file and the type of Diff View
250 # The type view must be inline or sbs (side_by_side)
250 # The type view must be inline or sbs (side_by_side)
251 def initialize(type="inline")
251 def initialize(type="inline")
252 @parsing = false
252 @parsing = false
253 @nb_line = 1
253 @nb_line = 1
254 @start = false
254 @start = false
255 @before = 'same'
255 @before = 'same'
256 @second = true
256 @second = true
257 @type = type
257 @type = type
258 end
258 end
259
259
260 # Function for add a line of this Diff
260 # Function for add a line of this Diff
261 def add_line(line)
261 def add_line(line)
262 unless @parsing
262 unless @parsing
263 if line =~ /^(---|\+\+\+) (.*)$/
263 if line =~ /^(---|\+\+\+) (.*)$/
264 @file_name = $2
264 @file_name = $2
265 return false
265 return false
266 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
266 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
267 @line_num_l = $5.to_i
267 @line_num_l = $5.to_i
268 @line_num_r = $2.to_i
268 @line_num_r = $2.to_i
269 @parsing = true
269 @parsing = true
270 end
270 end
271 else
271 else
272 if line =~ /^[^\+\-\s@\\]/
272 if line =~ /^[^\+\-\s@\\]/
273 self.delete(self.keys.sort.last)
273 self.delete(self.keys.sort.last)
274 @parsing = false
274 @parsing = false
275 return false
275 return false
276 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
276 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
277 @line_num_l = $5.to_i
277 @line_num_l = $5.to_i
278 @line_num_r = $2.to_i
278 @line_num_r = $2.to_i
279 else
279 else
280 @nb_line += 1 if parse_line(line, @type)
280 @nb_line += 1 if parse_line(line, @type)
281 end
281 end
282 end
282 end
283 return true
283 return true
284 end
284 end
285
285
286 def inspect
286 def inspect
287 puts '### DIFF TABLE ###'
287 puts '### DIFF TABLE ###'
288 puts "file : #{file_name}"
288 puts "file : #{file_name}"
289 self.each do |d|
289 self.each do |d|
290 d.inspect
290 d.inspect
291 end
291 end
292 end
292 end
293
293
294 private
294 private
295 # Test if is a Side By Side type
295 # Test if is a Side By Side type
296 def sbs?(type, func)
296 def sbs?(type, func)
297 if @start and type == "sbs"
297 if @start and type == "sbs"
298 if @before == func and @second
298 if @before == func and @second
299 tmp_nb_line = @nb_line
299 tmp_nb_line = @nb_line
300 self[tmp_nb_line] = Diff.new
300 self[tmp_nb_line] = Diff.new
301 else
301 else
302 @second = false
302 @second = false
303 tmp_nb_line = @start
303 tmp_nb_line = @start
304 @start += 1
304 @start += 1
305 @nb_line -= 1
305 @nb_line -= 1
306 end
306 end
307 else
307 else
308 tmp_nb_line = @nb_line
308 tmp_nb_line = @nb_line
309 @start = @nb_line
309 @start = @nb_line
310 self[tmp_nb_line] = Diff.new
310 self[tmp_nb_line] = Diff.new
311 @second = true
311 @second = true
312 end
312 end
313 unless self[tmp_nb_line]
313 unless self[tmp_nb_line]
314 @nb_line += 1
314 @nb_line += 1
315 self[tmp_nb_line] = Diff.new
315 self[tmp_nb_line] = Diff.new
316 else
316 else
317 self[tmp_nb_line]
317 self[tmp_nb_line]
318 end
318 end
319 end
319 end
320
320
321 # Escape the HTML for the diff
321 # Escape the HTML for the diff
322 def escapeHTML(line)
322 def escapeHTML(line)
323 CGI.escapeHTML(line)
323 CGI.escapeHTML(line)
324 end
324 end
325
325
326 def parse_line(line, type="inline")
326 def parse_line(line, type="inline")
327 if line[0, 1] == "+"
327 if line[0, 1] == "+"
328 diff = sbs? type, 'add'
328 diff = sbs? type, 'add'
329 @before = 'add'
329 @before = 'add'
330 diff.line_left = escapeHTML line[1..-1]
330 diff.line_left = escapeHTML line[1..-1]
331 diff.nb_line_left = @line_num_l
331 diff.nb_line_left = @line_num_l
332 diff.type_diff_left = 'diff_in'
332 diff.type_diff_left = 'diff_in'
333 @line_num_l += 1
333 @line_num_l += 1
334 true
334 true
335 elsif line[0, 1] == "-"
335 elsif line[0, 1] == "-"
336 diff = sbs? type, 'remove'
336 diff = sbs? type, 'remove'
337 @before = 'remove'
337 @before = 'remove'
338 diff.line_right = escapeHTML line[1..-1]
338 diff.line_right = escapeHTML line[1..-1]
339 diff.nb_line_right = @line_num_r
339 diff.nb_line_right = @line_num_r
340 diff.type_diff_right = 'diff_out'
340 diff.type_diff_right = 'diff_out'
341 @line_num_r += 1
341 @line_num_r += 1
342 true
342 true
343 elsif line[0, 1] =~ /\s/
343 elsif line[0, 1] =~ /\s/
344 @before = 'same'
344 @before = 'same'
345 @start = false
345 @start = false
346 diff = Diff.new
346 diff = Diff.new
347 diff.line_right = escapeHTML line[1..-1]
347 diff.line_right = escapeHTML line[1..-1]
348 diff.nb_line_right = @line_num_r
348 diff.nb_line_right = @line_num_r
349 diff.line_left = escapeHTML line[1..-1]
349 diff.line_left = escapeHTML line[1..-1]
350 diff.nb_line_left = @line_num_l
350 diff.nb_line_left = @line_num_l
351 self[@nb_line] = diff
351 self[@nb_line] = diff
352 @line_num_l += 1
352 @line_num_l += 1
353 @line_num_r += 1
353 @line_num_r += 1
354 true
354 true
355 elsif line[0, 1] = "\\"
355 elsif line[0, 1] = "\\"
356 true
356 true
357 else
357 else
358 false
358 false
359 end
359 end
360 end
360 end
361 end
361 end
362
362
363 class Annotate
363 class Annotate
364 attr_reader :lines, :revisions
364 attr_reader :lines, :revisions
365
365
366 def initialize
366 def initialize
367 @lines = []
367 @lines = []
368 @revisions = []
368 @revisions = []
369 end
369 end
370
370
371 def add_line(line, revision)
371 def add_line(line, revision)
372 @lines << line
372 @lines << line
373 @revisions << revision
373 @revisions << revision
374 end
374 end
375
375
376 def content
376 def content
377 content = lines.join("\n")
377 content = lines.join("\n")
378 end
378 end
379
379
380 def empty?
380 def empty?
381 lines.empty?
381 lines.empty?
382 end
382 end
383 end
383 end
384 end
384 end
385 end
385 end
386 end
386 end
@@ -1,193 +1,193
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'rexml/document'
19 require 'rexml/document'
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 = "svn"
27 SVN_BIN = "svn"
28
28
29 # Get info about the svn repository
29 # Get info about the svn repository
30 def info
30 def info
31 cmd = "#{SVN_BIN} info --xml #{target('')}"
31 cmd = "#{SVN_BIN} info --xml #{target('')}"
32 cmd << credentials_string
32 cmd << credentials_string
33 info = nil
33 info = nil
34 shellout(cmd) do |io|
34 shellout(cmd) do |io|
35 begin
35 begin
36 doc = REXML::Document.new(io)
36 doc = REXML::Document.new(io)
37 #root_url = doc.elements["info/entry/repository/root"].text
37 #root_url = doc.elements["info/entry/repository/root"].text
38 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
38 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
39 :lastrev => Revision.new({
39 :lastrev => Revision.new({
40 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
40 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
41 :time => Time.parse(doc.elements["info/entry/commit/date"].text).localtime,
41 :time => Time.parse(doc.elements["info/entry/commit/date"].text).localtime,
42 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
42 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
43 })
43 })
44 })
44 })
45 rescue
45 rescue
46 end
46 end
47 end
47 end
48 return nil if $? && $?.exitstatus != 0
48 return nil if $? && $?.exitstatus != 0
49 info
49 info
50 rescue CommandFailed
50 rescue CommandFailed
51 return nil
51 return nil
52 end
52 end
53
53
54 # Returns the entry identified by path and revision identifier
54 # Returns the entry identified by path and revision identifier
55 # or nil if entry doesn't exist in the repository
55 # or nil if entry doesn't exist in the repository
56 def entry(path=nil, identifier=nil)
56 def entry(path=nil, identifier=nil)
57 e = entries(path, identifier)
57 e = entries(path, identifier)
58 e ? e.first : nil
58 e ? e.first : nil
59 end
59 end
60
60
61 # Returns an Entries collection
61 # Returns an Entries collection
62 # or nil if the given path doesn't exist in the repository
62 # or nil if the given path doesn't exist in the repository
63 def entries(path=nil, identifier=nil)
63 def entries(path=nil, identifier=nil)
64 path ||= ''
64 path ||= ''
65 identifier = 'HEAD' unless identifier and identifier > 0
65 identifier = 'HEAD' unless identifier and identifier > 0
66 entries = Entries.new
66 entries = Entries.new
67 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
67 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
68 cmd << credentials_string
68 cmd << credentials_string
69 shellout(cmd) do |io|
69 shellout(cmd) do |io|
70 output = io.read
70 output = io.read
71 begin
71 begin
72 doc = REXML::Document.new(output)
72 doc = REXML::Document.new(output)
73 doc.elements.each("lists/list/entry") do |entry|
73 doc.elements.each("lists/list/entry") do |entry|
74 entries << Entry.new({:name => entry.elements['name'].text,
74 entries << Entry.new({:name => entry.elements['name'].text,
75 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
75 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
76 :kind => entry.attributes['kind'],
76 :kind => entry.attributes['kind'],
77 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
77 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
78 :lastrev => Revision.new({
78 :lastrev => Revision.new({
79 :identifier => entry.elements['commit'].attributes['revision'],
79 :identifier => entry.elements['commit'].attributes['revision'],
80 :time => Time.parse(entry.elements['commit'].elements['date'].text).localtime,
80 :time => Time.parse(entry.elements['commit'].elements['date'].text).localtime,
81 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
81 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
82 })
82 })
83 })
83 })
84 end
84 end
85 rescue Exception => e
85 rescue Exception => e
86 logger.error("Error parsing svn output: #{e.message}")
86 logger.error("Error parsing svn output: #{e.message}")
87 logger.error("Output was:\n #{output}")
87 logger.error("Output was:\n #{output}")
88 end
88 end
89 end
89 end
90 return nil if $? && $?.exitstatus != 0
90 return nil if $? && $?.exitstatus != 0
91 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
91 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
92 entries.sort_by_name
92 entries.sort_by_name
93 end
93 end
94
94
95 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
95 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
96 path ||= ''
96 path ||= ''
97 identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
97 identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
98 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
98 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
99 revisions = Revisions.new
99 revisions = Revisions.new
100 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
100 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
101 cmd << credentials_string
101 cmd << credentials_string
102 cmd << " --verbose " if options[:with_paths]
102 cmd << " --verbose " if options[:with_paths]
103 cmd << target(path)
103 cmd << ' ' + target(path)
104 shellout(cmd) do |io|
104 shellout(cmd) do |io|
105 begin
105 begin
106 doc = REXML::Document.new(io)
106 doc = REXML::Document.new(io)
107 doc.elements.each("log/logentry") do |logentry|
107 doc.elements.each("log/logentry") do |logentry|
108 paths = []
108 paths = []
109 logentry.elements.each("paths/path") do |path|
109 logentry.elements.each("paths/path") do |path|
110 paths << {:action => path.attributes['action'],
110 paths << {:action => path.attributes['action'],
111 :path => path.text,
111 :path => path.text,
112 :from_path => path.attributes['copyfrom-path'],
112 :from_path => path.attributes['copyfrom-path'],
113 :from_revision => path.attributes['copyfrom-rev']
113 :from_revision => path.attributes['copyfrom-rev']
114 }
114 }
115 end
115 end
116 paths.sort! { |x,y| x[:path] <=> y[:path] }
116 paths.sort! { |x,y| x[:path] <=> y[:path] }
117
117
118 revisions << Revision.new({:identifier => logentry.attributes['revision'],
118 revisions << Revision.new({:identifier => logentry.attributes['revision'],
119 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
119 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
120 :time => Time.parse(logentry.elements['date'].text).localtime,
120 :time => Time.parse(logentry.elements['date'].text).localtime,
121 :message => logentry.elements['msg'].text,
121 :message => logentry.elements['msg'].text,
122 :paths => paths
122 :paths => paths
123 })
123 })
124 end
124 end
125 rescue
125 rescue
126 end
126 end
127 end
127 end
128 return nil if $? && $?.exitstatus != 0
128 return nil if $? && $?.exitstatus != 0
129 revisions
129 revisions
130 end
130 end
131
131
132 def diff(path, identifier_from, identifier_to=nil, type="inline")
132 def diff(path, identifier_from, identifier_to=nil, type="inline")
133 path ||= ''
133 path ||= ''
134 if identifier_to and identifier_to.to_i > 0
134 if identifier_to and identifier_to.to_i > 0
135 identifier_to = identifier_to.to_i
135 identifier_to = identifier_to.to_i
136 else
136 else
137 identifier_to = identifier_from.to_i - 1
137 identifier_to = identifier_from.to_i - 1
138 end
138 end
139 cmd = "#{SVN_BIN} diff -r "
139 cmd = "#{SVN_BIN} diff -r "
140 cmd << "#{identifier_to}:"
140 cmd << "#{identifier_to}:"
141 cmd << "#{identifier_from}"
141 cmd << "#{identifier_from}"
142 cmd << "#{target(path)}@#{identifier_from}"
142 cmd << " #{target(path)}@#{identifier_from}"
143 cmd << credentials_string
143 cmd << credentials_string
144 diff = []
144 diff = []
145 shellout(cmd) do |io|
145 shellout(cmd) do |io|
146 io.each_line do |line|
146 io.each_line do |line|
147 diff << line
147 diff << line
148 end
148 end
149 end
149 end
150 return nil if $? && $?.exitstatus != 0
150 return nil if $? && $?.exitstatus != 0
151 DiffTableList.new diff, type
151 DiffTableList.new diff, type
152 end
152 end
153
153
154 def cat(path, identifier=nil)
154 def cat(path, identifier=nil)
155 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
155 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
156 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
156 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
157 cmd << credentials_string
157 cmd << credentials_string
158 cat = nil
158 cat = nil
159 shellout(cmd) do |io|
159 shellout(cmd) do |io|
160 io.binmode
160 io.binmode
161 cat = io.read
161 cat = io.read
162 end
162 end
163 return nil if $? && $?.exitstatus != 0
163 return nil if $? && $?.exitstatus != 0
164 cat
164 cat
165 end
165 end
166
166
167 def annotate(path, identifier=nil)
167 def annotate(path, identifier=nil)
168 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
168 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
169 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
169 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
170 cmd << credentials_string
170 cmd << credentials_string
171 blame = Annotate.new
171 blame = Annotate.new
172 shellout(cmd) do |io|
172 shellout(cmd) do |io|
173 io.each_line do |line|
173 io.each_line do |line|
174 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
174 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
175 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
175 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
176 end
176 end
177 end
177 end
178 return nil if $? && $?.exitstatus != 0
178 return nil if $? && $?.exitstatus != 0
179 blame
179 blame
180 end
180 end
181
181
182 private
182 private
183
183
184 def credentials_string
184 def credentials_string
185 str = ''
185 str = ''
186 str << " --username #{shell_quote(@login)}" unless @login.blank?
186 str << " --username #{shell_quote(@login)}" unless @login.blank?
187 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
187 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
188 str
188 str
189 end
189 end
190 end
190 end
191 end
191 end
192 end
192 end
193 end
193 end
General Comments 0
You need to be logged in to leave comments. Login now