annotate lib/redmine/scm/adapters/.svn/text-base/cvs_adapter.rb.svn-base @ 863:818ff422eece bug_168

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