Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

unified_diff.rb 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2017 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. module Redmine
  19. # Class used to parse unified diffs
  20. class UnifiedDiff < Array
  21. attr_reader :diff_type, :diff_style
  22. def initialize(diff, options={})
  23. options.assert_valid_keys(:type, :style, :max_lines)
  24. diff = diff.split("\n") if diff.is_a?(String)
  25. @diff_type = options[:type] || 'inline'
  26. @diff_style = options[:style]
  27. lines = 0
  28. @truncated = false
  29. diff_table = DiffTable.new(diff_type, diff_style)
  30. diff.each do |line_raw|
  31. line = Redmine::CodesetUtil.to_utf8_by_setting(line_raw)
  32. unless diff_table.add_line(line)
  33. self << diff_table if diff_table.length > 0
  34. diff_table = DiffTable.new(diff_type, diff_style)
  35. end
  36. lines += 1
  37. if options[:max_lines] && lines > options[:max_lines]
  38. @truncated = true
  39. break
  40. end
  41. end
  42. self << diff_table unless diff_table.empty?
  43. self
  44. end
  45. def truncated?; @truncated; end
  46. end
  47. # Class that represents a file diff
  48. class DiffTable < Array
  49. attr_reader :file_name, :previous_file_name
  50. # Initialize with a Diff file and the type of Diff View
  51. # The type view must be inline or sbs (side_by_side)
  52. def initialize(type="inline", style=nil)
  53. @parsing = false
  54. @added = 0
  55. @removed = 0
  56. @type = type
  57. @style = style
  58. @file_name = nil
  59. @previous_file_name = nil
  60. @git_diff = false
  61. end
  62. # Function for add a line of this Diff
  63. # Returns false when the diff ends
  64. def add_line(line)
  65. unless @parsing
  66. if line =~ /^(---|\+\+\+) (.*)$/
  67. self.file_name = $2
  68. elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
  69. @line_num_l = $2.to_i
  70. @line_num_r = $5.to_i
  71. @parsing = true
  72. end
  73. else
  74. if %r{^[^\+\-\s@\\]}.match?(line)
  75. @parsing = false
  76. return false
  77. elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
  78. @line_num_l = $2.to_i
  79. @line_num_r = $5.to_i
  80. else
  81. parse_line(line, @type)
  82. end
  83. end
  84. return true
  85. end
  86. def each_line
  87. prev_line_left, prev_line_right = nil, nil
  88. each do |line|
  89. spacing = prev_line_left && prev_line_right && (line.nb_line_left != prev_line_left+1) && (line.nb_line_right != prev_line_right+1)
  90. yield spacing, line
  91. prev_line_left = line.nb_line_left.to_i if line.nb_line_left.to_i > 0
  92. prev_line_right = line.nb_line_right.to_i if line.nb_line_right.to_i > 0
  93. end
  94. end
  95. def inspect
  96. puts '### DIFF TABLE ###'
  97. puts "file : #{file_name}"
  98. self.each do |d|
  99. d.inspect
  100. end
  101. end
  102. private
  103. def file_name=(arg)
  104. both_git_diff = false
  105. if file_name.nil?
  106. @git_diff = true if %r{^(a/|/dev/null)}.match?(arg)
  107. else
  108. both_git_diff = (@git_diff && %r{^(b/|/dev/null)}.match?(arg))
  109. end
  110. if both_git_diff
  111. if file_name && arg == "/dev/null"
  112. # keep the original file name
  113. @file_name = file_name.sub(%r{^a/}, '')
  114. else
  115. # remove leading a/
  116. @previous_file_name = file_name.sub(%r{^a/}, '') unless file_name == "/dev/null"
  117. # remove leading b/
  118. @file_name = arg.sub(%r{^b/}, '')
  119. @previous_file_name = nil if @previous_file_name == @file_name
  120. end
  121. elsif @style == "Subversion"
  122. # removing trailing "(revision nn)"
  123. @file_name = arg.sub(%r{\t+\(.*\)$}, '')
  124. else
  125. @file_name = arg
  126. end
  127. end
  128. def diff_for_added_line
  129. if @type == 'sbs' && @removed > 0 && @added < @removed
  130. self[-(@removed - @added)]
  131. else
  132. diff = Diff.new
  133. self << diff
  134. diff
  135. end
  136. end
  137. def parse_line(line, type="inline")
  138. if line[0, 1] == "+"
  139. diff = diff_for_added_line
  140. diff.line_right = line[1..-1]
  141. diff.nb_line_right = @line_num_r
  142. diff.type_diff_right = 'diff_in'
  143. @line_num_r += 1
  144. @added += 1
  145. true
  146. elsif line[0, 1] == "-"
  147. diff = Diff.new
  148. diff.line_left = line[1..-1]
  149. diff.nb_line_left = @line_num_l
  150. diff.type_diff_left = 'diff_out'
  151. self << diff
  152. @line_num_l += 1
  153. @removed += 1
  154. true
  155. else
  156. write_offsets
  157. if /\s/.match?(line[0, 1])
  158. diff = Diff.new
  159. diff.line_right = line[1..-1]
  160. diff.nb_line_right = @line_num_r
  161. diff.line_left = line[1..-1]
  162. diff.nb_line_left = @line_num_l
  163. self << diff
  164. @line_num_l += 1
  165. @line_num_r += 1
  166. true
  167. elsif line[0, 1] = "\\"
  168. true
  169. else
  170. false
  171. end
  172. end
  173. end
  174. def write_offsets
  175. if @added > 0 && @added == @removed
  176. @added.times do |i|
  177. line = self[-(1 + i)]
  178. removed = (@type == 'sbs') ? line : self[-(1 + @added + i)]
  179. offsets = offsets(removed.line_left, line.line_right)
  180. removed.offsets = line.offsets = offsets
  181. end
  182. end
  183. @added = 0
  184. @removed = 0
  185. end
  186. def offsets(line_left, line_right)
  187. if line_left.present? && line_right.present? && line_left != line_right
  188. max = [line_left.size, line_right.size].min
  189. starting = 0
  190. while starting < max && line_left[starting] == line_right[starting]
  191. starting += 1
  192. end
  193. ending = -1
  194. while ending >= -(max - starting) && (line_left[ending] == line_right[ending])
  195. ending -= 1
  196. end
  197. unless starting == 0 && ending == -1
  198. [starting, ending]
  199. end
  200. end
  201. end
  202. end
  203. # A line of diff
  204. class Diff
  205. attr_accessor :nb_line_left
  206. attr_accessor :line_left
  207. attr_accessor :nb_line_right
  208. attr_accessor :line_right
  209. attr_accessor :type_diff_right
  210. attr_accessor :type_diff_left
  211. attr_accessor :offsets
  212. def initialize()
  213. self.nb_line_left = ''
  214. self.nb_line_right = ''
  215. self.line_left = ''
  216. self.line_right = ''
  217. self.type_diff_right = ''
  218. self.type_diff_left = ''
  219. end
  220. def type_diff
  221. type_diff_right == 'diff_in' ? type_diff_right : type_diff_left
  222. end
  223. def line
  224. type_diff_right == 'diff_in' ? line_right : line_left
  225. end
  226. def html_line_left
  227. line_to_html(line_left, offsets)
  228. end
  229. def html_line_right
  230. line_to_html(line_right, offsets)
  231. end
  232. def html_line
  233. line_to_html(line, offsets)
  234. end
  235. def inspect
  236. puts '### Start Line Diff ###'
  237. puts self.nb_line_left
  238. puts self.line_left
  239. puts self.nb_line_right
  240. puts self.line_right
  241. end
  242. private
  243. def line_to_html(line, offsets)
  244. html = line_to_html_raw(line, offsets)
  245. html.force_encoding('UTF-8')
  246. html
  247. end
  248. def line_to_html_raw(line, offsets)
  249. if offsets
  250. s = +''
  251. unless offsets.first == 0
  252. s << CGI.escapeHTML(line[0..offsets.first-1])
  253. end
  254. s << '<span>' + CGI.escapeHTML(line[offsets.first..offsets.last]) + '</span>'
  255. unless offsets.last == -1
  256. s << CGI.escapeHTML(line[offsets.last+1..-1])
  257. end
  258. s
  259. else
  260. CGI.escapeHTML(line)
  261. end
  262. end
  263. end
  264. end