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 @ 923:bd2afdf7e446

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