Mercurial > hg > soundsoftware-site
comparison .svn/pristine/b7/b75d5f0ff60be5f80b39e65c645dcf825ec9984b.svn-base @ 1295:622f24f53b42 redmine-2.3
Update to Redmine SVN revision 11972 on 2.3-stable branch
author | Chris Cannam |
---|---|
date | Fri, 14 Jun 2013 09:02:21 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1294:3e4c3460b6ca | 1295:622f24f53b42 |
---|---|
1 # redMine - project management software | |
2 # Copyright (C) 2006-2007 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 | |
20 module Redmine | |
21 module Scm | |
22 module Adapters | |
23 class CvsAdapter < AbstractAdapter | |
24 | |
25 # CVS executable name | |
26 CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs" | |
27 | |
28 class << self | |
29 def client_command | |
30 @@bin ||= CVS_BIN | |
31 end | |
32 | |
33 def sq_bin | |
34 @@sq_bin ||= shell_quote_command | |
35 end | |
36 | |
37 def client_version | |
38 @@client_version ||= (scm_command_version || []) | |
39 end | |
40 | |
41 def client_available | |
42 client_version_above?([1, 12]) | |
43 end | |
44 | |
45 def scm_command_version | |
46 scm_version = scm_version_from_command_line.dup | |
47 if scm_version.respond_to?(:force_encoding) | |
48 scm_version.force_encoding('ASCII-8BIT') | |
49 end | |
50 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}m) | |
51 m[2].scan(%r{\d+}).collect(&:to_i) | |
52 end | |
53 end | |
54 | |
55 def scm_version_from_command_line | |
56 shellout("#{sq_bin} --version") { |io| io.read }.to_s | |
57 end | |
58 end | |
59 | |
60 # Guidelines for the input: | |
61 # url -> the project-path, relative to the cvsroot (eg. module name) | |
62 # root_url -> the good old, sometimes damned, CVSROOT | |
63 # login -> unnecessary | |
64 # password -> unnecessary too | |
65 def initialize(url, root_url=nil, login=nil, password=nil, | |
66 path_encoding=nil) | |
67 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding | |
68 @url = url | |
69 # TODO: better Exception here (IllegalArgumentException) | |
70 raise CommandFailed if root_url.blank? | |
71 @root_url = root_url | |
72 | |
73 # These are unused. | |
74 @login = login if login && !login.empty? | |
75 @password = (password || "") if @login | |
76 end | |
77 | |
78 def path_encoding | |
79 @path_encoding | |
80 end | |
81 | |
82 def info | |
83 logger.debug "<cvs> info" | |
84 Info.new({:root_url => @root_url, :lastrev => nil}) | |
85 end | |
86 | |
87 def get_previous_revision(revision) | |
88 CvsRevisionHelper.new(revision).prevRev | |
89 end | |
90 | |
91 # Returns an Entries collection | |
92 # or nil if the given path doesn't exist in the repository | |
93 # this method is used by the repository-browser (aka LIST) | |
94 def entries(path=nil, identifier=nil, options={}) | |
95 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'" | |
96 path_locale = scm_iconv(@path_encoding, 'UTF-8', path) | |
97 path_locale.force_encoding("ASCII-8BIT") if path_locale.respond_to?(:force_encoding) | |
98 entries = Entries.new | |
99 cmd_args = %w|-q rls -e| | |
100 cmd_args << "-D" << time_to_cvstime_rlog(identifier) if identifier | |
101 cmd_args << path_with_proj(path) | |
102 scm_cmd(*cmd_args) do |io| | |
103 io.each_line() do |line| | |
104 fields = line.chop.split('/',-1) | |
105 logger.debug(">>InspectLine #{fields.inspect}") | |
106 if fields[0]!="D" | |
107 time = nil | |
108 # Thu Dec 13 16:27:22 2007 | |
109 time_l = fields[-3].split(' ') | |
110 if time_l.size == 5 && time_l[4].length == 4 | |
111 begin | |
112 time = Time.parse( | |
113 "#{time_l[1]} #{time_l[2]} #{time_l[3]} GMT #{time_l[4]}") | |
114 rescue | |
115 end | |
116 end | |
117 entries << Entry.new( | |
118 { | |
119 :name => scm_iconv('UTF-8', @path_encoding, fields[-5]), | |
120 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]), | |
121 :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[-5]}"), | |
122 :kind => 'file', | |
123 :size => nil, | |
124 :lastrev => Revision.new( | |
125 { | |
126 :revision => fields[-4], | |
127 :name => scm_iconv('UTF-8', @path_encoding, fields[-4]), | |
128 :time => time, | |
129 :author => '' | |
130 }) | |
131 }) | |
132 else | |
133 entries << Entry.new( | |
134 { | |
135 :name => scm_iconv('UTF-8', @path_encoding, fields[1]), | |
136 :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[1]}"), | |
137 :kind => 'dir', | |
138 :size => nil, | |
139 :lastrev => nil | |
140 }) | |
141 end | |
142 end | |
143 end | |
144 entries.sort_by_name | |
145 rescue ScmCommandAborted | |
146 nil | |
147 end | |
148 | |
149 STARTLOG="----------------------------" | |
150 ENDLOG ="=============================================================================" | |
151 | |
152 # Returns all revisions found between identifier_from and identifier_to | |
153 # in the repository. both identifier have to be dates or nil. | |
154 # these method returns nothing but yield every result in block | |
155 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block) | |
156 path_with_project_utf8 = path_with_proj(path) | |
157 path_with_project_locale = scm_iconv(@path_encoding, 'UTF-8', path_with_project_utf8) | |
158 logger.debug "<cvs> revisions path:" + | |
159 "'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" | |
160 cmd_args = %w|-q rlog| | |
161 cmd_args << "-d" << ">#{time_to_cvstime_rlog(identifier_from)}" if identifier_from | |
162 cmd_args << path_with_project_utf8 | |
163 scm_cmd(*cmd_args) do |io| | |
164 state = "entry_start" | |
165 commit_log = String.new | |
166 revision = nil | |
167 date = nil | |
168 author = nil | |
169 entry_path = nil | |
170 entry_name = nil | |
171 file_state = nil | |
172 branch_map = nil | |
173 io.each_line() do |line| | |
174 if state != "revision" && /^#{ENDLOG}/ =~ line | |
175 commit_log = String.new | |
176 revision = nil | |
177 state = "entry_start" | |
178 end | |
179 if state == "entry_start" | |
180 branch_map = Hash.new | |
181 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project_locale)}(.+),v$/ =~ line | |
182 entry_path = normalize_cvs_path($1) | |
183 entry_name = normalize_path(File.basename($1)) | |
184 logger.debug("Path #{entry_path} <=> Name #{entry_name}") | |
185 elsif /^head: (.+)$/ =~ line | |
186 entry_headRev = $1 #unless entry.nil? | |
187 elsif /^symbolic names:/ =~ line | |
188 state = "symbolic" #unless entry.nil? | |
189 elsif /^#{STARTLOG}/ =~ line | |
190 commit_log = String.new | |
191 state = "revision" | |
192 end | |
193 next | |
194 elsif state == "symbolic" | |
195 if /^(.*):\s(.*)/ =~ (line.strip) | |
196 branch_map[$1] = $2 | |
197 else | |
198 state = "tags" | |
199 next | |
200 end | |
201 elsif state == "tags" | |
202 if /^#{STARTLOG}/ =~ line | |
203 commit_log = "" | |
204 state = "revision" | |
205 elsif /^#{ENDLOG}/ =~ line | |
206 state = "head" | |
207 end | |
208 next | |
209 elsif state == "revision" | |
210 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line | |
211 if revision | |
212 revHelper = CvsRevisionHelper.new(revision) | |
213 revBranch = "HEAD" | |
214 branch_map.each() do |branch_name, branch_point| | |
215 if revHelper.is_in_branch_with_symbol(branch_point) | |
216 revBranch = branch_name | |
217 end | |
218 end | |
219 logger.debug("********** YIELD Revision #{revision}::#{revBranch}") | |
220 yield Revision.new({ | |
221 :time => date, | |
222 :author => author, | |
223 :message => commit_log.chomp, | |
224 :paths => [{ | |
225 :revision => revision.dup, | |
226 :branch => revBranch.dup, | |
227 :path => scm_iconv('UTF-8', @path_encoding, entry_path), | |
228 :name => scm_iconv('UTF-8', @path_encoding, entry_name), | |
229 :kind => 'file', | |
230 :action => file_state | |
231 }] | |
232 }) | |
233 end | |
234 commit_log = String.new | |
235 revision = nil | |
236 if /^#{ENDLOG}/ =~ line | |
237 state = "entry_start" | |
238 end | |
239 next | |
240 end | |
241 | |
242 if /^branches: (.+)$/ =~ line | |
243 # TODO: version.branch = $1 | |
244 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line | |
245 revision = $1 | |
246 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line | |
247 date = Time.parse($1) | |
248 line_utf8 = scm_iconv('UTF-8', options[:log_encoding], line) | |
249 author_utf8 = /author: ([^;]+)/.match(line_utf8)[1] | |
250 author = scm_iconv(options[:log_encoding], 'UTF-8', author_utf8) | |
251 file_state = /state: ([^;]+)/.match(line)[1] | |
252 # TODO: | |
253 # linechanges only available in CVS.... | |
254 # maybe a feature our SVN implementation. | |
255 # I'm sure, they are useful for stats or something else | |
256 # linechanges =/lines: \+(\d+) -(\d+)/.match(line) | |
257 # unless linechanges.nil? | |
258 # version.line_plus = linechanges[1] | |
259 # version.line_minus = linechanges[2] | |
260 # else | |
261 # version.line_plus = 0 | |
262 # version.line_minus = 0 | |
263 # end | |
264 else | |
265 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/ | |
266 end | |
267 end | |
268 end | |
269 end | |
270 rescue ScmCommandAborted | |
271 Revisions.new | |
272 end | |
273 | |
274 def diff(path, identifier_from, identifier_to=nil) | |
275 logger.debug "<cvs> diff path:'#{path}'" + | |
276 ",identifier_from #{identifier_from}, identifier_to #{identifier_to}" | |
277 cmd_args = %w|rdiff -u| | |
278 cmd_args << "-r#{identifier_to}" | |
279 cmd_args << "-r#{identifier_from}" | |
280 cmd_args << path_with_proj(path) | |
281 diff = [] | |
282 scm_cmd(*cmd_args) do |io| | |
283 io.each_line do |line| | |
284 diff << line | |
285 end | |
286 end | |
287 diff | |
288 rescue ScmCommandAborted | |
289 nil | |
290 end | |
291 | |
292 def cat(path, identifier=nil) | |
293 identifier = (identifier) ? identifier : "HEAD" | |
294 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}" | |
295 cmd_args = %w|-q co| | |
296 cmd_args << "-D" << time_to_cvstime(identifier) if identifier | |
297 cmd_args << "-p" << path_with_proj(path) | |
298 cat = nil | |
299 scm_cmd(*cmd_args) do |io| | |
300 io.binmode | |
301 cat = io.read | |
302 end | |
303 cat | |
304 rescue ScmCommandAborted | |
305 nil | |
306 end | |
307 | |
308 def annotate(path, identifier=nil) | |
309 identifier = (identifier) ? identifier : "HEAD" | |
310 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}" | |
311 cmd_args = %w|rannotate| | |
312 cmd_args << "-D" << time_to_cvstime(identifier) if identifier | |
313 cmd_args << path_with_proj(path) | |
314 blame = Annotate.new | |
315 scm_cmd(*cmd_args) do |io| | |
316 io.each_line do |line| | |
317 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$} | |
318 blame.add_line( | |
319 $3.rstrip, | |
320 Revision.new( | |
321 :revision => $1, | |
322 :identifier => nil, | |
323 :author => $2.strip | |
324 )) | |
325 end | |
326 end | |
327 blame | |
328 rescue ScmCommandAborted | |
329 Annotate.new | |
330 end | |
331 | |
332 private | |
333 | |
334 # Returns the root url without the connexion string | |
335 # :pserver:anonymous@foo.bar:/path => /path | |
336 # :ext:cvsservername:/path => /path | |
337 def root_url_path | |
338 root_url.to_s.gsub(/^:.+:\d*/, '') | |
339 end | |
340 | |
341 # convert a date/time into the CVS-format | |
342 def time_to_cvstime(time) | |
343 return nil if time.nil? | |
344 time = Time.now if time == 'HEAD' | |
345 | |
346 unless time.kind_of? Time | |
347 time = Time.parse(time) | |
348 end | |
349 return time_to_cvstime_rlog(time) | |
350 end | |
351 | |
352 def time_to_cvstime_rlog(time) | |
353 return nil if time.nil? | |
354 t1 = time.clone.localtime | |
355 return t1.strftime("%Y-%m-%d %H:%M:%S") | |
356 end | |
357 | |
358 def normalize_cvs_path(path) | |
359 normalize_path(path.gsub(/Attic\//,'')) | |
360 end | |
361 | |
362 def normalize_path(path) | |
363 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1') | |
364 end | |
365 | |
366 def path_with_proj(path) | |
367 "#{url}#{with_leading_slash(path)}" | |
368 end | |
369 private :path_with_proj | |
370 | |
371 class Revision < Redmine::Scm::Adapters::Revision | |
372 # Returns the readable identifier | |
373 def format_identifier | |
374 revision.to_s | |
375 end | |
376 end | |
377 | |
378 def scm_cmd(*args, &block) | |
379 full_args = ['-d', root_url] | |
380 full_args += args | |
381 full_args_locale = [] | |
382 full_args.map do |e| | |
383 full_args_locale << scm_iconv(@path_encoding, 'UTF-8', e) | |
384 end | |
385 ret = shellout( | |
386 self.class.sq_bin + ' ' + full_args_locale.map { |e| shell_quote e.to_s }.join(' '), | |
387 &block | |
388 ) | |
389 if $? && $?.exitstatus != 0 | |
390 raise ScmCommandAborted, "cvs exited with non-zero status: #{$?.exitstatus}" | |
391 end | |
392 ret | |
393 end | |
394 private :scm_cmd | |
395 end | |
396 | |
397 class CvsRevisionHelper | |
398 attr_accessor :complete_rev, :revision, :base, :branchid | |
399 | |
400 def initialize(complete_rev) | |
401 @complete_rev = complete_rev | |
402 parseRevision() | |
403 end | |
404 | |
405 def branchPoint | |
406 return @base | |
407 end | |
408 | |
409 def branchVersion | |
410 if isBranchRevision | |
411 return @base+"."+@branchid | |
412 end | |
413 return @base | |
414 end | |
415 | |
416 def isBranchRevision | |
417 !@branchid.nil? | |
418 end | |
419 | |
420 def prevRev | |
421 unless @revision == 0 | |
422 return buildRevision( @revision - 1 ) | |
423 end | |
424 return buildRevision( @revision ) | |
425 end | |
426 | |
427 def is_in_branch_with_symbol(branch_symbol) | |
428 bpieces = branch_symbol.split(".") | |
429 branch_start = "#{bpieces[0..-3].join(".")}.#{bpieces[-1]}" | |
430 return ( branchVersion == branch_start ) | |
431 end | |
432 | |
433 private | |
434 def buildRevision(rev) | |
435 if rev == 0 | |
436 if @branchid.nil? | |
437 @base + ".0" | |
438 else | |
439 @base | |
440 end | |
441 elsif @branchid.nil? | |
442 @base + "." + rev.to_s | |
443 else | |
444 @base + "." + @branchid + "." + rev.to_s | |
445 end | |
446 end | |
447 | |
448 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15 | |
449 def parseRevision() | |
450 pieces = @complete_rev.split(".") | |
451 @revision = pieces.last.to_i | |
452 baseSize = 1 | |
453 baseSize += (pieces.size / 2) | |
454 @base = pieces[0..-baseSize].join(".") | |
455 if baseSize > 2 | |
456 @branchid = pieces[-2] | |
457 end | |
458 end | |
459 end | |
460 end | |
461 end | |
462 end |