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
|