Mercurial > hg > soundsoftware-site
diff lib/redmine/scm/adapters/cvs_adapter.rb @ 0:513646585e45
* Import Redmine trunk SVN rev 3859
author | Chris Cannam |
---|---|
date | Fri, 23 Jul 2010 15:52:44 +0100 |
parents | |
children | af80e5618e9b 8661b858af72 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/redmine/scm/adapters/cvs_adapter.rb Fri Jul 23 15:52:44 2010 +0100 @@ -0,0 +1,362 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/abstract_adapter' + +module Redmine + module Scm + module Adapters + class CvsAdapter < AbstractAdapter + + # CVS executable name + CVS_BIN = "cvs" + + # Guidelines for the input: + # url -> the project-path, relative to the cvsroot (eg. module name) + # root_url -> the good old, sometimes damned, CVSROOT + # login -> unnecessary + # password -> unnecessary too + def initialize(url, root_url=nil, login=nil, password=nil) + @url = url + @login = login if login && !login.empty? + @password = (password || "") if @login + #TODO: better Exception here (IllegalArgumentException) + raise CommandFailed if root_url.blank? + @root_url = root_url + end + + def root_url + @root_url + end + + def url + @url + end + + def info + logger.debug "<cvs> info" + Info.new({:root_url => @root_url, :lastrev => nil}) + end + + def get_previous_revision(revision) + CvsRevisionHelper.new(revision).prevRev + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + # this method is used by the repository-browser (aka LIST) + def entries(path=nil, identifier=nil) + logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'" + path_with_project="#{url}#{with_leading_slash(path)}" + entries = Entries.new + cmd = "#{CVS_BIN} -d #{root_url} rls -e" + cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier + cmd << " #{shell_quote path_with_project}" + shellout(cmd) do |io| + io.each_line(){|line| + fields=line.chop.split('/',-1) + logger.debug(">>InspectLine #{fields.inspect}") + + if fields[0]!="D" + entries << Entry.new({:name => fields[-5], + #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]), + :path => "#{path}/#{fields[-5]}", + :kind => 'file', + :size => nil, + :lastrev => Revision.new({ + :revision => fields[-4], + :name => fields[-4], + :time => Time.parse(fields[-3]), + :author => '' + }) + }) + else + entries << Entry.new({:name => fields[1], + :path => "#{path}/#{fields[1]}", + :kind => 'dir', + :size => nil, + :lastrev => nil + }) + end + } + end + return nil if $? && $?.exitstatus != 0 + entries.sort_by_name + end + + STARTLOG="----------------------------" + ENDLOG ="=============================================================================" + + # Returns all revisions found between identifier_from and identifier_to + # in the repository. both identifier have to be dates or nil. + # these method returns nothing but yield every result in block + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block) + logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" + + path_with_project="#{url}#{with_leading_slash(path)}" + cmd = "#{CVS_BIN} -d #{root_url} rlog" + cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from + cmd << " #{shell_quote path_with_project}" + shellout(cmd) do |io| + state="entry_start" + + commit_log=String.new + revision=nil + date=nil + author=nil + entry_path=nil + entry_name=nil + file_state=nil + branch_map=nil + + io.each_line() do |line| + + if state!="revision" && /^#{ENDLOG}/ =~ line + commit_log=String.new + revision=nil + state="entry_start" + end + + if state=="entry_start" + branch_map=Hash.new + if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line + entry_path = normalize_cvs_path($1) + entry_name = normalize_path(File.basename($1)) + logger.debug("Path #{entry_path} <=> Name #{entry_name}") + elsif /^head: (.+)$/ =~ line + entry_headRev = $1 #unless entry.nil? + elsif /^symbolic names:/ =~ line + state="symbolic" #unless entry.nil? + elsif /^#{STARTLOG}/ =~ line + commit_log=String.new + state="revision" + end + next + elsif state=="symbolic" + if /^(.*):\s(.*)/ =~ (line.strip) + branch_map[$1]=$2 + else + state="tags" + next + end + elsif state=="tags" + if /^#{STARTLOG}/ =~ line + commit_log = "" + state="revision" + elsif /^#{ENDLOG}/ =~ line + state="head" + end + next + elsif state=="revision" + if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line + if revision + + revHelper=CvsRevisionHelper.new(revision) + revBranch="HEAD" + + branch_map.each() do |branch_name,branch_point| + if revHelper.is_in_branch_with_symbol(branch_point) + revBranch=branch_name + end + end + + logger.debug("********** YIELD Revision #{revision}::#{revBranch}") + + yield Revision.new({ + :time => date, + :author => author, + :message=>commit_log.chomp, + :paths => [{ + :revision => revision, + :branch=> revBranch, + :path=>entry_path, + :name=>entry_name, + :kind=>'file', + :action=>file_state + }] + }) + end + + commit_log=String.new + revision=nil + + if /^#{ENDLOG}/ =~ line + state="entry_start" + end + next + end + + if /^branches: (.+)$/ =~ line + #TODO: version.branch = $1 + elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line + revision = $1 + elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line + date = Time.parse($1) + author = /author: ([^;]+)/.match(line)[1] + file_state = /state: ([^;]+)/.match(line)[1] + #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are + # useful for stats or something else + # linechanges =/lines: \+(\d+) -(\d+)/.match(line) + # unless linechanges.nil? + # version.line_plus = linechanges[1] + # version.line_minus = linechanges[2] + # else + # version.line_plus = 0 + # version.line_minus = 0 + # end + else + commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/ + end + end + end + end + end + + def diff(path, identifier_from, identifier_to=nil) + logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" + path_with_project="#{url}#{with_leading_slash(path)}" + cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}" + diff = [] + shellout(cmd) do |io| + io.each_line do |line| + diff << line + end + end + return nil if $? && $?.exitstatus != 0 + diff + end + + def cat(path, identifier=nil) + identifier = (identifier) ? identifier : "HEAD" + logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}" + path_with_project="#{url}#{with_leading_slash(path)}" + cmd = "#{CVS_BIN} -d #{root_url} co" + cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier + cmd << " -p #{shell_quote path_with_project}" + cat = nil + shellout(cmd) do |io| + cat = io.read + end + return nil if $? && $?.exitstatus != 0 + cat + end + + def annotate(path, identifier=nil) + identifier = (identifier) ? identifier : "HEAD" + logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}" + path_with_project="#{url}#{with_leading_slash(path)}" + cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{shell_quote path_with_project}" + blame = Annotate.new + shellout(cmd) do |io| + io.each_line do |line| + next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$} + blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip)) + end + end + return nil if $? && $?.exitstatus != 0 + blame + end + + private + + # Returns the root url without the connexion string + # :pserver:anonymous@foo.bar:/path => /path + # :ext:cvsservername:/path => /path + def root_url_path + root_url.to_s.gsub(/^:.+:\d*/, '') + end + + # convert a date/time into the CVS-format + def time_to_cvstime(time) + return nil if time.nil? + unless time.kind_of? Time + time = Time.parse(time) + end + return time.strftime("%Y-%m-%d %H:%M:%S") + end + + def normalize_cvs_path(path) + normalize_path(path.gsub(/Attic\//,'')) + end + + def normalize_path(path) + path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1') + end + end + + class CvsRevisionHelper + attr_accessor :complete_rev, :revision, :base, :branchid + + def initialize(complete_rev) + @complete_rev = complete_rev + parseRevision() + end + + def branchPoint + return @base + end + + def branchVersion + if isBranchRevision + return @base+"."+@branchid + end + return @base + end + + def isBranchRevision + !@branchid.nil? + end + + def prevRev + unless @revision==0 + return buildRevision(@revision-1) + end + return buildRevision(@revision) + end + + def is_in_branch_with_symbol(branch_symbol) + bpieces=branch_symbol.split(".") + branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}" + return (branchVersion==branch_start) + end + + private + def buildRevision(rev) + if rev== 0 + @base + elsif @branchid.nil? + @base+"."+rev.to_s + else + @base+"."+@branchid+"."+rev.to_s + end + end + + # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15 + def parseRevision() + pieces=@complete_rev.split(".") + @revision=pieces.last.to_i + baseSize=1 + baseSize+=(pieces.size/2) + @base=pieces[0..-baseSize].join(".") + if baseSize > 2 + @branchid=pieces[-2] + end + end + end + end + end +end