# HG changeset patch
# User edavis10@e93f8b46-1217-0410-a6f0-8f06a7374b81
# Date 1265130152 0
# Branch 0.9-stable
# Node ID 12c12857957cb9c19816ece21cce30431f7a38f0
# Parent  5922d9dd5dc818bccf7d80c4a0ee36473fb73ab0
Refactor: Extract method to create a Change from a Changeset.

diff --git a/app/models/changeset.rb b/app/models/changeset.rb
--- a/app/models/changeset.rb
+++ b/app/models/changeset.rb
@@ -152,6 +152,15 @@ class Changeset < ActiveRecord::Base
   def self.normalize_comments(str)
     to_utf8(str.to_s.strip)
   end
+
+  # Creates a new Change from it's common parameters
+  def create_change(change)
+    Change.create(:changeset => self,
+                  :action => change[:action],
+                  :path => change[:path],
+                  :from_path => change[:from_path],
+                  :from_revision => change[:from_revision])
+  end
   
   private
 
diff --git a/app/models/repository/darcs.rb b/app/models/repository/darcs.rb
--- a/app/models/repository/darcs.rb
+++ b/app/models/repository/darcs.rb
@@ -85,11 +85,7 @@ class Repository::Darcs < Repository
                                          :comments => revision.message)
                                          
             revision.paths.each do |change|
-              Change.create(:changeset => changeset,
-                            :action => change[:action],
-                            :path => change[:path],
-                            :from_path => change[:from_path],
-                            :from_revision => change[:from_revision])
+              changeset.create_change(change)
             end
             next_rev += 1
           end if revisions
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
@@ -78,11 +78,7 @@ class Repository::Mercurial < Repository
                                            :comments => revision.message)
               
               revision.paths.each do |change|
-                Change.create(:changeset => changeset,
-                              :action => change[:action],
-                              :path => change[:path],
-                              :from_path => change[:from_path],
-                              :from_revision => change[:from_revision])
+                changeset.create_change(change)
               end
             end
           end unless revisions.nil?
diff --git a/app/models/repository/subversion.rb b/app/models/repository/subversion.rb
--- a/app/models/repository/subversion.rb
+++ b/app/models/repository/subversion.rb
@@ -63,11 +63,7 @@ class Repository::Subversion < Repositor
                                            :comments => revision.message)
               
               revision.paths.each do |change|
-                Change.create(:changeset => changeset,
-                              :action => change[:action],
-                              :path => change[:path],
-                              :from_path => change[:from_path],
-                              :from_revision => change[:from_revision])
+                changeset.create_change(change)
               end unless changeset.new_record?
             end
           end unless revisions.nil?
# HG changeset patch
# User Alessio Franceschelli
# Date 1269692768 -32400
# Branch 0.9-stable
# Node ID f6f0d074f500176f61ea9b22758311ed2d002902
# Parent  12c12857957cb9c19816ece21cce30431f7a38f0
mercurial/redminehelper: helper extension to fetch info from hg repo [DRAFT]

Downloaded http://www.redmine.org/attachments/3395/overhaul.py
as redminehelper.py

Then, untabified and removed trailing white spaces.

diff --git a/extra/mercurial/redminehelper.py b/extra/mercurial/redminehelper.py
new file mode 100644
--- /dev/null
+++ b/extra/mercurial/redminehelper.py
@@ -0,0 +1,29 @@
+# redminehelper: draft extension for Mercurial
+# it's a draft to show a possible way to explore repository by the Redmine overhaul patch
+# see: http://www.redmine.org/issues/4455
+#
+# Copyright 2010 Alessio Franceschelli (alefranz.net)
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+'''command to list revision of each file
+'''
+
+from mercurial import cmdutil, commands
+from mercurial.i18n import _
+
+def overhaul(ui, repo, rev=None, **opts):
+    mf = repo[rev].manifest()
+    for f in repo[rev]:
+        try:
+            fctx = repo.filectx(f, fileid=mf[f])
+            ctx = fctx.changectx()
+            ui.write('%s\t%d\t%s\n' %
+                     (ctx,fctx.size(),f))
+        except LookupError:
+            pass
+
+cmdtable = {
+    'overhaul': (overhaul,commands.templateopts, _('hg overhaul [rev]'))
+}
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269692768 -32400
# Branch 0.9-stable
# Node ID 0b3fbe25e80dbe8e34df12c2cb91b7ed94111960
# Parent  f6f0d074f500176f61ea9b22758311ed2d002902
mercurial/redminehelper: renamed overhaul command to rhmanifest
* * *
mercurial/redminehelper: dropped dependency to i18n
* * *
mercurial/redminehelper: print out revision number
* * *
mercurial/redminehelper: redesigned rhmanifest for Redmine's entries API
* * *
mercurial/redminehelper: changed separator from tab to colon

it's just more mercurial-like separator.
* * *
mercurial/redminehelper: implemented rhsummary command to gather info
* * *
mercurial/redminehelper: redesigned output of rhentries

diff --git a/extra/mercurial/redminehelper.py b/extra/mercurial/redminehelper.py
--- a/extra/mercurial/redminehelper.py
+++ b/extra/mercurial/redminehelper.py
@@ -1,8 +1,9 @@
-# redminehelper: draft extension for Mercurial
+# redminehelper: Redmine helper extension for Mercurial
 # it's a draft to show a possible way to explore repository by the Redmine overhaul patch
 # see: http://www.redmine.org/issues/4455
 #
 # Copyright 2010 Alessio Franceschelli (alefranz.net)
+# Copyright 2010 Yuya Nishihara <yuya@tcha.org>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
@@ -10,20 +11,77 @@
 '''command to list revision of each file
 '''
 
-from mercurial import cmdutil, commands
-from mercurial.i18n import _
+import re, time
+from mercurial import cmdutil, commands, node, error
 
-def overhaul(ui, repo, rev=None, **opts):
+SPECIAL_TAGS = ('tip',)
+
+def rhsummary(ui, repo, **opts):
+    """output the summary of the repository"""
+    # see mercurial/commands.py:tip
+    ui.write(':tip: rev node\n')
+    tipctx = repo[len(repo) - 1]
+    ui.write('%d %s\n' % (tipctx.rev(), tipctx))
+
+    # see mercurial/commands.py:tags
+    ui.write(':tags: rev node name\n')
+    for t, n in reversed(repo.tagslist()):
+        if t in SPECIAL_TAGS:
+            continue
+        try:
+            r = repo.changelog.rev(n)
+        except error.LookupError:
+            r = -1
+        ui.write('%d %s %s\n' % (r, node.short(n), t))
+
+    # see mercurial/commands.py:branches
+    def iterbranches():
+        for t, n in repo.branchtags().iteritems():
+            yield t, n, repo.changelog.rev(n)
+
+    # TODO: closed branch?
+    ui.write(':branches: rev node name\n')
+    for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True):
+        ui.write('%d %s %s\n' % (r, node.short(n), t))
+
+def rhentries(ui, repo, path='', **opts):
+    """output the entries of the specified directory"""
+    rev = opts.get('rev')
+    pathprefix = (path.rstrip('/') + '/').lstrip('/')
+
+    # TODO: clean up
+    dirs, files = {}, {}
     mf = repo[rev].manifest()
     for f in repo[rev]:
-        try:
-            fctx = repo.filectx(f, fileid=mf[f])
-            ctx = fctx.changectx()
-            ui.write('%s\t%d\t%s\n' %
-                     (ctx,fctx.size(),f))
-        except LookupError:
-            pass
+        if not f.startswith(pathprefix):
+            continue
+
+        name = re.sub(r'/.*', '', f[len(pathprefix):])
+        if '/' in f[len(pathprefix):]:
+            dirs[name] = (name,)
+        else:
+            try:
+                fctx = repo.filectx(f, fileid=mf[f])
+                ctx = fctx.changectx()
+                tm, tzoffset = ctx.date()
+                localtime = int(tm) + tzoffset - time.timezone
+                files[name] = (ctx.rev(), node.short(ctx.node()), localtime,
+                               fctx.size(), name)
+            except LookupError:  # TODO: when this occurs?
+                pass
+
+    ui.write(':dirs: name\n')
+    for n, v in sorted(dirs.iteritems(), key=lambda e: e[0]):
+        ui.write(' '.join(v) + '\n')
+
+    ui.write(':files: rev node time size name\n')
+    for n, v in sorted(files.iteritems(), key=lambda e: e[0]):
+        ui.write(' '.join(str(e) for e in v) + '\n')
+
 
 cmdtable = {
-    'overhaul': (overhaul,commands.templateopts, _('hg overhaul [rev]'))
+    'rhsummary': (rhsummary, [], 'hg rhsummary'),
+    'rhentries': (rhentries,
+                  [('r', 'rev', '', 'show the specified revision')],
+                  'hg rhentries [path]'),
 }
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269243776 -32400
# Branch 0.9-stable
# Node ID 86ae0a474867db009fa37c59d370c59fca60f70f
# Parent  0b3fbe25e80dbe8e34df12c2cb91b7ed94111960
mercurial: run hg command in plain environment

 * LANG, LANGUAGE and LC_MESSAGES should be suppressed
 * HGPLAIN was introduced at Mercurial 1.5 for scripting output
 * Set HGENCODING to use utf-8 by default

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
@@ -24,6 +24,8 @@ module Redmine
         
         # Mercurial executable name
         HG_BIN = "hg"
+        HG_ENV = {'HGPLAIN' => '', 'HGENCODING' => 'utf-8',
+          'LANG' => nil, 'LANGUAGE' => nil, 'LC_MESSAGES' => nil}
         TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
         TEMPLATE_NAME = "hg-template"
         TEMPLATE_EXTENSION = "tmpl"
@@ -59,6 +61,17 @@ module Redmine
             end
             "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}"
           end
+
+          def shellout(cmd, &block)
+            orig = Hash[HG_ENV.keys.zip(ENV.values_at(*HG_ENV.keys))]
+            ENV.update(HG_ENV)
+            begin
+              ret = super
+            ensure
+              ENV.update(orig)
+              ret
+            end
+          end
         end
         
         def info
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269692768 -32400
# Branch 0.9-stable
# Node ID e1d83cfcfda3e839fb94a2ff9becda40ca1fab54
# Parent  86ae0a474867db009fa37c59d370c59fca60f70f
mercurial: introduced helper method to run hg command

Then, the following methods use it: cat.
I intend to redesign the others.

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
@@ -26,6 +26,7 @@ module Redmine
         HG_BIN = "hg"
         HG_ENV = {'HGPLAIN' => '', 'HGENCODING' => 'utf-8',
           'LANG' => nil, 'LANGUAGE' => nil, 'LC_MESSAGES' => nil}
+        HG_HELPER_EXT = "#{RAILS_ROOT}/extra/mercurial/redminehelper.py"
         TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial"
         TEMPLATE_NAME = "hg-template"
         TEMPLATE_EXTENSION = "tmpl"
@@ -183,16 +184,10 @@ module Redmine
         end
         
         def cat(path, identifier=nil)
-          cmd = "#{HG_BIN} -R #{target('')} cat"
-          cmd << " -r " + (identifier ? identifier.to_s : "tip")
-          cmd << " #{target(path)}"
-          cat = nil
-          shellout(cmd) do |io|
+          hg 'cat', '-r', hgrev(identifier), without_leading_slash(path) do |io|
             io.binmode
-            cat = io.read
+            io.read
           end
-          return nil if $? && $?.exitstatus != 0
-          cat
         end
         
         def annotate(path, identifier=nil)
@@ -212,6 +207,25 @@ module Redmine
           return nil if $? && $?.exitstatus != 0
           blame
         end
+
+        # Runs 'hg' command with the given args
+        def hg(*args, &block)
+          full_args = [HG_BIN, '--cwd', url]
+          full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}"
+          full_args += args
+          ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
+          if $? && $?.exitstatus != 0
+            raise CommandFailed, "hg exited with non-zero status: #{$?.exitstatus}"
+          end
+          ret
+        end
+        private :hg
+
+        # Returns correct revision identifier
+        def hgrev(identifier)
+          identifier.blank? ? 'tip' : identifier.to_s
+        end
+        private :hgrev
       end
     end
   end
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269692768 -32400
# Branch 0.9-stable
# Node ID 8cd03122f747d112bac845966473d5790baf807b
# Parent  e1d83cfcfda3e839fb94a2ff9becda40ca1fab54
mercurial: rewrite scm.diff to generate correct changeset diff

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
@@ -165,22 +165,17 @@ module Redmine
         end
         
         def diff(path, identifier_from, identifier_to=nil)
-          path ||= ''
+          hg_args = ['diff', '--nodates']
           if identifier_to
-            identifier_to = identifier_to.to_i 
+            hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
           else
-            identifier_to = identifier_from.to_i - 1
+            hg_args << '-c' << hgrev(identifier_from)
           end
-          cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
-          cmd << " -I #{target(path)}" unless path.empty?
-          diff = []
-          shellout(cmd) do |io|
-            io.each_line do |line|
-              diff << line
-            end
+          hg_args << without_leading_slash(path) unless path.blank?
+
+          hg *hg_args do |io|
+            io.collect
           end
-          return nil if $? && $?.exitstatus != 0
-          diff
         end
         
         def cat(path, identifier=nil)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269353640 -32400
# Branch 0.9-stable
# Node ID ae600099277108460391bdcb41b3990ad8c79f0b
# Parent  8cd03122f747d112bac845966473d5790baf807b
mercurial: rewrite scm.annotate

Now fetches nodeid.

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
@@ -186,20 +186,15 @@ module Redmine
         end
         
         def annotate(path, identifier=nil)
-          path ||= ''
-          cmd = "#{HG_BIN} -R #{target('')}"
-          cmd << " annotate -n -u"
-          cmd << " -r " + (identifier ? identifier.to_s : "tip")
-          cmd << " -r #{identifier.to_i}" if identifier
-          cmd << " #{target(path)}"
           blame = Annotate.new
-          shellout(cmd) do |io|
-            io.each_line do |line|
-              next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
-              blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
+          hg 'annotate', '-ncu', '-r', hgrev(identifier), without_leading_slash(path) do |io|
+            io.each do |line|
+              next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):(.*)$}
+              blame.add_line($4.rstrip,
+                             Revision.new(:identifier => $2.to_i, :author => $1.strip,
+                                          :revision => $2, :scmid => $3))
             end
           end
-          return nil if $? && $?.exitstatus != 0
           blame
         end
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269692768 -32400
# Branch 0.9-stable
# Node ID 5fb860a525f7c000c10709a467de46865c5f0dc2
# Parent  ae600099277108460391bdcb41b3990ad8c79f0b
mercurial: rewrite MercurialAdapter.revisions as iterator

Reimplemented .revisions as .each_revision.

Also added "require 'rexml/document'" to suppress "uninitialized constant"
error when running as rake task.

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
@@ -9,4 +9,4 @@ file_del = '<path action="D">{file_del|e
 file_copy = '<path-copied copyfrom-path="{source|escape}">{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
+footer='</log>'
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
@@ -9,4 +9,4 @@ file_del = '<path action="D">{file_del|e
 file_copy = '<path-copied copyfrom-path="{source|escape}">{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>"
+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 'rexml/document'
 
 module Redmine
   module Scm
@@ -115,53 +116,44 @@ module Redmine
           entries.sort_by_name
         end
         
-        # Fetch the revisions by using a template file that 
+        # TODO: is this api necessary?
+        def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
+          revisions = Revisions.new
+          each_revision { |e| revisions << e }
+          revisions
+        end
+
+        # Iterates the revisions by using a template file that
         # makes Mercurial produce 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 #{shell_quote 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}:"
+        def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={})
+          hg_args = ['log', '--debug', '-C', '--style', self.class.template_path]
+          hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
+          hg_args << '--limit' << options[:limit] if options[:limit]
+          hg_args << without_leading_slash(path) unless path.blank?
+          doc = hg(*hg_args) { |io| REXML::Document.new(io.read) }
+          # TODO: ??? HG doesn't close the XML Document...
+
+          doc.each_element('log/logentry') do |le|
+            cpalist = le.get_elements('paths/path-copied').map do |e|
+              [e.text, e.attributes['copyfrom-path']]
+            end
+            cpmap = Hash[*cpalist.flatten]
+
+            paths = le.get_elements('paths/path').map do |e|
+              {:action => e.attributes['action'], :path => with_leading_slash(e.text),
+                :from_path => (cpmap.member?(e.text) ? with_leading_slash(cpmap[e.text]) : nil),
+                :from_revision => (cpmap.member?(e.text) ? le.attributes['revision'] : nil)}
+            end.sort { |a, b| a[:path] <=> b[:path] }
+
+            yield Revision.new(:identifier => le.attributes['revision'],
+                               :revision => le.attributes['revision'],
+                               :scmid => le.attributes['node'],
+                               :author => (le.elements['author'].text rescue ''),
+                               :time => Time.parse(le.elements['date'].text).localtime,
+                               :message => le.elements['msg'].text,
+                               :paths => paths)
           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 << "</log>")
-              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
+          self
         end
         
         def diff(path, identifier_from, identifier_to=nil)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269692769 -32400
# Branch 0.9-stable
# Node ID ebe74c4ac997cab1c76556963490ba7df8b49077
# Parent  5fb860a525f7c000c10709a467de46865c5f0dc2
mercurial: rewrite scm.info by using helper ext

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
@@ -77,19 +77,17 @@ module Redmine
         end
         
         def info
-          cmd = "#{HG_BIN} -R #{target('')} root"
-          root_url = nil
-          shellout(cmd) do |io|
-            root_url = io.gets
-          end
-          return nil if $? && $?.exitstatus != 0
-          info = Info.new({:root_url => root_url.chomp,
-                            :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
-                          })
-          info
-        rescue CommandFailed
-          return nil
+          tip = summary['tip'].first
+          Info.new(:root_url => root_url,
+                   :lastrev => Revision.new(:identifier => tip['rev'].to_i,
+                                            :revision => tip['rev'],
+                                            :scmid => tip['node']))
         end
+
+        def summary
+          @summary ||= fetchg 'rhsummary'
+        end
+        private :summary
         
         def entries(path=nil, identifier=nil)
           path ||= ''
@@ -203,6 +201,32 @@ module Redmine
         end
         private :hg
 
+        # Runs 'hg' helper, then parses output to return
+        def fetchg(*args)
+          # command output example:
+          #   :tip: rev node
+          #   100 abcdef012345
+          #   :tags: rev node name
+          #   100 abcdef012345 tip
+          #   ...
+          data = Hash.new { |h, k| h[k] = [] }
+          hg(*args) do |io|
+            key, attrs = nil, nil
+            io.each do |line|
+              next if line.chomp.empty?
+              if /^:(\w+): ([\w ]+)/ =~ line
+                key = $1
+                attrs = $2.split(/ /)
+              elsif key
+                alist = attrs.zip(line.chomp.split(/ /, attrs.size))
+                data[key] << Hash[*alist.flatten]
+              end
+            end
+          end
+          data
+        end
+        private :fetchg
+
         # Returns correct revision identifier
         def hgrev(identifier)
           identifier.blank? ? 'tip' : identifier.to_s
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269692769 -32400
# Branch 0.9-stable
# Node ID eacbfded2455c77b4ce4b611331d69d8997ae55f
# Parent  ebe74c4ac997cab1c76556963490ba7df8b49077
mercurial: cleaned up fetch_changesets

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
@@ -21,6 +21,8 @@ class Repository::Mercurial < Repository
   attr_protected :root_url
   validates_presence_of :url
 
+  FETCH_AT_ONCE = 100  # number of changesets to fetch at once
+
   def scm_adapter
     Redmine::Scm::Adapters::MercurialAdapter
   end
@@ -53,38 +55,24 @@ class Repository::Mercurial < Repository
   end
 
   def fetch_changesets
-    scm_info = scm.info
-    if scm_info
-      # latest revision found in database
-      db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
-      # latest revision in the repository
-      latest_revision = scm_info.lastrev
-      return if latest_revision.nil?
-      scm_revision = latest_revision.identifier.to_i
-      if db_revision < scm_revision
-        logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
-        identifier_from = db_revision + 1
-        while (identifier_from <= scm_revision)
-          # loads changesets by batches of 100
-          identifier_to = [identifier_from + 99, scm_revision].min
-          revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
-          transaction do
-            revisions.each do |revision|
-              changeset = Changeset.create(:repository => self,
-                                           :revision => revision.identifier,
-                                           :scmid => revision.scmid,
-                                           :committer => revision.author, 
-                                           :committed_on => revision.time,
-                                           :comments => revision.message)
-              
-              revision.paths.each do |change|
-                changeset.create_change(change)
-              end
-            end
-          end unless revisions.nil?
-          identifier_from = identifier_to + 1
+    scm_rev = scm.info.lastrev.revision.to_i
+    db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
+    return unless db_rev < scm_rev  # already up-to-date
+
+    logger.debug "Fetching changesets for repository #{url}" if logger
+    (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
+      transaction do
+        scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
+          cs = Changeset.create(:repository => self,
+                                :revision => re.revision,
+                                :scmid => re.scmid,
+                                :committer => re.author,
+                                :committed_on => re.time,
+                                :comments => re.message)
+          re.paths.each { |e| cs.create_change(e) }
         end
       end
     end
+    self
   end
 end
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269442376 -32400
# Branch 0.9-stable
# Node ID f6a96b76049811c055a74cc97c1d5c8f2801be1e
# Parent  eacbfded2455c77b4ce4b611331d69d8997ae55f
mercurial: override latest_changesets to support rev and path filter

Mercurial doesn't have directory object.

TODO: better way to build sub-query cond

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
@@ -54,6 +54,34 @@ class Repository::Mercurial < Repository
     entries
   end
 
+  # Returns the latest changesets for +path+
+  def latest_changesets(path, rev, limit=10)
+    changesets.find(:all, :include => :user,
+                    :conditions => latest_changesets_cond(path, rev),
+                    :order => "#{Changeset.table_name}.id DESC",
+                    :limit => limit)
+  end
+
+  def latest_changesets_cond(path, rev)
+    cond, args = [], []
+
+    if last = rev ? find_changeset_by_name(rev) : nil
+      cond << "#{Changeset.table_name}.id <= ?"
+      args << last.id
+    end
+
+    unless path.blank?
+      # TODO: there must be a better way to build sub-query
+      cond << "EXISTS (SELECT * FROM #{Change.table_name}
+                 WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
+                 AND (#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ?))"
+      args << path.with_leading_slash << "#{path.with_leading_slash}/%"
+    end
+
+    [cond.join(' AND '), *args] unless cond.empty?
+  end
+  private :latest_changesets_cond
+
   def fetch_changesets
     scm_rev = scm.info.lastrev.revision.to_i
     db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269349610 -32400
# Branch 0.9-stable
# Node ID 6f5d5355d01173da4fbf92304ab08e17ff81b3d3
# Parent  f6a96b76049811c055a74cc97c1d5c8f2801be1e
mercruail: query against scmid if rev looks like node id

Now Repository::Mercurial.find_changeset_by_name queries against scmid.
Note that scmid can match /[^\d]/, e.g. '012345678901' is valid hash.

TODO: need a test case

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
@@ -23,6 +23,8 @@ class Repository::Mercurial < Repository
 
   FETCH_AT_ONCE = 100  # number of changesets to fetch at once
 
+  SHORT_NODEID_LEN = 12
+
   def scm_adapter
     Redmine::Scm::Adapters::MercurialAdapter
   end
@@ -82,6 +84,19 @@ class Repository::Mercurial < Repository
   end
   private :latest_changesets_cond
 
+  # Finds and returns a revision with a number or the beginning of a hash
+  def find_changeset_by_name(name)
+    if /[^\d]/ =~ name or name.length >= SHORT_NODEID_LEN
+      if e = changesets.find(:first, :conditions => ["scmid = ?", name])
+        e
+      else
+        changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])
+      end
+    else
+      super
+    end
+  end
+
   def fetch_changesets
     scm_rev = scm.info.lastrev.revision.to_i
     db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269353727 -32400
# Branch 0.9-stable
# Node ID 9e6bc0601f65f4c07aff3b0c20916ca8df46729d
# Parent  6f5d5355d01173da4fbf92304ab08e17ff81b3d3
mercurial: support tags on repository browser

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
@@ -67,7 +67,7 @@ class Repository::Mercurial < Repository
   def latest_changesets_cond(path, rev)
     cond, args = [], []
 
-    if last = rev ? find_changeset_by_name(rev) : nil
+    if last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
       cond << "#{Changeset.table_name}.id <= ?"
       args << last.id
     end
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
@@ -84,6 +84,16 @@ module Redmine
                                             :scmid => tip['node']))
         end
 
+        def tags
+          summary['tags'].map { |e| e['name'] }
+        end
+
+        # Returns map of {'tag' => 'nodeid', ...}
+        def tagmap
+          alist = summary['tags'].map { |e| e.values_at('name', 'node') }
+          Hash[*alist.flatten]
+        end
+
         def summary
           @summary ||= fetchg 'rhsummary'
         end
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269692769 -32400
# Branch 0.9-stable
# Node ID 30e7cfd697adf596a24da8b124a7af741c743a07
# Parent  9e6bc0601f65f4c07aff3b0c20916ca8df46729d
mercurial: rewrite .entries from scratch

Now it uses redminehelper extension.

TODO: we shouldn't refetch changeset from db, done by template

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
@@ -34,26 +34,7 @@ class Repository::Mercurial < Repository
   end
   
   def entries(path=nil, identifier=nil)
-    entries=scm.entries(path, identifier)
-    if entries
-      entries.each do |entry|
-        next unless entry.is_file?
-        # Set the filesize unless browsing a specific revision
-        if identifier.nil?
-          full_path = File.join(root_url, entry.path)
-          entry.size = File.stat(full_path).size if File.file?(full_path)
-        end
-        # Search the DB for the entry's last change
-        change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
-        if change
-          entry.lastrev.identifier = change.changeset.revision
-          entry.lastrev.name = change.changeset.revision
-          entry.lastrev.author = change.changeset.committer
-          entry.lastrev.revision = change.revision
-        end
-      end
-    end
-    entries
+    scm.entries(path, identifier)
   end
 
   # Returns the latest changesets for +path+
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
@@ -100,28 +100,26 @@ module Redmine
         private :summary
         
         def entries(path=nil, identifier=nil)
-          path ||= ''
           entries = Entries.new
-          cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
-          cmd << " -r " + (identifier ? identifier.to_s : "tip")
-          cmd << " " + shell_quote("path:#{path}") unless path.empty?
-          shellout(cmd) do |io|
-            io.each_line do |line|
-              # 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 e.empty? || entries.detect{|entry| entry.name == e.first}
-              end
-            end
+          fetched_entries = fetchg('rhentries', '-r', hgrev(identifier),
+                                   without_leading_slash(path.to_s))
+
+          fetched_entries['dirs'].each do |e|
+            entries << Entry.new(:name => e['name'],
+                                 :path => "#{with_trailling_slash(path)}#{e['name']}",
+                                 :kind => 'dir')
           end
-          return nil if $? && $?.exitstatus != 0
-          entries.sort_by_name
+
+          fetched_entries['files'].each do |e|
+            entries << Entry.new(:name => e['name'],
+                                 :path => "#{with_trailling_slash(path)}#{e['name']}",
+                                 :kind => 'file',
+                                 :size => e['size'].to_i,
+                                 :lastrev => Revision.new(:identifier => e['rev'].to_i,
+                                                          :time => Time.at(e['time'].to_i)))
+          end
+
+          entries
         end
         
         # TODO: is this api necessary?
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269692769 -32400
# Branch 0.9-stable
# Node ID ce17da25176495a7a9b20b04712667eeac00f7ba
# Parent  30e7cfd697adf596a24da8b124a7af741c743a07
mercurial: now supports named branches

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
@@ -39,16 +39,21 @@ class Repository::Mercurial < Repository
 
   # Returns the latest changesets for +path+
   def latest_changesets(path, rev, limit=10)
+    # TODO: filter by branch if rev is branch name
     changesets.find(:all, :include => :user,
-                    :conditions => latest_changesets_cond(path, rev),
+                    :conditions => latest_changesets_cond(path, rev, limit),
                     :order => "#{Changeset.table_name}.id DESC",
                     :limit => limit)
   end
 
-  def latest_changesets_cond(path, rev)
+  def latest_changesets_cond(path, rev, limit)
     cond, args = [], []
 
-    if last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
+    if scm.branchmap.member? rev
+      # dirty hack to filter by branch. branch name should be in database.
+      cond << "#{Changeset.table_name}.scmid IN (?)"
+      args << scm.nodes_in_branch(rev, path, rev, 0, :limit => limit)
+    elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
       cond << "#{Changeset.table_name}.id <= ?"
       args << last.id
     end
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
@@ -94,6 +94,19 @@ module Redmine
           Hash[*alist.flatten]
         end
 
+        def branches
+          summary['branches'].map { |e| e['name'] }
+        end
+
+        # Returns map of {'branch' => 'nodeid', ...}
+        def branchmap
+          alist = summary['branches'].map { |e| e.values_at('name', 'node') }
+          Hash[*alist.flatten]
+        end
+
+        # NOTE: DO NOT IMPLEMENT default_branch !!
+        # It's used as the default revision by RepositoriesController.
+
         def summary
           @summary ||= fetchg 'rhsummary'
         end
@@ -161,6 +174,15 @@ module Redmine
           end
           self
         end
+
+        # Returns list of nodes in the specified branch
+        def nodes_in_branch(branch, path=nil, identifier_from=nil, identifier_to=nil, options={})
+          hg_args = ['log', '--template', '{node|short}\n', '-b', branch]
+          hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
+          hg_args << '--limit' << options[:limit] if options[:limit]
+          hg_args << without_leading_slash(path) unless path.blank?
+          hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } }
+        end
         
         def diff(path, identifier_from, identifier_to=nil)
           hg_args = ['diff', '--nodates']
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269243033 -32400
# Branch 0.9-stable
# Node ID e7412830a2972fe5d8fa84f0c0974b27f091c610
# Parent  ce17da25176495a7a9b20b04712667eeac00f7ba
mercurial: use shellout to get hgversion

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
@@ -48,7 +48,9 @@ module Redmine
           end
           
           def hgversion_from_command_line
-            %x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1]
+            shellout("#{HG_BIN} --version") do |io|
+              io.read.match(/\(version (.*)\)/)[1]
+            end
           end
           
           def template_path
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1260712247 -32400
# Branch 0.9-stable
# Node ID 9b7624b4f421427043714b8fb2915cfb3af64f25
# Parent  e7412830a2972fe5d8fa84f0c0974b27f091c610
repository: sort changesets only by id, not by commited date

In DVCS world, changesets are not in date order.

TODO: Check if it's okay for the other scm backends
TODO: or, if possible, override sort keys only for Mercurial

diff --git a/app/models/issue.rb b/app/models/issue.rb
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -27,7 +27,7 @@ class Issue < ActiveRecord::Base
 
   has_many :journals, :as => :journalized, :dependent => :destroy
   has_many :time_entries, :dependent => :delete_all
-  has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
+  has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.id ASC"
   
   has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
   has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
diff --git a/app/models/repository.rb b/app/models/repository.rb
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -17,7 +17,7 @@
 
 class Repository < ActiveRecord::Base
   belongs_to :project
-  has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
+  has_many :changesets, :order => "#{Changeset.table_name}.id DESC"
   has_many :changes, :through => :changesets
   
   # Raw SQL to delete changesets and changes in the database
@@ -106,12 +106,12 @@ class Repository < ActiveRecord::Base
   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",
+                            :order => "#{Changeset.table_name}.id DESC",
                             :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",
+                         :order => "#{Changeset.table_name}.id DESC",
                          :limit => limit).collect(&:changeset)
     end
   end
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269353648 -32400
# Branch 0.9-stable
# Node ID d60cfd8819c1f124f77faa4e0b278f6e9b78e376
# Parent  9b7624b4f421427043714b8fb2915cfb3af64f25
changeset/revision: introduce attrs to identify/represent revision

Mercurial needs them. It has revision number, node id and their combo.

TODO: good name for revision_id and revision_repr
TODO: is there a way to sub-class Changeset?
TODO: duplicates exist on Changeset and Revision

diff --git a/app/models/changeset.rb b/app/models/changeset.rb
--- a/app/models/changeset.rb
+++ b/app/models/changeset.rb
@@ -47,6 +47,23 @@ class Changeset < ActiveRecord::Base
   def revision=(r)
     write_attribute :revision, (r.nil? ? nil : r.to_s)
   end
+
+  # Returns identifier of the revision.
+  # e.g. revision number for centralized system; hash id for DVCS
+  def revision_id
+    # TODO: should be 'revision' by default, and overriden by sub-class or mixin
+    scmid || revision
+  end
+
+  # Returns human-readable string representing the revision
+  def revision_repr
+    # TODO: should be 'revision' by default, and overriden by sub-class or mixin
+    if scmid and revision != scmid
+      "#{revision}:#{scmid}"  # Mercurial style
+    else
+      revision[0, 8]  # TODO: [0, 8] maybe for git
+    end
+  end
   
   def comments=(comment)
     write_attribute(:comments, Changeset.normalize_comments(comment))
diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb
--- a/lib/redmine/scm/adapters/abstract_adapter.rb
+++ b/lib/redmine/scm/adapters/abstract_adapter.rb
@@ -285,6 +285,32 @@ module Redmine
           self.branch = attributes[:branch]
         end
 
+        # FIXME: Changeset class has same methods to revision_id and revision_repr.
+        # They should be mix-in module or something.
+
+        # FIXME: It seems 'revision_id' should be 'identifier', but Revision.save uses
+        # 'identifier' for :revision. I need to check usage of Revision class to
+        # improve class design.
+
+        # Returns identifier of the revision.
+        # e.g. revision number for centralized system; hash id for DVCS
+        def revision_id  # TODO: confusing name; there's already 'identifier'
+          # TODO: should be 'revision' by default, and overriden by sub-class or mixin
+          scmid || revision || identifier
+        end
+
+        # Returns human-readable string representing the revision
+        def revision_repr
+          # TODO: should be 'revision' by default, and overriden by sub-class or mixin
+          if scmid and revision != scmid
+            "#{revision}:#{scmid}"  # Mercurial style
+          elsif revision
+            revision[0, 8]  # TODO: [0, 8] maybe for git
+          else
+            "#{identifier}"[0, 8]
+          end
+        end
+
         def save(repo)
           Changeset.transaction do
             changeset = Changeset.new(
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269353648 -32400
# Branch 0.9-stable
# Node ID 4fc223fd86417a0686458f17f291f6f417649a5c
# Parent  d60cfd8819c1f124f77faa4e0b278f6e9b78e376
repository: better format_revision for Mercurial

From now, link_to_revision and format_revision can accept Changeset object
directly. Then, if it seems to have scmid, format_revision uses 'rev:scmid'
style.

TODO: need a test case
TODO: rename variable 'changeset' to 'revision' or something.
It can be Changeset, Revision or string.

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -99,10 +99,11 @@ module ApplicationHelper
   # Generates a link to a SCM revision
   # Options:
   # * :text - Link text (default to the formatted revision)
-  def link_to_revision(revision, project, options={})
-    text = options.delete(:text) || format_revision(revision)
+  def link_to_revision(changeset, project, options={})
+    text = options.delete(:text) || format_revision(changeset)
+    revision = changeset.respond_to?(:revision_id) ? changeset.revision_id : changeset
 
-    link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
+    link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, format_revision(changeset)))
   end
 
   def toggle_link(name, id, options={})
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
@@ -18,8 +18,12 @@
 require 'iconv'
 
 module RepositoriesHelper
-  def format_revision(txt)
-    txt.to_s[0,8]
+  def format_revision(changeset)
+    if changeset.respond_to? :revision_repr
+      changeset.revision_repr
+    else
+      return changeset.to_s[0,8]
+    end
   end
   
   def truncate_at_line_break(text, length = 255)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269353648 -32400
# Branch 0.9-stable
# Node ID fd287c35ef2cfb022ca6a573c915958bec7035bf
# Parent  4fc223fd86417a0686458f17f291f6f417649a5c
repository: use improved format_revision and link_to_revision

Improvements mainly for Mercurial backend.
It has both revision number and nodeid, aka scmid on Redmine.

TODO: not completed yet

diff --git a/app/views/repositories/_dir_list_content.rhtml b/app/views/repositories/_dir_list_content.rhtml
--- a/app/views/repositories/_dir_list_content.rhtml
+++ b/app/views/repositories/_dir_list_content.rhtml
@@ -17,7 +17,7 @@
 </td>
 <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
 <% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
-<td class="revision"><%= link_to_revision(changeset.revision, @project) if changeset %></td>
+<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td>
 <td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
 <td class="author"><%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %></td>
 <td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td>
diff --git a/app/views/repositories/_revisions.rhtml b/app/views/repositories/_revisions.rhtml
--- a/app/views/repositories/_revisions.rhtml
+++ b/app/views/repositories/_revisions.rhtml
@@ -13,7 +13,7 @@
 <% line_num = 1 %>
 <% revisions.each do |changeset| %>
 <tr class="changeset <%= cycle 'odd', 'even' %>">
-<td class="id"><%= link_to_revision(changeset.revision, project) %></td>
+<td class="id"><%= link_to_revision(changeset, project) %></td>
 <td class="checkbox"><%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
 <td class="checkbox"><%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
 <td class="committed_on"><%= format_time(changeset.committed_on) %></td>
diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml
--- a/app/views/repositories/revision.rhtml
+++ b/app/views/repositories/revision.rhtml
@@ -1,13 +1,13 @@
 <div class="contextual">
   &#171;
   <% unless @changeset.previous.nil? -%>
-    <%= link_to_revision(@changeset.previous.revision, @project, :text => l(:label_previous)) %>
+    <%= link_to_revision(@changeset.previous, @project, :text => l(:label_previous)) %>
   <% else -%>
     <%= l(:label_previous) %>
   <% end -%>
 |
   <% unless @changeset.next.nil? -%>
-    <%= link_to_revision(@changeset.next.revision, @project, :text => l(:label_next)) %>
+    <%= link_to_revision(@changeset.next, @project, :text => l(:label_next)) %>
   <% else -%>
     <%= l(:label_next) %>
   <% end -%>
@@ -19,7 +19,7 @@
   <% end %>
 </div>
 
-<h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
+<h2><%= l(:label_revision) %> <%= format_revision(@changeset) %></h2>
 
 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
 <span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p>
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1269353648 -32400
# Branch 0.9-stable
# Node ID 0c2963505cd1c1950f21b87c1a0788a842b7fd76
# Parent  fd287c35ef2cfb022ca6a573c915958bec7035bf
imported patch repoview-annotate-nodeid.diff

diff --git a/app/views/repositories/annotate.rhtml b/app/views/repositories/annotate.rhtml
--- a/app/views/repositories/annotate.rhtml
+++ b/app/views/repositories/annotate.rhtml
@@ -19,7 +19,7 @@
     <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
       <th class="line-num" id="L<%= line_num %>"><a href="#L<%= line_num %>"><%= line_num %></a></th>
       <td class="revision">
-      <%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td>
+      <%= (revision.revision_id ? link_to_revision(revision, @project) : format_revision(revision)) if revision %></td>
       <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td>
       <td class="line-code"><pre><%= line %></pre></td>
     </tr>
