PageRenderTime 57ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/redmine/scm/adapters/mercurial_adapter.rb

https://github.com/xpresso/redmine
Ruby | 225 lines | 181 code | 17 blank | 27 comment | 32 complexity | c084fab581021c9e215c54af6815d6c9 MD5 | raw file
  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 'redmine/scm/adapters/abstract_adapter'
  18. require 'cgi'
  19. module Redmine
  20. module Scm
  21. module Adapters
  22. class MercurialAdapter < AbstractAdapter
  23. # Mercurial executable name
  24. HG_BIN = "hg"
  25. TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
  26. TEMPLATE_NAME = "hg-template"
  27. TEMPLATE_EXTENSION = "tmpl"
  28. class << self
  29. def client_version
  30. @@client_version ||= (hgversion || [])
  31. end
  32. def hgversion
  33. # The hg version is expressed either as a
  34. # release number (eg 0.9.5 or 1.0) or as a revision
  35. # id composed of 12 hexa characters.
  36. theversion = hgversion_from_command_line
  37. if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
  38. m[2].scan(%r{\d+}).collect(&:to_i)
  39. end
  40. end
  41. def hgversion_from_command_line
  42. shellout("#{HG_BIN} --version") { |io| io.read }.to_s
  43. end
  44. def template_path
  45. @@template_path ||= template_path_for(client_version)
  46. end
  47. def template_path_for(version)
  48. if ((version <=> [0,9,5]) > 0) || version.empty?
  49. ver = "1.0"
  50. else
  51. ver = "0.9.5"
  52. end
  53. "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
  54. end
  55. end
  56. def info
  57. cmd = "#{HG_BIN} -R #{target('')} root"
  58. root_url = nil
  59. shellout(cmd) do |io|
  60. root_url = io.read
  61. end
  62. return nil if $? && $?.exitstatus != 0
  63. info = Info.new({:root_url => root_url.chomp,
  64. :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
  65. })
  66. info
  67. rescue CommandFailed
  68. return nil
  69. end
  70. def entries(path=nil, identifier=nil)
  71. path ||= ''
  72. entries = Entries.new
  73. cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
  74. cmd << " -r #{hgrev(identifier)}"
  75. cmd << " " + shell_quote("path:#{path}") unless path.empty?
  76. shellout(cmd) do |io|
  77. io.each_line do |line|
  78. # HG uses antislashs as separator on Windows
  79. line = line.gsub(/\\/, "/")
  80. if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'')
  81. e ||= line
  82. e = e.chomp.split(%r{[\/\\]})
  83. entries << Entry.new({:name => e.first,
  84. :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"),
  85. :kind => (e.size > 1 ? 'dir' : 'file'),
  86. :lastrev => Revision.new
  87. }) unless e.empty? || entries.detect{|entry| entry.name == e.first}
  88. end
  89. end
  90. end
  91. return nil if $? && $?.exitstatus != 0
  92. entries.sort_by_name
  93. end
  94. # Fetch the revisions by using a template file that
  95. # makes Mercurial produce a xml output.
  96. def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
  97. revisions = Revisions.new
  98. cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
  99. if identifier_from && identifier_to
  100. cmd << " -r #{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
  101. elsif identifier_from
  102. cmd << " -r #{hgrev(identifier_from)}:"
  103. end
  104. cmd << " --limit #{options[:limit].to_i}" if options[:limit]
  105. cmd << " #{shell_quote path}" unless path.blank?
  106. shellout(cmd) do |io|
  107. begin
  108. # HG doesn't close the XML Document...
  109. doc = REXML::Document.new(io.read << "</log>")
  110. doc.elements.each("log/logentry") do |logentry|
  111. paths = []
  112. copies = logentry.get_elements('paths/path-copied')
  113. logentry.elements.each("paths/path") do |path|
  114. # Detect if the added file is a copy
  115. if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text }
  116. from_path = c.attributes['copyfrom-path']
  117. from_rev = logentry.attributes['revision']
  118. end
  119. paths << {:action => path.attributes['action'],
  120. :path => "/#{CGI.unescape(path.text)}",
  121. :from_path => from_path ? "/#{CGI.unescape(from_path)}" : nil,
  122. :from_revision => from_rev ? from_rev : nil
  123. }
  124. end
  125. paths.sort! { |x,y| x[:path] <=> y[:path] }
  126. revisions << Revision.new({:identifier => logentry.attributes['revision'],
  127. :scmid => logentry.attributes['node'],
  128. :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
  129. :time => Time.parse(logentry.elements['date'].text).localtime,
  130. :message => logentry.elements['msg'].text,
  131. :paths => paths
  132. })
  133. end
  134. rescue
  135. logger.debug($!)
  136. end
  137. end
  138. return nil if $? && $?.exitstatus != 0
  139. revisions
  140. end
  141. def diff(path, identifier_from, identifier_to=nil)
  142. path ||= ''
  143. diff_args = ''
  144. diff = []
  145. if identifier_to
  146. diff_args = "-r #{hgrev(identifier_to)} -r #{hgrev(identifier_from)}"
  147. else
  148. if self.class.client_version_above?([1, 2])
  149. diff_args = "-c #{hgrev(identifier_from)}"
  150. else
  151. return []
  152. end
  153. end
  154. cmd = "#{HG_BIN} -R #{target('')} --config diff.git=false diff --nodates #{diff_args}"
  155. cmd << " -I #{target(path)}" unless path.empty?
  156. shellout(cmd) do |io|
  157. io.each_line do |line|
  158. diff << line
  159. end
  160. end
  161. return nil if $? && $?.exitstatus != 0
  162. diff
  163. end
  164. def cat(path, identifier=nil)
  165. cmd = "#{HG_BIN} -R #{target('')} cat"
  166. cmd << " -r #{hgrev(identifier)}"
  167. cmd << " #{target(path)}"
  168. cat = nil
  169. shellout(cmd) do |io|
  170. io.binmode
  171. cat = io.read
  172. end
  173. return nil if $? && $?.exitstatus != 0
  174. cat
  175. end
  176. def annotate(path, identifier=nil)
  177. path ||= ''
  178. cmd = "#{HG_BIN} -R #{target('')}"
  179. cmd << " annotate -ncu"
  180. cmd << " -r #{hgrev(identifier)}"
  181. cmd << " #{target(path)}"
  182. blame = Annotate.new
  183. shellout(cmd) do |io|
  184. io.each_line do |line|
  185. next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
  186. r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
  187. :identifier => $3)
  188. blame.add_line($4.rstrip, r)
  189. end
  190. end
  191. return nil if $? && $?.exitstatus != 0
  192. blame
  193. end
  194. class Revision < Redmine::Scm::Adapters::Revision
  195. # Returns the readable identifier
  196. def format_identifier
  197. "#{revision}:#{scmid}"
  198. end
  199. end
  200. # Returns correct revision identifier
  201. def hgrev(identifier)
  202. shell_quote(identifier.blank? ? 'tip' : identifier.to_s)
  203. end
  204. private :hgrev
  205. end
  206. end
  207. end
  208. end