Mercurial > hg > soundsoftware-site
comparison lib/redmine/scm/adapters/.svn/text-base/abstract_adapter.rb.svn-base @ 511:107d36338b70 live
Merge from branch "cannam"
author | Chris Cannam |
---|---|
date | Thu, 14 Jul 2011 10:43:07 +0100 |
parents | 0c939c159af4 |
children |
comparison
equal
deleted
inserted
replaced
451:a9f6345cb43d | 511:107d36338b70 |
---|---|
1 # redMine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006-2007 Jean-Philippe Lang | 2 # Copyright (C) 2006-2011 Jean-Philippe Lang |
3 # | 3 # |
4 # This program is free software; you can redistribute it and/or | 4 # This program is free software; you can redistribute it and/or |
5 # modify it under the terms of the GNU General Public License | 5 # modify it under the terms of the GNU General Public License |
6 # as published by the Free Software Foundation; either version 2 | 6 # as published by the Free Software Foundation; either version 2 |
7 # of the License, or (at your option) any later version. | 7 # of the License, or (at your option) any later version. |
8 # | 8 # |
9 # This program is distributed in the hope that it will be useful, | 9 # This program is distributed in the hope that it will be useful, |
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 # GNU General Public License for more details. | 12 # GNU General Public License for more details. |
13 # | 13 # |
14 # You should have received a copy of the GNU General Public License | 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 | 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. | 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 |
18 require 'cgi' | 18 require 'cgi' |
19 | 19 |
20 module Redmine | 20 module Redmine |
21 module Scm | 21 module Scm |
22 module Adapters | 22 module Adapters |
23 class CommandFailed < StandardError #:nodoc: | 23 class CommandFailed < StandardError #:nodoc: |
24 end | 24 end |
25 | 25 |
26 class AbstractAdapter #:nodoc: | 26 class AbstractAdapter #:nodoc: |
27 class << self | 27 class << self |
28 def client_command | |
29 "" | |
30 end | |
31 | |
28 # Returns the version of the scm client | 32 # Returns the version of the scm client |
29 # Eg: [1, 5, 0] or [] if unknown | 33 # Eg: [1, 5, 0] or [] if unknown |
30 def client_version | 34 def client_version |
31 [] | 35 [] |
32 end | 36 end |
33 | 37 |
34 # Returns the version string of the scm client | 38 # Returns the version string of the scm client |
35 # Eg: '1.5.0' or 'Unknown version' if unknown | 39 # Eg: '1.5.0' or 'Unknown version' if unknown |
36 def client_version_string | 40 def client_version_string |
37 v = client_version || 'Unknown version' | 41 v = client_version || 'Unknown version' |
38 v.is_a?(Array) ? v.join('.') : v.to_s | 42 v.is_a?(Array) ? v.join('.') : v.to_s |
39 end | 43 end |
40 | 44 |
41 # Returns true if the current client version is above | 45 # Returns true if the current client version is above |
42 # or equals the given one | 46 # or equals the given one |
43 # If option is :unknown is set to true, it will return | 47 # If option is :unknown is set to true, it will return |
44 # true if the client version is unknown | 48 # true if the client version is unknown |
45 def client_version_above?(v, options={}) | 49 def client_version_above?(v, options={}) |
46 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) | 50 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) |
47 end | 51 end |
48 end | 52 |
49 | 53 def client_available |
50 def initialize(url, root_url=nil, login=nil, password=nil) | 54 true |
55 end | |
56 | |
57 def shell_quote(str) | |
58 if Redmine::Platform.mswin? | |
59 '"' + str.gsub(/"/, '\\"') + '"' | |
60 else | |
61 "'" + str.gsub(/'/, "'\"'\"'") + "'" | |
62 end | |
63 end | |
64 end | |
65 | |
66 def initialize(url, root_url=nil, login=nil, password=nil, | |
67 path_encoding=nil) | |
51 @url = url | 68 @url = url |
52 @login = login if login && !login.empty? | 69 @login = login if login && !login.empty? |
53 @password = (password || "") if @login | 70 @password = (password || "") if @login |
54 @root_url = root_url.blank? ? retrieve_root_url : root_url | 71 @root_url = root_url.blank? ? retrieve_root_url : root_url |
55 end | 72 end |
56 | 73 |
57 def adapter_name | 74 def adapter_name |
58 'Abstract' | 75 'Abstract' |
59 end | 76 end |
60 | 77 |
61 def supports_cat? | 78 def supports_cat? |
62 true | 79 true |
63 end | 80 end |
64 | 81 |
65 def supports_annotate? | 82 def supports_annotate? |
66 respond_to?('annotate') | 83 respond_to?('annotate') |
67 end | 84 end |
68 | 85 |
69 def root_url | 86 def root_url |
70 @root_url | 87 @root_url |
71 end | 88 end |
72 | 89 |
73 def url | 90 def url |
74 @url | 91 @url |
75 end | 92 end |
76 | 93 |
94 def path_encoding | |
95 nil | |
96 end | |
97 | |
77 # get info about the svn repository | 98 # get info about the svn repository |
78 def info | 99 def info |
79 return nil | 100 return nil |
80 end | 101 end |
81 | 102 |
82 # Returns the entry identified by path and revision identifier | 103 # Returns the entry identified by path and revision identifier |
83 # or nil if entry doesn't exist in the repository | 104 # or nil if entry doesn't exist in the repository |
84 def entry(path=nil, identifier=nil) | 105 def entry(path=nil, identifier=nil) |
85 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} | 106 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} |
86 search_path = parts[0..-2].join('/') | 107 search_path = parts[0..-2].join('/') |
92 # Search for the entry in the parent directory | 113 # Search for the entry in the parent directory |
93 es = entries(search_path, identifier) | 114 es = entries(search_path, identifier) |
94 es ? es.detect {|e| e.name == search_name} : nil | 115 es ? es.detect {|e| e.name == search_name} : nil |
95 end | 116 end |
96 end | 117 end |
97 | 118 |
98 # Returns an Entries collection | 119 # Returns an Entries collection |
99 # or nil if the given path doesn't exist in the repository | 120 # or nil if the given path doesn't exist in the repository |
100 def entries(path=nil, identifier=nil) | 121 def entries(path=nil, identifier=nil, options={}) |
101 return nil | 122 return nil |
102 end | 123 end |
103 | 124 |
104 def branches | 125 def branches |
105 return nil | 126 return nil |
106 end | 127 end |
107 | 128 |
108 def tags | 129 def tags |
109 return nil | 130 return nil |
110 end | 131 end |
111 | 132 |
112 def default_branch | 133 def default_branch |
113 return nil | 134 return nil |
114 end | 135 end |
115 | 136 |
116 def properties(path, identifier=nil) | 137 def properties(path, identifier=nil) |
117 return nil | 138 return nil |
118 end | 139 end |
119 | 140 |
120 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) | 141 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
121 return nil | 142 return nil |
122 end | 143 end |
123 | 144 |
124 def diff(path, identifier_from, identifier_to=nil) | 145 def diff(path, identifier_from, identifier_to=nil) |
125 return nil | 146 return nil |
126 end | 147 end |
127 | 148 |
128 def cat(path, identifier=nil) | 149 def cat(path, identifier=nil) |
129 return nil | 150 return nil |
130 end | 151 end |
131 | 152 |
132 def with_leading_slash(path) | 153 def with_leading_slash(path) |
133 path ||= '' | 154 path ||= '' |
134 (path[0,1]!="/") ? "/#{path}" : path | 155 (path[0,1]!="/") ? "/#{path}" : path |
135 end | 156 end |
136 | 157 |
137 def with_trailling_slash(path) | 158 def with_trailling_slash(path) |
138 path ||= '' | 159 path ||= '' |
139 (path[-1,1] == "/") ? path : "#{path}/" | 160 (path[-1,1] == "/") ? path : "#{path}/" |
140 end | 161 end |
141 | 162 |
142 def without_leading_slash(path) | 163 def without_leading_slash(path) |
143 path ||= '' | 164 path ||= '' |
144 path.gsub(%r{^/+}, '') | 165 path.gsub(%r{^/+}, '') |
145 end | 166 end |
146 | 167 |
147 def without_trailling_slash(path) | 168 def without_trailling_slash(path) |
148 path ||= '' | 169 path ||= '' |
149 (path[-1,1] == "/") ? path[0..-2] : path | 170 (path[-1,1] == "/") ? path[0..-2] : path |
150 end | 171 end |
151 | 172 |
152 def shell_quote(str) | 173 def shell_quote(str) |
153 if Redmine::Platform.mswin? | 174 self.class.shell_quote(str) |
154 '"' + str.gsub(/"/, '\\"') + '"' | |
155 else | |
156 "'" + str.gsub(/'/, "'\"'\"'") + "'" | |
157 end | |
158 end | 175 end |
159 | 176 |
160 private | 177 private |
161 def retrieve_root_url | 178 def retrieve_root_url |
162 info = self.info | 179 info = self.info |
163 info ? info.root_url : nil | 180 info ? info.root_url : nil |
164 end | 181 end |
165 | 182 |
166 def target(path) | 183 def target(path) |
167 path ||= '' | 184 path ||= '' |
168 base = path.match(/^\//) ? root_url : url | 185 base = path.match(/^\//) ? root_url : url |
169 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, '')) | 186 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, '')) |
170 end | 187 end |
171 | 188 |
172 def logger | 189 def logger |
173 self.class.logger | 190 self.class.logger |
174 end | 191 end |
175 | 192 |
176 def shellout(cmd, &block) | 193 def shellout(cmd, &block) |
177 self.class.shellout(cmd, &block) | 194 self.class.shellout(cmd, &block) |
178 end | 195 end |
179 | 196 |
180 def self.logger | 197 def self.logger |
181 RAILS_DEFAULT_LOGGER | 198 RAILS_DEFAULT_LOGGER |
182 end | 199 end |
183 | 200 |
184 def self.shellout(cmd, &block) | 201 def self.shellout(cmd, &block) |
185 logger.debug "Shelling out: #{strip_credential(cmd)}" if logger && logger.debug? | 202 if logger && logger.debug? |
203 logger.debug "Shelling out: #{strip_credential(cmd)}" | |
204 end | |
186 if Rails.env == 'development' | 205 if Rails.env == 'development' |
187 # Capture stderr when running in dev environment | 206 # Capture stderr when running in dev environment |
188 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log" | 207 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log" |
189 end | 208 end |
190 begin | 209 begin |
191 IO.popen(cmd, "r+") do |io| | 210 if RUBY_VERSION < '1.9' |
211 mode = "r+" | |
212 else | |
213 mode = "r+:ASCII-8BIT" | |
214 end | |
215 IO.popen(cmd, mode) do |io| | |
192 io.close_write | 216 io.close_write |
193 block.call(io) if block_given? | 217 block.call(io) if block_given? |
194 end | 218 end |
195 rescue Errno::ENOENT => e | 219 rescue Errno::ENOENT => e |
196 msg = strip_credential(e.message) | 220 msg = strip_credential(e.message) |
197 # The command failed, log it and re-raise | 221 # The command failed, log it and re-raise |
198 logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{strip_credential(cmd)}\n with: #{msg}") | 222 logmsg = "SCM command failed, " |
223 logmsg += "make sure that your SCM command (e.g. svn) is " | |
224 logmsg += "in PATH (#{ENV['PATH']})\n" | |
225 logmsg += "You can configure your scm commands in config/configuration.yml.\n" | |
226 logmsg += "#{strip_credential(cmd)}\n" | |
227 logmsg += "with: #{msg}" | |
228 logger.error(logmsg) | |
199 raise CommandFailed.new(msg) | 229 raise CommandFailed.new(msg) |
200 end | 230 end |
201 end | 231 end |
202 | 232 |
203 # Hides username/password in a given command | 233 # Hides username/password in a given command |
204 def self.strip_credential(cmd) | 234 def self.strip_credential(cmd) |
205 q = (Redmine::Platform.mswin? ? '"' : "'") | 235 q = (Redmine::Platform.mswin? ? '"' : "'") |
206 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx') | 236 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx') |
207 end | 237 end |
208 | 238 |
209 def strip_credential(cmd) | 239 def strip_credential(cmd) |
210 self.class.strip_credential(cmd) | 240 self.class.strip_credential(cmd) |
211 end | 241 end |
212 end | 242 |
213 | 243 def scm_iconv(to, from, str) |
244 return nil if str.nil? | |
245 return str if to == from | |
246 begin | |
247 Iconv.conv(to, from, str) | |
248 rescue Iconv::Failure => err | |
249 logger.error("failed to convert from #{from} to #{to}. #{err}") | |
250 nil | |
251 end | |
252 end | |
253 end | |
254 | |
214 class Entries < Array | 255 class Entries < Array |
215 def sort_by_name | 256 def sort_by_name |
216 sort {|x,y| | 257 sort {|x,y| |
217 if x.kind == y.kind | 258 if x.kind == y.kind |
218 x.name.to_s <=> y.name.to_s | 259 x.name.to_s <=> y.name.to_s |
219 else | 260 else |
220 x.kind <=> y.kind | 261 x.kind <=> y.kind |
221 end | 262 end |
222 } | 263 } |
223 end | 264 end |
224 | 265 |
225 def revisions | 266 def revisions |
226 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact) | 267 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact) |
227 end | 268 end |
228 end | 269 end |
229 | 270 |
230 class Info | 271 class Info |
231 attr_accessor :root_url, :lastrev | 272 attr_accessor :root_url, :lastrev |
232 def initialize(attributes={}) | 273 def initialize(attributes={}) |
233 self.root_url = attributes[:root_url] if attributes[:root_url] | 274 self.root_url = attributes[:root_url] if attributes[:root_url] |
234 self.lastrev = attributes[:lastrev] | 275 self.lastrev = attributes[:lastrev] |
235 end | 276 end |
236 end | 277 end |
237 | 278 |
238 class Entry | 279 class Entry |
239 attr_accessor :name, :path, :kind, :size, :lastrev | 280 attr_accessor :name, :path, :kind, :size, :lastrev |
240 def initialize(attributes={}) | 281 def initialize(attributes={}) |
241 self.name = attributes[:name] if attributes[:name] | 282 self.name = attributes[:name] if attributes[:name] |
242 self.path = attributes[:path] if attributes[:path] | 283 self.path = attributes[:path] if attributes[:path] |
243 self.kind = attributes[:kind] if attributes[:kind] | 284 self.kind = attributes[:kind] if attributes[:kind] |
244 self.size = attributes[:size].to_i if attributes[:size] | 285 self.size = attributes[:size].to_i if attributes[:size] |
245 self.lastrev = attributes[:lastrev] | 286 self.lastrev = attributes[:lastrev] |
246 end | 287 end |
247 | 288 |
248 def is_file? | 289 def is_file? |
249 'file' == self.kind | 290 'file' == self.kind |
250 end | 291 end |
251 | 292 |
252 def is_dir? | 293 def is_dir? |
253 'dir' == self.kind | 294 'dir' == self.kind |
254 end | 295 end |
255 | 296 |
256 def is_text? | 297 def is_text? |
257 Redmine::MimeType.is_type?('text', name) | 298 Redmine::MimeType.is_type?('text', name) |
258 end | 299 end |
259 end | 300 end |
260 | 301 |
261 class Revisions < Array | 302 class Revisions < Array |
262 def latest | 303 def latest |
263 sort {|x,y| | 304 sort {|x,y| |
264 unless x.time.nil? or y.time.nil? | 305 unless x.time.nil? or y.time.nil? |
265 x.time <=> y.time | 306 x.time <=> y.time |
266 else | 307 else |
267 0 | 308 0 |
268 end | 309 end |
269 }.last | 310 }.last |
270 end | 311 end |
271 end | 312 end |
272 | 313 |
273 class Revision | 314 class Revision |
274 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch | 315 attr_accessor :scmid, :name, :author, :time, :message, |
316 :paths, :revision, :branch, :identifier | |
275 | 317 |
276 def initialize(attributes={}) | 318 def initialize(attributes={}) |
277 self.identifier = attributes[:identifier] | 319 self.identifier = attributes[:identifier] |
278 self.scmid = attributes[:scmid] | 320 self.scmid = attributes[:scmid] |
279 self.name = attributes[:name] || self.identifier | 321 self.name = attributes[:name] || self.identifier |
280 self.author = attributes[:author] | 322 self.author = attributes[:author] |
281 self.time = attributes[:time] | 323 self.time = attributes[:time] |
282 self.message = attributes[:message] || "" | 324 self.message = attributes[:message] || "" |
283 self.paths = attributes[:paths] | 325 self.paths = attributes[:paths] |
284 self.revision = attributes[:revision] | 326 self.revision = attributes[:revision] |
285 self.branch = attributes[:branch] | 327 self.branch = attributes[:branch] |
286 end | 328 end |
287 | 329 |
288 def save(repo) | 330 # Returns the readable identifier. |
289 Changeset.transaction do | 331 def format_identifier |
290 changeset = Changeset.new( | 332 self.identifier.to_s |
291 :repository => repo, | 333 end |
292 :revision => identifier, | 334 end |
293 :scmid => scmid, | 335 |
294 :committer => author, | |
295 :committed_on => time, | |
296 :comments => message) | |
297 | |
298 if changeset.save | |
299 paths.each do |file| | |
300 Change.create( | |
301 :changeset => changeset, | |
302 :action => file[:action], | |
303 :path => file[:path]) | |
304 end | |
305 end | |
306 end | |
307 end | |
308 end | |
309 | |
310 class Annotate | 336 class Annotate |
311 attr_reader :lines, :revisions | 337 attr_reader :lines, :revisions |
312 | 338 |
313 def initialize | 339 def initialize |
314 @lines = [] | 340 @lines = [] |
315 @revisions = [] | 341 @revisions = [] |
316 end | 342 end |
317 | 343 |
318 def add_line(line, revision) | 344 def add_line(line, revision) |
319 @lines << line | 345 @lines << line |
320 @revisions << revision | 346 @revisions << revision |
321 end | 347 end |
322 | 348 |
323 def content | 349 def content |
324 content = lines.join("\n") | 350 content = lines.join("\n") |
325 end | 351 end |
326 | 352 |
327 def empty? | 353 def empty? |
328 lines.empty? | 354 lines.empty? |
329 end | 355 end |
330 end | 356 end |
331 end | 357 end |