OSDN Git Service

Display svn properties in the browser, svn >= 1.5.0 only (#1581).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 5 Jul 2008 08:59:04 +0000 (08:59 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 5 Jul 2008 08:59:04 +0000 (08:59 +0000)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@1627 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/controllers/repositories_controller.rb
app/helpers/repositories_helper.rb
app/models/repository.rb
app/views/repositories/browse.rhtml
app/views/repositories/changes.rhtml
lib/redmine/scm/adapters/abstract_adapter.rb
lib/redmine/scm/adapters/subversion_adapter.rb
public/stylesheets/application.css
test/functional/repositories_subversion_controller_test.rb
test/unit/subversion_adapter_test.rb [new file with mode: 0644]

index 7b33873..13bba62 100644 (file)
@@ -66,6 +66,7 @@ class RepositoriesController < ApplicationController
       @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
     else
       show_error_not_found and return unless @entries
+      @properties = @repository.properties(@path, @rev)
       render :action => 'browse'
     end
   end
@@ -74,6 +75,7 @@ class RepositoriesController < ApplicationController
     @entry = @repository.entry(@path, @rev)
     show_error_not_found and return unless @entry
     @changesets = @repository.changesets_for_path(@path)
+    @properties = @repository.properties(@path, @rev)
   end
   
   def revisions
@@ -106,7 +108,7 @@ class RepositoriesController < ApplicationController
     else
       # Prevent empty lines when displaying a file with Windows style eol
       @content.gsub!("\r\n", "\n")
-    end
+   end
   end
   
   def annotate
index 4ef337c..59e1e0f 100644 (file)
@@ -22,6 +22,16 @@ module RepositoriesHelper
     txt.to_s[0,8]
   end
   
+  def render_properties(properties)
+    unless properties.nil? || properties.empty?
+      content = ''
+      properties.keys.sort.each do |property|
+        content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>")
+      end
+      content_tag('ul', content, :class => 'properties')
+    end
+  end
+  
   def to_path_param(path)
     path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
   end
index c7bf0db..2a8728f 100644 (file)
@@ -59,6 +59,10 @@ class Repository < ActiveRecord::Base
     scm.entries(path, identifier)
   end
   
+  def properties(path, identifier=nil)
+    scm.properties(path, identifier)
+  end
+  
   def cat(path, identifier=nil)
     scm.cat(path, identifier)
   end
index 868388f..4029a77 100644 (file)
@@ -7,6 +7,7 @@
 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2>
 
 <%= render :partial => 'dir_list' %>
+<%= render_properties(@properties) %>
 
 <% content_for :header_tags do %>
 <%= stylesheet_link_tag "scm" %>
index 85695ec..ca5c583 100644 (file)
@@ -1,7 +1,5 @@
 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
 
-<h3><%=h @entry.name %></h3>
-
 <p>
 <% if @repository.supports_cat? %>
     <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
@@ -13,6 +11,8 @@
 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
 </p>
 
+<%= render_properties(@properties) %>
+
 <%= render(:partial => 'revisions', 
            :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
 
index 0bacda7..a876de9 100644 (file)
@@ -24,6 +24,20 @@ module Redmine
       end
       
       class AbstractAdapter #:nodoc:
+        class << self
+          # Returns the version of the scm client
+          # Eg: [1, 5, 0]
+          def client_version
+            'Unknown version'
+          end
+          
+          # Returns the version string of the scm client
+          # Eg: '1.5.0'
+          def client_version_string
+            client_version.is_a?(Array) ? client_version.join('.') : client_version.to_s
+          end
+        end
+                
         def initialize(url, root_url=nil, login=nil, password=nil)
           @url = url
           @login = login if login && !login.empty?
@@ -77,6 +91,10 @@ module Redmine
         def entries(path=nil, identifier=nil)
           return nil
         end
+        
+        def properties(path, identifier=nil)
+          return nil
+        end
     
         def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
           return nil
@@ -131,10 +149,18 @@ module Redmine
         end
             
         def logger
-          RAILS_DEFAULT_LOGGER
+          self.class.logger
         end
         
         def shellout(cmd, &block)
+          self.class.shellout(cmd, &block)
+        end
+        
+        def self.logger
+          RAILS_DEFAULT_LOGGER
+        end
+        
+        def self.shellout(cmd, &block)
           logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
           begin
             IO.popen(cmd, "r+") do |io|
index 7c98eee..9094071 100644 (file)
@@ -26,6 +26,25 @@ module Redmine
         # SVN executable name\r
         SVN_BIN = "svn"\r
         \r
+        class << self\r
+          def client_version\r
+            @@client_version ||= (svn_binary_version || 'Unknown version')\r
+          end\r
+          \r
+          def svn_binary_version\r
+            cmd = "#{SVN_BIN} --version"\r
+            version = nil\r
+            shellout(cmd) do |io|\r
+              # Read svn version in first returned line\r
+              if m = io.gets.match(%r{((\d+\.)+\d+)})\r
+                version = m[0].scan(%r{\d+}).collect(&:to_i)\r
+              end\r
+            end\r
+            return nil if $? && $?.exitstatus != 0\r
+            version\r
+          end\r
+        end\r
+        \r
         # Get info about the svn repository\r
         def info\r
           cmd = "#{SVN_BIN} info --xml #{target('')}"\r
@@ -87,7 +106,29 @@ module Redmine
           logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?\r
           entries.sort_by_name\r
         end\r
-    \r
+        \r
+        def properties(path, identifier=nil)\r
+          # proplist xml output supported in svn 1.5.0 and higher\r
+          return nil if (self.class.client_version <=> [1, 5, 0]) < 0\r
+          \r
+          identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"\r
+          cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"\r
+          cmd << credentials_string\r
+          properties = {}\r
+          shellout(cmd) do |io|\r
+            output = io.read\r
+            begin\r
+              doc = REXML::Document.new(output)\r
+              doc.elements.each("properties/target/property") do |property|\r
+                properties[ property.attributes['name'] ] = property.text\r
+              end\r
+            rescue\r
+            end\r
+          end\r
+          return nil if $? && $?.exitstatus != 0\r
+          properties\r
+        end\r
+        \r
         def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})\r
           path ||= ''\r
           identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"\r
index c10dfbe..8f092ea 100644 (file)
@@ -217,6 +217,10 @@ table#time-report tbody tr.last-level { font-style: normal; color: #555; }
 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
 table#time-report .hours-dec { font-size: 0.9em; }
 
+ul.properties {padding:0; font-size: 0.9em; color: #777;}
+ul.properties li {list-style-type:none;}
+ul.properties li span {font-style:italic;}
+
 .total-hours { font-size: 110%; font-weight: bold; }
 .total-hours span.hours-int { font-size: 120%; }
 
index 6af5cd5..35dfbb1 100644 (file)
@@ -71,6 +71,19 @@ class RepositoriesSubversionControllerTest < Test::Unit::TestCase
       assert_not_nil assigns(:entries)
       assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], assigns(:entries).collect(&:name)
     end
+    
+    def test_changes
+      get :changes, :id => 1, :path => ['subversion_test', 'folder', 'helloworld.rb' ]
+      assert_response :success
+      assert_template 'changes'
+      # svn properties
+      assert_not_nil assigns(:properties)
+      assert_equal 'native', assigns(:properties)['svn:eol-style']
+      assert_tag :ul,
+                 :child => { :tag => 'li',
+                             :child => { :tag => 'b', :content => 'svn:eol-style' },
+                             :child => { :tag => 'span', :content => 'native' } }
+    end
       
     def test_entry
       get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c']
diff --git a/test/unit/subversion_adapter_test.rb b/test/unit/subversion_adapter_test.rb
new file mode 100644 (file)
index 0000000..9f20883
--- /dev/null
@@ -0,0 +1,33 @@
+# redMine - project management software
+# Copyright (C) 2006-2008  Jean-Philippe Lang
+#
+# 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 'mkmf'
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class SubversionAdapterTest < Test::Unit::TestCase
+  
+  if find_executable0('svn')
+    def test_client_version
+      v = Redmine::Scm::Adapters::SubversionAdapter.client_version
+      assert v.is_a?(Array)
+    end
+  else
+    puts "Subversion binary NOT FOUND. Skipping unit tests !!!"
+    def test_fake; assert true end
+  end
+end