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