##// END OF EJS Templates
Merge(backport) r6230 from trunk (#8825)....
Toshi MARUYAMA -
r6153:68e97858cb02
parent child
Show More
@@ -1,359 +1,363
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 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
28 def client_command
29 ""
29 ""
30 end
30 end
31
31
32 # Returns the version of the scm client
32 # Returns the version of the scm client
33 # Eg: [1, 5, 0] or [] if unknown
33 # Eg: [1, 5, 0] or [] if unknown
34 def client_version
34 def client_version
35 []
35 []
36 end
36 end
37
37
38 # Returns the version string of the scm client
38 # Returns the version string of the scm client
39 # Eg: '1.5.0' or 'Unknown version' if unknown
39 # Eg: '1.5.0' or 'Unknown version' if unknown
40 def client_version_string
40 def client_version_string
41 v = client_version || 'Unknown version'
41 v = client_version || 'Unknown version'
42 v.is_a?(Array) ? v.join('.') : v.to_s
42 v.is_a?(Array) ? v.join('.') : v.to_s
43 end
43 end
44
44
45 # Returns true if the current client version is above
45 # Returns true if the current client version is above
46 # or equals the given one
46 # or equals the given one
47 # If option is :unknown is set to true, it will return
47 # If option is :unknown is set to true, it will return
48 # true if the client version is unknown
48 # true if the client version is unknown
49 def client_version_above?(v, options={})
49 def client_version_above?(v, options={})
50 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
50 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
51 end
51 end
52
52
53 def client_available
53 def client_available
54 true
54 true
55 end
55 end
56
56
57 def shell_quote(str)
57 def shell_quote(str)
58 if Redmine::Platform.mswin?
58 if Redmine::Platform.mswin?
59 '"' + str.gsub(/"/, '\\"') + '"'
59 '"' + str.gsub(/"/, '\\"') + '"'
60 else
60 else
61 "'" + str.gsub(/'/, "'\"'\"'") + "'"
61 "'" + str.gsub(/'/, "'\"'\"'") + "'"
62 end
62 end
63 end
63 end
64 end
64 end
65
65
66 def initialize(url, root_url=nil, login=nil, password=nil,
66 def initialize(url, root_url=nil, login=nil, password=nil,
67 path_encoding=nil)
67 path_encoding=nil)
68 @url = url
68 @url = url
69 @login = login if login && !login.empty?
69 @login = login if login && !login.empty?
70 @password = (password || "") if @login
70 @password = (password || "") if @login
71 @root_url = root_url.blank? ? retrieve_root_url : root_url
71 @root_url = root_url.blank? ? retrieve_root_url : root_url
72 end
72 end
73
73
74 def adapter_name
74 def adapter_name
75 'Abstract'
75 'Abstract'
76 end
76 end
77
77
78 def supports_cat?
78 def supports_cat?
79 true
79 true
80 end
80 end
81
81
82 def supports_annotate?
82 def supports_annotate?
83 respond_to?('annotate')
83 respond_to?('annotate')
84 end
84 end
85
85
86 def root_url
86 def root_url
87 @root_url
87 @root_url
88 end
88 end
89
89
90 def url
90 def url
91 @url
91 @url
92 end
92 end
93
93
94 def path_encoding
94 def path_encoding
95 nil
95 nil
96 end
96 end
97
97
98 # get info about the svn repository
98 # get info about the svn repository
99 def info
99 def info
100 return nil
100 return nil
101 end
101 end
102
102
103 # Returns the entry identified by path and revision identifier
103 # Returns the entry identified by path and revision identifier
104 # or nil if entry doesn't exist in the repository
104 # or nil if entry doesn't exist in the repository
105 def entry(path=nil, identifier=nil)
105 def entry(path=nil, identifier=nil)
106 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
106 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
107 search_path = parts[0..-2].join('/')
107 search_path = parts[0..-2].join('/')
108 search_name = parts[-1]
108 search_name = parts[-1]
109 if search_path.blank? && search_name.blank?
109 if search_path.blank? && search_name.blank?
110 # Root entry
110 # Root entry
111 Entry.new(:path => '', :kind => 'dir')
111 Entry.new(:path => '', :kind => 'dir')
112 else
112 else
113 # Search for the entry in the parent directory
113 # Search for the entry in the parent directory
114 es = entries(search_path, identifier)
114 es = entries(search_path, identifier)
115 es ? es.detect {|e| e.name == search_name} : nil
115 es ? es.detect {|e| e.name == search_name} : nil
116 end
116 end
117 end
117 end
118
118
119 # Returns an Entries collection
119 # Returns an Entries collection
120 # 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
121 def entries(path=nil, identifier=nil, options={})
121 def entries(path=nil, identifier=nil, options={})
122 return nil
122 return nil
123 end
123 end
124
124
125 def branches
125 def branches
126 return nil
126 return nil
127 end
127 end
128
128
129 def tags
129 def tags
130 return nil
130 return nil
131 end
131 end
132
132
133 def default_branch
133 def default_branch
134 return nil
134 return nil
135 end
135 end
136
136
137 def properties(path, identifier=nil)
137 def properties(path, identifier=nil)
138 return nil
138 return nil
139 end
139 end
140
140
141 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
141 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
142 return nil
142 return nil
143 end
143 end
144
144
145 def diff(path, identifier_from, identifier_to=nil)
145 def diff(path, identifier_from, identifier_to=nil)
146 return nil
146 return nil
147 end
147 end
148
148
149 def cat(path, identifier=nil)
149 def cat(path, identifier=nil)
150 return nil
150 return nil
151 end
151 end
152
152
153 def with_leading_slash(path)
153 def with_leading_slash(path)
154 path ||= ''
154 path ||= ''
155 (path[0,1]!="/") ? "/#{path}" : path
155 (path[0,1]!="/") ? "/#{path}" : path
156 end
156 end
157
157
158 def with_trailling_slash(path)
158 def with_trailling_slash(path)
159 path ||= ''
159 path ||= ''
160 (path[-1,1] == "/") ? path : "#{path}/"
160 (path[-1,1] == "/") ? path : "#{path}/"
161 end
161 end
162
162
163 def without_leading_slash(path)
163 def without_leading_slash(path)
164 path ||= ''
164 path ||= ''
165 path.gsub(%r{^/+}, '')
165 path.gsub(%r{^/+}, '')
166 end
166 end
167
167
168 def without_trailling_slash(path)
168 def without_trailling_slash(path)
169 path ||= ''
169 path ||= ''
170 (path[-1,1] == "/") ? path[0..-2] : path
170 (path[-1,1] == "/") ? path[0..-2] : path
171 end
171 end
172
172
173 def shell_quote(str)
173 def shell_quote(str)
174 self.class.shell_quote(str)
174 self.class.shell_quote(str)
175 end
175 end
176
176
177 private
177 private
178 def retrieve_root_url
178 def retrieve_root_url
179 info = self.info
179 info = self.info
180 info ? info.root_url : nil
180 info ? info.root_url : nil
181 end
181 end
182
182
183 def target(path)
183 def target(path)
184 path ||= ''
184 path ||= ''
185 base = path.match(/^\//) ? root_url : url
185 base = path.match(/^\//) ? root_url : url
186 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
186 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
187 end
187 end
188
188
189 def logger
189 def logger
190 self.class.logger
190 self.class.logger
191 end
191 end
192
192
193 def shellout(cmd, &block)
193 def shellout(cmd, &block)
194 self.class.shellout(cmd, &block)
194 self.class.shellout(cmd, &block)
195 end
195 end
196
196
197 def self.logger
197 def self.logger
198 RAILS_DEFAULT_LOGGER
198 RAILS_DEFAULT_LOGGER
199 end
199 end
200
200
201 def self.shellout(cmd, &block)
201 def self.shellout(cmd, &block)
202 if logger && logger.debug?
202 if logger && logger.debug?
203 logger.debug "Shelling out: #{strip_credential(cmd)}"
203 logger.debug "Shelling out: #{strip_credential(cmd)}"
204 end
204 end
205 if Rails.env == 'development'
205 if Rails.env == 'development'
206 # Capture stderr when running in dev environment
206 # Capture stderr when running in dev environment
207 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log"
207 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log"
208 end
208 end
209 begin
209 begin
210 if RUBY_VERSION < '1.9'
210 if RUBY_VERSION < '1.9'
211 mode = "r+"
211 mode = "r+"
212 else
212 else
213 mode = "r+:ASCII-8BIT"
213 mode = "r+:ASCII-8BIT"
214 end
214 end
215 IO.popen(cmd, mode) do |io|
215 IO.popen(cmd, mode) do |io|
216 io.close_write
216 io.close_write
217 block.call(io) if block_given?
217 block.call(io) if block_given?
218 end
218 end
219 rescue Errno::ENOENT => e
219 ## If scm command does not exist,
220 ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
221 ## in production environment.
222 # rescue Errno::ENOENT => e
223 rescue Exception => e
220 msg = strip_credential(e.message)
224 msg = strip_credential(e.message)
221 # The command failed, log it and re-raise
225 # The command failed, log it and re-raise
222 logmsg = "SCM command failed, "
226 logmsg = "SCM command failed, "
223 logmsg += "make sure that your SCM command (e.g. svn) is "
227 logmsg += "make sure that your SCM command (e.g. svn) is "
224 logmsg += "in PATH (#{ENV['PATH']})\n"
228 logmsg += "in PATH (#{ENV['PATH']})\n"
225 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
229 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
226 logmsg += "#{strip_credential(cmd)}\n"
230 logmsg += "#{strip_credential(cmd)}\n"
227 logmsg += "with: #{msg}"
231 logmsg += "with: #{msg}"
228 logger.error(logmsg)
232 logger.error(logmsg)
229 raise CommandFailed.new(msg)
233 raise CommandFailed.new(msg)
230 end
234 end
231 end
235 end
232
236
233 # Hides username/password in a given command
237 # Hides username/password in a given command
234 def self.strip_credential(cmd)
238 def self.strip_credential(cmd)
235 q = (Redmine::Platform.mswin? ? '"' : "'")
239 q = (Redmine::Platform.mswin? ? '"' : "'")
236 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
240 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
237 end
241 end
238
242
239 def strip_credential(cmd)
243 def strip_credential(cmd)
240 self.class.strip_credential(cmd)
244 self.class.strip_credential(cmd)
241 end
245 end
242
246
243 def scm_iconv(to, from, str)
247 def scm_iconv(to, from, str)
244 return nil if str.nil?
248 return nil if str.nil?
245 return str if to == from
249 return str if to == from
246 begin
250 begin
247 Iconv.conv(to, from, str)
251 Iconv.conv(to, from, str)
248 rescue Iconv::Failure => err
252 rescue Iconv::Failure => err
249 logger.error("failed to convert from #{from} to #{to}. #{err}")
253 logger.error("failed to convert from #{from} to #{to}. #{err}")
250 nil
254 nil
251 end
255 end
252 end
256 end
253 end
257 end
254
258
255 class Entries < Array
259 class Entries < Array
256 def sort_by_name
260 def sort_by_name
257 sort {|x,y|
261 sort {|x,y|
258 if x.kind == y.kind
262 if x.kind == y.kind
259 x.name.to_s <=> y.name.to_s
263 x.name.to_s <=> y.name.to_s
260 else
264 else
261 x.kind <=> y.kind
265 x.kind <=> y.kind
262 end
266 end
263 }
267 }
264 end
268 end
265
269
266 def revisions
270 def revisions
267 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
271 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
268 end
272 end
269 end
273 end
270
274
271 class Info
275 class Info
272 attr_accessor :root_url, :lastrev
276 attr_accessor :root_url, :lastrev
273 def initialize(attributes={})
277 def initialize(attributes={})
274 self.root_url = attributes[:root_url] if attributes[:root_url]
278 self.root_url = attributes[:root_url] if attributes[:root_url]
275 self.lastrev = attributes[:lastrev]
279 self.lastrev = attributes[:lastrev]
276 end
280 end
277 end
281 end
278
282
279 class Entry
283 class Entry
280 attr_accessor :name, :path, :kind, :size, :lastrev
284 attr_accessor :name, :path, :kind, :size, :lastrev
281 def initialize(attributes={})
285 def initialize(attributes={})
282 self.name = attributes[:name] if attributes[:name]
286 self.name = attributes[:name] if attributes[:name]
283 self.path = attributes[:path] if attributes[:path]
287 self.path = attributes[:path] if attributes[:path]
284 self.kind = attributes[:kind] if attributes[:kind]
288 self.kind = attributes[:kind] if attributes[:kind]
285 self.size = attributes[:size].to_i if attributes[:size]
289 self.size = attributes[:size].to_i if attributes[:size]
286 self.lastrev = attributes[:lastrev]
290 self.lastrev = attributes[:lastrev]
287 end
291 end
288
292
289 def is_file?
293 def is_file?
290 'file' == self.kind
294 'file' == self.kind
291 end
295 end
292
296
293 def is_dir?
297 def is_dir?
294 'dir' == self.kind
298 'dir' == self.kind
295 end
299 end
296
300
297 def is_text?
301 def is_text?
298 Redmine::MimeType.is_type?('text', name)
302 Redmine::MimeType.is_type?('text', name)
299 end
303 end
300 end
304 end
301
305
302 class Revisions < Array
306 class Revisions < Array
303 def latest
307 def latest
304 sort {|x,y|
308 sort {|x,y|
305 unless x.time.nil? or y.time.nil?
309 unless x.time.nil? or y.time.nil?
306 x.time <=> y.time
310 x.time <=> y.time
307 else
311 else
308 0
312 0
309 end
313 end
310 }.last
314 }.last
311 end
315 end
312 end
316 end
313
317
314 class Revision
318 class Revision
315 attr_accessor :scmid, :name, :author, :time, :message,
319 attr_accessor :scmid, :name, :author, :time, :message,
316 :paths, :revision, :branch, :identifier
320 :paths, :revision, :branch, :identifier
317
321
318 def initialize(attributes={})
322 def initialize(attributes={})
319 self.identifier = attributes[:identifier]
323 self.identifier = attributes[:identifier]
320 self.scmid = attributes[:scmid]
324 self.scmid = attributes[:scmid]
321 self.name = attributes[:name] || self.identifier
325 self.name = attributes[:name] || self.identifier
322 self.author = attributes[:author]
326 self.author = attributes[:author]
323 self.time = attributes[:time]
327 self.time = attributes[:time]
324 self.message = attributes[:message] || ""
328 self.message = attributes[:message] || ""
325 self.paths = attributes[:paths]
329 self.paths = attributes[:paths]
326 self.revision = attributes[:revision]
330 self.revision = attributes[:revision]
327 self.branch = attributes[:branch]
331 self.branch = attributes[:branch]
328 end
332 end
329
333
330 # Returns the readable identifier.
334 # Returns the readable identifier.
331 def format_identifier
335 def format_identifier
332 self.identifier.to_s
336 self.identifier.to_s
333 end
337 end
334 end
338 end
335
339
336 class Annotate
340 class Annotate
337 attr_reader :lines, :revisions
341 attr_reader :lines, :revisions
338
342
339 def initialize
343 def initialize
340 @lines = []
344 @lines = []
341 @revisions = []
345 @revisions = []
342 end
346 end
343
347
344 def add_line(line, revision)
348 def add_line(line, revision)
345 @lines << line
349 @lines << line
346 @revisions << revision
350 @revisions << revision
347 end
351 end
348
352
349 def content
353 def content
350 content = lines.join("\n")
354 content = lines.join("\n")
351 end
355 end
352
356
353 def empty?
357 def empty?
354 lines.empty?
358 lines.empty?
355 end
359 end
356 end
360 end
357 end
361 end
358 end
362 end
359 end
363 end
General Comments 0
You need to be logged in to leave comments. Login now