Index: extra/mercurial/hg-template-0.9.5.tmpl =================================================================== --- extra/mercurial/hg-template-0.9.5.tmpl (revision 0) +++ extra/mercurial/hg-template-0.9.5.tmpl (revision 0) @@ -0,0 +1,12 @@ +changeset = 'This template must be used with --debug option\n' +changeset_quiet = 'This template must be used with --debug option\n' +changeset_verbose = 'This template must be used with --debug option\n' +changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{files}{file_adds}{file_dels}{file_copies}\n{desc|strip|escape|addbreaks}\n{tags}\n\n' + +file = '{file|escape}\n' +file_add = '{file_add|escape}\n' +file_del = '{file_del|escape}\n' +file_copy = '{name|urlescape}\n' +tag = '{tag|escape}\n' +header='\n\n\n' +# footer="" \ No newline at end of file Index: extra/mercurial/hg-template-1.0.tmpl =================================================================== --- extra/mercurial/hg-template-1.0.tmpl (revision 0) +++ extra/mercurial/hg-template-1.0.tmpl (revision 0) @@ -0,0 +1,12 @@ +changeset = 'This template must be used with --debug option\n' +changeset_quiet = 'This template must be used with --debug option\n' +changeset_verbose = 'This template must be used with --debug option\n' +changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{file_mods}{file_adds}{file_dels}{file_copies}\n{desc|strip|escape|addbreaks}\n{tags}\n\n' + +file_mod = '{file_mod|escape}\n' +file_add = '{file_add|escape}\n' +file_del = '{file_del|escape}\n' +file_copy = '{name|urlescape}\n' +tag = '{tag|escape}\n' +header='\n\n\n' +# footer="" Index: lib/redmine/scm/adapters/abstract_adapter.rb =================================================================== --- lib/redmine/scm/adapters/abstract_adapter.rb (revision 1420) +++ lib/redmine/scm/adapters/abstract_adapter.rb (working copy) @@ -94,6 +94,11 @@ path ||= '' (path[0,1]!="/") ? "/#{path}" : path end + + def with_trailling_slash(path) + path ||= '' + (path[-1,1] == "/") ? path : "#{path}/" + end def shell_quote(str) if RUBY_PLATFORM =~ /mswin/ @@ -102,7 +107,7 @@ "'" + str.gsub(/'/, "'\"'\"'") + "'" end end - + private def retrieve_root_url info = self.info Index: lib/redmine/scm/adapters/mercurial_adapter.rb =================================================================== --- lib/redmine/scm/adapters/mercurial_adapter.rb (revision 1420) +++ lib/redmine/scm/adapters/mercurial_adapter.rb (working copy) @@ -21,9 +21,12 @@ module Scm module Adapters class MercurialAdapter < AbstractAdapter - + # Mercurial executable name HG_BIN = "hg" + TEMPLATES_DIR = "#{RAILS_ROOT}/extra/mercurial" + TEMPLATE_NAME = "hg-template" + TEMPLATE_EXTENSION = "tmpl" def info cmd = "#{HG_BIN} -R #{target('')} root" @@ -33,35 +36,88 @@ end return nil if $? && $?.exitstatus != 0 info = Info.new({:root_url => root_url.chomp, - :lastrev => revisions(nil,nil,nil,{:limit => 1}).last - }) + :lastrev => revisions(nil,nil,nil,{:limit => 1}).last + }) info rescue CommandFailed return nil end - def entries(path=nil, identifier=nil) - path ||= '' + def entries(path="", identifier=nil) entries = Entries.new - cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate" + cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" cmd << " -r + (identifier ? identifier.to_i : "tip") - cmd << " " + shell_quote('glob:**') + cmd << " " + shell_quote("path:#{path}") unless path.empty? shellout(cmd) do |io| io.each_line do |line| - e = line.chomp.split(%r{[\/\\]}) - entries << Entry.new({:name => e.first, - :path => (path.empty? ? e.first : "#{path}/#{e.first}"), - :kind => (e.size > 1 ? 'dir' : 'file'), - :lastrev => Revision.new - }) unless entries.detect{|entry| entry.name == e.first} + # HG uses antislashs as separator on Windows + line = line.gsub(/\\/, "/") + if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'') + e ||= line + e = e.chomp.split(%r{[\/\\]}) + entries << Entry.new({:name => e.first, + :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"), + :kind => (e.size > 1 ? 'dir' : 'file'), + :lastrev => Revision.new + }) unless entries.detect{|entry| entry.name == e.first} + end end end return nil if $? && $?.exitstatus != 0 entries.sort_by_name end - - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + + # Fetch the revisions by using a template file that make Mercurial producing a xml output. + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) revisions = Revisions.new + cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{self.class.template_path}" + if identifier_from && identifier_to + cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" + elsif identifier_from + cmd << " -r #{identifier_from.to_i}:" + end + cmd << " --limit #{options[:limit].to_i}" if options[:limit] + cmd << " #{path}" if path + shellout(cmd) do |io| + begin + # HG doesn't close the XML Document... + doc = REXML::Document.new(io.read << "") + doc.elements.each("log/logentry") do |logentry| + paths = [] + copies = logentry.get_elements('paths/path-copied') + logentry.elements.each("paths/path") do |path| + # Detect if the added file is a copy + if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } + from_path = c.attributes['copyfrom-path'] + from_rev = logentry.attributes['revision'] + end + paths << {:action => path.attributes['action'], + :path => "/#{path.text}", + :from_path => from_path ? "/#{from_path}" : nil, + :from_revision => from_rev ? from_rev : nil + } + end + paths.sort! { |x,y| x[:path] <=> y[:path] } + + revisions << Revision.new({:identifier => logentry.attributes['revision'], + :scmid => logentry.attributes['node'], + :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), + :time => Time.parse(logentry.elements['date'].text).localtime, + :message => logentry.elements['msg'].text, + :paths => paths + }) + end + rescue + logger.debug($!) + end + end + return nil if $? && $?.exitstatus != 0 + revisions + end + + + def revisions_old(path=nil, identifier_from=nil, identifier_to=nil, options={}) + revisions = Revisions.new cmd = "#{HG_BIN} -v --encoding utf8 -R #{target('')} log" if identifier_from && identifier_to cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" @@ -161,16 +217,16 @@ # Changes paths = (rev_id == 0) ? - # Can't get changes for revision 0 with hg status - changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} : + # Can't get changes for revision 0 with hg status + changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} : status(rev_id) Revision.new({:identifier => rev_id, - :scmid => changeset[:changeset].to_s.split(':').last, - :author => changeset[:user], - :time => Time.parse(changeset[:date]), - :message => changeset[:description], - :paths => paths + :scmid => changeset[:changeset].to_s.split(':').last, + :author => changeset[:user], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => paths }) end @@ -193,6 +249,20 @@ end result end + + def self.hgversion + @@hgversion ||= %x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1].split(".").collect(&:to_i) + end + + def self.template_path + if (self.hgversion <=> [0,9,5]) > 0 + ver = "1.0" + else + ver = "0.9.5" + end + @@template ||= "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" + end + end end end