You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

abstract_adapter.rb 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. # redMine - project management software
  2. # Copyright (C) 2006-2007 Jean-Philippe Lang
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; either version 2
  7. # of the License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  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
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. require 'cgi'
  18. module Redmine
  19. module Scm
  20. module Adapters
  21. class CommandFailed < StandardError #:nodoc:
  22. end
  23. class AbstractAdapter #:nodoc:
  24. def initialize(url, root_url=nil, login=nil, password=nil)
  25. @url = url
  26. @login = login if login && !login.empty?
  27. @password = (password || "") if @login
  28. @root_url = root_url.blank? ? retrieve_root_url : root_url
  29. end
  30. def adapter_name
  31. 'Abstract'
  32. end
  33. def supports_cat?
  34. true
  35. end
  36. def supports_annotate?
  37. respond_to?('annotate')
  38. end
  39. def root_url
  40. @root_url
  41. end
  42. def url
  43. @url
  44. end
  45. # get info about the svn repository
  46. def info
  47. return nil
  48. end
  49. # Returns the entry identified by path and revision identifier
  50. # or nil if entry doesn't exist in the repository
  51. def entry(path=nil, identifier=nil)
  52. parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
  53. search_path = parts[0..-2].join('/')
  54. search_name = parts[-1]
  55. if search_path.blank? && search_name.blank?
  56. # Root entry
  57. Entry.new(:path => '', :kind => 'dir')
  58. else
  59. # Search for the entry in the parent directory
  60. es = entries(search_path, identifier)
  61. es ? es.detect {|e| e.name == search_name} : nil
  62. end
  63. end
  64. # Returns an Entries collection
  65. # or nil if the given path doesn't exist in the repository
  66. def entries(path=nil, identifier=nil)
  67. return nil
  68. end
  69. def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
  70. return nil
  71. end
  72. def diff(path, identifier_from, identifier_to=nil, type="inline")
  73. return nil
  74. end
  75. def cat(path, identifier=nil)
  76. return nil
  77. end
  78. def with_leading_slash(path)
  79. path ||= ''
  80. (path[0,1]!="/") ? "/#{path}" : path
  81. end
  82. def shell_quote(str)
  83. if RUBY_PLATFORM =~ /mswin/
  84. '"' + str.gsub(/"/, '\\"') + '"'
  85. else
  86. "'" + str.gsub(/'/, "'\"'\"'") + "'"
  87. end
  88. end
  89. private
  90. def retrieve_root_url
  91. info = self.info
  92. info ? info.root_url : nil
  93. end
  94. def target(path)
  95. path ||= ''
  96. base = path.match(/^\//) ? root_url : url
  97. shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
  98. end
  99. def logger
  100. RAILS_DEFAULT_LOGGER
  101. end
  102. def shellout(cmd, &block)
  103. logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
  104. begin
  105. IO.popen(cmd, "r+") do |io|
  106. io.close_write
  107. block.call(io) if block_given?
  108. end
  109. rescue Errno::ENOENT => e
  110. msg = strip_credential(e.message)
  111. # The command failed, log it and re-raise
  112. logger.error("SCM command failed: #{strip_credential(cmd)}\n with: #{msg}")
  113. raise CommandFailed.new(msg)
  114. end
  115. end
  116. # Hides username/password in a given command
  117. def self.hide_credential(cmd)
  118. q = (RUBY_PLATFORM =~ /mswin/ ? '"' : "'")
  119. cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
  120. end
  121. def strip_credential(cmd)
  122. self.class.hide_credential(cmd)
  123. end
  124. end
  125. class Entries < Array
  126. def sort_by_name
  127. sort {|x,y|
  128. if x.kind == y.kind
  129. x.name <=> y.name
  130. else
  131. x.kind <=> y.kind
  132. end
  133. }
  134. end
  135. def revisions
  136. revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
  137. end
  138. end
  139. class Info
  140. attr_accessor :root_url, :lastrev
  141. def initialize(attributes={})
  142. self.root_url = attributes[:root_url] if attributes[:root_url]
  143. self.lastrev = attributes[:lastrev]
  144. end
  145. end
  146. class Entry
  147. attr_accessor :name, :path, :kind, :size, :lastrev
  148. def initialize(attributes={})
  149. self.name = attributes[:name] if attributes[:name]
  150. self.path = attributes[:path] if attributes[:path]
  151. self.kind = attributes[:kind] if attributes[:kind]
  152. self.size = attributes[:size].to_i if attributes[:size]
  153. self.lastrev = attributes[:lastrev]
  154. end
  155. def is_file?
  156. 'file' == self.kind
  157. end
  158. def is_dir?
  159. 'dir' == self.kind
  160. end
  161. def is_text?
  162. Redmine::MimeType.is_type?('text', name)
  163. end
  164. end
  165. class Revisions < Array
  166. def latest
  167. sort {|x,y|
  168. unless x.time.nil? or y.time.nil?
  169. x.time <=> y.time
  170. else
  171. 0
  172. end
  173. }.last
  174. end
  175. end
  176. class Revision
  177. attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
  178. def initialize(attributes={})
  179. self.identifier = attributes[:identifier]
  180. self.scmid = attributes[:scmid]
  181. self.name = attributes[:name] || self.identifier
  182. self.author = attributes[:author]
  183. self.time = attributes[:time]
  184. self.message = attributes[:message] || ""
  185. self.paths = attributes[:paths]
  186. self.revision = attributes[:revision]
  187. self.branch = attributes[:branch]
  188. end
  189. end
  190. # A line of Diff
  191. class Diff
  192. attr_accessor :nb_line_left
  193. attr_accessor :line_left
  194. attr_accessor :nb_line_right
  195. attr_accessor :line_right
  196. attr_accessor :type_diff_right
  197. attr_accessor :type_diff_left
  198. def initialize ()
  199. self.nb_line_left = ''
  200. self.nb_line_right = ''
  201. self.line_left = ''
  202. self.line_right = ''
  203. self.type_diff_right = ''
  204. self.type_diff_left = ''
  205. end
  206. def inspect
  207. puts '### Start Line Diff ###'
  208. puts self.nb_line_left
  209. puts self.line_left
  210. puts self.nb_line_right
  211. puts self.line_right
  212. end
  213. end
  214. class DiffTableList < Array
  215. def initialize (diff, type="inline")
  216. diff_table = DiffTable.new type
  217. diff.each do |line|
  218. if line =~ /^(---|\+\+\+) (.*)$/
  219. self << diff_table if diff_table.length > 1
  220. diff_table = DiffTable.new type
  221. end
  222. a = diff_table.add_line line
  223. end
  224. self << diff_table unless diff_table.empty?
  225. self
  226. end
  227. end
  228. # Class for create a Diff
  229. class DiffTable < Hash
  230. attr_reader :file_name, :line_num_l, :line_num_r
  231. # Initialize with a Diff file and the type of Diff View
  232. # The type view must be inline or sbs (side_by_side)
  233. def initialize(type="inline")
  234. @parsing = false
  235. @nb_line = 1
  236. @start = false
  237. @before = 'same'
  238. @second = true
  239. @type = type
  240. end
  241. # Function for add a line of this Diff
  242. def add_line(line)
  243. unless @parsing
  244. if line =~ /^(---|\+\+\+) (.*)$/
  245. @file_name = $2
  246. return false
  247. elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
  248. @line_num_l = $5.to_i
  249. @line_num_r = $2.to_i
  250. @parsing = true
  251. end
  252. else
  253. if line =~ /^[^\+\-\s@\\]/
  254. @parsing = false
  255. return false
  256. elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
  257. @line_num_l = $5.to_i
  258. @line_num_r = $2.to_i
  259. else
  260. @nb_line += 1 if parse_line(line, @type)
  261. end
  262. end
  263. return true
  264. end
  265. def inspect
  266. puts '### DIFF TABLE ###'
  267. puts "file : #{file_name}"
  268. self.each do |d|
  269. d.inspect
  270. end
  271. end
  272. private
  273. # Test if is a Side By Side type
  274. def sbs?(type, func)
  275. if @start and type == "sbs"
  276. if @before == func and @second
  277. tmp_nb_line = @nb_line
  278. self[tmp_nb_line] = Diff.new
  279. else
  280. @second = false
  281. tmp_nb_line = @start
  282. @start += 1
  283. @nb_line -= 1
  284. end
  285. else
  286. tmp_nb_line = @nb_line
  287. @start = @nb_line
  288. self[tmp_nb_line] = Diff.new
  289. @second = true
  290. end
  291. unless self[tmp_nb_line]
  292. @nb_line += 1
  293. self[tmp_nb_line] = Diff.new
  294. else
  295. self[tmp_nb_line]
  296. end
  297. end
  298. # Escape the HTML for the diff
  299. def escapeHTML(line)
  300. CGI.escapeHTML(line)
  301. end
  302. def parse_line(line, type="inline")
  303. if line[0, 1] == "+"
  304. diff = sbs? type, 'add'
  305. @before = 'add'
  306. diff.line_left = escapeHTML line[1..-1]
  307. diff.nb_line_left = @line_num_l
  308. diff.type_diff_left = 'diff_in'
  309. @line_num_l += 1
  310. true
  311. elsif line[0, 1] == "-"
  312. diff = sbs? type, 'remove'
  313. @before = 'remove'
  314. diff.line_right = escapeHTML line[1..-1]
  315. diff.nb_line_right = @line_num_r
  316. diff.type_diff_right = 'diff_out'
  317. @line_num_r += 1
  318. true
  319. elsif line[0, 1] =~ /\s/
  320. @before = 'same'
  321. @start = false
  322. diff = Diff.new
  323. diff.line_right = escapeHTML line[1..-1]
  324. diff.nb_line_right = @line_num_r
  325. diff.line_left = escapeHTML line[1..-1]
  326. diff.nb_line_left = @line_num_l
  327. self[@nb_line] = diff
  328. @line_num_l += 1
  329. @line_num_r += 1
  330. true
  331. elsif line[0, 1] = "\\"
  332. true
  333. else
  334. false
  335. end
  336. end
  337. end
  338. class Annotate
  339. attr_reader :lines, :revisions
  340. def initialize
  341. @lines = []
  342. @revisions = []
  343. end
  344. def add_line(line, revision)
  345. @lines << line
  346. @revisions << revision
  347. end
  348. def content
  349. content = lines.join("\n")
  350. end
  351. def empty?
  352. lines.empty?
  353. end
  354. end
  355. end
  356. end
  357. end