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