PageRenderTime 53ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/redmine/scm/adapters/abstract_adapter.rb

https://bitbucket.org/prdixit/redmine
Ruby | 410 lines | 374 code | 11 blank | 25 comment | 7 complexity | 912141f8c5fde1a19ff30e7c8498e67d MD5 | raw file
Possible License(s): GPL-2.0
  1. # Redmine - project management software
  2. # Copyright (C) 2006-2012 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. # raised if scm command exited with error, e.g. unknown revision.
  25. class ScmCommandAborted < CommandFailed; end
  26. class << self
  27. def client_command
  28. ""
  29. end
  30. def shell_quote_command
  31. if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
  32. client_command
  33. else
  34. shell_quote(client_command)
  35. end
  36. end
  37. # Returns the version of the scm client
  38. # Eg: [1, 5, 0] or [] if unknown
  39. def client_version
  40. []
  41. end
  42. # Returns the version string of the scm client
  43. # Eg: '1.5.0' or 'Unknown version' if unknown
  44. def client_version_string
  45. v = client_version || 'Unknown version'
  46. v.is_a?(Array) ? v.join('.') : v.to_s
  47. end
  48. # Returns true if the current client version is above
  49. # or equals the given one
  50. # If option is :unknown is set to true, it will return
  51. # true if the client version is unknown
  52. def client_version_above?(v, options={})
  53. ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
  54. end
  55. def client_available
  56. true
  57. end
  58. def shell_quote(str)
  59. if Redmine::Platform.mswin?
  60. '"' + str.gsub(/"/, '\\"') + '"'
  61. else
  62. "'" + str.gsub(/'/, "'\"'\"'") + "'"
  63. end
  64. end
  65. end
  66. def initialize(url, root_url=nil, login=nil, password=nil,
  67. path_encoding=nil)
  68. @url = url
  69. @login = login if login && !login.empty?
  70. @password = (password || "") if @login
  71. @root_url = root_url.blank? ? retrieve_root_url : root_url
  72. end
  73. def adapter_name
  74. 'Abstract'
  75. end
  76. def supports_cat?
  77. true
  78. end
  79. def supports_annotate?
  80. respond_to?('annotate')
  81. end
  82. def root_url
  83. @root_url
  84. end
  85. def url
  86. @url
  87. end
  88. def path_encoding
  89. nil
  90. end
  91. # get info about the svn repository
  92. def info
  93. return nil
  94. end
  95. # Returns the entry identified by path and revision identifier
  96. # or nil if entry doesn't exist in the repository
  97. def entry(path=nil, identifier=nil)
  98. parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
  99. search_path = parts[0..-2].join('/')
  100. search_name = parts[-1]
  101. if search_path.blank? && search_name.blank?
  102. # Root entry
  103. Entry.new(:path => '', :kind => 'dir')
  104. else
  105. # Search for the entry in the parent directory
  106. es = entries(search_path, identifier)
  107. es ? es.detect {|e| e.name == search_name} : nil
  108. end
  109. end
  110. # Returns an Entries collection
  111. # or nil if the given path doesn't exist in the repository
  112. def entries(path=nil, identifier=nil, options={})
  113. return nil
  114. end
  115. def branches
  116. return nil
  117. end
  118. def tags
  119. return nil
  120. end
  121. def default_branch
  122. return nil
  123. end
  124. def properties(path, identifier=nil)
  125. return nil
  126. end
  127. def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
  128. return nil
  129. end
  130. def diff(path, identifier_from, identifier_to=nil)
  131. return nil
  132. end
  133. def cat(path, identifier=nil)
  134. return nil
  135. end
  136. def with_leading_slash(path)
  137. path ||= ''
  138. (path[0,1]!="/") ? "/#{path}" : path
  139. end
  140. def with_trailling_slash(path)
  141. path ||= ''
  142. (path[-1,1] == "/") ? path : "#{path}/"
  143. end
  144. def without_leading_slash(path)
  145. path ||= ''
  146. path.gsub(%r{^/+}, '')
  147. end
  148. def without_trailling_slash(path)
  149. path ||= ''
  150. (path[-1,1] == "/") ? path[0..-2] : path
  151. end
  152. def shell_quote(str)
  153. self.class.shell_quote(str)
  154. end
  155. private
  156. def retrieve_root_url
  157. info = self.info
  158. info ? info.root_url : nil
  159. end
  160. def target(path, sq=true)
  161. path ||= ''
  162. base = path.match(/^\//) ? root_url : url
  163. str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
  164. if sq
  165. str = shell_quote(str)
  166. end
  167. str
  168. end
  169. def logger
  170. self.class.logger
  171. end
  172. def shellout(cmd, options = {}, &block)
  173. self.class.shellout(cmd, options, &block)
  174. end
  175. def self.logger
  176. Rails.logger
  177. end
  178. def self.shellout(cmd, options = {}, &block)
  179. if logger && logger.debug?
  180. logger.debug "Shelling out: #{strip_credential(cmd)}"
  181. end
  182. if Rails.env == 'development'
  183. # Capture stderr when running in dev environment
  184. cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
  185. end
  186. begin
  187. mode = "r+"
  188. IO.popen(cmd, mode) do |io|
  189. io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
  190. io.close_write unless options[:write_stdin]
  191. block.call(io) if block_given?
  192. end
  193. ## If scm command does not exist,
  194. ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
  195. ## in production environment.
  196. # rescue Errno::ENOENT => e
  197. rescue Exception => e
  198. msg = strip_credential(e.message)
  199. # The command failed, log it and re-raise
  200. logmsg = "SCM command failed, "
  201. logmsg += "make sure that your SCM command (e.g. svn) is "
  202. logmsg += "in PATH (#{ENV['PATH']})\n"
  203. logmsg += "You can configure your scm commands in config/configuration.yml.\n"
  204. logmsg += "#{strip_credential(cmd)}\n"
  205. logmsg += "with: #{msg}"
  206. logger.error(logmsg)
  207. raise CommandFailed.new(msg)
  208. end
  209. end
  210. # Hides username/password in a given command
  211. def self.strip_credential(cmd)
  212. q = (Redmine::Platform.mswin? ? '"' : "'")
  213. cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
  214. end
  215. def strip_credential(cmd)
  216. self.class.strip_credential(cmd)
  217. end
  218. def scm_iconv(to, from, str)
  219. return nil if str.nil?
  220. return str if to == from
  221. begin
  222. Iconv.conv(to, from, str)
  223. rescue Iconv::Failure => err
  224. logger.error("failed to convert from #{from} to #{to}. #{err}")
  225. nil
  226. end
  227. end
  228. def parse_xml(xml)
  229. if RUBY_PLATFORM == 'java'
  230. xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
  231. end
  232. ActiveSupport::XmlMini.parse(xml)
  233. end
  234. end
  235. class Entries < Array
  236. def sort_by_name
  237. dup.sort! {|x,y|
  238. if x.kind == y.kind
  239. x.name.to_s <=> y.name.to_s
  240. else
  241. x.kind <=> y.kind
  242. end
  243. }
  244. end
  245. def revisions
  246. revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
  247. end
  248. end
  249. class Info
  250. attr_accessor :root_url, :lastrev
  251. def initialize(attributes={})
  252. self.root_url = attributes[:root_url] if attributes[:root_url]
  253. self.lastrev = attributes[:lastrev]
  254. end
  255. end
  256. class Entry
  257. attr_accessor :name, :path, :kind, :size, :lastrev, :changeset
  258. def initialize(attributes={})
  259. self.name = attributes[:name] if attributes[:name]
  260. self.path = attributes[:path] if attributes[:path]
  261. self.kind = attributes[:kind] if attributes[:kind]
  262. self.size = attributes[:size].to_i if attributes[:size]
  263. self.lastrev = attributes[:lastrev]
  264. end
  265. def is_file?
  266. 'file' == self.kind
  267. end
  268. def is_dir?
  269. 'dir' == self.kind
  270. end
  271. def is_text?
  272. Redmine::MimeType.is_type?('text', name)
  273. end
  274. def author
  275. if changeset
  276. changeset.author.to_s
  277. elsif lastrev
  278. Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first)
  279. end
  280. end
  281. end
  282. class Revisions < Array
  283. def latest
  284. sort {|x,y|
  285. unless x.time.nil? or y.time.nil?
  286. x.time <=> y.time
  287. else
  288. 0
  289. end
  290. }.last
  291. end
  292. end
  293. class Revision
  294. attr_accessor :scmid, :name, :author, :time, :message,
  295. :paths, :revision, :branch, :identifier,
  296. :parents
  297. def initialize(attributes={})
  298. self.identifier = attributes[:identifier]
  299. self.scmid = attributes[:scmid]
  300. self.name = attributes[:name] || self.identifier
  301. self.author = attributes[:author]
  302. self.time = attributes[:time]
  303. self.message = attributes[:message] || ""
  304. self.paths = attributes[:paths]
  305. self.revision = attributes[:revision]
  306. self.branch = attributes[:branch]
  307. self.parents = attributes[:parents]
  308. end
  309. # Returns the readable identifier.
  310. def format_identifier
  311. self.identifier.to_s
  312. end
  313. def ==(other)
  314. if other.nil?
  315. false
  316. elsif scmid.present?
  317. scmid == other.scmid
  318. elsif identifier.present?
  319. identifier == other.identifier
  320. elsif revision.present?
  321. revision == other.revision
  322. end
  323. end
  324. end
  325. class Annotate
  326. attr_reader :lines, :revisions
  327. def initialize
  328. @lines = []
  329. @revisions = []
  330. end
  331. def add_line(line, revision)
  332. @lines << line
  333. @revisions << revision
  334. end
  335. def content
  336. content = lines.join("\n")
  337. end
  338. def empty?
  339. lines.empty?
  340. end
  341. end
  342. class Branch < String
  343. attr_accessor :revision, :scmid
  344. end
  345. end
  346. end
  347. end