File: app/models/repository/cvs.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  class: Repository
  class: Cvs#21
inherits from
  Repository   
has properties
class method: human_attribute_name / 2 #24
class method: scm_adapter_class #34
class method: scm_name #38
method: entry / 2 #42
method: entries / 2 #47
method: cat / 2 #72
method: annotate / 2 #81
method: diff / 3 #90
method: fetch_changesets #119
method: next_revision_number #197

Class Hierarchy

Object ( Builtin-Module )
Base ( ActiveRecord )
Repository
  Cvs ( Repository ) #21

Code

   1  # Redmine - project management software
   2  # Copyright (C) 2006-2011  Jean-Philippe Lang
   3  #
   4  # This program is free software; you can redistribute it and/or
   5  # modify it under the terms of the GNU General Public License
   6  # as published by the Free Software Foundation; either version 2
   7  # of the License, or (at your option) any later version.
   8  #
   9  # This program is distributed in the hope that it will be useful,
  10  # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  # GNU General Public License for more details.
  13  #
  14  # You should have received a copy of the GNU General Public License
  15  # along with this program; if not, write to the Free Software
  16  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  17 
  18  require 'redmine/scm/adapters/cvs_adapter'
  19  require 'digest/sha1'
  20 
  21  class Repository::Cvs < Repository
  22    validates_presence_of :url, :root_url, :log_encoding
  23 
  24    def self.human_attribute_name(attribute_key_name, *args)
  25      attr_name = attribute_key_name.to_s
  26      if attr_name == "root_url"
  27        attr_name = "cvsroot"
  28      elsif attr_name == "url"
  29        attr_name = "cvs_module"
  30      end
  31      super(attr_name, *args)
  32    end
  33 
  34    def self.scm_adapter_class
  35      Redmine::Scm::Adapters::CvsAdapter
  36    end
  37 
  38    def self.scm_name
  39      'CVS'
  40    end
  41 
  42    def entry(path=nil, identifier=nil)
  43      rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
  44      scm.entry(path, rev.nil? ? nil : rev.committed_on)
  45    end
  46 
  47    def entries(path=nil, identifier=nil)
  48      rev = nil
  49      if ! identifier.nil?
  50        rev = changesets.find_by_revision(identifier)
  51        return nil if rev.nil?
  52      end
  53      entries = scm.entries(path, rev.nil? ? nil : rev.committed_on)
  54      if entries
  55        entries.each() do |entry|
  56          if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? )
  57            change=changes.find_by_revision_and_path(
  58                       entry.lastrev.revision,
  59                       scm.with_leading_slash(entry.path) )
  60            if change
  61              entry.lastrev.identifier = change.changeset.revision
  62              entry.lastrev.revision   = change.changeset.revision
  63              entry.lastrev.author     = change.changeset.committer
  64              # entry.lastrev.branch     = change.branch
  65            end
  66          end
  67        end
  68      end
  69      entries
  70    end
  71 
  72    def cat(path, identifier=nil)
  73      rev = nil
  74      if ! identifier.nil?
  75        rev = changesets.find_by_revision(identifier)
  76        return nil if rev.nil?
  77      end
  78      scm.cat(path, rev.nil? ? nil : rev.committed_on)
  79    end
  80 
  81    def annotate(path, identifier=nil)
  82      rev = nil
  83      if ! identifier.nil?
  84        rev = changesets.find_by_revision(identifier)
  85        return nil if rev.nil?
  86      end
  87      scm.annotate(path, rev.nil? ? nil : rev.committed_on)
  88    end
  89 
  90    def diff(path, rev, rev_to)
  91      # convert rev to revision. CVS can't handle changesets here
  92      diff=[]
  93      changeset_from = changesets.find_by_revision(rev)
  94      if rev_to.to_i > 0
  95        changeset_to = changesets.find_by_revision(rev_to)
  96      end
  97      changeset_from.changes.each() do |change_from|
  98        revision_from = nil
  99        revision_to   = nil
 100        if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
 101          revision_from = change_from.revision
 102        end
 103        if revision_from
 104          if changeset_to
 105            changeset_to.changes.each() do |change_to|
 106              revision_to = change_to.revision if change_to.path == change_from.path
 107            end
 108          end
 109          unless revision_to
 110            revision_to = scm.get_previous_revision(revision_from)
 111          end
 112          file_diff = scm.diff(change_from.path, revision_from, revision_to)
 113          diff = diff + file_diff unless file_diff.nil?
 114        end
 115      end
 116      return diff
 117    end
 118 
 119    def fetch_changesets
 120      # some nifty bits to introduce a commit-id with cvs
 121      # natively cvs doesn't provide any kind of changesets,
 122      # there is only a revision per file.
 123      # we now take a guess using the author, the commitlog and the commit-date.
 124 
 125      # last one is the next step to take. the commit-date is not equal for all
 126      # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
 127      # we use a small delta here, to merge all changes belonging to _one_ changeset
 128      time_delta  = 10.seconds
 129      fetch_since = latest_changeset ? latest_changeset.committed_on : nil
 130      transaction do
 131        tmp_rev_num = 1
 132        scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision|
 133          # only add the change to the database, if it doen't exists. the cvs log
 134          # is not exclusive at all.
 135          tmp_time = revision.time.clone
 136          unless changes.find_by_path_and_revision(
 137                                   scm.with_leading_slash(revision.paths[0][:path]),
 138                                   revision.paths[0][:revision]
 139                                     )
 140            cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
 141            author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding)
 142            cs  = changesets.find(
 143              :first,
 144              :conditions => {
 145                  :committed_on => tmp_time - time_delta .. tmp_time + time_delta,
 146                  :committer    => author_utf8,
 147                  :comments     => cmt
 148                  }
 149               )
 150            # create a new changeset....
 151            unless cs
 152              # we use a temporaray revision number here (just for inserting)
 153              # later on, we calculate a continous positive number
 154              tmp_time2 = tmp_time.clone.gmtime
 155              branch    = revision.paths[0][:branch]
 156              scmid     = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S")
 157              cs = Changeset.create(:repository   => self,
 158                                    :revision     => "tmp#{tmp_rev_num}",
 159                                    :scmid        => scmid,
 160                                    :committer    => revision.author,
 161                                    :committed_on => tmp_time,
 162                                    :comments     => revision.message)
 163              tmp_rev_num += 1
 164            end
 165            # convert CVS-File-States to internal Action-abbrevations
 166            # default action is (M)odified
 167            action = "M"
 168            if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1"
 169              action = "A" # add-action always at first revision (= 1.1)
 170            elsif revision.paths[0][:action] == "dead"
 171              action = "D" # dead-state is similar to Delete
 172            end
 173            Change.create(
 174               :changeset => cs,
 175               :action    => action,
 176               :path      => scm.with_leading_slash(revision.paths[0][:path]),
 177               :revision  => revision.paths[0][:revision],
 178               :branch    => revision.paths[0][:branch]
 179                )
 180          end
 181        end
 182 
 183        # Renumber new changesets in chronological order
 184        Changeset.all(
 185                :order => 'committed_on ASC, id ASC',
 186                :conditions => ["repository_id = ? AND revision LIKE 'tmp%'", id]
 187             ).each do |changeset|
 188          changeset.update_attribute :revision, next_revision_number
 189        end
 190      end # transaction
 191      @current_revision_number = nil
 192    end
 193 
 194    private
 195 
 196    # Returns the next revision number to assign to a CVS changeset
 197    def next_revision_number
 198      # Need to retrieve existing revision numbers to sort them as integers
 199      sql = "SELECT revision FROM #{Changeset.table_name} "
 200      sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
 201      @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0)
 202      @current_revision_number += 1
 203    end
 204  end