File: lib/redmine/scm/adapters/abstract_adapter.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: Redmine#20
  module: Scm#21
  module: Adapters#22
  class: CommandFailed#23
inherits from
  StandardError ( Builtin-Module )
  class: AbstractAdapter#26
inherits from
  Object ( Builtin-Module )
has properties
class method: client_command #32
class method: shell_quote_command #36
class method: client_version #46
class method: client_version_string #52
class method: client_version_above? / 2 #61
class method: client_available #65
class method: shell_quote (1/2) / 1 #69
method: initialize / 5 #78
method: adapter_name #86
method: supports_cat? #90
method: supports_annotate? #94
method: root_url #98
method: url #102
method: path_encoding #106
method: info #111
method: entry / 2 #117
method: entries / 3 #133
method: branches #137
method: tags #141
method: default_branch #145
method: properties / 2 #149
method: revisions / 4 #153
method: diff / 3 #157
method: cat / 2 #161
method: with_leading_slash / 1 #165
method: with_trailling_slash / 1 #170
method: without_leading_slash / 1 #175
method: without_trailling_slash / 1 #180
method: shell_quote (2/E) / 1 #185
method: retrieve_root_url #190
method: target / 2 #195
method: logger (1/2) #205
method: shellout (1/2) / 3 #209
class method: logger (2/E) #213
class method: shellout (2/E) / 3 #217
class method: strip_credential (1/2) / 1 #251
method: strip_credential (2/E) / 1 #256
method: scm_iconv / 3 #260
  class: ScmCommandAborted#29
inherits from
  CommandFailed ( Redmine::Scm::Adapters )
  class: Entries#272
inherits from
  Array ( Builtin-Module )
has properties
method: sort_by_name #273
method: revisions #283
  class: Info#288
inherits from
  Object ( Builtin-Module )
has properties
attribute: root_url [RW] #289
attribute: lastrev [RW] #289
method: initialize / 1 #290
  class: Entry#296
inherits from
  Object ( Builtin-Module )
has properties
attribute: name [RW] #297
attribute: path [RW] #297
attribute: kind [RW] #297
attribute: size [RW] #297
attribute: lastrev [RW] #297
method: initialize / 1 #298
method: is_file? #306
method: is_dir? #310
method: is_text? #314
  class: Revisions#319
inherits from
  Array ( Builtin-Module )
has properties
method: latest #320
  class: Revision#331
inherits from
  Object ( Builtin-Module )
has properties
attribute: scmid [RW] #332
attribute: name [RW] #332
attribute: author [RW] #332
attribute: time [RW] #332
attribute: message [RW] #332
attribute: paths [RW] #333
attribute: revision [RW] #333
attribute: branch [RW] #333
attribute: identifier [RW] #333
attribute: parents [RW] #334
method: initialize / 1 #336
method: format_identifier #350
  class: Annotate#355
inherits from
  Object ( Builtin-Module )
has properties
attribute: lines [R] #356
attribute: revisions [R] #356
method: initialize #358
method: add_line / 2 #363
method: content #368
method: empty? #372
  class: Branch#377
inherits from
  String ( Builtin-Module )
has properties
attribute: revision [RW] #378
attribute: scmid [RW] #378

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 'cgi'
  19 
  20  module Redmine
  21    module Scm
  22      module Adapters
  23        class CommandFailed < StandardError #:nodoc:
  24        end
  25 
  26        class AbstractAdapter #:nodoc:
  27 
  28          # raised if scm command exited with error, e.g. unknown revision.
  29          class ScmCommandAborted < CommandFailed; end
  30 
  31          class << self
  32            def client_command
  33              ""
  34            end
  35 
  36            def shell_quote_command
  37              if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
  38                client_command
  39              else
  40                shell_quote(client_command)
  41              end
  42            end
  43 
  44            # Returns the version of the scm client
  45            # Eg: [1, 5, 0] or [] if unknown
  46            def client_version
  47              []
  48            end
  49 
  50            # Returns the version string of the scm client
  51            # Eg: '1.5.0' or 'Unknown version' if unknown
  52            def client_version_string
  53              v = client_version || 'Unknown version'
  54              v.is_a?(Array) ? v.join('.') : v.to_s
  55            end
  56 
  57            # Returns true if the current client version is above
  58            # or equals the given one
  59            # If option is :unknown is set to true, it will return
  60            # true if the client version is unknown
  61            def client_version_above?(v, options={})
  62              ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
  63            end
  64 
  65            def client_available
  66              true
  67            end
  68 
  69            def shell_quote(str)
  70              if Redmine::Platform.mswin?
  71                '"' + str.gsub(/"/, '\\"') + '"'
  72              else
  73                "'" + str.gsub(/'/, "'\"'\"'") + "'"
  74              end
  75            end
  76          end
  77 
  78          def initialize(url, root_url=nil, login=nil, password=nil,
  79                         path_encoding=nil)
  80            @url = url
  81            @login = login if login && !login.empty?
  82            @password = (password || "") if @login
  83            @root_url = root_url.blank? ? retrieve_root_url : root_url
  84          end
  85 
  86          def adapter_name
  87            'Abstract'
  88          end
  89 
  90          def supports_cat?
  91            true
  92          end
  93 
  94          def supports_annotate?
  95            respond_to?('annotate')
  96          end
  97 
  98          def root_url
  99            @root_url
 100          end
 101 
 102          def url
 103            @url
 104          end
 105 
 106          def path_encoding
 107            nil
 108          end
 109 
 110          # get info about the svn repository
 111          def info
 112            return nil
 113          end
 114 
 115          # Returns the entry identified by path and revision identifier
 116          # or nil if entry doesn't exist in the repository
 117          def entry(path=nil, identifier=nil)
 118            parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
 119            search_path = parts[0..-2].join('/')
 120            search_name = parts[-1]
 121            if search_path.blank? && search_name.blank?
 122              # Root entry
 123              Entry.new(:path => '', :kind => 'dir')
 124            else
 125              # Search for the entry in the parent directory
 126              es = entries(search_path, identifier)
 127              es ? es.detect {|e| e.name == search_name} : nil
 128            end
 129          end
 130 
 131          # Returns an Entries collection
 132          # or nil if the given path doesn't exist in the repository
 133          def entries(path=nil, identifier=nil, options={})
 134            return nil
 135          end
 136 
 137          def branches
 138            return nil
 139          end
 140 
 141          def tags
 142            return nil
 143          end
 144 
 145          def default_branch
 146            return nil
 147          end
 148 
 149          def properties(path, identifier=nil)
 150            return nil
 151          end
 152 
 153          def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
 154            return nil
 155          end
 156 
 157          def diff(path, identifier_from, identifier_to=nil)
 158            return nil
 159          end
 160 
 161          def cat(path, identifier=nil)
 162            return nil
 163          end
 164 
 165          def with_leading_slash(path)
 166            path ||= ''
 167            (path[0,1]!="/") ? "/#{path}" : path
 168          end
 169 
 170          def with_trailling_slash(path)
 171            path ||= ''
 172            (path[-1,1] == "/") ? path : "#{path}/"
 173          end
 174 
 175          def without_leading_slash(path)
 176            path ||= ''
 177            path.gsub(%r{^/+}, '')
 178          end
 179 
 180          def without_trailling_slash(path)
 181            path ||= ''
 182            (path[-1,1] == "/") ? path[0..-2] : path
 183           end
 184 
 185          def shell_quote(str)
 186            self.class.shell_quote(str)
 187          end
 188 
 189        private
 190          def retrieve_root_url
 191            info = self.info
 192            info ? info.root_url : nil
 193          end
 194 
 195          def target(path, sq=true)
 196            path ||= ''
 197            base = path.match(/^\//) ? root_url : url
 198            str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
 199            if sq
 200              str = shell_quote(str)
 201            end
 202            str
 203          end
 204 
 205          def logger
 206            self.class.logger
 207          end
 208 
 209          def shellout(cmd, options = {}, &block)
 210            self.class.shellout(cmd, options, &block)
 211          end
 212 
 213          def self.logger
 214            Rails.logger
 215          end
 216 
 217          def self.shellout(cmd, options = {}, &block)
 218            if logger && logger.debug?
 219              logger.debug "Shelling out: #{strip_credential(cmd)}"
 220            end
 221            if Rails.env == 'development'
 222              # Capture stderr when running in dev environment
 223              cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
 224            end
 225            begin
 226              mode = "r+"
 227              IO.popen(cmd, mode) do |io|
 228                io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
 229                io.close_write unless options[:write_stdin]
 230                block.call(io) if block_given?
 231              end
 232            ## If scm command does not exist,
 233            ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
 234            ## in production environment.
 235            # rescue Errno::ENOENT => e
 236            rescue Exception => e
 237              msg = strip_credential(e.message)
 238              # The command failed, log it and re-raise
 239              logmsg = "SCM command failed, "
 240              logmsg += "make sure that your SCM command (e.g. svn) is "
 241              logmsg += "in PATH (#{ENV['PATH']})\n"
 242              logmsg += "You can configure your scm commands in config/configuration.yml.\n"
 243              logmsg += "#{strip_credential(cmd)}\n"
 244              logmsg += "with: #{msg}"
 245              logger.error(logmsg)
 246              raise CommandFailed.new(msg)
 247            end
 248          end
 249 
 250          # Hides username/password in a given command
 251          def self.strip_credential(cmd)
 252            q = (Redmine::Platform.mswin? ? '"' : "'")
 253            cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
 254          end
 255 
 256          def strip_credential(cmd)
 257            self.class.strip_credential(cmd)
 258          end
 259 
 260          def scm_iconv(to, from, str)
 261            return nil if str.nil?
 262            return str if to == from
 263            begin
 264              Iconv.conv(to, from, str)
 265            rescue Iconv::Failure => err
 266              logger.error("failed to convert from #{from} to #{to}. #{err}")
 267              nil
 268            end
 269          end
 270        end
 271 
 272        class Entries < Array
 273          def sort_by_name
 274            sort {|x,y|
 275              if x.kind == y.kind
 276                x.name.to_s <=> y.name.to_s
 277              else
 278                x.kind <=> y.kind
 279              end
 280            }
 281          end
 282 
 283          def revisions
 284            revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
 285          end
 286        end
 287 
 288        class Info
 289          attr_accessor :root_url, :lastrev
 290          def initialize(attributes={})
 291            self.root_url = attributes[:root_url] if attributes[:root_url]
 292            self.lastrev = attributes[:lastrev]
 293          end
 294        end
 295 
 296        class Entry
 297          attr_accessor :name, :path, :kind, :size, :lastrev
 298          def initialize(attributes={})
 299            self.name = attributes[:name] if attributes[:name]
 300            self.path = attributes[:path] if attributes[:path]
 301            self.kind = attributes[:kind] if attributes[:kind]
 302            self.size = attributes[:size].to_i if attributes[:size]
 303            self.lastrev = attributes[:lastrev]
 304          end
 305 
 306          def is_file?
 307            'file' == self.kind
 308          end
 309 
 310          def is_dir?
 311            'dir' == self.kind
 312          end
 313 
 314          def is_text?
 315            Redmine::MimeType.is_type?('text', name)
 316          end
 317        end
 318 
 319        class Revisions < Array
 320          def latest
 321            sort {|x,y|
 322              unless x.time.nil? or y.time.nil?
 323                x.time <=> y.time
 324              else
 325                0
 326              end
 327            }.last
 328          end
 329        end
 330 
 331        class Revision
 332          attr_accessor :scmid, :name, :author, :time, :message,
 333                        :paths, :revision, :branch, :identifier,
 334                        :parents
 335 
 336          def initialize(attributes={})
 337            self.identifier = attributes[:identifier]
 338            self.scmid      = attributes[:scmid]
 339            self.name       = attributes[:name] || self.identifier
 340            self.author     = attributes[:author]
 341            self.time       = attributes[:time]
 342            self.message    = attributes[:message] || ""
 343            self.paths      = attributes[:paths]
 344            self.revision   = attributes[:revision]
 345            self.branch     = attributes[:branch]
 346            self.parents    = attributes[:parents]
 347          end
 348 
 349          # Returns the readable identifier.
 350          def format_identifier
 351            self.identifier.to_s
 352          end
 353        end
 354 
 355        class Annotate
 356          attr_reader :lines, :revisions
 357 
 358          def initialize
 359            @lines = []
 360            @revisions = []
 361          end
 362 
 363          def add_line(line, revision)
 364            @lines << line
 365            @revisions << revision
 366          end
 367 
 368          def content
 369            content = lines.join("\n")
 370          end
 371 
 372          def empty?
 373            lines.empty?
 374          end
 375        end
 376 
 377        class Branch < String
 378          attr_accessor :revision, :scmid
 379        end
 380      end
 381    end
 382  end