From 12fbd06c02d44fdb96922154476302f002783e23 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sat, 5 Jul 2008 08:59:04 +0000 Subject: [PATCH] Display svn properties in the browser, svn >= 1.5.0 only (#1581). git-svn-id: http://redmine.rubyforge.org/svn/trunk@1627 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/repositories_controller.rb | 4 +- app/helpers/repositories_helper.rb | 10 +++++ app/models/repository.rb | 4 ++ app/views/repositories/browse.rhtml | 1 + app/views/repositories/changes.rhtml | 4 +- lib/redmine/scm/adapters/abstract_adapter.rb | 28 +++++++++++++- lib/redmine/scm/adapters/subversion_adapter.rb | 43 +++++++++++++++++++++- public/stylesheets/application.css | 4 ++ .../repositories_subversion_controller_test.rb | 13 +++++++ test/unit/subversion_adapter_test.rb | 33 +++++++++++++++++ 10 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 test/unit/subversion_adapter_test.rb diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 7b338734..13bba620 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -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 diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 4ef337c2..59e1e0fb 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -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', "#{h property}: #{h properties[property]}") + end + content_tag('ul', content, :class => 'properties') + end + end + def to_path_param(path) path.to_s.split(%r{[/\\]}).select {|p| !p.blank?} end diff --git a/app/models/repository.rb b/app/models/repository.rb index c7bf0dbf..2a8728f9 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -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 diff --git a/app/views/repositories/browse.rhtml b/app/views/repositories/browse.rhtml index 868388f1..4029a77d 100644 --- a/app/views/repositories/browse.rhtml +++ b/app/views/repositories/browse.rhtml @@ -7,6 +7,7 @@

<%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %>

<%= render :partial => 'dir_list' %> +<%= render_properties(@properties) %> <% content_for :header_tags do %> <%= stylesheet_link_tag "scm" %> diff --git a/app/views/repositories/changes.rhtml b/app/views/repositories/changes.rhtml index 85695ec4..ca5c5832 100644 --- a/app/views/repositories/changes.rhtml +++ b/app/views/repositories/changes.rhtml @@ -1,7 +1,5 @@

<%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %>

-

<%=h @entry.name %>

-

<% 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 %>

+<%= render_properties(@properties) %> + <%= render(:partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %> diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb index 0bacda77..a876de93 100644 --- a/lib/redmine/scm/adapters/abstract_adapter.rb +++ b/lib/redmine/scm/adapters/abstract_adapter.rb @@ -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| diff --git a/lib/redmine/scm/adapters/subversion_adapter.rb b/lib/redmine/scm/adapters/subversion_adapter.rb index 7c98eee8..90940715 100644 --- a/lib/redmine/scm/adapters/subversion_adapter.rb +++ b/lib/redmine/scm/adapters/subversion_adapter.rb @@ -26,6 +26,25 @@ module Redmine # SVN executable name SVN_BIN = "svn" + class << self + def client_version + @@client_version ||= (svn_binary_version || 'Unknown version') + end + + def svn_binary_version + cmd = "#{SVN_BIN} --version" + version = nil + shellout(cmd) do |io| + # Read svn version in first returned line + if m = io.gets.match(%r{((\d+\.)+\d+)}) + version = m[0].scan(%r{\d+}).collect(&:to_i) + end + end + return nil if $? && $?.exitstatus != 0 + version + end + end + # Get info about the svn repository def info cmd = "#{SVN_BIN} info --xml #{target('')}" @@ -87,7 +106,29 @@ module Redmine logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? entries.sort_by_name end - + + def properties(path, identifier=nil) + # proplist xml output supported in svn 1.5.0 and higher + return nil if (self.class.client_version <=> [1, 5, 0]) < 0 + + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}" + cmd << credentials_string + properties = {} + shellout(cmd) do |io| + output = io.read + begin + doc = REXML::Document.new(output) + doc.elements.each("properties/target/property") do |property| + properties[ property.attributes['name'] ] = property.text + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + properties + end + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) path ||= '' identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index c10dfbef..8f092eae 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -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%; } diff --git a/test/functional/repositories_subversion_controller_test.rb b/test/functional/repositories_subversion_controller_test.rb index 6af5cd5d..35dfbb1a 100644 --- a/test/functional/repositories_subversion_controller_test.rb +++ b/test/functional/repositories_subversion_controller_test.rb @@ -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 index 00000000..9f208839 --- /dev/null +++ b/test/unit/subversion_adapter_test.rb @@ -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 -- 2.11.0