comparison lib/redmine/scm/adapters/subversion_adapter.rb @ 245:051f544170fe

* Update to SVN trunk revision 4993
author Chris Cannam
date Thu, 03 Mar 2011 11:42:28 +0000
parents 0579821a129a
children cbce1fd3b1b7
comparison
equal deleted inserted replaced
244:8972b600f4fb 245:051f544170fe
18 require 'redmine/scm/adapters/abstract_adapter' 18 require 'redmine/scm/adapters/abstract_adapter'
19 require 'uri' 19 require 'uri'
20 20
21 module Redmine 21 module Redmine
22 module Scm 22 module Scm
23 module Adapters 23 module Adapters
24 class SubversionAdapter < AbstractAdapter 24 class SubversionAdapter < AbstractAdapter
25 25
26 # SVN executable name 26 # SVN executable name
27 SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" 27 SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
28 28
29 class << self 29 class << self
30 def client_command
31 @@bin ||= SVN_BIN
32 end
33
34 def sq_bin
35 @@sq_bin ||= shell_quote(SVN_BIN)
36 end
37
30 def client_version 38 def client_version
31 @@client_version ||= (svn_binary_version || []) 39 @@client_version ||= (svn_binary_version || [])
32 end 40 end
33 41
42 def client_available
43 !client_version.empty?
44 end
45
34 def svn_binary_version 46 def svn_binary_version
35 cmd = "#{SVN_BIN} --version" 47 scm_version = scm_version_from_command_line.dup
36 version = nil 48 if scm_version.respond_to?(:force_encoding)
37 shellout(cmd) do |io| 49 scm_version.force_encoding('ASCII-8BIT')
38 # Read svn version in first returned line 50 end
39 if m = io.read.to_s.match(%r{\A(.*?)((\d+\.)+\d+)}) 51 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
40 version = m[2].scan(%r{\d+}).collect(&:to_i) 52 m[2].scan(%r{\d+}).collect(&:to_i)
41 end 53 end
42 end 54 end
43 return nil if $? && $?.exitstatus != 0 55
44 version 56 def scm_version_from_command_line
45 end 57 shellout("#{sq_bin} --version") { |io| io.read }.to_s
46 end 58 end
47 59 end
60
48 # Get info about the svn repository 61 # Get info about the svn repository
49 def info 62 def info
50 cmd = "#{SVN_BIN} info --xml #{target}" 63 cmd = "#{self.class.sq_bin} info --xml #{target}"
51 cmd << credentials_string 64 cmd << credentials_string
52 info = nil 65 info = nil
53 shellout(cmd) do |io| 66 shellout(cmd) do |io|
54 output = io.read 67 output = io.read
68 if output.respond_to?(:force_encoding)
69 output.force_encoding('UTF-8')
70 end
55 begin 71 begin
56 doc = ActiveSupport::XmlMini.parse(output) 72 doc = ActiveSupport::XmlMini.parse(output)
57 #root_url = doc.elements["info/entry/repository/root"].text 73 #root_url = doc.elements["info/entry/repository/root"].text
58 info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'], 74 info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
59 :lastrev => Revision.new({ 75 :lastrev => Revision.new({
68 return nil if $? && $?.exitstatus != 0 84 return nil if $? && $?.exitstatus != 0
69 info 85 info
70 rescue CommandFailed 86 rescue CommandFailed
71 return nil 87 return nil
72 end 88 end
73 89
74 # Returns an Entries collection 90 # Returns an Entries collection
75 # or nil if the given path doesn't exist in the repository 91 # or nil if the given path doesn't exist in the repository
76 def entries(path=nil, identifier=nil) 92 def entries(path=nil, identifier=nil)
77 path ||= '' 93 path ||= ''
78 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" 94 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
79 entries = Entries.new 95 entries = Entries.new
80 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}" 96 cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}"
81 cmd << credentials_string 97 cmd << credentials_string
82 shellout(cmd) do |io| 98 shellout(cmd) do |io|
83 output = io.read 99 output = io.read
100 if output.respond_to?(:force_encoding)
101 output.force_encoding('UTF-8')
102 end
84 begin 103 begin
85 doc = ActiveSupport::XmlMini.parse(output) 104 doc = ActiveSupport::XmlMini.parse(output)
86 each_xml_element(doc['lists']['list'], 'entry') do |entry| 105 each_xml_element(doc['lists']['list'], 'entry') do |entry|
87 commit = entry['commit'] 106 commit = entry['commit']
88 commit_date = commit['date'] 107 commit_date = commit['date']
108 end 127 end
109 return nil if $? && $?.exitstatus != 0 128 return nil if $? && $?.exitstatus != 0
110 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? 129 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
111 entries.sort_by_name 130 entries.sort_by_name
112 end 131 end
113 132
114 def properties(path, identifier=nil) 133 def properties(path, identifier=nil)
115 # proplist xml output supported in svn 1.5.0 and higher 134 # proplist xml output supported in svn 1.5.0 and higher
116 return nil unless self.class.client_version_above?([1, 5, 0]) 135 return nil unless self.class.client_version_above?([1, 5, 0])
117 136
118 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" 137 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
119 cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}" 138 cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}"
120 cmd << credentials_string 139 cmd << credentials_string
121 properties = {} 140 properties = {}
122 shellout(cmd) do |io| 141 shellout(cmd) do |io|
123 output = io.read 142 output = io.read
143 if output.respond_to?(:force_encoding)
144 output.force_encoding('UTF-8')
145 end
124 begin 146 begin
125 doc = ActiveSupport::XmlMini.parse(output) 147 doc = ActiveSupport::XmlMini.parse(output)
126 each_xml_element(doc['properties']['target'], 'property') do |property| 148 each_xml_element(doc['properties']['target'], 'property') do |property|
127 properties[ property['name'] ] = property['__content__'].to_s 149 properties[ property['name'] ] = property['__content__'].to_s
128 end 150 end
130 end 152 end
131 end 153 end
132 return nil if $? && $?.exitstatus != 0 154 return nil if $? && $?.exitstatus != 0
133 properties 155 properties
134 end 156 end
135 157
136 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) 158 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
137 path ||= '' 159 path ||= ''
138 identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" 160 identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
139 identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1 161 identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
140 revisions = Revisions.new 162 revisions = Revisions.new
141 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}" 163 cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}"
142 cmd << credentials_string 164 cmd << credentials_string
143 cmd << " --verbose " if options[:with_paths] 165 cmd << " --verbose " if options[:with_paths]
144 cmd << " --limit #{options[:limit].to_i}" if options[:limit] 166 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
145 cmd << ' ' + target(path) 167 cmd << ' ' + target(path)
146 shellout(cmd) do |io| 168 shellout(cmd) do |io|
147 output = io.read 169 output = io.read
170 if output.respond_to?(:force_encoding)
171 output.force_encoding('UTF-8')
172 end
148 begin 173 begin
149 doc = ActiveSupport::XmlMini.parse(output) 174 doc = ActiveSupport::XmlMini.parse(output)
150 each_xml_element(doc['log'], 'logentry') do |logentry| 175 each_xml_element(doc['log'], 'logentry') do |logentry|
151 paths = [] 176 paths = []
152 each_xml_element(logentry['paths'], 'path') do |path| 177 each_xml_element(logentry['paths'], 'path') do |path|
169 end 194 end
170 end 195 end
171 return nil if $? && $?.exitstatus != 0 196 return nil if $? && $?.exitstatus != 0
172 revisions 197 revisions
173 end 198 end
174 199
175 def diff(path, identifier_from, identifier_to=nil, type="inline") 200 def diff(path, identifier_from, identifier_to=nil, type="inline")
176 path ||= '' 201 path ||= ''
177 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : '' 202 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
203
178 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1) 204 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
179 205
180 cmd = "#{SVN_BIN} diff -r " 206 cmd = "#{self.class.sq_bin} diff -r "
181 cmd << "#{identifier_to}:" 207 cmd << "#{identifier_to}:"
182 cmd << "#{identifier_from}" 208 cmd << "#{identifier_from}"
183 cmd << " #{target(path)}@#{identifier_from}" 209 cmd << " #{target(path)}@#{identifier_from}"
184 cmd << credentials_string 210 cmd << credentials_string
185 diff = [] 211 diff = []
189 end 215 end
190 end 216 end
191 return nil if $? && $?.exitstatus != 0 217 return nil if $? && $?.exitstatus != 0
192 diff 218 diff
193 end 219 end
194 220
195 def cat(path, identifier=nil) 221 def cat(path, identifier=nil)
196 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" 222 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
197 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}" 223 cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}"
198 cmd << credentials_string 224 cmd << credentials_string
199 cat = nil 225 cat = nil
200 shellout(cmd) do |io| 226 shellout(cmd) do |io|
201 io.binmode 227 io.binmode
202 cat = io.read 228 cat = io.read
203 end 229 end
204 return nil if $? && $?.exitstatus != 0 230 return nil if $? && $?.exitstatus != 0
205 cat 231 cat
206 end 232 end
207 233
208 def annotate(path, identifier=nil) 234 def annotate(path, identifier=nil)
209 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" 235 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
210 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}" 236 cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}"
211 cmd << credentials_string 237 cmd << credentials_string
212 blame = Annotate.new 238 blame = Annotate.new
213 shellout(cmd) do |io| 239 shellout(cmd) do |io|
214 io.each_line do |line| 240 io.each_line do |line|
215 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$} 241 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}