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