| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  | # Redmine - project management software | 
					
						
							|  |  |  | # Copyright (C) 2006-2011  Jean-Philippe Lang | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | # | 
					
						
							|  |  |  | # This program is free software; you can redistribute it and/or | 
					
						
							|  |  |  | # modify it under the terms of the GNU General Public License | 
					
						
							|  |  |  | # as published by the Free Software Foundation; either version 2 | 
					
						
							|  |  |  | # of the License, or (at your option) any later version. | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | # This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | # GNU General Public License for more details. | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | # You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  | # along with this program; if not, write to the Free Software | 
					
						
							|  |  |  | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Redmine | 
					
						
							|  |  |  |   # Class used to parse unified diffs | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |   class UnifiedDiff < Array | 
					
						
							|  |  |  |     attr_reader :diff_type | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-12-07 14:40:33 +00:00
										 |  |  |     def initialize(diff, options={}) | 
					
						
							| 
									
										
										
										
											2008-12-07 15:21:40 +00:00
										 |  |  |       options.assert_valid_keys(:type, :max_lines) | 
					
						
							| 
									
										
										
										
											2009-10-10 15:19:27 +00:00
										 |  |  |       diff = diff.split("\n") if diff.is_a?(String) | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |       @diff_type = options[:type] || 'inline' | 
					
						
							| 
									
										
										
										
											2008-12-07 15:21:40 +00:00
										 |  |  |       lines = 0
 | 
					
						
							|  |  |  |       @truncated = false | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |       diff_table = DiffTable.new(@diff_type) | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |       diff.each do |line| | 
					
						
							| 
									
										
										
										
											2011-02-21 03:59:50 +00:00
										 |  |  |         line_encoding = nil | 
					
						
							|  |  |  |         if line.respond_to?(:force_encoding) | 
					
						
							|  |  |  |           line_encoding = line.encoding | 
					
						
							|  |  |  |           # TODO: UTF-16 and Japanese CP932 which is imcompatible with ASCII | 
					
						
							|  |  |  |           #       In Japan, diffrence between file path encoding | 
					
						
							|  |  |  |           #       and file contents encoding is popular. | 
					
						
							|  |  |  |           line.force_encoding('ASCII-8BIT') | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2009-11-11 13:25:53 +00:00
										 |  |  |         unless diff_table.add_line line | 
					
						
							| 
									
										
										
										
											2011-02-21 03:59:50 +00:00
										 |  |  |           line.force_encoding(line_encoding) if line_encoding | 
					
						
							| 
									
										
										
										
											2011-02-23 07:03:45 +00:00
										 |  |  |           self << diff_table if diff_table.length > 0
 | 
					
						
							| 
									
										
										
										
											2008-12-07 14:40:33 +00:00
										 |  |  |           diff_table = DiffTable.new(diff_type) | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2008-12-07 15:21:40 +00:00
										 |  |  |         lines += 1
 | 
					
						
							|  |  |  |         if options[:max_lines] && lines > options[:max_lines] | 
					
						
							|  |  |  |           @truncated = true | 
					
						
							|  |  |  |           break | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |       end | 
					
						
							|  |  |  |       self << diff_table unless diff_table.empty? | 
					
						
							|  |  |  |       self | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-02-21 03:59:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-12-07 15:21:40 +00:00
										 |  |  |     def truncated?; @truncated; end | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Class that represents a file diff | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  |   class DiffTable < Array | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     attr_reader :file_name | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Initialize with a Diff file and the type of Diff View | 
					
						
							|  |  |  |     # The type view must be inline or sbs (side_by_side) | 
					
						
							|  |  |  |     def initialize(type="inline") | 
					
						
							|  |  |  |       @parsing = false | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |       @added = 0
 | 
					
						
							|  |  |  |       @removed = 0
 | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |       @type = type | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function for add a line of this Diff | 
					
						
							| 
									
										
										
										
											2009-11-11 13:25:53 +00:00
										 |  |  |     # Returns false when the diff ends | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |     def add_line(line) | 
					
						
							|  |  |  |       unless @parsing | 
					
						
							|  |  |  |         if line =~ /^(---|\+\+\+) (.*)$/ | 
					
						
							|  |  |  |           @file_name = $2 | 
					
						
							|  |  |  |         elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ | 
					
						
							| 
									
										
										
										
											2008-09-21 18:45:30 +00:00
										 |  |  |           @line_num_l = $2.to_i | 
					
						
							|  |  |  |           @line_num_r = $5.to_i | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |           @parsing = true | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         if line =~ /^[^\+\-\s@\\]/ | 
					
						
							|  |  |  |           @parsing = false | 
					
						
							|  |  |  |           return false | 
					
						
							|  |  |  |         elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ | 
					
						
							| 
									
										
										
										
											2008-09-21 18:45:30 +00:00
										 |  |  |           @line_num_l = $2.to_i | 
					
						
							|  |  |  |           @line_num_r = $5.to_i | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |         else | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  |           parse_line(line, @type) | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |       return true | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def each_line | 
					
						
							|  |  |  |       prev_line_left, prev_line_right = nil, nil | 
					
						
							|  |  |  |       each do |line| | 
					
						
							|  |  |  |         spacing = prev_line_left && prev_line_right && (line.nb_line_left != prev_line_left+1) && (line.nb_line_right != prev_line_right+1) | 
					
						
							|  |  |  |         yield spacing, line | 
					
						
							|  |  |  |         prev_line_left = line.nb_line_left.to_i if line.nb_line_left.to_i > 0
 | 
					
						
							|  |  |  |         prev_line_right = line.nb_line_right.to_i if line.nb_line_right.to_i > 0
 | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def inspect | 
					
						
							|  |  |  |       puts '### DIFF TABLE ###' | 
					
						
							|  |  |  |       puts "file : #{file_name}" | 
					
						
							|  |  |  |       self.each do |d| | 
					
						
							|  |  |  |         d.inspect | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     private | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Escape the HTML for the diff | 
					
						
							|  |  |  |     def escapeHTML(line) | 
					
						
							|  |  |  |         CGI.escapeHTML(line) | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def diff_for_added_line | 
					
						
							|  |  |  |       if @type == 'sbs' && @removed > 0 && @added < @removed | 
					
						
							|  |  |  |         self[-(@removed - @added)] | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         diff = Diff.new | 
					
						
							|  |  |  |         self << diff | 
					
						
							|  |  |  |         diff | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def parse_line(line, type="inline") | 
					
						
							|  |  |  |       if line[0, 1] == "+" | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |         diff = diff_for_added_line | 
					
						
							| 
									
										
										
										
											2008-09-21 18:45:30 +00:00
										 |  |  |         diff.line_right = escapeHTML line[1..-1] | 
					
						
							|  |  |  |         diff.nb_line_right = @line_num_r | 
					
						
							|  |  |  |         diff.type_diff_right = 'diff_in' | 
					
						
							|  |  |  |         @line_num_r += 1
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |         @added += 1
 | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |         true | 
					
						
							|  |  |  |       elsif line[0, 1] == "-" | 
					
						
							|  |  |  |         diff = Diff.new | 
					
						
							|  |  |  |         diff.line_left = escapeHTML line[1..-1] | 
					
						
							|  |  |  |         diff.nb_line_left = @line_num_l | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |         diff.type_diff_left = 'diff_out' | 
					
						
							|  |  |  |         self << diff | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |         @line_num_l += 1
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |         @removed += 1
 | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |         true | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |       else | 
					
						
							|  |  |  |         write_offsets | 
					
						
							|  |  |  |         if line[0, 1] =~ /\s/ | 
					
						
							|  |  |  |           diff = Diff.new | 
					
						
							|  |  |  |           diff.line_right = escapeHTML line[1..-1] | 
					
						
							|  |  |  |           diff.nb_line_right = @line_num_r | 
					
						
							|  |  |  |           diff.line_left = escapeHTML line[1..-1] | 
					
						
							|  |  |  |           diff.nb_line_left = @line_num_l | 
					
						
							|  |  |  |           self << diff | 
					
						
							|  |  |  |           @line_num_l += 1
 | 
					
						
							|  |  |  |           @line_num_r += 1
 | 
					
						
							|  |  |  |           true | 
					
						
							|  |  |  |         elsif line[0, 1] = "\\" | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |           true | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           false | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def write_offsets | 
					
						
							|  |  |  |       if @added > 0 && @added == @removed | 
					
						
							|  |  |  |         @added.times do |i| | 
					
						
							|  |  |  |           line = self[-(1 + i)] | 
					
						
							|  |  |  |           removed = (@type == 'sbs') ? line : self[-(1 + @added + i)] | 
					
						
							|  |  |  |           offsets = offsets(removed.line_left, line.line_right) | 
					
						
							|  |  |  |           removed.offsets = line.offsets = offsets | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |       @added = 0
 | 
					
						
							|  |  |  |       @removed = 0
 | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def offsets(line_left, line_right) | 
					
						
							|  |  |  |       if line_left.present? && line_right.present? && line_left != line_right | 
					
						
							|  |  |  |         max = [line_left.size, line_right.size].min | 
					
						
							|  |  |  |         starting = 0
 | 
					
						
							|  |  |  |         while starting < max && line_left[starting] == line_right[starting] | 
					
						
							|  |  |  |           starting += 1
 | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |         ending = -1
 | 
					
						
							|  |  |  |         while ending >= -(max - starting) && line_left[ending] == line_right[ending] | 
					
						
							|  |  |  |           ending -= 1
 | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |         unless starting == 0 && ending == -1
 | 
					
						
							|  |  |  |           [starting, ending] | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   # A line of diff | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  |   class Diff | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |     attr_accessor :nb_line_left | 
					
						
							|  |  |  |     attr_accessor :line_left | 
					
						
							|  |  |  |     attr_accessor :nb_line_right | 
					
						
							|  |  |  |     attr_accessor :line_right | 
					
						
							|  |  |  |     attr_accessor :type_diff_right | 
					
						
							|  |  |  |     attr_accessor :type_diff_left | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     attr_accessor :offsets | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  |     def initialize() | 
					
						
							|  |  |  |       self.nb_line_left = '' | 
					
						
							|  |  |  |       self.nb_line_right = '' | 
					
						
							|  |  |  |       self.line_left = '' | 
					
						
							|  |  |  |       self.line_right = '' | 
					
						
							|  |  |  |       self.type_diff_right = '' | 
					
						
							|  |  |  |       self.type_diff_left = '' | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def type_diff | 
					
						
							|  |  |  |       type_diff_right == 'diff_in' ? type_diff_right : type_diff_left | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def line | 
					
						
							|  |  |  |       type_diff_right == 'diff_in' ? line_right : line_left | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def html_line_left | 
					
						
							|  |  |  |       if offsets | 
					
						
							|  |  |  |         line_left.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         line_left | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def html_line_right | 
					
						
							|  |  |  |       if offsets | 
					
						
							|  |  |  |         line_right.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         line_right | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-09-01 00:44:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-11 20:23:29 +00:00
										 |  |  |     def html_line | 
					
						
							|  |  |  |       if offsets | 
					
						
							|  |  |  |         line.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         line | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2008-06-08 16:28:42 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def inspect | 
					
						
							|  |  |  |       puts '### Start Line Diff ###' | 
					
						
							|  |  |  |       puts self.nb_line_left | 
					
						
							|  |  |  |       puts self.line_left | 
					
						
							|  |  |  |       puts self.nb_line_right | 
					
						
							|  |  |  |       puts self.line_right | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |