Project

General

Profile

Defect #11834 » bazaar_adapter.rb

Alexander Usenko, 2012-09-14 17:15

 
1
# Redmine - project management software
2
# Copyright (C) 2006-2012  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/abstract_adapter'
19

    
20
module Redmine
21
  module Scm
22
    module Adapters
23
      class BazaarAdapter < AbstractAdapter
24

    
25
        # Bazaar executable name
26
        BZR_BIN = Redmine::Configuration['scm_bazaar_command'] || "bzr"
27

    
28
        class << self
29
          def client_command
30
            @@bin    ||= BZR_BIN
31
          end
32

    
33
          def sq_bin
34
            @@sq_bin ||= shell_quote_command
35
          end
36

    
37
          def client_version
38
            @@client_version ||= (scm_command_version || [])
39
          end
40

    
41
          def client_available
42
            !client_version.empty?
43
          end
44

    
45
          def scm_command_version
46
            scm_version = scm_version_from_command_line.dup
47
            if scm_version.respond_to?(:force_encoding)
48
              scm_version.force_encoding('ASCII-8BIT')
49
            end
50
            if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
51
              m[2].scan(%r{\d+}).collect(&:to_i)
52
            end
53
          end
54

    
55
          def scm_version_from_command_line
56
            shellout("#{sq_bin} --version") { |io| io.read }.to_s
57
          end
58
        end
59

    
60
#--- Begin: added by Alexander Usenko ---
61
# Need to know shell encoding for russian paths
62
        def initialize(url, root_url=nil, login=nil, password=nil,
63
                       path_encoding=nil)
64
          super
65
          @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
66
#          @log_encoding = 'UTF-8'
67
        end
68

    
69
        def path_encoding
70
          @path_encoding
71
        end
72

    
73
#        def log_encoding
74
#          @log_encoding
75
#        end
76
#--- End: added by Alexander Usenko ---
77

    
78
#--- All of 'output = scm_iconv('UTF-8', @path_encoding, io.read)' added by Alexander Usenko ---
79

    
80
        # Get info about the repository
81
        def info
82
          cmd_args = %w|revno|
83
          cmd_args << bzr_target('')
84
          info = nil
85
          scm_cmd(*cmd_args) do |io|            
86
            if io.read =~ %r{^(\d+)\r?$}
87
              info = Info.new({:root_url => url,
88
                               :lastrev => Revision.new({
89
                                 :identifier => $1
90
                               })
91
                             })
92
            end
93
          end
94
          info
95
        rescue ScmCommandAborted
96
          return nil
97
        end
98

    
99
        # Returns an Entries collection
100
        # or nil if the given path doesn't exist in the repository
101
        def entries(path=nil, identifier=nil, options={})
102
          path ||= ''
103
          entries = Entries.new
104
          identifier = -1 unless identifier && identifier.to_i > 0
105
          cmd_args = %w|ls -v --show-ids|
106
          cmd_args << "-r#{identifier.to_i}"
107
          cmd_args << bzr_target(path)
108
          scm_cmd(*cmd_args) do |io|
109
            output = scm_iconv('UTF-8', @path_encoding, io.read)
110
            prefix = "#{url}/#{path}".gsub('\\', '/')
111
            logger.debug "PREFIX: #{prefix}"
112
            re = %r{^V\s+(#{Regexp.escape(prefix)})?(\/?)([^\/]+)(\/?)\s+(\S+)\r?$}
113
            output.each_line do |line|
114
              next unless line =~ re
115
              entries << Entry.new({:name => $3.strip,
116
                                    :path => ((path.empty? ? "" : "#{path}/") + $3.strip),
117
                                    :kind => ($4.blank? ? 'file' : 'dir'),
118
                                    :size => nil,
119
                                    :lastrev => Revision.new(:revision => $5.strip)
120
                                  })
121
            end
122
          end
123
          if logger && logger.debug?
124
            logger.debug("Found #{entries.size} entries in the repository for #{target(path)}")
125
          end
126
          entries.sort_by_name
127
        rescue ScmCommandAborted
128
          return nil
129
        end
130

    
131
        def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
132
          path ||= ''
133
          identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : 'last:1'
134
          identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
135
          revisions = Revisions.new
136
          cmd_args = %w|log -v --show-ids|
137
          cmd_args << "-r#{identifier_to}..#{identifier_from}"
138
          cmd_args << bzr_target(path)
139
          scm_cmd(*cmd_args) do |io|
140
            output = scm_iconv('UTF-8', @path_encoding, io.read)
141
            revision = nil
142
            parsing  = nil
143
            output.each_line do |line|
144
              if line =~ /^----/
145
                revisions << revision if revision
146
                revision = Revision.new(:paths => [], :message => '')
147
                parsing = nil
148
              else
149
                next unless revision
150
                if line =~ /^revno: (\d+)($|\s\[merge\]$)/
151
                  revision.identifier = $1.to_i
152
                elsif line =~ /^committer: (.+)$/
153
                  revision.author = $1.strip
154
                elsif line =~ /^revision-id:(.+)$/
155
                  revision.scmid = $1.strip
156
                elsif line =~ /^timestamp: (.+)$/
157
                  revision.time = Time.parse($1).localtime
158
                elsif line =~ /^    -----/
159
                  # partial revisions
160
                  parsing = nil unless parsing == 'message'
161
                elsif line =~ /^(message|added|modified|removed|renamed):/
162
                  parsing = $1
163
                elsif line =~ /^  (.*)$/
164
                  if parsing == 'message'
165
                    # Convert back to original encoding?
166
                    # revision.message << scm_iconv(@path_encoding, 'UTF-8', "#{$1}\n")
167
                    revision.message << "#{$1}\n"
168
                  else
169
                    if $1 =~ /^(.*)\s+(\S+)$/
170
                      path = $1.strip
171
                      revid = $2
172
                      case parsing
173
                      when 'added'
174
                        revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid}
175
                      when 'modified'
176
                        revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid}
177
                      when 'removed'
178
                        revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid}
179
                      when 'renamed'
180
                        new_path = path.split('=>').last
181
                        revision.paths << {:action => 'M', :path => "/#{new_path.strip}", :revision => revid} if new_path
182
                      end
183
                    end
184
                  end
185
                else
186
                  parsing = nil
187
                end
188
              end
189
            end
190
            revisions << revision if revision
191
          end
192
          revisions
193
        rescue ScmCommandAborted
194
          return nil
195
        end
196

    
197
        def diff(path, identifier_from, identifier_to=nil)
198
          path ||= ''
199
          if identifier_to
200
            identifier_to = identifier_to.to_i
201
          else
202
            identifier_to = identifier_from.to_i - 1
203
          end
204
          if identifier_from
205
            identifier_from = identifier_from.to_i
206
          end
207
          diff = []
208
          cmd_args = %w|diff|
209
          cmd_args << "-r#{identifier_to}..#{identifier_from}"
210
          cmd_args << bzr_target(path)
211
          scm_cmd_no_raise(*cmd_args) do |io|
212
            io.each_line do |line|
213
              diff << line
214
            end
215
          end
216
          diff
217
        end
218

    
219
        def cat(path, identifier=nil)
220
          cat = nil
221
          cmd_args = %w|cat|
222
          cmd_args << "-r#{identifier.to_i}" if identifier && identifier.to_i > 0
223
          cmd_args << bzr_target(path)
224
          scm_cmd(*cmd_args) do |io|
225
            io.binmode
226
            cat = io.read
227
          end
228
          cat
229
        rescue ScmCommandAborted
230
          return nil
231
        end
232

    
233
        def annotate(path, identifier=nil)
234
          blame = Annotate.new
235
          cmd_args = %w|annotate -q --all|
236
          cmd_args << "-r#{identifier.to_i}" if identifier && identifier.to_i > 0
237
          cmd_args << bzr_target(path)
238
          scm_cmd(*cmd_args) do |io|
239
            output = scm_iconv('UTF-8', @path_encoding, io.read)
240
            author     = nil
241
            identifier = nil
242
            output.each_line do |line|
243
              next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$}
244
              rev = $1
245
              blame.add_line($3.rstrip,
246
                 Revision.new(
247
                  :identifier => rev,
248
                  :revision   => rev,
249
                  :author     => $2.strip
250
                  ))
251
            end
252
          end
253
          blame
254
        rescue ScmCommandAborted
255
          return nil
256
        end
257

    
258
        def self.branch_conf_path(path)
259
          bcp = nil
260
          m = path.match(%r{^(.*[/\\])\.bzr.*$})
261
          if m
262
            bcp = m[1]
263
          else
264
            bcp = path
265
          end
266
          bcp.gsub!(%r{[\/\\]$}, "")
267
          if bcp
268
            bcp = File.join(bcp, ".bzr", "branch", "branch.conf")
269
          end
270
          bcp
271
        end
272

    
273
        def append_revisions_only
274
          return @aro if ! @aro.nil?
275
          @aro = false
276
          bcp = self.class.branch_conf_path(url)
277
          if bcp && File.exist?(bcp)
278
            begin
279
              f = File::open(bcp, "r")
280
              cnt = 0
281
              f.each_line do |line|
282
                l = line.chomp.to_s
283
                if l =~ /^\s*append_revisions_only\s*=\s*(\w+)\s*$/
284
                  str_aro = $1
285
                  if str_aro.upcase == "TRUE"
286
                    @aro = true
287
                    cnt += 1
288
                  elsif str_aro.upcase == "FALSE"
289
                    @aro = false
290
                    cnt += 1
291
                  end
292
                  if cnt > 1
293
                    @aro = false
294
                    break
295
                  end
296
                end
297
              end
298
            ensure
299
              f.close
300
            end
301
          end
302
          @aro
303
        end
304

    
305
        def scm_cmd(*args, &block)
306
          full_args = []
307
          full_args += args
308
          ret = shellout(
309
                   self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
310
                   &block
311
                   )
312
          if $? && $?.exitstatus != 0
313
            raise ScmCommandAborted, "bzr exited with non-zero status: #{$?.exitstatus}"
314
          end
315
          ret
316
        end
317
        private :scm_cmd
318

    
319
        def scm_cmd_no_raise(*args, &block)
320
          full_args = []
321
          full_args += args
322
          ret = shellout(
323
                   self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
324
                   &block
325
                   )
326
          ret
327
        end
328
        private :scm_cmd_no_raise
329

    
330
        def bzr_target(path)
331
#--- Begin: added by Alexander Usenko ---
332
          if path != nil && !path.empty?
333
            path = scm_iconv(@path_encoding, 'UTF-8', path)
334
          end
335
#--- End: added by Alexander Usenko ---
336
          target(path, false)
337
        end
338
        private :bzr_target
339
      end
340
    end
341
  end
342
end
(2-2/3)