Commit d2f9f71b65c7e33a2d67a3d678824ba4ebd7794c

Add the Diff::Display library to display diffs

Commit diff

app/helpers/browse_helper.rb

 
3737 end
3838
3939 # Takes a unified diff as input and renders it as html
40 # this is the crappy one from collaboa
41 # TODO: finish up the proper OO one
42 def render_diff(udiff, src_sha, dst_sha)
43 return if udiff.blank?
44 out = "<table class=\"codediff\">\n"
45
46 lines = udiff.split("\n")
47 lines.reject!{ |line| line =~ /^(new|deleted) file mode [0-9]+/ }
48 lines.reject!{ |line| line =~ /^index [a-z0-9]+\.\.[a-z0-9]+/ }
49
50 lines_that_differs = /@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@/
51
52 out << "<thead>\n"
53 out << %Q{\t<tr><td class="line-numbers">#{src_sha}</td>}
54 out << %Q{<td class="line-numbers">#{dst_sha}</td><td>&nbsp</td></tr>\n}
55 out << "</thead>\n"
56
57 prev_counter = 0
58 cur_counter = 0
59 change_num = 0
60
61 if lines.size < 3
62 return
63 end
64
65 lines[3..lines.length].each do |line|
66 if line_nums = line.match(lines_that_differs)
67 prev_line_numbers = line_nums[1].to_i...(line_nums[1].to_i + (line_nums[2]).to_i)
68 cur_line_numbers = line_nums[3].to_i...(line_nums[3].to_i + (line_nums[4]).to_i)
69 prev_counter = prev_line_numbers.first - 1
70 cur_counter = cur_line_numbers.first - 1
71 change_num += 1
72 end
73
74 line = h(line)
75 line.gsub!(/^\s/, '') # The column where + or - would be
76 line.gsub!(/^(\+{1}(\s+|\t+)?(.*))/, '\2<ins>\3</ins>')
77 line.gsub!(/^(-{1}(\s+|\t+)?(.*))/, '\2<del>\3</del>')
78 #line.gsub!('\ No newline at end of file', '')
79
80 out << "<tr class=\"changes\">\n"
81
82 if line.match(/^(\s+|\t+)?<del>/)
83 out << "\t<td class=\"line-numbers\">" + prev_counter.to_s + "</td>\n"
84 out << "\t<td class=\"line-numbers\">&nbsp;</td>\n"
85 prev_counter += 1
86 action_class = 'del'
87 elsif line.match(/^(\s+|\t+)?<ins>/)
88 out << "\t<td class=\"line-numbers\">&nbsp;</td>\n"
89 out << "\t<td class=\"line-numbers\">" + cur_counter.to_s + "</td>\n"
90 cur_counter += 1
91 action_class = 'ins'
92 else
93 if line.match(lines_that_differs)
94 line = ''
95 if change_num > 1
96 out << "\t<td class=\"line-numbers line-num-cut\">...</td>\n"
97 out << "\t<td class=\"line-numbers line-num-cut\">...</td>\n"
98 action_class = 'cut-line'
99 else
100 out << "\t<td class=\"line-numbers\"></td>\n"
101 out << "\t<td class=\"line-numbers\"></td>\n"
102 action_class = 'unchanged'
103 end
104 else
105 out << "\t<td class=\"line-numbers\"></td>\n"
106 out << "\t<td class=\"line-numbers\"></td>\n"
107 action_class = 'unchanged'
108 end
109 prev_counter += 1
110 cur_counter += 1
111 end
112
113 out << "\t<td class=\"code #{action_class}\">" + line + "</td></tr>\n"
114 end
115 out << "\n</table>\n"
116 end
40 def render_diff(udiff, src_sha, dst_sha)
41 return if udiff.blank?
42
43 callback = Gitorious::Diff::InlineTableCallback.new
44
45 out = %Q{<table class="codediff">\n}
46 out << "<thead>\n"
47 out << "<tr>"
48 out << %Q{<td class="line-numbers">#{src_sha}</td>}
49 out << %Q{<td class="line-numbers">#{dst_sha}</td>}
50 out << "<td>&nbsp</td></tr>\n"
51 out << "</thead>\n"
52 out << Diff::Display::Unified::Renderer.run(udiff, callback)
53 out << "</table>"
54 out
55 end
11756
11857end
toggle raw diff

config/initializers/requires.rb

 
11require "core_ext"
22require "fileutils"
3require "git"
3require "git"
4require "diff-display/lib/diff/display/unified"
toggle raw diff

lib/gitorious/diff/inline_table_callback.rb

 
1module Gitorious
2 module Diff
3 class InlineTableCallback
4
5 # Before blocks
6 def before_addblock(block)
7 end
8
9 def before_remblock(block)
10 end
11
12 def before_modblock(block)
13 end
14
15 def before_unmodblock(block)
16 end
17
18 def before_sepblock(block)
19 end
20
21 # After blocks
22 def after_addblock(block)
23 end
24
25 def after_remblock(block)
26 end
27
28 def after_modblock(block)
29 end
30
31 def after_unmodblock(block)
32 end
33
34 def after_sepblock(block)
35 end
36
37 # Before lines
38 def before_addline(line)
39 %Q{<tr class="changes">} +
40 %Q{<td class="line-numbers">&nbsp;</td>} +
41 %Q{<td class="line-numbers">#{line.number}</td>} +
42 %Q{<td class="code ins"><ins>}
43 end
44
45 def before_remline(line)
46 %Q{<tr class="changes">} +
47 %Q{<td class="line-numbers">#{line.number}</td>} +
48 %Q{<td class="line-numbers">&nbsp;</td>} +
49 %Q{<td class="code del"><del>}
50 end
51
52 def before_modline(line)
53 %Q{<tr class="changes">} +
54 %Q{<td class="line-numbers">&nbsp;</td>} +
55 %Q{<td class="line-numbers">#{line.number}</td>} +
56 %Q{<td class="code unchanged mod">}
57 end
58
59 def before_unmodline(line)
60 %Q{<tr class="changes">} +
61 %Q{<td class="line-numbers">&nbsp;</td>} +
62 %Q{<td class="line-numbers">#{line.number}</td>} +
63 %Q{<td class="code unchanged unmod">}
64 end
65
66 def before_sepline(line)
67 %Q{<tr class="changes">} +
68 %Q{<td class="line-numbers line-num-cut">...</td>} +
69 %Q{<td class="line-numbers line-num-cut">...</td>} +
70 %Q{<td class="code cut-line">}
71 end
72
73 # After lines
74 def after_addline(line)
75 "</ins></td></tr>"
76 end
77
78 def after_remline(line)
79 "</del></td></tr>"
80 end
81
82 def after_modline(line)
83 "</td></tr>"
84 end
85
86 def after_unmodline(line)
87 "</td></tr>"
88 end
89
90 def after_sepline(line)
91 "</td></tr>"
92 end
93
94 def new_line
95 end
96 end
97 end
98end
toggle raw diff

public/stylesheets/base.css

 
592592 border-top: 1px dashed #aaa;
593593 border-bottom: 1px dashed #aaa;
594594}
595
596del { background: #fbb; text-decoration: none; }
597ins { background: #afa; text-decoration: none; }
598595table.codediff td.del { background: #fdd; }
599table.codediff td.ins { background: #dfd; }
596table.codediff td.ins { background: #dfd; }
597table.codediff td.del del { /*background: #fbb;*/ text-decoration: none; }
598table.codediff td.ins ins { /*background: #afa;*/ text-decoration: none; }
toggle raw diff

vendor/diff-display/LICENSE

 
1Copyright (c) 2003 Marcel Molina Jr.
2
3Permission is hereby granted, free of charge, to any person obtaining
4a copy of this software and associated documentation files (the
5"Software"), to deal in the Software without restriction, including
6without limitation the rights to use, copy, modify, merge, publish,
7distribute, sublicense, and/or sell copies of the Software, and to
8permit persons to whom the Software is furnished to do so, subject to
9the following conditions:
10
11The above copyright notice and this permission notice shall be
12included in all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
toggle raw diff

vendor/diff-display/README

 
1Marcel Molina Jr. wrote this library probably back in 2004 or so.
toggle raw diff

vendor/diff-display/lib/diff/display/unified.rb

 
1module Diff #:nodoc:#
2 module Display #:nodoc:#
3 # = Diff::Display::Unified
4 #
5 # Diff::Display::Unified is meant to make dealing with the presentation of
6 # diffs easy, customizable and succinct. It breaks a diff up into sections,
7 # or blocks, which are defined by the types of lines they contain. If, for
8 # example, there is a section where five lines have been added then an
9 # AddBlock is created and those five lines are placed into that AddBlock.
10 # The design is quite simple: The generated object is made up of Block
11 # objects which are themselves made up of Line objects.
12 #
13 # === Blocks
14 #
15 # Blocks represent various sections that one finds in a diff.
16 # There are five different Block classes:
17 #
18 # [AddBlock]
19 # Contains only instances of AddLine
20 #
21 # [RemBlock]
22 # Contains only instances of RemLine
23 #
24 # [ModBlock]
25 # Contains a set of RemLine objects followed by a set of AddLine
26 # objects
27 #
28 # [UnModBlock]
29 # Contains instances of UnModLine which represent sets of context lines
30 # that are unchanged in both the old and modified data set that
31 # surround Mod, Add or Rem blocks
32 #
33 # [SepBlock]
34 # Contains a single SepLine. SepBlocks are placed between blocks when
35 # the distances between one modification set and the next exceeds the
36 # number of context buffer surrounding them.
37 #
38 # === Lines
39 #
40 # The Line classes are much line the Block classes, just on a smaller
41 # scale.
42 #
43 # There are 4 lines classes:
44 #
45 # === Example
46 #
47 # Consider the following before and after on a diff.
48 #
49 # Before:
50 #
51 # - class OldName < Array
52 # + class NewName < Array
53 #
54 # - def initialize(boundry)
55 # - @boundry = boundry
56 # - end
57 # -
58 # def stay(the, same)
59 # + end
60 # +
61 # + def all_new
62 # + @this, @method = *IS_ALL_NEW
63 #
64 # After:
65 #
66 # ---------------------------------------- ModBlock
67 # 1 [RemLine] class OldName < Array
68 # 1 [AddLine] class NewName < Array
69 # ----------------------------------------
70 #
71 # ---------------------------------------- UnModBlock
72 # 2 [UnModLine]
73 # ----------------------------------------
74 #
75 # ---------------------------------------- RemBlock
76 # 3 [RemLine] def initialize(boundry)
77 # 4 [RemLine] @boundry = boundry
78 # 5 [RemLine] end
79 # 6 [RemLine]
80 # ----------------------------------------
81 #
82 # ---------------------------------------- UnModBlock
83 # 7 [UnModLine] def stay(the, same)
84 # ----------------------------------------
85 #
86 # ---------------------------------------- AddBlock
87 # 8 [AddLine] end
88 # 9 [AddLine]
89 # 10 [AddLine] def all_new
90 # 11 [AddLine] @this, @method = -IS_ALL_NEW
91 # ----------------------------------------
92 #
93 # Note: That is just a representation of the structure of the generated
94 # object. Also note that this example does not include any SepBlocks since
95 # the changes in the example diff are all contiguous.
96 #
97 # Internally the datastructure is quite simple: The Data object has an
98 # array of Block objects which themselves have an array of Line
99 # objects. Traversing the object on the block level or line level is
100 # equally simple so you can focus on what to do for each type of block and
101 # line.
102 module Unified
103 # Every line from the passed in diff gets transformed into an instance of
104 # one of line Line class's subclasses. One subclass exists for each line
105 # type in a diff. As such there is an AddLine class for added lines, a RemLine
106 # class for removed lines, an UnModLine class for lines which remain unchanged and
107 # a SepLine class which represents all the lines that aren't part of the diff.
108 class Line < String
109 def initialize(line, line_number)
110 super(line)
111 @line_number = line_number
112 self
113 end
114
115 def contains_inline_change?
116 @inline
117 end
118
119 # Returns the line number of the diff line
120 def number
121 @line_number
122 end
123
124 def decorate(&block)
125 yield self
126 end
127
128 protected
129
130 def inline_add_open; '' end
131 def inline_add_close; '' end
132 def inline_rem_open; '' end
133 def inline_rem_close; '' end
134
135 def escape
136 self
137 end
138
139 def expand
140 escape.gsub("\t", ' ' * tabwidth).gsub(/ ( +)|^ /) do |match|
141 (space + ' ') * (match.size / 2) +
142 space * (match.size % 2)
143 end
144 end
145
146 def tabwidth
147 4
148 end
149
150
151 def space
152 ' '
153 end
154
155 class << self
156 def add(line, line_number, inline = false)
157 AddLine.new(line, line_number, inline)
158 end
159
160 def rem(line, line_number, inline = false)
161 RemLine.new(line, line_number, inline)
162 end
163
164 def unmod(line, line_number)
165 UnModLine.new(line, line_number)
166 end
167 end
168 end
169
170 class AddLine < Line #:nodoc:#
171 def initialize(line, line_number, inline = false)
172 line = inline ? line % [inline_add_open, inline_add_close] : line
173 super(line, line_number)
174 @inline = inline
175 self
176 end
177 end
178
179 class RemLine < Line #:nodoc:#
180 def initialize(line, line_number, inline = false)
181 line = inline ? line % [inline_rem_open, inline_rem_close] : line
182 super(line, line_number)
183 @inline = inline
184 self
185 end
186 end
187
188 class UnModLine < Line #:nodoc:#
189 def initialize(line, line_number)
190 super(line, line_number)
191 end
192 end
193
194 class SepLine < Line #:nodoc:#
195 def initialize(line = '...')
196 super(line, nil)
197 end
198 end
199
200 # This class is an array which contains Line objects. Just like Line
201 # classes, several Block classes inherit from Block. If all the lines
202 # in the block are added lines then it is an AddBlock. If all lines
203 # in the block are removed lines then it is a RemBlock. If the lines
204 # in the block are all unmodified then it is an UnMod block. If the
205 # lines in the block are a mixture of added and removed lines then
206 # it is a ModBlock. There are no blocks that contain a mixture of
207 # modified and unmodified lines.
208 class Block < Array
209 def initialize
210 super
211 end
212
213 def <<(line_object)
214 super(line_object)
215 self
216 end
217
218 def decorate(&block)
219 yield self
220 end
221
222 class << self
223 def add; AddBlock.new end
224 def rem; RemBlock.new end
225 def mod; ModBlock.new end
226 def unmod; UnModBlock.new end
227 end
228 end
229
230 #:stopdoc:#
231 class AddBlock < Block; end
232 class RemBlock < Block; end
233 class ModBlock < Block; end
234 class UnModBlock < Block; end
235 class SepBlock < Block; end
236 #:startdoc:#
237
238 # A Data object contains the generated diff data structure. It is an
239 # array of Block objects which are themselves arrays of Line objects. The
240 # Generator class returns a Data instance object after it is done
241 # processing the diff.
242 class Data < Array
243 def initialize
244 super
245 end
246
247 def debug
248 demodularize = Proc.new {|obj| obj.class.name[/\w+$/]}
249 each do |diff_block|
250 print "-" * 40, ' ', demodularize.call(diff_block)
251 puts
252 puts diff_block.map {|line|
253 "%5d" % line.number +
254 " [#{demodularize.call(line)}]" +
255 line
256 }.join("\n")
257 puts "-" * 40, ' '
258 end
259 end
260
261 end
262
263 # Processes the diff and generates a Data object which contains the
264 # resulting data structure.
265 #
266 # The +run+ class method is fed a diff and returns a Data object. It will
267 # accept as its argument a String, an Array or a File object:
268 #
269 # Diff::Display::Unified::Generator.run(diff)
270 #
271 class Generator
272
273 # Extracts the line number info for a given diff section
274 LINE_NUM_RE = /@@ [+-]([0-9]+),([0-9]+) [+-]([0-9]+),([0-9]+) @@/
275 LINE_TYPES = {'+' => :add, '-' => :rem, ' ' => :unmod}
276
277 class << self
278
279 # Runs the generator on a diff and returns a Data object without
280 # instantiating a Generator object
281 def run(udiff)
282 raise ArgumentError, "Object must be enumerable" unless udiff.respond_to?(:each)
283 generator = new
284 udiff.each {|line| generator.process(line.chomp)}
285 generator.data
286 end
287 end
288
289 def initialize
290 @buffer = []
291 @prev_buffer = []
292 @line_type = nil
293 @prev_line_type = nil
294 @offset_base = 0
295 @offset_changed = 0
296 @data = Diff::Display::Unified::Data.new
297 self
298 end
299
300 # Operates on a single line from the diff and passes along the
301 # collected data to the appropriate method for further processing. The
302 # cycle of processing is in general:
303 #
304 # process --> identify_block --> process_block --> process_line
305 #
306 def process(line)
307 return if ['++', '--'].include?(line[0,2])
308
309 if match = LINE_NUM_RE.match(line)
310 identify_block
311 add_separator unless @offset_changed.zero?
312 @line_type = nil
313 @offset_base = match[1].to_i - 1
314 @offset_changed = match[3].to_i - 1
315 return
316 end
317
318 new_line_type, line = LINE_TYPES[car(line)], cdr(line)
319
320 # Add line to the buffer if it's the same diff line type
321 # as the previous line
322 #
323 # e.g.
324 #
325 # + This is a new line
326 # + As is this one
327 # + And yet another one...
328 #
329 if new_line_type.eql?(@line_type)
330 @buffer.push(line)
331 else
332 # Side by side inline diff
333 #
334 # e.g.
335 #
336 # - This line just had to go
337 # + This line is on the way in
338 #
339 if new_line_type.eql?(LINE_TYPES['+']) and @line_type.eql?(LINE_TYPES['-'])
340 @prev_buffer = @buffer
341 @prev_line_type = @line_type
342 else
343 identify_block
344 end
345 @buffer = [line]
346 @line_type = new_line_type
347 end
348 end
349
350 # Finishes up with the generation and returns the Data object (could
351 # probably use a better name...maybe just #data?)
352 def data
353 close
354 @data
355 end
356
357 protected
358
359 def identify_block
360 if @prev_line_type.eql?(LINE_TYPES['-']) and @line_type.eql?(LINE_TYPES['+'])
361 process_block(:mod, true, true)
362 else
363 if LINE_TYPES.values.include?(@line_type)
364 process_block(@line_type, true)
365 end
366 end
367
368 @prev_line_type = nil
369 end
370
371 def process_block(diff_line_type, new = false, old = false)
372 push Block.send(diff_line_type)
373
374 # Mod block
375 if diff_line_type.eql?(:mod) and @prev_buffer.size & @buffer.size == 1
376 process_line(@prev_buffer.first, @buffer.first)
377 return
378 end
379
380 unroll_prev_buffer if old
381 unroll_buffer if new
382 end
383
384 # TODO Needs a better name...it does process a line (two in fact) but
385 # its primary function is to add a Rem and an Add pair which
386 # potentially have inline changes
387 def process_line(oldline, newline)
388 start, ending = get_change_extent(oldline, newline)
389
390 # -
391 line = inline_diff(oldline, start, ending)
392 current_block << Line.rem(line, @offset_base += 1, true)
393
394 # +
395 line = inline_diff(newline, start, ending)
396 current_block << Line.add(line, @offset_changed += 1, true)
397 end
398
399 # Inserts string formating characters around the section of a string
400 # that differs internally from another line so that the Line class
401 # can insert the desired formating
402 def inline_diff(line, start, ending)
403 line[0, start] +
404 '%s' + extract_change(line, start, ending) + '%s' +
405 line[ending, ending.abs]
406 end
407
408 def add_separator
409 push SepBlock.new
410 current_block << SepLine.new
411 end
412
413 def extract_change(line, start, ending)
414 line.size > (start - ending) ? line[start...ending] : ''
415 end
416
417 def car(line)
418 line[0,1]
419 end
420
421 def cdr(line)
422 line[1..-1]
423 end
424
425 # Returns the current Block object
426 def current_block
427 @data.last
428 end
429
430 # Adds a Line object onto the current Block object
431 def push(line)
432 @data.push line
433 end
434
435 def prev_buffer
436 @prev_buffer
437 end
438
439 def unroll_prev_buffer
440 return if @prev_buffer.empty?
441 @prev_buffer.each do |line|
442 @offset_base += 1
443 current_block << Line.send(@prev_line_type, line, @offset_base)
444 end
445 end
446
447 def unroll_buffer
448 return if @buffer.empty?
449 @buffer.each do |line|
450 @offset_changed += 1
451 current_block << Line.send(@line_type, line, @offset_changed)
452 end
453 end
454
455 # This method is called once the generator is done with the unified
456 # diff. It is a finalizer of sorts. By the time it is called all data
457 # has been collected and processed.