To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / lib / redmine / scm / adapters / abstract_adapter.rb @ 924:18beae6cb226

History | View | Annotate | Download (10.7 KB)

1 441:cbce1fd3b1b7 Chris
# Redmine - project management software
2
# Copyright (C) 2006-2011  Jean-Philippe Lang
3 0:513646585e45 Chris
#
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 441:cbce1fd3b1b7 Chris
#
9 0:513646585e45 Chris
# 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 441:cbce1fd3b1b7 Chris
#
14 0:513646585e45 Chris
# 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 245:051f544170fe Chris
    module Adapters
23 0:513646585e45 Chris
      class CommandFailed < StandardError #:nodoc:
24
      end
25 245:051f544170fe Chris
26 0:513646585e45 Chris
      class AbstractAdapter #:nodoc:
27 909:cbb26bc654de Chris
28
        # raised if scm command exited with error, e.g. unknown revision.
29
        class ScmCommandAborted < CommandFailed; end
30
31 0:513646585e45 Chris
        class << self
32 245:051f544170fe Chris
          def client_command
33
            ""
34
          end
35
36 909:cbb26bc654de Chris
          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 0:513646585e45 Chris
          # Returns the version of the scm client
45
          # Eg: [1, 5, 0] or [] if unknown
46
          def client_version
47
            []
48
          end
49 245:051f544170fe Chris
50 0:513646585e45 Chris
          # 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 245:051f544170fe Chris
57 0:513646585e45 Chris
          # 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 245:051f544170fe Chris
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 0:513646585e45 Chris
        end
77 245:051f544170fe Chris
78
        def initialize(url, root_url=nil, login=nil, password=nil,
79
                       path_encoding=nil)
80 0:513646585e45 Chris
          @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 245:051f544170fe Chris
86 0:513646585e45 Chris
        def adapter_name
87
          'Abstract'
88
        end
89 245:051f544170fe Chris
90 0:513646585e45 Chris
        def supports_cat?
91
          true
92
        end
93
94
        def supports_annotate?
95
          respond_to?('annotate')
96
        end
97 245:051f544170fe Chris
98 0:513646585e45 Chris
        def root_url
99
          @root_url
100
        end
101 245:051f544170fe Chris
102 0:513646585e45 Chris
        def url
103
          @url
104
        end
105 441:cbce1fd3b1b7 Chris
106
        def path_encoding
107
          nil
108
        end
109
110 0:513646585e45 Chris
        # get info about the svn repository
111
        def info
112
          return nil
113
        end
114 441:cbce1fd3b1b7 Chris
115 0:513646585e45 Chris
        # 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 441:cbce1fd3b1b7 Chris
131 0:513646585e45 Chris
        # Returns an Entries collection
132
        # or nil if the given path doesn't exist in the repository
133 441:cbce1fd3b1b7 Chris
        def entries(path=nil, identifier=nil, options={})
134 0:513646585e45 Chris
          return nil
135
        end
136
137
        def branches
138
          return nil
139
        end
140
141 441:cbce1fd3b1b7 Chris
        def tags
142 0:513646585e45 Chris
          return nil
143
        end
144
145
        def default_branch
146
          return nil
147
        end
148 441:cbce1fd3b1b7 Chris
149 0:513646585e45 Chris
        def properties(path, identifier=nil)
150
          return nil
151
        end
152 441:cbce1fd3b1b7 Chris
153 0:513646585e45 Chris
        def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
154
          return nil
155
        end
156 441:cbce1fd3b1b7 Chris
157 0:513646585e45 Chris
        def diff(path, identifier_from, identifier_to=nil)
158
          return nil
159
        end
160 441:cbce1fd3b1b7 Chris
161 0:513646585e45 Chris
        def cat(path, identifier=nil)
162
          return nil
163
        end
164 441:cbce1fd3b1b7 Chris
165 0:513646585e45 Chris
        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 245:051f544170fe Chris
175 0:513646585e45 Chris
        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 245:051f544170fe Chris
185 0:513646585e45 Chris
        def shell_quote(str)
186 245:051f544170fe Chris
          self.class.shell_quote(str)
187 0:513646585e45 Chris
        end
188
189
      private
190
        def retrieve_root_url
191
          info = self.info
192
          info ? info.root_url : nil
193
        end
194 441:cbce1fd3b1b7 Chris
195 909:cbb26bc654de Chris
        def target(path, sq=true)
196 0:513646585e45 Chris
          path ||= ''
197
          base = path.match(/^\//) ? root_url : url
198 909:cbb26bc654de Chris
          str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
199
          if sq
200
            str = shell_quote(str)
201
          end
202
          str
203 0:513646585e45 Chris
        end
204 245:051f544170fe Chris
205 0:513646585e45 Chris
        def logger
206
          self.class.logger
207
        end
208 245:051f544170fe Chris
209 0:513646585e45 Chris
        def shellout(cmd, &block)
210
          self.class.shellout(cmd, &block)
211
        end
212 245:051f544170fe Chris
213 0:513646585e45 Chris
        def self.logger
214 909:cbb26bc654de Chris
          Rails.logger
215 0:513646585e45 Chris
        end
216 245:051f544170fe Chris
217 0:513646585e45 Chris
        def self.shellout(cmd, &block)
218 507:0c939c159af4 Chris
          if logger && logger.debug?
219
            logger.debug "Shelling out: #{strip_credential(cmd)}"
220
          end
221 0:513646585e45 Chris
          if Rails.env == 'development'
222
            # Capture stderr when running in dev environment
223 909:cbb26bc654de Chris
            cmd = "#{cmd} 2>>#{Rails.root}/log/scm.stderr.log"
224 0:513646585e45 Chris
          end
225
          begin
226 245:051f544170fe Chris
            if RUBY_VERSION < '1.9'
227
              mode = "r+"
228
            else
229
              mode = "r+:ASCII-8BIT"
230
            end
231
            IO.popen(cmd, mode) do |io|
232 0:513646585e45 Chris
              io.close_write
233
              block.call(io) if block_given?
234
            end
235 909:cbb26bc654de Chris
          ## If scm command does not exist,
236
          ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
237
          ## in production environment.
238
          # rescue Errno::ENOENT => e
239
          rescue Exception => e
240 0:513646585e45 Chris
            msg = strip_credential(e.message)
241
            # The command failed, log it and re-raise
242 507:0c939c159af4 Chris
            logmsg = "SCM command failed, "
243
            logmsg += "make sure that your SCM command (e.g. svn) is "
244
            logmsg += "in PATH (#{ENV['PATH']})\n"
245
            logmsg += "You can configure your scm commands in config/configuration.yml.\n"
246
            logmsg += "#{strip_credential(cmd)}\n"
247
            logmsg += "with: #{msg}"
248
            logger.error(logmsg)
249 0:513646585e45 Chris
            raise CommandFailed.new(msg)
250
          end
251 245:051f544170fe Chris
        end
252
253 0:513646585e45 Chris
        # Hides username/password in a given command
254
        def self.strip_credential(cmd)
255
          q = (Redmine::Platform.mswin? ? '"' : "'")
256
          cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
257
        end
258 441:cbce1fd3b1b7 Chris
259 0:513646585e45 Chris
        def strip_credential(cmd)
260
          self.class.strip_credential(cmd)
261
        end
262 245:051f544170fe Chris
263
        def scm_iconv(to, from, str)
264
          return nil if str.nil?
265 922:ad295b270cd4 Chris
          # bug 446: non-utf8 paths in repositories blow up repo viewer and reposman
266
          # -- Remove this short-circuit: we want the conversion to
267
          #    happen always, so we can trap the error here if the
268
          #    source text happens not to be in the advertised
269
          #    encoding (instead of having the database blow up later)
270
#          return str if to == from
271 245:051f544170fe Chris
          begin
272
            Iconv.conv(to, from, str)
273
          rescue Iconv::Failure => err
274
            logger.error("failed to convert from #{from} to #{to}. #{err}")
275
            nil
276
          end
277
        end
278 0:513646585e45 Chris
      end
279 245:051f544170fe Chris
280 0:513646585e45 Chris
      class Entries < Array
281
        def sort_by_name
282 441:cbce1fd3b1b7 Chris
          sort {|x,y|
283 0:513646585e45 Chris
            if x.kind == y.kind
284
              x.name.to_s <=> y.name.to_s
285
            else
286
              x.kind <=> y.kind
287
            end
288 245:051f544170fe Chris
          }
289 0:513646585e45 Chris
        end
290 441:cbce1fd3b1b7 Chris
291 0:513646585e45 Chris
        def revisions
292
          revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
293
        end
294
      end
295 441:cbce1fd3b1b7 Chris
296 0:513646585e45 Chris
      class Info
297
        attr_accessor :root_url, :lastrev
298
        def initialize(attributes={})
299
          self.root_url = attributes[:root_url] if attributes[:root_url]
300
          self.lastrev = attributes[:lastrev]
301
        end
302
      end
303 441:cbce1fd3b1b7 Chris
304 0:513646585e45 Chris
      class Entry
305
        attr_accessor :name, :path, :kind, :size, :lastrev
306
        def initialize(attributes={})
307
          self.name = attributes[:name] if attributes[:name]
308
          self.path = attributes[:path] if attributes[:path]
309
          self.kind = attributes[:kind] if attributes[:kind]
310
          self.size = attributes[:size].to_i if attributes[:size]
311
          self.lastrev = attributes[:lastrev]
312
        end
313 441:cbce1fd3b1b7 Chris
314 0:513646585e45 Chris
        def is_file?
315
          'file' == self.kind
316
        end
317 441:cbce1fd3b1b7 Chris
318 0:513646585e45 Chris
        def is_dir?
319
          'dir' == self.kind
320
        end
321 441:cbce1fd3b1b7 Chris
322 0:513646585e45 Chris
        def is_text?
323
          Redmine::MimeType.is_type?('text', name)
324
        end
325
      end
326 441:cbce1fd3b1b7 Chris
327 0:513646585e45 Chris
      class Revisions < Array
328
        def latest
329
          sort {|x,y|
330
            unless x.time.nil? or y.time.nil?
331
              x.time <=> y.time
332
            else
333
              0
334
            end
335
          }.last
336 441:cbce1fd3b1b7 Chris
        end
337 0:513646585e45 Chris
      end
338 441:cbce1fd3b1b7 Chris
339 0:513646585e45 Chris
      class Revision
340 441:cbce1fd3b1b7 Chris
        attr_accessor :scmid, :name, :author, :time, :message,
341 909:cbb26bc654de Chris
                      :paths, :revision, :branch, :identifier,
342
                      :parents
343 0:513646585e45 Chris
344
        def initialize(attributes={})
345
          self.identifier = attributes[:identifier]
346 441:cbce1fd3b1b7 Chris
          self.scmid      = attributes[:scmid]
347
          self.name       = attributes[:name] || self.identifier
348
          self.author     = attributes[:author]
349
          self.time       = attributes[:time]
350
          self.message    = attributes[:message] || ""
351
          self.paths      = attributes[:paths]
352
          self.revision   = attributes[:revision]
353
          self.branch     = attributes[:branch]
354 909:cbb26bc654de Chris
          self.parents    = attributes[:parents]
355 117:af80e5618e9b Chris
        end
356
357
        # Returns the readable identifier.
358
        def format_identifier
359 441:cbce1fd3b1b7 Chris
          self.identifier.to_s
360 117:af80e5618e9b Chris
        end
361 245:051f544170fe Chris
      end
362 117:af80e5618e9b Chris
363 0:513646585e45 Chris
      class Annotate
364
        attr_reader :lines, :revisions
365 441:cbce1fd3b1b7 Chris
366 0:513646585e45 Chris
        def initialize
367
          @lines = []
368
          @revisions = []
369
        end
370 441:cbce1fd3b1b7 Chris
371 0:513646585e45 Chris
        def add_line(line, revision)
372
          @lines << line
373
          @revisions << revision
374
        end
375 441:cbce1fd3b1b7 Chris
376 0:513646585e45 Chris
        def content
377
          content = lines.join("\n")
378
        end
379 441:cbce1fd3b1b7 Chris
380 0:513646585e45 Chris
        def empty?
381
          lines.empty?
382
        end
383
      end
384 909:cbb26bc654de Chris
385
      class Branch < String
386
        attr_accessor :revision, :scmid
387
      end
388 0:513646585e45 Chris
    end
389
  end
390
end