##// END OF EJS Templates
scm: cvs: code clean up lib/redmine/scm/adapters/cvs_adapter.rb....
Toshi MARUYAMA -
r5260:c6ec41473e62
parent child
Show More
@@ -1,439 +1,435
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 #TODO: better Exception here (IllegalArgumentException)
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 :name => fields[-4],
131 :time => time,
132 :author => ''
130 :name => fields[-4],
131 :time => time,
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 state="entry_start"
167
168 commit_log=String.new
169 revision=nil
170 date=nil
171 author=nil
172 entry_path=nil
173 entry_name=nil
174 file_state=nil
175 branch_map=nil
176
166 state = "entry_start"
167 commit_log = String.new
168 revision = nil
169 date = nil
170 author = nil
171 entry_path = nil
172 entry_name = nil
173 file_state = nil
174 branch_map = nil
177 175 io.each_line() do |line|
178
179 if state!="revision" && /^#{ENDLOG}/ =~ line
180 commit_log=String.new
181 revision=nil
182 state="entry_start"
176 if state != "revision" && /^#{ENDLOG}/ =~ line
177 commit_log = String.new
178 revision = nil
179 state = "entry_start"
183 180 end
184
185 181 if state=="entry_start"
186 182 branch_map=Hash.new
187 183 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
188 184 entry_path = normalize_cvs_path($1)
189 185 entry_name = normalize_path(File.basename($1))
190 186 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
191 187 elsif /^head: (.+)$/ =~ line
192 188 entry_headRev = $1 #unless entry.nil?
193 189 elsif /^symbolic names:/ =~ line
194 190 state="symbolic" #unless entry.nil?
195 191 elsif /^#{STARTLOG}/ =~ line
196 192 commit_log=String.new
197 193 state="revision"
198 194 end
199 195 next
200 196 elsif state=="symbolic"
201 197 if /^(.*):\s(.*)/ =~ (line.strip)
202 198 branch_map[$1]=$2
203 199 else
204 200 state="tags"
205 201 next
206 202 end
207 203 elsif state=="tags"
208 204 if /^#{STARTLOG}/ =~ line
209 205 commit_log = ""
210 206 state="revision"
211 207 elsif /^#{ENDLOG}/ =~ line
212 208 state="head"
213 209 end
214 210 next
215 211 elsif state=="revision"
216 212 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
217 213 if revision
218 214
219 215 revHelper=CvsRevisionHelper.new(revision)
220 216 revBranch="HEAD"
221 217
222 218 branch_map.each() do |branch_name,branch_point|
223 219 if revHelper.is_in_branch_with_symbol(branch_point)
224 220 revBranch=branch_name
225 221 end
226 222 end
227 223
228 224 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
229 225
230 226 yield Revision.new({
231 227 :time => date,
232 228 :author => author,
233 229 :message=>commit_log.chomp,
234 230 :paths => [{
235 231 :revision => revision,
236 232 :branch=> revBranch,
237 233 :path=>entry_path,
238 234 :name=>entry_name,
239 235 :kind=>'file',
240 236 :action=>file_state
241 237 }]
242 238 })
243 239 end
244 240
245 241 commit_log=String.new
246 242 revision=nil
247 243
248 244 if /^#{ENDLOG}/ =~ line
249 245 state="entry_start"
250 246 end
251 247 next
252 248 end
253 249
254 250 if /^branches: (.+)$/ =~ line
255 251 #TODO: version.branch = $1
256 252 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
257 253 revision = $1
258 254 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
259 255 date = Time.parse($1)
260 256 author = /author: ([^;]+)/.match(line)[1]
261 257 file_state = /state: ([^;]+)/.match(line)[1]
262 258 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
263 259 # useful for stats or something else
264 260 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
265 261 # unless linechanges.nil?
266 262 # version.line_plus = linechanges[1]
267 263 # version.line_minus = linechanges[2]
268 264 # else
269 265 # version.line_plus = 0
270 266 # version.line_minus = 0
271 267 # end
272 268 else
273 269 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
274 270 end
275 271 end
276 272 end
277 273 end
278 274 rescue ScmCommandAborted
279 275 Revisions.new
280 276 end
281 277
282 278 def diff(path, identifier_from, identifier_to=nil)
283 279 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
284 280 path_with_project="#{url}#{with_leading_slash(path)}"
285 281 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
286 282 diff = []
287 283 shellout(cmd) do |io|
288 284 io.each_line do |line|
289 285 diff << line
290 286 end
291 287 end
292 288 return nil if $? && $?.exitstatus != 0
293 289 diff
294 290 end
295 291
296 292 def cat(path, identifier=nil)
297 293 identifier = (identifier) ? identifier : "HEAD"
298 294 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
299 295 path_with_project="#{url}#{with_leading_slash(path)}"
300 296 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} co"
301 297 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
302 298 cmd << " -p #{shell_quote path_with_project}"
303 299 cat = nil
304 300 shellout(cmd) do |io|
305 301 io.binmode
306 302 cat = io.read
307 303 end
308 304 return nil if $? && $?.exitstatus != 0
309 305 cat
310 306 end
311 307
312 308 def annotate(path, identifier=nil)
313 309 identifier = (identifier) ? identifier.to_i : "HEAD"
314 310 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
315 311 path_with_project="#{url}#{with_leading_slash(path)}"
316 312 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
317 313 blame = Annotate.new
318 314 shellout(cmd) do |io|
319 315 io.each_line do |line|
320 316 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
321 317 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
322 318 end
323 319 end
324 320 return nil if $? && $?.exitstatus != 0
325 321 blame
326 322 end
327 323
328 324 private
329 325
330 326 # Returns the root url without the connexion string
331 327 # :pserver:anonymous@foo.bar:/path => /path
332 328 # :ext:cvsservername:/path => /path
333 329 def root_url_path
334 330 root_url.to_s.gsub(/^:.+:\d*/, '')
335 331 end
336 332
337 333 # convert a date/time into the CVS-format
338 334 def time_to_cvstime(time)
339 335 return nil if time.nil?
340 336 return Time.now if time == 'HEAD'
341 337
342 338 unless time.kind_of? Time
343 339 time = Time.parse(time)
344 340 end
345 341 return time.strftime("%Y-%m-%d %H:%M:%S")
346 342 end
347 343
348 344 def time_to_cvstime_rlog(time)
349 345 return nil if time.nil?
350 346 t1 = time.clone.localtime
351 347 return t1.strftime("%Y-%m-%d %H:%M:%S")
352 348 end
353
349
354 350 def normalize_cvs_path(path)
355 351 normalize_path(path.gsub(/Attic\//,''))
356 352 end
357
353
358 354 def normalize_path(path)
359 355 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
360 356 end
361 357
362 358 def scm_cmd(*args, &block)
363 359 full_args = [CVS_BIN, '-d', root_url]
364 360 full_args += args
365 361 ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
366 362 if $? && $?.exitstatus != 0
367 363 raise ScmCommandAborted, "cvs exited with non-zero status: #{$?.exitstatus}"
368 364 end
369 365 ret
370 366 end
371 367 private :scm_cmd
372 368 end
373 369
374 370 class CvsRevisionHelper
375 371 attr_accessor :complete_rev, :revision, :base, :branchid
376 372
377 373 def initialize(complete_rev)
378 374 @complete_rev = complete_rev
379 375 parseRevision()
380 376 end
381 377
382 378 def branchPoint
383 379 return @base
384 380 end
385 381
386 382 def branchVersion
387 383 if isBranchRevision
388 384 return @base+"."+@branchid
389 385 end
390 386 return @base
391 387 end
392 388
393 389 def isBranchRevision
394 390 !@branchid.nil?
395 391 end
396 392
397 393 def prevRev
398 394 unless @revision == 0
399 395 return buildRevision( @revision - 1 )
400 396 end
401 397 return buildRevision( @revision )
402 398 end
403 399
404 400 def is_in_branch_with_symbol(branch_symbol)
405 401 bpieces = branch_symbol.split(".")
406 402 branch_start = "#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
407 403 return ( branchVersion == branch_start )
408 404 end
409 405
410 406 private
411 407 def buildRevision(rev)
412 408 if rev== 0
413 409 if @branchid.nil?
414 410 @base+".0"
415 411 else
416 412 @base
417 413 end
418 414 elsif @branchid.nil?
419 415 @base+"."+rev.to_s
420 416 else
421 417 @base+"."+@branchid+"."+rev.to_s
422 418 end
423 419 end
424 420
425 421 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
426 422 def parseRevision()
427 423 pieces = @complete_rev.split(".")
428 424 @revision = pieces.last.to_i
429 425 baseSize = 1
430 426 baseSize += (pieces.size / 2)
431 427 @base = pieces[0..-baseSize].join(".")
432 428 if baseSize > 2
433 429 @branchid = pieces[-2]
434 430 end
435 431 end
436 432 end
437 433 end
438 434 end
439 435 end
General Comments 0
You need to be logged in to leave comments. Login now