annotate lib/redmine/scm/adapters/.svn/text-base/subversion_adapter.rb.svn-base @ 872:9d7526c3a78a feature_124

Close obsolete branch feature_124
author Chris Cannam
date Sat, 02 Apr 2011 11:56:49 +0100
parents 513646585e45
children af80e5618e9b
rev   line source
Chris@0 1 # Redmine - project management software
Chris@0 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require 'redmine/scm/adapters/abstract_adapter'
Chris@0 19 require 'uri'
Chris@0 20
Chris@0 21 module Redmine
Chris@0 22 module Scm
Chris@0 23 module Adapters
Chris@0 24 class SubversionAdapter < AbstractAdapter
Chris@0 25
Chris@0 26 # SVN executable name
Chris@0 27 SVN_BIN = "svn"
Chris@0 28
Chris@0 29 class << self
Chris@0 30 def client_version
Chris@0 31 @@client_version ||= (svn_binary_version || [])
Chris@0 32 end
Chris@0 33
Chris@0 34 def svn_binary_version
Chris@0 35 cmd = "#{SVN_BIN} --version"
Chris@0 36 version = nil
Chris@0 37 shellout(cmd) do |io|
Chris@0 38 # Read svn version in first returned line
Chris@0 39 if m = io.gets.to_s.match(%r{((\d+\.)+\d+)})
Chris@0 40 version = m[0].scan(%r{\d+}).collect(&:to_i)
Chris@0 41 end
Chris@0 42 end
Chris@0 43 return nil if $? && $?.exitstatus != 0
Chris@0 44 version
Chris@0 45 end
Chris@0 46 end
Chris@0 47
Chris@0 48 # Get info about the svn repository
Chris@0 49 def info
Chris@0 50 cmd = "#{SVN_BIN} info --xml #{target}"
Chris@0 51 cmd << credentials_string
Chris@0 52 info = nil
Chris@0 53 shellout(cmd) do |io|
Chris@0 54 output = io.read
Chris@0 55 begin
Chris@0 56 doc = ActiveSupport::XmlMini.parse(output)
Chris@0 57 #root_url = doc.elements["info/entry/repository/root"].text
Chris@0 58 info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
Chris@0 59 :lastrev => Revision.new({
Chris@0 60 :identifier => doc['info']['entry']['commit']['revision'],
Chris@0 61 :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
Chris@0 62 :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
Chris@0 63 })
Chris@0 64 })
Chris@0 65 rescue
Chris@0 66 end
Chris@0 67 end
Chris@0 68 return nil if $? && $?.exitstatus != 0
Chris@0 69 info
Chris@0 70 rescue CommandFailed
Chris@0 71 return nil
Chris@0 72 end
Chris@0 73
Chris@0 74 # Returns an Entries collection
Chris@0 75 # or nil if the given path doesn't exist in the repository
Chris@0 76 def entries(path=nil, identifier=nil)
Chris@0 77 path ||= ''
Chris@0 78 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
Chris@0 79 entries = Entries.new
Chris@0 80 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
Chris@0 81 cmd << credentials_string
Chris@0 82 shellout(cmd) do |io|
Chris@0 83 output = io.read
Chris@0 84 begin
Chris@0 85 doc = ActiveSupport::XmlMini.parse(output)
Chris@0 86 each_xml_element(doc['lists']['list'], 'entry') do |entry|
Chris@0 87 commit = entry['commit']
Chris@0 88 commit_date = commit['date']
Chris@0 89 # Skip directory if there is no commit date (usually that
Chris@0 90 # means that we don't have read access to it)
Chris@0 91 next if entry['kind'] == 'dir' && commit_date.nil?
Chris@0 92 name = entry['name']['__content__']
Chris@0 93 entries << Entry.new({:name => URI.unescape(name),
Chris@0 94 :path => ((path.empty? ? "" : "#{path}/") + name),
Chris@0 95 :kind => entry['kind'],
Chris@0 96 :size => ((s = entry['size']) ? s['__content__'].to_i : nil),
Chris@0 97 :lastrev => Revision.new({
Chris@0 98 :identifier => commit['revision'],
Chris@0 99 :time => Time.parse(commit_date['__content__'].to_s).localtime,
Chris@0 100 :author => ((a = commit['author']) ? a['__content__'] : nil)
Chris@0 101 })
Chris@0 102 })
Chris@0 103 end
Chris@0 104 rescue Exception => e
Chris@0 105 logger.error("Error parsing svn output: #{e.message}")
Chris@0 106 logger.error("Output was:\n #{output}")
Chris@0 107 end
Chris@0 108 end
Chris@0 109 return nil if $? && $?.exitstatus != 0
Chris@0 110 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
Chris@0 111 entries.sort_by_name
Chris@0 112 end
Chris@0 113
Chris@0 114 def properties(path, identifier=nil)
Chris@0 115 # proplist xml output supported in svn 1.5.0 and higher
Chris@0 116 return nil unless self.class.client_version_above?([1, 5, 0])
Chris@0 117
Chris@0 118 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
Chris@0 119 cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"
Chris@0 120 cmd << credentials_string
Chris@0 121 properties = {}
Chris@0 122 shellout(cmd) do |io|
Chris@0 123 output = io.read
Chris@0 124 begin
Chris@0 125 doc = ActiveSupport::XmlMini.parse(output)
Chris@0 126 each_xml_element(doc['properties']['target'], 'property') do |property|
Chris@0 127 properties[ property['name'] ] = property['__content__'].to_s
Chris@0 128 end
Chris@0 129 rescue
Chris@0 130 end
Chris@0 131 end
Chris@0 132 return nil if $? && $?.exitstatus != 0
Chris@0 133 properties
Chris@0 134 end
Chris@0 135
Chris@0 136 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
Chris@0 137 path ||= ''
Chris@0 138 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
Chris@0 139 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
Chris@0 140 revisions = Revisions.new
Chris@0 141 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
Chris@0 142 cmd << credentials_string
Chris@0 143 cmd << " --verbose " if options[:with_paths]
Chris@0 144 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
Chris@0 145 cmd << ' ' + target(path)
Chris@0 146 shellout(cmd) do |io|
Chris@0 147 output = io.read
Chris@0 148 begin
Chris@0 149 doc = ActiveSupport::XmlMini.parse(output)
Chris@0 150 each_xml_element(doc['log'], 'logentry') do |logentry|
Chris@0 151 paths = []
Chris@0 152 each_xml_element(logentry['paths'], 'path') do |path|
Chris@0 153 paths << {:action => path['action'],
Chris@0 154 :path => path['__content__'],
Chris@0 155 :from_path => path['copyfrom-path'],
Chris@0 156 :from_revision => path['copyfrom-rev']
Chris@0 157 }
Chris@0 158 end if logentry['paths'] && logentry['paths']['path']
Chris@0 159 paths.sort! { |x,y| x[:path] <=> y[:path] }
Chris@0 160
Chris@0 161 revisions << Revision.new({:identifier => logentry['revision'],
Chris@0 162 :author => (logentry['author'] ? logentry['author']['__content__'] : ""),
Chris@0 163 :time => Time.parse(logentry['date']['__content__'].to_s).localtime,
Chris@0 164 :message => logentry['msg']['__content__'],
Chris@0 165 :paths => paths
Chris@0 166 })
Chris@0 167 end
Chris@0 168 rescue
Chris@0 169 end
Chris@0 170 end
Chris@0 171 return nil if $? && $?.exitstatus != 0
Chris@0 172 revisions
Chris@0 173 end
Chris@0 174
Chris@0 175 def diff(path, identifier_from, identifier_to=nil, type="inline")
Chris@0 176 path ||= ''
Chris@0 177 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
Chris@0 178 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
Chris@0 179
Chris@0 180 cmd = "#{SVN_BIN} diff -r "
Chris@0 181 cmd << "#{identifier_to}:"
Chris@0 182 cmd << "#{identifier_from}"
Chris@0 183 cmd << " #{target(path)}@#{identifier_from}"
Chris@0 184 cmd << credentials_string
Chris@0 185 diff = []
Chris@0 186 shellout(cmd) do |io|
Chris@0 187 io.each_line do |line|
Chris@0 188 diff << line
Chris@0 189 end
Chris@0 190 end
Chris@0 191 return nil if $? && $?.exitstatus != 0
Chris@0 192 diff
Chris@0 193 end
Chris@0 194
Chris@0 195 def cat(path, identifier=nil)
Chris@0 196 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
Chris@0 197 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
Chris@0 198 cmd << credentials_string
Chris@0 199 cat = nil
Chris@0 200 shellout(cmd) do |io|
Chris@0 201 io.binmode
Chris@0 202 cat = io.read
Chris@0 203 end
Chris@0 204 return nil if $? && $?.exitstatus != 0
Chris@0 205 cat
Chris@0 206 end
Chris@0 207
Chris@0 208 def annotate(path, identifier=nil)
Chris@0 209 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
Chris@0 210 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
Chris@0 211 cmd << credentials_string
Chris@0 212 blame = Annotate.new
Chris@0 213 shellout(cmd) do |io|
Chris@0 214 io.each_line do |line|
Chris@0 215 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
Chris@0 216 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
Chris@0 217 end
Chris@0 218 end
Chris@0 219 return nil if $? && $?.exitstatus != 0
Chris@0 220 blame
Chris@0 221 end
Chris@0 222
Chris@0 223 private
Chris@0 224
Chris@0 225 def credentials_string
Chris@0 226 str = ''
Chris@0 227 str << " --username #{shell_quote(@login)}" unless @login.blank?
Chris@0 228 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
Chris@0 229 str << " --no-auth-cache --non-interactive"
Chris@0 230 str
Chris@0 231 end
Chris@0 232
Chris@0 233 # Helper that iterates over the child elements of a xml node
Chris@0 234 # MiniXml returns a hash when a single child is found or an array of hashes for multiple children
Chris@0 235 def each_xml_element(node, name)
Chris@0 236 if node && node[name]
Chris@0 237 if node[name].is_a?(Hash)
Chris@0 238 yield node[name]
Chris@0 239 else
Chris@0 240 node[name].each do |element|
Chris@0 241 yield element
Chris@0 242 end
Chris@0 243 end
Chris@0 244 end
Chris@0 245 end
Chris@0 246
Chris@0 247 def target(path = '')
Chris@0 248 base = path.match(/^\//) ? root_url : url
Chris@0 249 uri = "#{base}/#{path}"
Chris@0 250 uri = URI.escape(URI.escape(uri), '[]')
Chris@0 251 shell_quote(uri.gsub(/[?<>\*]/, ''))
Chris@0 252 end
Chris@0 253 end
Chris@0 254 end
Chris@0 255 end
Chris@0 256 end