# HG changeset patch
# Parent 153ac6070d126f38bc22df89c0473a4ef7fb2c7c
# User Toshi MARUYAMA
[mq]: 7064-hg-encode/hg.diff

diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
--- a/app/helpers/repositories_helper.rb
+++ b/app/helpers/repositories_helper.rb
@@ -166,7 +166,9 @@ module RepositoriesHelper
   end
   
   def mercurial_field_tags(form, repository)
-      content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
+    content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) +
+    content_tag('p', form.select(:path_encoding, [nil] + Setting::ENCODINGS,
+                                 :label => 'Path encoding'))
   end
 
   def git_field_tags(form, repository)
diff --git a/app/models/repository.rb b/app/models/repository.rb
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -15,6 +15,8 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
+require 'redmine/scm/adapters/path_encodable_wrapper'
+
 class Repository < ActiveRecord::Base
   belongs_to :project
   has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
@@ -38,7 +40,7 @@ class Repository < ActiveRecord::Base
   end
 
   def scm
-    @scm ||= self.scm_adapter.new url, root_url, login, password
+    @scm ||= new_scm
     update_attribute(:root_url, @scm.root_url) if root_url.blank?
     @scm
   end
@@ -105,14 +107,14 @@ class Repository < ActiveRecord::Base
   # Default behaviour is to search in cached changesets
   def latest_changesets(path, rev, limit=10)
     if path.blank?
-      changesets.find(:all, :include => :user,
-                            :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
-                            :limit => limit)
+      changesets.find(:all, :include => :user, :limit => limit)
     else
-      changes.find(:all, :include => {:changeset => :user}, 
-                         :conditions => ["path = ?", path.with_leading_slash],
-                         :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
-                         :limit => limit).collect(&:changeset)
+      changesets.find(:all, :select => "DISTINCT #{Changeset.table_name}.*",
+                      :joins => :changes,
+                      :conditions => ["#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ? ESCAPE ?",
+                                      path.with_leading_slash,
+                                      "#{path.with_leading_slash.gsub(/[%_\\]/) { |s| "\\#{s}" }}/%", '\\'],
+                      :include => :user, :limit => limit)
     end
   end
     
@@ -198,6 +200,12 @@ class Repository < ActiveRecord::Base
   end
   
   private
+
+  def new_scm
+    scm = self.scm_adapter.new url, root_url, login, password
+    scm = Redmine::Scm::Adapters::PathEncodableWrapper.new(scm, path_encoding) unless path_encoding.blank?
+    scm
+  end
   
   def before_save
     # Strips url and root_url
diff --git a/app/models/repository/mercurial.rb b/app/models/repository/mercurial.rb
--- a/app/models/repository/mercurial.rb
+++ b/app/models/repository/mercurial.rb
@@ -18,6 +18,9 @@
 require 'redmine/scm/adapters/mercurial_adapter'
 
 class Repository::Mercurial < Repository
+  # sort changesets by revision number
+  has_many :changesets, :order => "#{Changeset.table_name}.id DESC", :foreign_key => 'repository_id'
+
   attr_protected :root_url
   validates_presence_of :url
 
diff --git a/db/migrate/20101115000000_add_repositories_path_encoding.rb b/db/migrate/20101115000000_add_repositories_path_encoding.rb
new file mode 100644
--- /dev/null
+++ b/db/migrate/20101115000000_add_repositories_path_encoding.rb
@@ -0,0 +1,9 @@
+class AddRepositoriesPathEncoding < ActiveRecord::Migration
+  def self.up
+    add_column :repositories, :path_encoding, :string, :limit => 64, :default => nil
+  end
+
+  def self.down
+    remove_column :repositories, :path_encoding
+  end
+end
diff --git a/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl b/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl
--- a/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl
+++ b/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl
@@ -3,10 +3,10 @@ changeset_quiet =  'This template must b
 changeset_verbose = 'This template must be used with --debug option\n'
 changeset_debug = '<logentry revision="{rev}" node="{node|short}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<paths>\n{files}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n'
 
-file = '<path action="M">{file|escape}</path>\n'
-file_add = '<path action="A">{file_add|escape}</path>\n'
-file_del = '<path action="D">{file_del|escape}</path>\n'
-file_copy = '<path-copied copyfrom-path="{source|escape}">{name|urlescape}</path-copied>\n'
+file = '<path action="M">{file|urlescape}</path>\n'
+file_add = '<path action="A">{file_add|urlescape}</path>\n'
+file_del = '<path action="D">{file_del|urlescape}</path>\n'
+file_copy = '<path-copied copyfrom-path="{source|urlescape}">{name|urlescape}</path-copied>\n'
 tag = '<tag>{tag|escape}</tag>\n'
 header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n'
 # footer="</log>"
\ No newline at end of file
diff --git a/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl b/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl
--- a/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl
+++ b/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl
@@ -3,10 +3,10 @@ changeset_quiet =  'This template must b
 changeset_verbose = 'This template must be used with --debug option\n'
 changeset_debug = '<logentry revision="{rev}" node="{node|short}">\n<author>{author|escape}</author>\n<date>{date|isodate}</date>\n<paths>\n{file_mods}{file_adds}{file_dels}{file_copies}</paths>\n<msg>{desc|escape}</msg>\n{tags}</logentry>\n\n'
 
-file_mod = '<path action="M">{file_mod|escape}</path>\n'
-file_add = '<path action="A">{file_add|escape}</path>\n'
-file_del = '<path action="D">{file_del|escape}</path>\n'
-file_copy = '<path-copied copyfrom-path="{source|escape}">{name|urlescape}</path-copied>\n'
+file_mod = '<path action="M">{file_mod|urlescape}</path>\n'
+file_add = '<path action="A">{file_add|urlescape}</path>\n'
+file_del = '<path action="D">{file_del|urlescape}</path>\n'
+file_copy = '<path-copied copyfrom-path="{source|urlescape}">{name|urlescape}</path-copied>\n'
 tag = '<tag>{tag|escape}</tag>\n'
 header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n'
 # footer="</log>"
diff --git a/lib/redmine/scm/adapters/mercurial_adapter.rb b/lib/redmine/scm/adapters/mercurial_adapter.rb
--- a/lib/redmine/scm/adapters/mercurial_adapter.rb
+++ b/lib/redmine/scm/adapters/mercurial_adapter.rb
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 require 'redmine/scm/adapters/abstract_adapter'
+require 'cgi'
 
 module Redmine
   module Scm
@@ -127,8 +128,8 @@ module Redmine
                     from_rev = logentry.attributes['revision']
                   end
                   paths << {:action => path.attributes['action'],
-                    :path => "/#{path.text}",
-                    :from_path => from_path ? "/#{from_path}" : nil,
+                    :path => "/#{CGI.unescape(path.text)}",
+                    :from_path => from_path ? "/#{CGI.unescape(from_path)}" : nil,
                     :from_revision => from_rev ? from_rev : nil
                   }
                 end
diff --git a/lib/redmine/scm/adapters/path_encodable_wrapper.rb b/lib/redmine/scm/adapters/path_encodable_wrapper.rb
new file mode 100644
--- /dev/null
+++ b/lib/redmine/scm/adapters/path_encodable_wrapper.rb
@@ -0,0 +1,109 @@
+# redMine - project management software
+# Copyright (C) 2006-2010  Jean-Philippe Lang
+# Copyright (C) 2010 Yuya Nishihara <yuya@tcha.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+require 'delegate'
+require 'iconv'
+
+module Redmine
+  module Scm
+    module Adapters
+      # wraps scm adapter to convert path encodings
+      class PathEncodableWrapper < SimpleDelegator  # :nodoc:
+        def initialize(scm, path_encoding)
+          super(scm)
+          @path_encoding = path_encoding
+        end
+
+        def entry(path=nil, identifier=nil)
+          convert_entry!(super(to_scm_path(path), identifier))
+        end
+
+        def entries(path=nil, identifier=nil)
+          convert_entries!(super(to_scm_path(path), identifier))
+        end
+
+        def properties(path, identifier=nil)
+          super(to_scm_path(path), identifier)
+        end
+
+        def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
+          convert_revisions!(super(to_scm_path(path), identifier_from, identifier_to, options))
+        end
+
+        def diff(path, identifier_from, identifier_to=nil)
+          super(to_scm_path(path), identifier_from, identifier_to)
+        end
+
+        def cat(path, identifier=nil)
+          super(to_scm_path(path), identifier)
+        end
+
+        def annotate(path, identifier=nil)
+          super(to_scm_path(path), identifier)
+        end
+
+        private
+
+        def convert_entry!(entry)
+          return unless entry
+          entry.name = from_scm_path(entry.name)
+          entry.path = from_scm_path(entry.path)
+          entry
+        end
+
+        def convert_entries!(entries)
+          return unless entries
+          entries.each { |e| convert_entry!(e) }
+          entries
+        end
+
+        def convert_revisions!(revisions)
+          return unless revisions
+          revisions.each do |rev|
+            next unless rev.paths
+            rev.paths.each do |e|
+              e[:path] = from_scm_path(e[:path])
+              e[:from_path] = from_scm_path(e[:from_path])
+            end
+          end
+          revisions
+        end
+
+        # convert repository path string to utf-8
+        def from_scm_path(s)
+          return unless s
+          begin
+            Iconv.conv('UTF-8', @path_encoding, s)
+          rescue Iconv::Failure => err
+            raise CommandFailed, "failed to convert path from #{@path_encoding} to UTF-8. #{err}"
+          end
+        end
+
+        # convert utf-8 path string to repository encoding
+        def to_scm_path(s)
+          return unless s
+          begin
+            Iconv.conv(@path_encoding, 'UTF-8', s)
+          rescue Iconv::Failure => err
+            raise CommandFailed, "failed to convert path from UTF-8 to #{@path_encoding}. #{err}"
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/test/functional/repositories_mercurial_controller_test.rb b/test/functional/repositories_mercurial_controller_test.rb
--- a/test/functional/repositories_mercurial_controller_test.rb
+++ b/test/functional/repositories_mercurial_controller_test.rb
@@ -32,7 +32,13 @@ class RepositoriesMercurialControllerTes
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
     User.current = nil
-    Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH)
+    assert @repository = Repository::Mercurial.create(
+                            :project       => Project.find(3),
+                            :url           => REPOSITORY_PATH,
+                            :path_encoding => 'ISO-8859-1'
+                            )
+    @repository.fetch_changesets
+    @repository.reload
   end
   
   if File.directory?(REPOSITORY_PATH)
@@ -49,7 +55,7 @@ class RepositoriesMercurialControllerTes
       assert_response :success
       assert_template 'show'
       assert_not_nil assigns(:entries)
-      assert_equal 3, assigns(:entries).size
+      assert_equal 4, assigns(:entries).size
       assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
       assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
       assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
@@ -74,7 +80,31 @@ class RepositoriesMercurialControllerTes
       assert_not_nil assigns(:entries)
       assert_equal ['delete.png'], assigns(:entries).collect(&:name)
     end
-    
+
+    def test_show_directory_sql_escape_percent
+      get :show, :id => 3, :path => ['sql_escape', 'percent%dir'], :rev => 13
+      assert_response :success
+      assert_template 'show'
+
+      assert_not_nil assigns(:entries)
+      assert_equal ['percent%file1.txt', 'percentfile1.txt'], assigns(:entries).collect(&:name)
+      changesets = assigns(:changesets)
+      assert_not_nil changesets
+      assert_equal %w(13 11 10 9), changesets.collect(&:revision)
+    end
+
+    def test_show_directory_latin_1
+      get :show, :id => 3, :path => ['latin-1-dir'], :rev => 19
+      assert_response :success
+      assert_template 'show'
+
+      assert_not_nil assigns(:entries)
+      assert_equal ["make-latin-1-file.rb", "test-\xc3\x9c-1.txt","test-\xc3\x9c-2.txt", "test-\xc3\x9c.txt"], assigns(:entries).collect(&:name)
+      changesets = assigns(:changesets)
+      assert_not_nil changesets
+      assert_equal %w(19 18 17 16 15), changesets.collect(&:revision)
+    end
+
     def test_changes
       get :changes, :id => 3, :path => ['images', 'edit.png']
       assert_response :success
@@ -88,10 +118,21 @@ class RepositoriesMercurialControllerTes
       assert_template 'entry'
       # Line 19
       assert_tag :tag => 'th',
-                 :content => /10/,
-                 :attributes => { :class => /line-num/ },
+                 :content => '10',
+                 :attributes => { :class => 'line-num' },
                  :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
     end
+
+    def test_entry_show_latin_1
+      get :entry, :id => 3, :path => ['latin-1-dir', "test-\xc3\x9c-2.txt"], :rev => 19
+      assert_response :success
+      assert_template 'entry'
+      # Line 19
+      assert_tag :tag => 'th',
+                 :content => '1',
+                 :attributes => { :class => 'line-num' },
+                 :sibling => { :tag => 'td', :content => /Mercurial is a distributed version control system/ }
+    end
     
     def test_entry_download
       get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
@@ -115,24 +156,87 @@ class RepositoriesMercurialControllerTes
       assert_template 'diff'
       # Line 22 removed
       assert_tag :tag => 'th',
-                 :content => /22/,
+                 :content => '22',
                  :sibling => { :tag => 'td', 
                                :attributes => { :class => /diff_out/ },
                                :content => /def remove/ }
     end
     
+    def test_diff_latin_1
+      get :diff, :id => 3, :rev => 19
+      assert_response :success
+      assert_template 'diff'
+      assert_tag :tag => 'th',
+                 :content => '2',
+                 :sibling => { :tag => 'td', 
+                               :attributes => { :class => /diff_in/ },
+                               :content => /It is written in Python/ }
+    end
+
     def test_annotate
       get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
       assert_response :success
       assert_template 'annotate'
-      # Line 23, revision 4
-      assert_tag :tag => 'th', :content => /23/,
-                 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /4/ } },
-                 :sibling => { :tag => 'td', :content => /jsmith/ },
+      # Line 23, revision 4:def6d2f1254a
+      assert_tag :tag => 'th',
+                 :content => '23',
+                 :attributes => { :class => 'line-num' },
+                 :sibling =>
+                       {
+                         :tag => 'td',
+                         :attributes => { :class => 'revision' },
+                         :child => { :tag => 'a', :content => '4' }
+                         # :child => { :tag => 'a', :content => /4:def6d2f1/ }
+                       }
+      assert_tag :tag => 'th',
+                 :content => '23',
+                 :attributes => { :class => 'line-num' },
+                 :sibling =>
+                       {
+                          :tag     => 'td'    ,
+                          :content => 'jsmith' ,
+                          :attributes => { :class   => 'author' },
+                          
+                        }
+      assert_tag :tag => 'th',
+                 :content => '23',
+                 :attributes => { :class => 'line-num' },
                  :sibling => { :tag => 'td', :content => /watcher =/ }
     end
+
+    def test_annotate_latin_1
+      get :annotate, :id => 3, :path => ['latin-1-dir', "test-\xc3\x9c-2.txt"], :rev => 19
+      assert_response :success
+      assert_template 'annotate'
+      assert_tag :tag => 'th',
+                 :content => '1',
+                 :attributes => { :class => 'line-num' },
+                 :sibling =>
+                       {
+                         :tag => 'td',
+                         :attributes => { :class => 'revision' },
+                         :child => { :tag => 'a', :content => '18' }
+                       }
+      assert_tag :tag => 'th',
+                 :content => '1',
+                 :attributes => { :class => 'line-num' },
+                 :sibling =>
+                       {
+                          :tag     => 'td'    ,
+                          :content => 'jsmith' ,
+                          :attributes => { :class   => 'author' },
+                          
+                        }
+      assert_tag :tag => 'th',
+                 :content => '1',
+                 :attributes => { :class => 'line-num' },
+                 :sibling => { :tag => 'td', :content => /Mercurial is a distributed version control system/ }
+
+    end
+
   else
     puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!"
     def test_fake; assert true end
   end
 end
+
diff --git a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb
--- a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb
@@ -9,49 +9,62 @@ begin
     TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION
     
     REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
-    
-    def test_hgversion
-      to_test = { "Mercurial Distributed SCM (version 0.9.5)\n" => [0,9,5],
-                  "Mercurial Distributed SCM (1.0)\n" => [1,0],
-                  "Mercurial Distributed SCM (1e4ddc9ac9f7+20080325)\n" => nil,
-                  "Mercurial Distributed SCM (1.0.1+20080525)\n" => [1,0,1],
-                  "Mercurial Distributed SCM (1916e629a29d)\n" => nil,
-                  "Mercurial SCM Distribuito (versione 0.9.5)\n" => [0,9,5],
-                  "(1.6)\n(1.7)\n(1.8)"           => [1,6],
-                  "(1.7.1)\r\n(1.8.1)\r\n(1.9.1)" => [1,7,1]}
 
-      to_test.each do |s, v|
-        test_hgversion_for(s, v)
+    if File.directory?(REPOSITORY_PATH)  
+      def setup
+        @adapter = Redmine::Scm::Adapters::MercurialAdapter.new(REPOSITORY_PATH)
       end
-    end
-    
-    def test_template_path
-      to_test = { [0,9,5] => "0.9.5",
-                  [1,0] => "1.0",
-                  [] => "1.0",
-                  [1,0,1] => "1.0",
-                  [1,7]   => "1.0",
-                  [1,7,1] => "1.0"}
-      to_test.each do |v, template|
-        test_template_path_for(v, template)
+
+      def test_hgversion
+        to_test = { "Mercurial Distributed SCM (version 0.9.5)\n"  => [0,9,5],
+                    "Mercurial Distributed SCM (1.0)\n"            => [1,0],
+                    "Mercurial Distributed SCM (1e4ddc9ac9f7+20080325)\n" => nil,
+                    "Mercurial Distributed SCM (1.0.1+20080525)\n" => [1,0,1],
+                    "Mercurial Distributed SCM (1916e629a29d)\n"   => nil,
+                    "Mercurial SCM Distribuito (versione 0.9.5)\n" => [0,9,5],
+                    "(1.6)\n(1.7)\n(1.8)"           => [1,6],
+                    "(1.7.1)\r\n(1.8.1)\r\n(1.9.1)" => [1,7,1]}
+
+        to_test.each do |s, v|
+          test_hgversion_for(s, v)
+        end
       end
-    end
-    
-    private
-    
-    def test_hgversion_for(hgversion, version)
-      Redmine::Scm::Adapters::MercurialAdapter.expects(:hgversion_from_command_line).returns(hgversion)
-      adapter = Redmine::Scm::Adapters::MercurialAdapter
-      assert_equal version, adapter.hgversion
-    end
-    
-    def test_template_path_for(version, template)
-      adapter = Redmine::Scm::Adapters::MercurialAdapter
-      assert_equal "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}", adapter.template_path_for(version)
-      assert File.exist?(adapter.template_path_for(version))
+
+      def test_template_path
+        to_test = { [0,9,5] => "0.9.5",
+                       [1,0]    => "1.0",
+                       []       => "1.0",
+                       [1,0,1]  => "1.0",
+                       [1,7]    => "1.0",
+                       [1,7,1]  => "1.0"}
+        to_test.each do |v, template|
+          test_template_path_for(v, template)
+        end
+      end
+
+      def test_cat
+        assert     @adapter.cat("sources/welcome_controller.rb", 2)
+        assert_nil @adapter.cat("sources/welcome_controller.rb")
+      end
+
+      private
+
+      def test_hgversion_for(hgversion, version)
+        @adapter.class.expects(:hgversion_from_command_line).returns(hgversion)
+        assert_equal version, @adapter.class.hgversion
+      end
+
+      def test_template_path_for(version, template)
+        assert_equal "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}",
+                     @adapter.class.template_path_for(version)
+        assert File.exist?(@adapter.class.template_path_for(version))
+      end
+    else
+      puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!"
+      def test_fake; assert true end
     end
   end
-  
+
 rescue LoadError
   class MercurialMochaFake < ActiveSupport::TestCase
     def test_fake; assert(false, "Requires mocha to run those tests")  end
diff --git a/test/unit/repository_mercurial_test.rb b/test/unit/repository_mercurial_test.rb
--- a/test/unit/repository_mercurial_test.rb
+++ b/test/unit/repository_mercurial_test.rb
@@ -19,23 +19,28 @@ require File.dirname(__FILE__) + '/../te
 
 class RepositoryMercurialTest < ActiveSupport::TestCase
   fixtures :projects
-  
+
   # No '..' in the repository path
   REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
-  
+
   def setup
     @project = Project.find(1)
-    assert @repository = Repository::Mercurial.create(:project => @project, :url => REPOSITORY_PATH)
+    assert @repository = Repository::Mercurial.create(
+                            :project       => @project,
+                            :url           => REPOSITORY_PATH,
+                            :path_encoding => 'ISO-8859-1'
+                            )
   end
-  
+
   if File.directory?(REPOSITORY_PATH)  
     def test_fetch_changesets_from_scratch
       @repository.fetch_changesets
       @repository.reload
       
-      assert_equal 6, @repository.changesets.count
-      assert_equal 11, @repository.changes.count
-      assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find_by_revision('0').comments
+      assert_equal 21, @repository.changesets.count
+      assert_equal 30, @repository.changes.count
+      assert_equal "Initial import.\nThe repository contains 3 files.",
+                   @repository.changesets.find_by_revision('0').comments
     end
     
     def test_fetch_changesets_incremental
@@ -46,30 +51,79 @@ class RepositoryMercurialTest < ActiveSu
       assert_equal 3, @repository.changesets.count
       
       @repository.fetch_changesets
-      assert_equal 6, @repository.changesets.count
+      assert_equal 21, @repository.changesets.count
     end
-    
+
     def test_entries
       assert_equal 2, @repository.entries("sources", 2).size
       assert_equal 1, @repository.entries("sources", 3).size
     end
 
     def test_locate_on_outdated_repository
-      # Change the working dir state
-      %x{hg -R #{REPOSITORY_PATH} up -r 0}
+      # For bare repository; that is, a repository without a working copy
+      # $ hg update null
+      # See http://mercurial.selenic.com/wiki/GitConcepts?action=recall&rev=46#Bare_repositories
       assert_equal 1, @repository.entries("images", 0).size
       assert_equal 2, @repository.entries("images").size
       assert_equal 2, @repository.entries("images", 2).size
     end
 
+    def test_changeset_order_by_revision
+      @repository.fetch_changesets
+      @repository.reload
 
-    def test_cat
-      assert @repository.scm.cat("sources/welcome_controller.rb", 2)
-      assert_nil @repository.scm.cat("sources/welcome_controller.rb")
+      c0 = @repository.latest_changeset
+      c1 = @repository.changesets.find_by_revision('0')
+      # sorted by revision (id), not by date
+      assert c0.revision.to_i > c1.revision.to_i
+      assert c0.committed_on  < c1.committed_on
     end
 
+    def test_latest_changesets
+      @repository.fetch_changesets
+      @repository.reload
+
+      # with_limit
+      changesets = @repository.latest_changesets('', nil, 2)
+      assert_equal @repository.latest_changesets('', nil)[0, 2], changesets
+
+      # with_filepath
+      changesets = @repository.latest_changesets('sql_escape/percent%dir/percent%file1.txt', nil)
+      assert_equal %w|11 10 9|, changesets.collect(&:revision)
+
+      changesets = @repository.latest_changesets('/sql_escape/underscore_dir/understrike_file.txt', nil)
+      assert_equal %w|12 9|, changesets.collect(&:revision)
+
+      # with_dirpath
+      changesets = @repository.latest_changesets('sql_escape/percent%dir', nil)
+      assert_equal %w|13 11 10 9|, changesets.collect(&:revision)
+    end
+
+    def test_copied_files
+      @repository.fetch_changesets
+      @repository.reload
+
+      cs1 = @repository.changesets.find_by_revision('13')
+      c1  = cs1.changes
+      assert_equal 2, c1.size
+      assert_equal 'A', c1[0].action
+      assert_equal '/sql_escape/percent%dir/percentfile1.txt',  c1[0].path
+      assert_equal '/sql_escape/percent%dir/percent%file1.txt', c1[0].from_path
+
+      assert_equal 'A', c1[1].action
+      assert_equal '/sql_escape/underscore_dir/understrike-file.txt', c1[1].path
+      assert_equal '/sql_escape/underscore_dir/understrike_file.txt', c1[1].from_path
+
+      cs2 = @repository.changesets.find_by_revision('17')
+      c2  = cs2.changes
+      assert_equal 1, c2.size
+      assert_equal 'A', c2[0].action
+      assert_equal "/latin-1-dir/test-\xc3\x9c-1.txt",  c2[0].path
+      assert_equal "/latin-1-dir/test-\xc3\x9c.txt",    c2[0].from_path
+    end
   else
     puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!"
     def test_fake; assert true end
   end
 end
+
