##// END OF EJS Templates
Merged r12017 and r12027 from trunk (#14422)....
Jean-Philippe Lang -
r11835:e1e006f09e1a
parent child
Show More
@@ -1,462 +1,462
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class CvsAdapter < AbstractAdapter
23 class CvsAdapter < AbstractAdapter
24
24
25 # CVS executable name
25 # CVS executable name
26 CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs"
26 CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs"
27
27
28 class << self
28 class << self
29 def client_command
29 def client_command
30 @@bin ||= CVS_BIN
30 @@bin ||= CVS_BIN
31 end
31 end
32
32
33 def sq_bin
33 def sq_bin
34 @@sq_bin ||= shell_quote_command
34 @@sq_bin ||= shell_quote_command
35 end
35 end
36
36
37 def client_version
37 def client_version
38 @@client_version ||= (scm_command_version || [])
38 @@client_version ||= (scm_command_version || [])
39 end
39 end
40
40
41 def client_available
41 def client_available
42 client_version_above?([1, 12])
42 client_version_above?([1, 12])
43 end
43 end
44
44
45 def scm_command_version
45 def scm_command_version
46 scm_version = scm_version_from_command_line.dup
46 scm_version = scm_version_from_command_line.dup
47 if scm_version.respond_to?(:force_encoding)
47 if scm_version.respond_to?(:force_encoding)
48 scm_version.force_encoding('ASCII-8BIT')
48 scm_version.force_encoding('ASCII-8BIT')
49 end
49 end
50 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}m)
50 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}m)
51 m[2].scan(%r{\d+}).collect(&:to_i)
51 m[2].scan(%r{\d+}).collect(&:to_i)
52 end
52 end
53 end
53 end
54
54
55 def scm_version_from_command_line
55 def scm_version_from_command_line
56 shellout("#{sq_bin} --version") { |io| io.read }.to_s
56 shellout("#{sq_bin} --version") { |io| io.read }.to_s
57 end
57 end
58 end
58 end
59
59
60 # Guidelines for the input:
60 # Guidelines for the input:
61 # url -> the project-path, relative to the cvsroot (eg. module name)
61 # url -> the project-path, relative to the cvsroot (eg. module name)
62 # root_url -> the good old, sometimes damned, CVSROOT
62 # root_url -> the good old, sometimes damned, CVSROOT
63 # login -> unnecessary
63 # login -> unnecessary
64 # password -> unnecessary too
64 # password -> unnecessary too
65 def initialize(url, root_url=nil, login=nil, password=nil,
65 def initialize(url, root_url=nil, login=nil, password=nil,
66 path_encoding=nil)
66 path_encoding=nil)
67 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
67 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
68 @url = url
68 @url = url
69 # TODO: better Exception here (IllegalArgumentException)
69 # TODO: better Exception here (IllegalArgumentException)
70 raise CommandFailed if root_url.blank?
70 raise CommandFailed if root_url.blank?
71 @root_url = root_url
71 @root_url = root_url
72
72
73 # These are unused.
73 # These are unused.
74 @login = login if login && !login.empty?
74 @login = login if login && !login.empty?
75 @password = (password || "") if @login
75 @password = (password || "") if @login
76 end
76 end
77
77
78 def path_encoding
78 def path_encoding
79 @path_encoding
79 @path_encoding
80 end
80 end
81
81
82 def info
82 def info
83 logger.debug "<cvs> info"
83 logger.debug "<cvs> info"
84 Info.new({:root_url => @root_url, :lastrev => nil})
84 Info.new({:root_url => @root_url, :lastrev => nil})
85 end
85 end
86
86
87 def get_previous_revision(revision)
87 def get_previous_revision(revision)
88 CvsRevisionHelper.new(revision).prevRev
88 CvsRevisionHelper.new(revision).prevRev
89 end
89 end
90
90
91 # Returns an Entries collection
91 # Returns an Entries collection
92 # or nil if the given path doesn't exist in the repository
92 # or nil if the given path doesn't exist in the repository
93 # this method is used by the repository-browser (aka LIST)
93 # this method is used by the repository-browser (aka LIST)
94 def entries(path=nil, identifier=nil, options={})
94 def entries(path=nil, identifier=nil, options={})
95 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
95 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
96 path_locale = scm_iconv(@path_encoding, 'UTF-8', path)
96 path_locale = scm_iconv(@path_encoding, 'UTF-8', path)
97 path_locale.force_encoding("ASCII-8BIT") if path_locale.respond_to?(:force_encoding)
97 path_locale.force_encoding("ASCII-8BIT") if path_locale.respond_to?(:force_encoding)
98 entries = Entries.new
98 entries = Entries.new
99 cmd_args = %w|-q rls -e|
99 cmd_args = %w|-q rls -e|
100 cmd_args << "-D" << time_to_cvstime_rlog(identifier) if identifier
100 cmd_args << "-D" << time_to_cvstime_rlog(identifier) if identifier
101 cmd_args << path_with_proj(path)
101 cmd_args << path_with_proj(path)
102 scm_cmd(*cmd_args) do |io|
102 scm_cmd(*cmd_args) do |io|
103 io.each_line() do |line|
103 io.each_line() do |line|
104 fields = line.chop.split('/',-1)
104 fields = line.chop.split('/',-1)
105 logger.debug(">>InspectLine #{fields.inspect}")
105 logger.debug(">>InspectLine #{fields.inspect}")
106 if fields[0]!="D"
106 if fields[0]!="D"
107 time = nil
107 time = nil
108 # Thu Dec 13 16:27:22 2007
108 # Thu Dec 13 16:27:22 2007
109 time_l = fields[-3].split(' ')
109 time_l = fields[-3].split(' ')
110 if time_l.size == 5 && time_l[4].length == 4
110 if time_l.size == 5 && time_l[4].length == 4
111 begin
111 begin
112 time = Time.parse(
112 time = Time.parse(
113 "#{time_l[1]} #{time_l[2]} #{time_l[3]} GMT #{time_l[4]}")
113 "#{time_l[1]} #{time_l[2]} #{time_l[3]} GMT #{time_l[4]}")
114 rescue
114 rescue
115 end
115 end
116 end
116 end
117 entries << Entry.new(
117 entries << Entry.new(
118 {
118 {
119 :name => scm_iconv('UTF-8', @path_encoding, fields[-5]),
119 :name => scm_iconv('UTF-8', @path_encoding, fields[-5]),
120 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
120 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
121 :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[-5]}"),
121 :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[-5]}"),
122 :kind => 'file',
122 :kind => 'file',
123 :size => nil,
123 :size => nil,
124 :lastrev => Revision.new(
124 :lastrev => Revision.new(
125 {
125 {
126 :revision => fields[-4],
126 :revision => fields[-4],
127 :name => scm_iconv('UTF-8', @path_encoding, fields[-4]),
127 :name => scm_iconv('UTF-8', @path_encoding, fields[-4]),
128 :time => time,
128 :time => time,
129 :author => ''
129 :author => ''
130 })
130 })
131 })
131 })
132 else
132 else
133 entries << Entry.new(
133 entries << Entry.new(
134 {
134 {
135 :name => scm_iconv('UTF-8', @path_encoding, fields[1]),
135 :name => scm_iconv('UTF-8', @path_encoding, fields[1]),
136 :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[1]}"),
136 :path => scm_iconv('UTF-8', @path_encoding, "#{path_locale}/#{fields[1]}"),
137 :kind => 'dir',
137 :kind => 'dir',
138 :size => nil,
138 :size => nil,
139 :lastrev => nil
139 :lastrev => nil
140 })
140 })
141 end
141 end
142 end
142 end
143 end
143 end
144 entries.sort_by_name
144 entries.sort_by_name
145 rescue ScmCommandAborted
145 rescue ScmCommandAborted
146 nil
146 nil
147 end
147 end
148
148
149 STARTLOG="----------------------------"
149 STARTLOG="----------------------------"
150 ENDLOG ="============================================================================="
150 ENDLOG ="============================================================================="
151
151
152 # Returns all revisions found between identifier_from and identifier_to
152 # Returns all revisions found between identifier_from and identifier_to
153 # in the repository. both identifier have to be dates or nil.
153 # in the repository. both identifier have to be dates or nil.
154 # these method returns nothing but yield every result in block
154 # these method returns nothing but yield every result in block
155 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
155 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
156 path_with_project_utf8 = path_with_proj(path)
156 path_with_project_utf8 = path_with_proj(path)
157 path_with_project_locale = scm_iconv(@path_encoding, 'UTF-8', path_with_project_utf8)
157 path_with_project_locale = scm_iconv(@path_encoding, 'UTF-8', path_with_project_utf8)
158 logger.debug "<cvs> revisions path:" +
158 logger.debug "<cvs> revisions path:" +
159 "'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
159 "'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
160 cmd_args = %w|-q rlog|
160 cmd_args = %w|-q rlog|
161 cmd_args << "-d" << ">#{time_to_cvstime_rlog(identifier_from)}" if identifier_from
161 cmd_args << "-d" << ">#{time_to_cvstime_rlog(identifier_from)}" if identifier_from
162 cmd_args << path_with_project_utf8
162 cmd_args << path_with_project_utf8
163 scm_cmd(*cmd_args) do |io|
163 scm_cmd(*cmd_args) do |io|
164 state = "entry_start"
164 state = "entry_start"
165 commit_log = String.new
165 commit_log = String.new
166 revision = nil
166 revision = nil
167 date = nil
167 date = nil
168 author = nil
168 author = nil
169 entry_path = nil
169 entry_path = nil
170 entry_name = nil
170 entry_name = nil
171 file_state = nil
171 file_state = nil
172 branch_map = nil
172 branch_map = nil
173 io.each_line() do |line|
173 io.each_line() do |line|
174 if state != "revision" && /^#{ENDLOG}/ =~ line
174 if state != "revision" && /^#{ENDLOG}/ =~ line
175 commit_log = String.new
175 commit_log = String.new
176 revision = nil
176 revision = nil
177 state = "entry_start"
177 state = "entry_start"
178 end
178 end
179 if state == "entry_start"
179 if state == "entry_start"
180 branch_map = Hash.new
180 branch_map = Hash.new
181 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project_locale)}(.+),v$/ =~ line
181 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project_locale)}(.+),v$/ =~ line
182 entry_path = normalize_cvs_path($1)
182 entry_path = normalize_cvs_path($1)
183 entry_name = normalize_path(File.basename($1))
183 entry_name = normalize_path(File.basename($1))
184 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
184 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
185 elsif /^head: (.+)$/ =~ line
185 elsif /^head: (.+)$/ =~ line
186 entry_headRev = $1 #unless entry.nil?
186 entry_headRev = $1 #unless entry.nil?
187 elsif /^symbolic names:/ =~ line
187 elsif /^symbolic names:/ =~ line
188 state = "symbolic" #unless entry.nil?
188 state = "symbolic" #unless entry.nil?
189 elsif /^#{STARTLOG}/ =~ line
189 elsif /^#{STARTLOG}/ =~ line
190 commit_log = String.new
190 commit_log = String.new
191 state = "revision"
191 state = "revision"
192 end
192 end
193 next
193 next
194 elsif state == "symbolic"
194 elsif state == "symbolic"
195 if /^(.*):\s(.*)/ =~ (line.strip)
195 if /^(.*):\s(.*)/ =~ (line.strip)
196 branch_map[$1] = $2
196 branch_map[$1] = $2
197 else
197 else
198 state = "tags"
198 state = "tags"
199 next
199 next
200 end
200 end
201 elsif state == "tags"
201 elsif state == "tags"
202 if /^#{STARTLOG}/ =~ line
202 if /^#{STARTLOG}/ =~ line
203 commit_log = ""
203 commit_log = ""
204 state = "revision"
204 state = "revision"
205 elsif /^#{ENDLOG}/ =~ line
205 elsif /^#{ENDLOG}/ =~ line
206 state = "head"
206 state = "head"
207 end
207 end
208 next
208 next
209 elsif state == "revision"
209 elsif state == "revision"
210 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
210 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
211 if revision
211 if revision
212 revHelper = CvsRevisionHelper.new(revision)
212 revHelper = CvsRevisionHelper.new(revision)
213 revBranch = "HEAD"
213 revBranch = "HEAD"
214 branch_map.each() do |branch_name, branch_point|
214 branch_map.each() do |branch_name, branch_point|
215 if revHelper.is_in_branch_with_symbol(branch_point)
215 if revHelper.is_in_branch_with_symbol(branch_point)
216 revBranch = branch_name
216 revBranch = branch_name
217 end
217 end
218 end
218 end
219 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
219 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
220 yield Revision.new({
220 yield Revision.new({
221 :time => date,
221 :time => date,
222 :author => author,
222 :author => author,
223 :message => commit_log.chomp,
223 :message => commit_log.chomp,
224 :paths => [{
224 :paths => [{
225 :revision => revision.dup,
225 :revision => revision.dup,
226 :branch => revBranch.dup,
226 :branch => revBranch.dup,
227 :path => scm_iconv('UTF-8', @path_encoding, entry_path),
227 :path => scm_iconv('UTF-8', @path_encoding, entry_path),
228 :name => scm_iconv('UTF-8', @path_encoding, entry_name),
228 :name => scm_iconv('UTF-8', @path_encoding, entry_name),
229 :kind => 'file',
229 :kind => 'file',
230 :action => file_state
230 :action => file_state
231 }]
231 }]
232 })
232 })
233 end
233 end
234 commit_log = String.new
234 commit_log = String.new
235 revision = nil
235 revision = nil
236 if /^#{ENDLOG}/ =~ line
236 if /^#{ENDLOG}/ =~ line
237 state = "entry_start"
237 state = "entry_start"
238 end
238 end
239 next
239 next
240 end
240 end
241
241
242 if /^branches: (.+)$/ =~ line
242 if /^branches: (.+)$/ =~ line
243 # TODO: version.branch = $1
243 # TODO: version.branch = $1
244 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
244 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
245 revision = $1
245 revision = $1
246 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
246 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
247 date = Time.parse($1)
247 date = Time.parse($1)
248 line_utf8 = scm_iconv('UTF-8', options[:log_encoding], line)
248 line_utf8 = scm_iconv('UTF-8', options[:log_encoding], line)
249 author_utf8 = /author: ([^;]+)/.match(line_utf8)[1]
249 author_utf8 = /author: ([^;]+)/.match(line_utf8)[1]
250 author = scm_iconv(options[:log_encoding], 'UTF-8', author_utf8)
250 author = scm_iconv(options[:log_encoding], 'UTF-8', author_utf8)
251 file_state = /state: ([^;]+)/.match(line)[1]
251 file_state = /state: ([^;]+)/.match(line)[1]
252 # TODO:
252 # TODO:
253 # linechanges only available in CVS....
253 # linechanges only available in CVS....
254 # maybe a feature our SVN implementation.
254 # maybe a feature our SVN implementation.
255 # I'm sure, they are useful for stats or something else
255 # I'm sure, they are useful for stats or something else
256 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
256 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
257 # unless linechanges.nil?
257 # unless linechanges.nil?
258 # version.line_plus = linechanges[1]
258 # version.line_plus = linechanges[1]
259 # version.line_minus = linechanges[2]
259 # version.line_minus = linechanges[2]
260 # else
260 # else
261 # version.line_plus = 0
261 # version.line_plus = 0
262 # version.line_minus = 0
262 # version.line_minus = 0
263 # end
263 # end
264 else
264 else
265 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
265 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
266 end
266 end
267 end
267 end
268 end
268 end
269 end
269 end
270 rescue ScmCommandAborted
270 rescue ScmCommandAborted
271 Revisions.new
271 Revisions.new
272 end
272 end
273
273
274 def diff(path, identifier_from, identifier_to=nil)
274 def diff(path, identifier_from, identifier_to=nil)
275 logger.debug "<cvs> diff path:'#{path}'" +
275 logger.debug "<cvs> diff path:'#{path}'" +
276 ",identifier_from #{identifier_from}, identifier_to #{identifier_to}"
276 ",identifier_from #{identifier_from}, identifier_to #{identifier_to}"
277 cmd_args = %w|rdiff -u|
277 cmd_args = %w|rdiff -u|
278 cmd_args << "-r#{identifier_to}"
278 cmd_args << "-r#{identifier_to}"
279 cmd_args << "-r#{identifier_from}"
279 cmd_args << "-r#{identifier_from}"
280 cmd_args << path_with_proj(path)
280 cmd_args << path_with_proj(path)
281 diff = []
281 diff = []
282 scm_cmd(*cmd_args) do |io|
282 scm_cmd(*cmd_args) do |io|
283 io.each_line do |line|
283 io.each_line do |line|
284 diff << line
284 diff << line
285 end
285 end
286 end
286 end
287 diff
287 diff
288 rescue ScmCommandAborted
288 rescue ScmCommandAborted
289 nil
289 nil
290 end
290 end
291
291
292 def cat(path, identifier=nil)
292 def cat(path, identifier=nil)
293 identifier = (identifier) ? identifier : "HEAD"
293 identifier = (identifier) ? identifier : "HEAD"
294 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
294 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
295 cmd_args = %w|-q co|
295 cmd_args = %w|-q co|
296 cmd_args << "-D" << time_to_cvstime(identifier) if identifier
296 cmd_args << "-D" << time_to_cvstime(identifier) if identifier
297 cmd_args << "-p" << path_with_proj(path)
297 cmd_args << "-p" << path_with_proj(path)
298 cat = nil
298 cat = nil
299 scm_cmd(*cmd_args) do |io|
299 scm_cmd(*cmd_args) do |io|
300 io.binmode
300 io.binmode
301 cat = io.read
301 cat = io.read
302 end
302 end
303 cat
303 cat
304 rescue ScmCommandAborted
304 rescue ScmCommandAborted
305 nil
305 nil
306 end
306 end
307
307
308 def annotate(path, identifier=nil)
308 def annotate(path, identifier=nil)
309 identifier = (identifier) ? identifier : "HEAD"
309 identifier = (identifier) ? identifier : "HEAD"
310 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
310 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
311 cmd_args = %w|rannotate|
311 cmd_args = %w|rannotate|
312 cmd_args << "-D" << time_to_cvstime(identifier) if identifier
312 cmd_args << "-D" << time_to_cvstime(identifier) if identifier
313 cmd_args << path_with_proj(path)
313 cmd_args << path_with_proj(path)
314 blame = Annotate.new
314 blame = Annotate.new
315 scm_cmd(*cmd_args) do |io|
315 scm_cmd(*cmd_args) do |io|
316 io.each_line do |line|
316 io.each_line do |line|
317 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
317 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
318 blame.add_line(
318 blame.add_line(
319 $3.rstrip,
319 $3.rstrip,
320 Revision.new(
320 Revision.new(
321 :revision => $1,
321 :revision => $1,
322 :identifier => nil,
322 :identifier => nil,
323 :author => $2.strip
323 :author => $2.strip
324 ))
324 ))
325 end
325 end
326 end
326 end
327 blame
327 blame
328 rescue ScmCommandAborted
328 rescue ScmCommandAborted
329 Annotate.new
329 Annotate.new
330 end
330 end
331
331
332 private
332 private
333
333
334 # Returns the root url without the connexion string
334 # Returns the root url without the connexion string
335 # :pserver:anonymous@foo.bar:/path => /path
335 # :pserver:anonymous@foo.bar:/path => /path
336 # :ext:cvsservername:/path => /path
336 # :ext:cvsservername:/path => /path
337 def root_url_path
337 def root_url_path
338 root_url.to_s.gsub(/^:.+:\d*/, '')
338 root_url.to_s.gsub(%r{^:.+?(?=/)}, '')
339 end
339 end
340
340
341 # convert a date/time into the CVS-format
341 # convert a date/time into the CVS-format
342 def time_to_cvstime(time)
342 def time_to_cvstime(time)
343 return nil if time.nil?
343 return nil if time.nil?
344 time = Time.now if time == 'HEAD'
344 time = Time.now if time == 'HEAD'
345
345
346 unless time.kind_of? Time
346 unless time.kind_of? Time
347 time = Time.parse(time)
347 time = Time.parse(time)
348 end
348 end
349 return time_to_cvstime_rlog(time)
349 return time_to_cvstime_rlog(time)
350 end
350 end
351
351
352 def time_to_cvstime_rlog(time)
352 def time_to_cvstime_rlog(time)
353 return nil if time.nil?
353 return nil if time.nil?
354 t1 = time.clone.localtime
354 t1 = time.clone.localtime
355 return t1.strftime("%Y-%m-%d %H:%M:%S")
355 return t1.strftime("%Y-%m-%d %H:%M:%S")
356 end
356 end
357
357
358 def normalize_cvs_path(path)
358 def normalize_cvs_path(path)
359 normalize_path(path.gsub(/Attic\//,''))
359 normalize_path(path.gsub(/Attic\//,''))
360 end
360 end
361
361
362 def normalize_path(path)
362 def normalize_path(path)
363 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
363 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
364 end
364 end
365
365
366 def path_with_proj(path)
366 def path_with_proj(path)
367 "#{url}#{with_leading_slash(path)}"
367 "#{url}#{with_leading_slash(path)}"
368 end
368 end
369 private :path_with_proj
369 private :path_with_proj
370
370
371 class Revision < Redmine::Scm::Adapters::Revision
371 class Revision < Redmine::Scm::Adapters::Revision
372 # Returns the readable identifier
372 # Returns the readable identifier
373 def format_identifier
373 def format_identifier
374 revision.to_s
374 revision.to_s
375 end
375 end
376 end
376 end
377
377
378 def scm_cmd(*args, &block)
378 def scm_cmd(*args, &block)
379 full_args = ['-d', root_url]
379 full_args = ['-d', root_url]
380 full_args += args
380 full_args += args
381 full_args_locale = []
381 full_args_locale = []
382 full_args.map do |e|
382 full_args.map do |e|
383 full_args_locale << scm_iconv(@path_encoding, 'UTF-8', e)
383 full_args_locale << scm_iconv(@path_encoding, 'UTF-8', e)
384 end
384 end
385 ret = shellout(
385 ret = shellout(
386 self.class.sq_bin + ' ' + full_args_locale.map { |e| shell_quote e.to_s }.join(' '),
386 self.class.sq_bin + ' ' + full_args_locale.map { |e| shell_quote e.to_s }.join(' '),
387 &block
387 &block
388 )
388 )
389 if $? && $?.exitstatus != 0
389 if $? && $?.exitstatus != 0
390 raise ScmCommandAborted, "cvs exited with non-zero status: #{$?.exitstatus}"
390 raise ScmCommandAborted, "cvs exited with non-zero status: #{$?.exitstatus}"
391 end
391 end
392 ret
392 ret
393 end
393 end
394 private :scm_cmd
394 private :scm_cmd
395 end
395 end
396
396
397 class CvsRevisionHelper
397 class CvsRevisionHelper
398 attr_accessor :complete_rev, :revision, :base, :branchid
398 attr_accessor :complete_rev, :revision, :base, :branchid
399
399
400 def initialize(complete_rev)
400 def initialize(complete_rev)
401 @complete_rev = complete_rev
401 @complete_rev = complete_rev
402 parseRevision()
402 parseRevision()
403 end
403 end
404
404
405 def branchPoint
405 def branchPoint
406 return @base
406 return @base
407 end
407 end
408
408
409 def branchVersion
409 def branchVersion
410 if isBranchRevision
410 if isBranchRevision
411 return @base+"."+@branchid
411 return @base+"."+@branchid
412 end
412 end
413 return @base
413 return @base
414 end
414 end
415
415
416 def isBranchRevision
416 def isBranchRevision
417 !@branchid.nil?
417 !@branchid.nil?
418 end
418 end
419
419
420 def prevRev
420 def prevRev
421 unless @revision == 0
421 unless @revision == 0
422 return buildRevision( @revision - 1 )
422 return buildRevision( @revision - 1 )
423 end
423 end
424 return buildRevision( @revision )
424 return buildRevision( @revision )
425 end
425 end
426
426
427 def is_in_branch_with_symbol(branch_symbol)
427 def is_in_branch_with_symbol(branch_symbol)
428 bpieces = branch_symbol.split(".")
428 bpieces = branch_symbol.split(".")
429 branch_start = "#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
429 branch_start = "#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
430 return ( branchVersion == branch_start )
430 return ( branchVersion == branch_start )
431 end
431 end
432
432
433 private
433 private
434 def buildRevision(rev)
434 def buildRevision(rev)
435 if rev == 0
435 if rev == 0
436 if @branchid.nil?
436 if @branchid.nil?
437 @base + ".0"
437 @base + ".0"
438 else
438 else
439 @base
439 @base
440 end
440 end
441 elsif @branchid.nil?
441 elsif @branchid.nil?
442 @base + "." + rev.to_s
442 @base + "." + rev.to_s
443 else
443 else
444 @base + "." + @branchid + "." + rev.to_s
444 @base + "." + @branchid + "." + rev.to_s
445 end
445 end
446 end
446 end
447
447
448 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
448 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
449 def parseRevision()
449 def parseRevision()
450 pieces = @complete_rev.split(".")
450 pieces = @complete_rev.split(".")
451 @revision = pieces.last.to_i
451 @revision = pieces.last.to_i
452 baseSize = 1
452 baseSize = 1
453 baseSize += (pieces.size / 2)
453 baseSize += (pieces.size / 2)
454 @base = pieces[0..-baseSize].join(".")
454 @base = pieces[0..-baseSize].join(".")
455 if baseSize > 2
455 if baseSize > 2
456 @branchid = pieces[-2]
456 @branchid = pieces[-2]
457 end
457 end
458 end
458 end
459 end
459 end
460 end
460 end
461 end
461 end
462 end
462 end
@@ -1,99 +1,115
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../../../test_helper', __FILE__)
19 begin
19 begin
20 require 'mocha'
20 require 'mocha'
21
21
22 class CvsAdapterTest < ActiveSupport::TestCase
22 class CvsAdapterTest < ActiveSupport::TestCase
23 REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s
23 REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s
24 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
24 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
25 MODULE_NAME = 'test'
25 MODULE_NAME = 'test'
26
26
27 if File.directory?(REPOSITORY_PATH)
27 if File.directory?(REPOSITORY_PATH)
28 def setup
28 def setup
29 @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH)
29 @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH)
30 end
30 end
31
31
32 def test_scm_version
32 def test_scm_version
33 to_test = { "\nConcurrent Versions System (CVS) 1.12.13 (client/server)\n" => [1,12,13],
33 to_test = { "\nConcurrent Versions System (CVS) 1.12.13 (client/server)\n" => [1,12,13],
34 "\r\n1.12.12\r\n1.12.11" => [1,12,12],
34 "\r\n1.12.12\r\n1.12.11" => [1,12,12],
35 "1.12.11\r\n1.12.10\r\n" => [1,12,11]}
35 "1.12.11\r\n1.12.10\r\n" => [1,12,11]}
36 to_test.each do |s, v|
36 to_test.each do |s, v|
37 test_scm_version_for(s, v)
37 test_scm_version_for(s, v)
38 end
38 end
39 end
39 end
40
40
41 def test_revisions_all
41 def test_revisions_all
42 cnt = 0
42 cnt = 0
43 @adapter.revisions('', nil, nil, :log_encoding => 'UTF-8') do |revision|
43 @adapter.revisions('', nil, nil, :log_encoding => 'UTF-8') do |revision|
44 cnt += 1
44 cnt += 1
45 end
45 end
46 assert_equal 16, cnt
46 assert_equal 16, cnt
47 end
47 end
48
48
49 def test_revisions_from_rev3
49 def test_revisions_from_rev3
50 rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22)
50 rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22)
51 cnt = 0
51 cnt = 0
52 @adapter.revisions('', rev3_committed_on, nil, :log_encoding => 'UTF-8') do |revision|
52 @adapter.revisions('', rev3_committed_on, nil, :log_encoding => 'UTF-8') do |revision|
53 cnt += 1
53 cnt += 1
54 end
54 end
55 assert_equal 4, cnt
55 assert_equal 4, cnt
56 end
56 end
57
57
58 def test_entries_rev3
58 def test_entries_rev3
59 rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22)
59 rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22)
60 entries = @adapter.entries('sources', rev3_committed_on)
60 entries = @adapter.entries('sources', rev3_committed_on)
61 assert_equal 2, entries.size
61 assert_equal 2, entries.size
62 assert_equal entries[0].name, "watchers_controller.rb"
62 assert_equal entries[0].name, "watchers_controller.rb"
63 assert_equal entries[0].lastrev.time, Time.gm(2007, 12, 13, 16, 27, 22)
63 assert_equal entries[0].lastrev.time, Time.gm(2007, 12, 13, 16, 27, 22)
64 end
64 end
65
65
66 def test_path_encoding_default_utf8
66 def test_path_encoding_default_utf8
67 adpt1 = Redmine::Scm::Adapters::CvsAdapter.new(
67 adpt1 = Redmine::Scm::Adapters::CvsAdapter.new(
68 MODULE_NAME,
68 MODULE_NAME,
69 REPOSITORY_PATH
69 REPOSITORY_PATH
70 )
70 )
71 assert_equal "UTF-8", adpt1.path_encoding
71 assert_equal "UTF-8", adpt1.path_encoding
72 adpt2 = Redmine::Scm::Adapters::CvsAdapter.new(
72 adpt2 = Redmine::Scm::Adapters::CvsAdapter.new(
73 MODULE_NAME,
73 MODULE_NAME,
74 REPOSITORY_PATH,
74 REPOSITORY_PATH,
75 nil,
75 nil,
76 nil,
76 nil,
77 ""
77 ""
78 )
78 )
79 assert_equal "UTF-8", adpt2.path_encoding
79 assert_equal "UTF-8", adpt2.path_encoding
80 end
80 end
81
81
82 def test_root_url_path
83 to_test = {
84 ':pserver:cvs_user:cvs_password@123.456.789.123:9876/repo' => '/repo',
85 ':pserver:cvs_user:cvs_password@123.456.789.123/repo' => '/repo',
86 ':pserver:cvs_user:cvs_password@cvs_server:/repo' => '/repo',
87 ':pserver:cvs_user:cvs_password@cvs_server:9876/repo' => '/repo',
88 ':pserver:cvs_user:cvs_password@cvs_server/repo' => '/repo',
89 ':pserver:cvs_user:cvs_password@cvs_server/path/repo' => '/path/repo',
90 ':ext:cvsservername:/path' => '/path'
91 }
92
93 to_test.each do |string, expected|
94 assert_equal expected, Redmine::Scm::Adapters::CvsAdapter.new('foo', string).send(:root_url_path), "#{string} failed"
95 end
96 end
97
82 private
98 private
83
99
84 def test_scm_version_for(scm_command_version, version)
100 def test_scm_version_for(scm_command_version, version)
85 @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version)
101 @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version)
86 assert_equal version, @adapter.class.scm_command_version
102 assert_equal version, @adapter.class.scm_command_version
87 end
103 end
88 else
104 else
89 puts "Cvs test repository NOT FOUND. Skipping unit tests !!!"
105 puts "Cvs test repository NOT FOUND. Skipping unit tests !!!"
90 def test_fake; assert true end
106 def test_fake; assert true end
91 end
107 end
92 end
108 end
93
109
94 rescue LoadError
110 rescue LoadError
95 class CvsMochaFake < ActiveSupport::TestCase
111 class CvsMochaFake < ActiveSupport::TestCase
96 def test_fake; assert(false, "Requires mocha to run those tests") end
112 def test_fake; assert(false, "Requires mocha to run those tests") end
97 end
113 end
98 end
114 end
99
115
General Comments 0
You need to be logged in to leave comments. Login now