From 2e1bcb2abff6f78f028064299125480cbf3c2c2a Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA <marutosijp2@yahoo.co.jp>
Date: Sun, 2 Jan 2011 09:45:05 +0000
Subject: [PATCH] Changing revision label and identifier at SCM adapter level
 (#3724, #6092)

Contributed by Yuya Nishihara.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4613 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 app/controllers/repositories_controller.rb    |  3 ++
 app/helpers/application_helper.rb             |  4 +-
 app/helpers/repositories_helper.rb            | 14 +++--
 app/models/changeset.rb                       | 22 +++++++-
 app/models/repository/git.rb                  | 10 ++++
 .../repositories/_dir_list_content.rhtml      |  2 +-
 app/views/repositories/_revisions.rhtml       |  6 +--
 app/views/repositories/annotate.rhtml         |  2 +-
 app/views/repositories/diff.rhtml             |  2 +-
 app/views/repositories/revision.rhtml         | 12 ++---
 lib/redmine/scm/adapters/abstract_adapter.rb  | 13 ++++-
 lib/redmine/scm/adapters/git_adapter.rb       |  7 +++
 test/unit/changeset_test.rb                   |  5 ++
 test/unit/repository_bazaar_test.rb           |  2 +-
 test/unit/repository_git_test.rb              | 28 +++++++++-
 test/unit/repository_subversion_test.rb       | 53 ++++++++++++++++++-
 16 files changed, 161 insertions(+), 24 deletions(-)

diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index a71f7bf45..6195c6409 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -174,6 +174,9 @@ class RepositoriesController < ApplicationController
         @diff = @repository.diff(@path, @rev, @rev_to)
         show_error_not_found unless @diff
       end
+
+      @changeset = @repository.find_changeset_by_name(@rev)
+      @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
     end
   end
   
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e1048ea22..3cdd3add5 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -104,8 +104,10 @@ module ApplicationHelper
   # * :text - Link text (default to the formatted revision)
   def link_to_revision(revision, project, options={})
     text = options.delete(:text) || format_revision(revision)
+    rev = revision.respond_to?(:identifier) ? revision.identifier : revision
 
-    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 => rev},
+            :title => l(:label_revision_id, format_revision(revision)))
   end
 
   # Generates a link to a project if active
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
index 19a86e00c..63ea52238 100644
--- 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(revision)
+    if revision.respond_to? :format_identifier
+      revision.format_identifier
+    else
+      revision.to_s
+    end
   end
   
   def truncate_at_line_break(text, length = 255)
@@ -87,7 +91,7 @@ module RepositoriesHelper
                              :action => 'show',
                              :id => @project,
                              :path => path_param,
-                             :rev => @changeset.revision)
+                             :rev => @changeset.identifier)
         output << "<li class='#{style}'>#{text}</li>"
         output << render_changes_tree(s)
       elsif c = tree[file][:c]
@@ -97,13 +101,13 @@ module RepositoriesHelper
                              :action => 'entry',
                              :id => @project,
                              :path => path_param,
-                             :rev => @changeset.revision) unless c.action == 'D'
+                             :rev => @changeset.identifier) unless c.action == 'D'
         text << " - #{c.revision}" unless c.revision.blank?
         text << ' (' + link_to('diff', :controller => 'repositories',
                                        :action => 'diff',
                                        :id => @project,
                                        :path => path_param,
-                                       :rev => @changeset.revision) + ') ' if c.action == 'M'
+                                       :rev => @changeset.identifier) + ') ' if c.action == 'M'
         text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank?
         output << "<li class='#{style}'>#{text}</li>"
       end
diff --git a/app/models/changeset.rb b/app/models/changeset.rb
index ca4daace8..e49113ebf 100644
--- a/app/models/changeset.rb
+++ b/app/models/changeset.rb
@@ -23,10 +23,10 @@ class Changeset < ActiveRecord::Base
   has_many :changes, :dependent => :delete_all
   has_and_belongs_to_many :issues
 
-  acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
+  acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
                 :description => :long_comments,
                 :datetime => :committed_on,
-                :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}}
+                :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
                 
   acts_as_searchable :columns => 'comments',
                      :include => {:repository => :project},
@@ -47,6 +47,15 @@ class Changeset < ActiveRecord::Base
   def revision=(r)
     write_attribute :revision, (r.nil? ? nil : r.to_s)
   end
+
+  # Returns the identifier of this changeset; depending on repository backends
+  def identifier
+    if repository.class.respond_to? :changeset_identifier
+      repository.class.changeset_identifier self
+    else
+      revision.to_s
+    end
+  end
   
   def comments=(comment)
     write_attribute(:comments, Changeset.normalize_comments(comment))
@@ -56,6 +65,15 @@ class Changeset < ActiveRecord::Base
     self.commit_date = date
     super
   end
+
+  # Returns the readable identifier
+  def format_identifier
+    if repository.class.respond_to? :format_changeset_identifier
+      repository.class.format_changeset_identifier self
+    else
+      identifier
+    end
+  end
   
   def committer=(arg)
     write_attribute(:committer, self.class.to_utf8(arg.to_s))
diff --git a/app/models/repository/git.rb b/app/models/repository/git.rb
index 473eb07e2..9349f3c11 100644
--- a/app/models/repository/git.rb
+++ b/app/models/repository/git.rb
@@ -29,6 +29,16 @@ class Repository::Git < Repository
     'Git'
   end
 
+  # Returns the identifier for the given git changeset
+  def self.changeset_identifier(changeset)
+    changeset.scmid
+  end
+
+  # Returns the readable identifier for the given git changeset
+  def self.format_changeset_identifier(changeset)
+    changeset.revision[0, 8]
+  end
+
   def branches
     scm.branches
   end
diff --git a/app/views/repositories/_dir_list_content.rhtml b/app/views/repositories/_dir_list_content.rhtml
index c5bd53ea7..66574f1c8 100644
--- 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
index 26fb5b699..92c6fb535 100644
--- a/app/views/repositories/_revisions.rhtml
+++ b/app/views/repositories/_revisions.rhtml
@@ -13,9 +13,9 @@
 <% 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="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="id"><%= link_to_revision(changeset, project) %></td>
+<td class="checkbox"><%= radio_button_tag('rev', changeset.identifier, (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.identifier, (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>
 <td class="author"><%=h changeset.author %></td>
 <td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
diff --git a/app/views/repositories/annotate.rhtml b/app/views/repositories/annotate.rhtml
index a18e9bbac..498507148 100644
--- 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.identifier ? 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>
diff --git a/app/views/repositories/diff.rhtml b/app/views/repositories/diff.rhtml
index 24f92a540..e2323549e 100644
--- a/app/views/repositories/diff.rhtml
+++ b/app/views/repositories/diff.rhtml
@@ -1,4 +1,4 @@
-<h2><%= l(:label_revision) %> <%= format_revision(@rev_to) + ':' if @rev_to %><%= format_revision(@rev) %> <%=h @path %></h2>
+<h2><%= l(:label_revision) %> <%= format_revision(@changeset_to) + ':' if @changeset_to %><%= format_revision(@changeset) %> <%=h @path %></h2>
 
 <!-- Choose view type -->
 <% form_tag({:path => to_path_param(@path)}, :method => 'get') do %>
diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml
index 92597dff7..483e358de 100644
--- a/app/views/repositories/revision.rhtml
+++ b/app/views/repositories/revision.rhtml
@@ -1,25 +1,25 @@
 <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 -%>
   &#187;&nbsp;
 
   <% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %>
-    <%= text_field_tag 'rev', @rev[0,8], :size => 8 %>
+    <%= text_field_tag 'rev', @rev, :size => 8 %>
     <%= submit_tag 'OK', :name => nil %>
   <% 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>
@@ -45,7 +45,7 @@
 <li class="change change-D"><%= l(:label_deleted) %></li>
 </ul>
 
-<p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
+<p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.identifier) if @changeset.changes.any? %></p>
 
 <div class="changeset-changes">
 <%= render_changeset_changes %>
@@ -56,4 +56,4 @@
 <%= stylesheet_link_tag "scm" %>
 <% end %>
 
-<% html_title("#{l(:label_revision)} #{@changeset.revision}") -%>
+<% html_title("#{l(:label_revision)} #{format_revision(@changeset)}") -%>
diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb
index a3ca61e23..f98e90958 100644
--- a/lib/redmine/scm/adapters/abstract_adapter.rb
+++ b/lib/redmine/scm/adapters/abstract_adapter.rb
@@ -271,7 +271,8 @@ module Redmine
       end
       
       class Revision
-        attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
+        attr_accessor :scmid, :name, :author, :time, :message, :paths, :revision, :branch
+        attr_writer :identifier
 
         def initialize(attributes={})
           self.identifier = attributes[:identifier]
@@ -285,6 +286,16 @@ module Redmine
           self.branch = attributes[:branch]
         end
 
+        # Returns the identifier of this revision; see also Changeset model
+        def identifier
+          (@identifier || revision).to_s
+        end
+
+        # Returns the readable identifier.
+        def format_identifier
+          identifier
+        end
+
         def save(repo)
           Changeset.transaction do
             changeset = Changeset.new(
diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb
index 7901f23d6..cd8fd95d4 100644
--- a/lib/redmine/scm/adapters/git_adapter.rb
+++ b/lib/redmine/scm/adapters/git_adapter.rb
@@ -264,6 +264,13 @@ module Redmine
           return nil if $? && $?.exitstatus != 0
           cat
         end
+
+        class Revision < Redmine::Scm::Adapters::Revision
+          # Returns the readable identifier
+          def format_identifier
+            identifier[0,8]
+          end
+        end
       end
     end
   end
diff --git a/test/unit/changeset_test.rb b/test/unit/changeset_test.rb
index 9265fe9c5..2f0415d3a 100644
--- a/test/unit/changeset_test.rb
+++ b/test/unit/changeset_test.rb
@@ -218,4 +218,9 @@ class ChangesetTest < ActiveSupport::TestCase
     c.comments = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
     assert_equal "Texte encod en ISO-8859-1.", c.comments
   end
+
+  def test_identifier
+    c = Changeset.find_by_revision('1')
+    assert_equal c.revision, c.identifier
+  end
 end
diff --git a/test/unit/repository_bazaar_test.rb b/test/unit/repository_bazaar_test.rb
index bd1c9a9b4..5cd7da13c 100644
--- a/test/unit/repository_bazaar_test.rb
+++ b/test/unit/repository_bazaar_test.rb
@@ -77,7 +77,7 @@ class RepositoryBazaarTest < ActiveSupport::TestCase
     def test_annotate
       annotate = @repository.scm.annotate('doc-mkdir.txt')
       assert_equal 17, annotate.lines.size
-      assert_equal 1, annotate.revisions[0].identifier
+      assert_equal '1', annotate.revisions[0].identifier
       assert_equal 'jsmith@', annotate.revisions[0].author
       assert_equal 'mkdir', annotate.lines[0]
     end
diff --git a/test/unit/repository_git_test.rb b/test/unit/repository_git_test.rb
index acf4f174a..cec091760 100644
--- a/test/unit/repository_git_test.rb
+++ b/test/unit/repository_git_test.rb
@@ -18,7 +18,7 @@
 require File.expand_path('../../test_helper', __FILE__)
 
 class RepositoryGitTest < ActiveSupport::TestCase
-  fixtures :projects
+  fixtures :projects, :repositories, :enabled_modules, :users, :roles 
   
   # No '..' in the repository path
   REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
@@ -62,6 +62,32 @@ class RepositoryGitTest < ActiveSupport::TestCase
       @repository.fetch_changesets
       assert_equal 15, @repository.changesets.count
     end
+
+    def test_identifier
+      @repository.fetch_changesets
+      @repository.reload
+      c = @repository.changesets.find_by_revision('7234cb2750b63f47bff735edc50a1c0a433c2518')
+      assert_equal c.scmid, c.identifier
+    end
+
+    def test_format_identifier
+      @repository.fetch_changesets
+      @repository.reload
+      c = @repository.changesets.find_by_revision('7234cb2750b63f47bff735edc50a1c0a433c2518')
+      assert_equal c.format_identifier, '7234cb27'
+    end
+
+    def test_activities
+      @repository.fetch_changesets
+      @repository.reload
+      f = Redmine::Activity::Fetcher.new(User.anonymous, :project => Project.find(1))
+      f.scope = ['changesets']
+      events = f.events
+      assert_kind_of Array, events
+      eve = events[-9]
+      assert eve.event_title.include?('7234cb27:')
+      assert_equal eve.event_url[:rev], '7234cb2750b63f47bff735edc50a1c0a433c2518'
+    end
   else
     puts "Git test repository NOT FOUND. Skipping unit tests !!!"
     def test_fake; assert true end
diff --git a/test/unit/repository_subversion_test.rb b/test/unit/repository_subversion_test.rb
index 903cdd049..5034c30cf 100644
--- a/test/unit/repository_subversion_test.rb
+++ b/test/unit/repository_subversion_test.rb
@@ -18,7 +18,7 @@
 require File.expand_path('../../test_helper', __FILE__)
 
 class RepositorySubversionTest < ActiveSupport::TestCase
-  fixtures :projects, :repositories
+  fixtures :projects, :repositories, :enabled_modules, :users, :roles 
   
   def setup
     @project = Project.find(1)
@@ -88,6 +88,57 @@ class RepositorySubversionTest < ActiveSupport::TestCase
       assert_equal 1, entries.size, 'Expect a single entry'
       assert_equal 'README.txt', entries.first.name
     end
+
+    def test_identifier
+      @repository.fetch_changesets
+      @repository.reload
+      c = @repository.changesets.find_by_revision('1')
+      assert_equal c.revision, c.identifier
+    end
+
+    def test_identifier_nine_digit
+      c = Changeset.new(:repository => @repository, :committed_on => Time.now,
+                        :revision => '123456789', :comments => 'test')
+      assert_equal c.identifier, c.revision
+    end
+
+    def test_format_identifier
+      @repository.fetch_changesets
+      @repository.reload
+      c = @repository.changesets.find_by_revision('1')
+      assert_equal c.format_identifier, c.revision
+    end
+
+    def test_format_identifier_nine_digit
+      c = Changeset.new(:repository => @repository, :committed_on => Time.now,
+                        :revision => '123456789', :comments => 'test')
+      assert_equal c.format_identifier, c.revision
+    end
+
+    def test_activities
+      @repository.fetch_changesets
+      @repository.reload
+      f = Redmine::Activity::Fetcher.new(User.anonymous, :project => Project.find(1))
+      f.scope = ['changesets']
+      events = f.events
+      assert_kind_of Array, events
+      eve = events[-9]
+      assert eve.event_title.include?('1:')
+      assert_equal eve.event_url[:rev], '1'
+    end
+
+    def test_activities_nine_digit
+      c = Changeset.new(:repository => @repository, :committed_on => Time.now,
+                        :revision => '123456789', :comments => 'test')
+      assert( c.save )
+      f = Redmine::Activity::Fetcher.new(User.anonymous, :project => Project.find(1))
+      f.scope = ['changesets']
+      events = f.events
+      assert_kind_of Array, events
+      eve = events[-11]
+      assert eve.event_title.include?('123456789:')
+      assert_equal eve.event_url[:rev], '123456789'
+    end
   else
     puts "Subversion test repository NOT FOUND. Skipping unit tests !!!"
     def test_fake; assert true end
-- 
GitLab