Mercurial > hg > soundsoftware-site
view lib/redmine/scm/adapters/subversion_adapter.rb @ 1082:997f6d7738f7 bug_531
In repo controller entry action, show the page for the file even if it's binary (so user still has access to history etc links). This makes it possible to use the entry action as the default when a file is clicked on
author | Chris Cannam <chris.cannam@soundsoftware.ac.uk> |
---|---|
date | Thu, 22 Nov 2012 18:04:17 +0000 |
parents | cbb26bc654de |
children | 433d4f72a19b |
line wrap: on
line source
# Redmine - project management software # Copyright (C) 2006-2011 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 'redmine/scm/adapters/abstract_adapter' require 'uri' module Redmine module Scm module Adapters class SubversionAdapter < AbstractAdapter # SVN executable name SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" class << self def client_command @@bin ||= SVN_BIN end def sq_bin @@sq_bin ||= shell_quote_command end def client_version @@client_version ||= (svn_binary_version || []) end def client_available # --xml options are introduced in 1.3. # http://subversion.apache.org/docs/release-notes/1.3.html client_version_above?([1, 3]) end def svn_binary_version scm_version = scm_version_from_command_line.dup if scm_version.respond_to?(:force_encoding) scm_version.force_encoding('ASCII-8BIT') end if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) m[2].scan(%r{\d+}).collect(&:to_i) end end def scm_version_from_command_line shellout("#{sq_bin} --version") { |io| io.read }.to_s end end # Get info about the svn repository def info cmd = "#{self.class.sq_bin} info --xml #{target}" cmd << credentials_string info = nil shellout(cmd) do |io| output = io.read if output.respond_to?(:force_encoding) output.force_encoding('UTF-8') end begin doc = ActiveSupport::XmlMini.parse(output) # root_url = doc.elements["info/entry/repository/root"].text info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'], :lastrev => Revision.new({ :identifier => doc['info']['entry']['commit']['revision'], :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime, :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "") }) }) rescue end end return nil if $? && $?.exitstatus != 0 info rescue CommandFailed return nil end # Returns an Entries collection # or nil if the given path doesn't exist in the repository def entries(path=nil, identifier=nil, options={}) path ||= '' identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" entries = Entries.new cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}" cmd << credentials_string shellout(cmd) do |io| output = io.read if output.respond_to?(:force_encoding) output.force_encoding('UTF-8') end begin doc = ActiveSupport::XmlMini.parse(output) each_xml_element(doc['lists']['list'], 'entry') do |entry| commit = entry['commit'] commit_date = commit['date'] # Skip directory if there is no commit date (usually that # means that we don't have read access to it) next if entry['kind'] == 'dir' && commit_date.nil? name = entry['name']['__content__'] entries << Entry.new({:name => URI.unescape(name), :path => ((path.empty? ? "" : "#{path}/") + name), :kind => entry['kind'], :size => ((s = entry['size']) ? s['__content__'].to_i : nil), :lastrev => Revision.new({ :identifier => commit['revision'], :time => Time.parse(commit_date['__content__'].to_s).localtime, :author => ((a = commit['author']) ? a['__content__'] : nil) }) }) end rescue Exception => e logger.error("Error parsing svn output: #{e.message}") logger.error("Output was:\n #{output}") end end return nil if $? && $?.exitstatus != 0 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 unless self.class.client_version_above?([1, 5, 0]) identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}" cmd << credentials_string properties = {} shellout(cmd) do |io| output = io.read if output.respond_to?(:force_encoding) output.force_encoding('UTF-8') end begin doc = ActiveSupport::XmlMini.parse(output) each_xml_element(doc['properties']['target'], 'property') do |property| properties[ property['name'] ] = property['__content__'].to_s 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 && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1 revisions = Revisions.new cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}" cmd << credentials_string cmd << " --verbose " if options[:with_paths] cmd << " --limit #{options[:limit].to_i}" if options[:limit] cmd << ' ' + target(path) shellout(cmd) do |io| output = io.read if output.respond_to?(:force_encoding) output.force_encoding('UTF-8') end begin doc = ActiveSupport::XmlMini.parse(output) each_xml_element(doc['log'], 'logentry') do |logentry| paths = [] each_xml_element(logentry['paths'], 'path') do |path| paths << {:action => path['action'], :path => path['__content__'], :from_path => path['copyfrom-path'], :from_revision => path['copyfrom-rev'] } end if logentry['paths'] && logentry['paths']['path'] paths.sort! { |x,y| x[:path] <=> y[:path] } revisions << Revision.new({:identifier => logentry['revision'], :author => (logentry['author'] ? logentry['author']['__content__'] : ""), :time => Time.parse(logentry['date']['__content__'].to_s).localtime, :message => logentry['msg']['__content__'], :paths => paths }) end rescue end end return nil if $? && $?.exitstatus != 0 revisions end def diff(path, identifier_from, identifier_to=nil, type="inline") path ||= '' identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : '' identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1) cmd = "#{self.class.sq_bin} diff -r " cmd << "#{identifier_to}:" cmd << "#{identifier_from}" cmd << " #{target(path)}@#{identifier_from}" cmd << credentials_string diff = [] shellout(cmd) do |io| io.each_line do |line| diff << line end end return nil if $? && $?.exitstatus != 0 diff end def cat(path, identifier=nil) identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}" cmd << credentials_string cat = nil shellout(cmd) do |io| io.binmode cat = io.read end return nil if $? && $?.exitstatus != 0 cat end def annotate(path, identifier=nil) identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}" cmd << credentials_string blame = Annotate.new shellout(cmd) do |io| io.each_line do |line| next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$} rev = $1 blame.add_line($3.rstrip, Revision.new( :identifier => rev, :revision => rev, :author => $2.strip )) end end return nil if $? && $?.exitstatus != 0 blame end private def credentials_string str = '' str << " --username #{shell_quote(@login)}" unless @login.blank? str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank? str << " --no-auth-cache --non-interactive" str end # Helper that iterates over the child elements of a xml node # MiniXml returns a hash when a single child is found # or an array of hashes for multiple children def each_xml_element(node, name) if node && node[name] if node[name].is_a?(Hash) yield node[name] else node[name].each do |element| yield element end end end end def target(path = '') base = path.match(/^\//) ? root_url : url uri = "#{base}/#{path}" uri = URI.escape(URI.escape(uri), '[]') shell_quote(uri.gsub(/[?<>\*]/, '')) end end end end end