##// END OF EJS Templates
Fixed: files with an apostrophe in their names can't be accessed in the repository...
Jean-Philippe Lang -
r519:98d08439dce6
parent child
Show More
@@ -1,436 +1,436
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'rexml/document'
19 19 require 'cgi'
20 20
21 21 module SvnRepos
22 22
23 23 class CommandFailed < StandardError #:nodoc:
24 24 end
25 25
26 26 class Base
27 27
28 28 def initialize(url, root_url=nil, login=nil, password=nil)
29 29 @url = url
30 30 @login = login if login && !login.empty?
31 31 @password = (password || "") if @login
32 32 @root_url = root_url.blank? ? retrieve_root_url : root_url
33 33 end
34 34
35 35 def root_url
36 36 @root_url
37 37 end
38 38
39 39 def url
40 40 @url
41 41 end
42 42
43 43 # get info about the svn repository
44 44 def info
45 45 cmd = "svn info --xml #{target('')}"
46 46 cmd << " --username #{@login} --password #{@password}" if @login
47 47 info = nil
48 48 shellout(cmd) do |io|
49 49 begin
50 50 doc = REXML::Document.new(io)
51 51 #root_url = doc.elements["info/entry/repository/root"].text
52 52 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
53 53 :lastrev => Revision.new({
54 54 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
55 55 :time => Time.parse(doc.elements["info/entry/commit/date"].text),
56 56 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
57 57 })
58 58 })
59 59 rescue
60 60 end
61 61 end
62 62 return nil if $? && $?.exitstatus != 0
63 63 info
64 64 rescue Errno::ENOENT => e
65 65 return nil
66 66 end
67 67
68 68 # Returns the entry identified by path and revision identifier
69 69 # or nil if entry doesn't exist in the repository
70 70 def entry(path=nil, identifier=nil)
71 71 e = entries(path, identifier)
72 72 e ? e.first : nil
73 73 end
74 74
75 75 # Returns an Entries collection
76 76 # or nil if the given path doesn't exist in the repository
77 77 def entries(path=nil, identifier=nil)
78 78 path ||= ''
79 79 identifier = 'HEAD' unless identifier and identifier > 0
80 80 entries = Entries.new
81 81 cmd = "svn list --xml #{target(path)}@#{identifier}"
82 82 cmd << " --username #{@login} --password #{@password}" if @login
83 83 shellout(cmd) do |io|
84 84 begin
85 85 doc = REXML::Document.new(io)
86 86 doc.elements.each("lists/list/entry") do |entry|
87 87 entries << Entry.new({:name => entry.elements['name'].text,
88 88 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
89 89 :kind => entry.attributes['kind'],
90 90 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
91 91 :lastrev => Revision.new({
92 92 :identifier => entry.elements['commit'].attributes['revision'],
93 93 :time => Time.parse(entry.elements['commit'].elements['date'].text),
94 94 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
95 95 })
96 96 })
97 97 end
98 98 rescue
99 99 end
100 100 end
101 101 return nil if $? && $?.exitstatus != 0
102 102 entries.sort_by_name
103 103 rescue Errno::ENOENT => e
104 104 raise CommandFailed
105 105 end
106 106
107 107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
108 108 path ||= ''
109 109 identifier_from = 'HEAD' unless identifier_from and identifier_from.to_i > 0
110 110 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
111 111 revisions = Revisions.new
112 112 cmd = "svn log --xml -r #{identifier_from}:#{identifier_to}"
113 113 cmd << " --username #{@login} --password #{@password}" if @login
114 114 cmd << " --verbose " if options[:with_paths]
115 115 cmd << target(path)
116 116 shellout(cmd) do |io|
117 117 begin
118 118 doc = REXML::Document.new(io)
119 119 doc.elements.each("log/logentry") do |logentry|
120 120 paths = []
121 121 logentry.elements.each("paths/path") do |path|
122 122 paths << {:action => path.attributes['action'],
123 123 :path => path.text,
124 124 :from_path => path.attributes['copyfrom-path'],
125 125 :from_revision => path.attributes['copyfrom-rev']
126 126 }
127 127 end
128 128 paths.sort! { |x,y| x[:path] <=> y[:path] }
129 129
130 130 revisions << Revision.new({:identifier => logentry.attributes['revision'],
131 131 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
132 132 :time => Time.parse(logentry.elements['date'].text),
133 133 :message => logentry.elements['msg'].text,
134 134 :paths => paths
135 135 })
136 136 end
137 137 rescue
138 138 end
139 139 end
140 140 return nil if $? && $?.exitstatus != 0
141 141 revisions
142 142 rescue Errno::ENOENT => e
143 143 raise CommandFailed
144 144 end
145 145
146 146 def diff(path, identifier_from, identifier_to=nil, type="inline")
147 147 path ||= ''
148 148 if identifier_to and identifier_to.to_i > 0
149 149 identifier_to = identifier_to.to_i
150 150 else
151 151 identifier_to = identifier_from.to_i - 1
152 152 end
153 153 cmd = "svn diff -r "
154 154 cmd << "#{identifier_to}:"
155 155 cmd << "#{identifier_from}"
156 156 cmd << "#{target(path)}@#{identifier_from}"
157 157 cmd << " --username #{@login} --password #{@password}" if @login
158 158 diff = []
159 159 shellout(cmd) do |io|
160 160 io.each_line do |line|
161 161 diff << line
162 162 end
163 163 end
164 164 return nil if $? && $?.exitstatus != 0
165 165 DiffTableList.new diff, type
166 166
167 167 rescue Errno::ENOENT => e
168 168 raise CommandFailed
169 169 end
170 170
171 171 def cat(path, identifier=nil)
172 172 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
173 173 cmd = "svn cat #{target(path)}@#{identifier}"
174 174 cmd << " --username #{@login} --password #{@password}" if @login
175 175 cat = nil
176 176 shellout(cmd) do |io|
177 177 io.binmode
178 178 cat = io.read
179 179 end
180 180 return nil if $? && $?.exitstatus != 0
181 181 cat
182 182 rescue Errno::ENOENT => e
183 183 raise CommandFailed
184 184 end
185 185
186 186 private
187 187 def retrieve_root_url
188 188 info = self.info
189 189 info ? info.root_url : nil
190 190 end
191 191
192 192 def target(path)
193 193 path ||= ""
194 194 base = path.match(/^\//) ? root_url : url
195 " \"" << "#{base}/#{path}".gsub(/["'?<>\*]/, '') << "\""
195 " \"" << "#{base}/#{path}".gsub(/["?<>\*]/, '') << "\""
196 196 end
197 197
198 198 def logger
199 199 RAILS_DEFAULT_LOGGER
200 200 end
201 201
202 202 def shellout(cmd, &block)
203 203 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
204 204 IO.popen(cmd, "r+") do |io|
205 205 io.close_write
206 206 block.call(io) if block_given?
207 207 end
208 208 end
209 209 end
210 210
211 211 class Entries < Array
212 212 def sort_by_name
213 213 sort {|x,y|
214 214 if x.kind == y.kind
215 215 x.name <=> y.name
216 216 else
217 217 x.kind <=> y.kind
218 218 end
219 219 }
220 220 end
221 221
222 222 def revisions
223 223 revisions ||= Revisions.new(collect{|entry| entry.lastrev})
224 224 end
225 225 end
226 226
227 227 class Info
228 228 attr_accessor :root_url, :lastrev
229 229 def initialize(attributes={})
230 230 self.root_url = attributes[:root_url] if attributes[:root_url]
231 231 self.lastrev = attributes[:lastrev]
232 232 end
233 233 end
234 234
235 235 class Entry
236 236 attr_accessor :name, :path, :kind, :size, :lastrev
237 237 def initialize(attributes={})
238 238 self.name = attributes[:name] if attributes[:name]
239 239 self.path = attributes[:path] if attributes[:path]
240 240 self.kind = attributes[:kind] if attributes[:kind]
241 241 self.size = attributes[:size].to_i if attributes[:size]
242 242 self.lastrev = attributes[:lastrev]
243 243 end
244 244
245 245 def is_file?
246 246 'file' == self.kind
247 247 end
248 248
249 249 def is_dir?
250 250 'dir' == self.kind
251 251 end
252 252
253 253 def is_text?
254 254 Redmine::MimeType.is_type?('text', name)
255 255 end
256 256 end
257 257
258 258 class Revisions < Array
259 259 def latest
260 260 sort {|x,y| x.time <=> y.time}.last
261 261 end
262 262 end
263 263
264 264 class Revision
265 265 attr_accessor :identifier, :author, :time, :message, :paths
266 266 def initialize(attributes={})
267 267 self.identifier = attributes[:identifier]
268 268 self.author = attributes[:author]
269 269 self.time = attributes[:time]
270 270 self.message = attributes[:message] || ""
271 271 self.paths = attributes[:paths]
272 272 end
273 273
274 274 end
275 275
276 276 # A line of Diff
277 277 class Diff
278 278
279 279 attr_accessor :nb_line_left
280 280 attr_accessor :line_left
281 281 attr_accessor :nb_line_right
282 282 attr_accessor :line_right
283 283 attr_accessor :type_diff_right
284 284 attr_accessor :type_diff_left
285 285
286 286 def initialize ()
287 287 self.nb_line_left = ''
288 288 self.nb_line_right = ''
289 289 self.line_left = ''
290 290 self.line_right = ''
291 291 self.type_diff_right = ''
292 292 self.type_diff_left = ''
293 293 end
294 294
295 295 def inspect
296 296 puts '### Start Line Diff ###'
297 297 puts self.nb_line_left
298 298 puts self.line_left
299 299 puts self.nb_line_right
300 300 puts self.line_right
301 301 end
302 302 end
303 303
304 304 class DiffTableList < Array
305 305
306 306 def initialize (diff, type="inline")
307 307 diff_table = DiffTable.new type
308 308 diff.each do |line|
309 309 if line =~ /^Index: (.*)$/
310 310 self << diff_table if diff_table.length > 1
311 311 diff_table = DiffTable.new type
312 312 end
313 313 a = diff_table.add_line line
314 314 end
315 315 self << diff_table
316 316 end
317 317 end
318 318
319 319 # Class for create a Diff
320 320 class DiffTable < Hash
321 321
322 322 attr_reader :file_name, :line_num_l, :line_num_r
323 323
324 324 # Initialize with a Diff file and the type of Diff View
325 325 # The type view must be inline or sbs (side_by_side)
326 326 def initialize (type="inline")
327 327 @parsing = false
328 328 @nb_line = 1
329 329 @start = false
330 330 @before = 'same'
331 331 @second = true
332 332 @type = type
333 333 end
334 334
335 335 # Function for add a line of this Diff
336 336 def add_line(line)
337 337 unless @parsing
338 338 if line =~ /^Index: (.*)$/
339 339 @file_name = $1
340 340 return false
341 341 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
342 342 @line_num_l = $2.to_i
343 343 @line_num_r = $5.to_i
344 344 @parsing = true
345 345 end
346 346 else
347 347 if line =~ /^_+$/
348 348 self.delete(self.keys.sort.last)
349 349 @parsing = false
350 350 return false
351 351 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
352 352 @line_num_l = $2.to_i
353 353 @line_num_r = $5.to_i
354 354 else
355 355 @nb_line += 1 if parse_line(line, @type)
356 356 end
357 357 end
358 358 return true
359 359 end
360 360
361 361 def inspect
362 362 puts '### DIFF TABLE ###'
363 363 puts "file : #{file_name}"
364 364 self.each do |d|
365 365 d.inspect
366 366 end
367 367 end
368 368
369 369 private
370 370
371 371 # Test if is a Side By Side type
372 372 def sbs?(type, func)
373 373 if @start and type == "sbs"
374 374 if @before == func and @second
375 375 tmp_nb_line = @nb_line
376 376 self[tmp_nb_line] = Diff.new
377 377 else
378 378 @second = false
379 379 tmp_nb_line = @start
380 380 @start += 1
381 381 @nb_line -= 1
382 382 end
383 383 else
384 384 tmp_nb_line = @nb_line
385 385 @start = @nb_line
386 386 self[tmp_nb_line] = Diff.new
387 387 @second = true
388 388 end
389 389 unless self[tmp_nb_line]
390 390 @nb_line += 1
391 391 self[tmp_nb_line] = Diff.new
392 392 else
393 393 self[tmp_nb_line]
394 394 end
395 395 end
396 396
397 397 # Escape the HTML for the diff
398 398 def escapeHTML(line)
399 399 CGI.escapeHTML(line).gsub(/\s/, '&nbsp;')
400 400 end
401 401
402 402 def parse_line (line, type="inline")
403 403 if line[0, 1] == "+"
404 404 diff = sbs? type, 'add'
405 405 @before = 'add'
406 406 diff.line_left = escapeHTML line[1..-1]
407 407 diff.nb_line_left = @line_num_l
408 408 diff.type_diff_left = 'diff_in'
409 409 @line_num_l += 1
410 410 true
411 411 elsif line[0, 1] == "-"
412 412 diff = sbs? type, 'remove'
413 413 @before = 'remove'
414 414 diff.line_right = escapeHTML line[1..-1]
415 415 diff.nb_line_right = @line_num_r
416 416 diff.type_diff_right = 'diff_out'
417 417 @line_num_r += 1
418 418 true
419 419 elsif line[0, 1] =~ /\s/
420 420 @before = 'same'
421 421 @start = false
422 422 diff = Diff.new
423 423 diff.line_right = escapeHTML line[1..-1]
424 424 diff.nb_line_right = @line_num_r
425 425 diff.line_left = escapeHTML line[1..-1]
426 426 diff.nb_line_left = @line_num_l
427 427 self[@nb_line] = diff
428 428 @line_num_l += 1
429 429 @line_num_r += 1
430 430 true
431 431 else
432 432 false
433 433 end
434 434 end
435 435 end
436 436 end No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now