##// END OF EJS Templates
scm: cvs: fix CVS diffs do not handle new files properly (#7615)....
Toshi MARUYAMA -
r4819:a2e47f9fbaf5
parent child
Show More
@@ -1,403 +1,407
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(CVS_BIN)
34 @@sq_bin ||= shell_quote(CVS_BIN)
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
46 scm_version = scm_version_from_command_line
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 @url = url
66 @url = url
67 @login = login if login && !login.empty?
67 @login = login if login && !login.empty?
68 @password = (password || "") if @login
68 @password = (password || "") if @login
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 end
72 end
73
73
74 def root_url
74 def root_url
75 @root_url
75 @root_url
76 end
76 end
77
77
78 def url
78 def url
79 @url
79 @url
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)
94 def entries(path=nil, identifier=nil)
95 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
95 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
96 path_with_project="#{url}#{with_leading_slash(path)}"
96 path_with_project="#{url}#{with_leading_slash(path)}"
97 entries = Entries.new
97 entries = Entries.new
98 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rls -e"
98 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rls -e"
99 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
99 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
100 cmd << " #{shell_quote path_with_project}"
100 cmd << " #{shell_quote path_with_project}"
101 shellout(cmd) do |io|
101 shellout(cmd) do |io|
102 io.each_line(){|line|
102 io.each_line(){|line|
103 fields=line.chop.split('/',-1)
103 fields=line.chop.split('/',-1)
104 logger.debug(">>InspectLine #{fields.inspect}")
104 logger.debug(">>InspectLine #{fields.inspect}")
105
105
106 if fields[0]!="D"
106 if fields[0]!="D"
107 entries << Entry.new({:name => fields[-5],
107 entries << Entry.new({:name => fields[-5],
108 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
108 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
109 :path => "#{path}/#{fields[-5]}",
109 :path => "#{path}/#{fields[-5]}",
110 :kind => 'file',
110 :kind => 'file',
111 :size => nil,
111 :size => nil,
112 :lastrev => Revision.new({
112 :lastrev => Revision.new({
113 :revision => fields[-4],
113 :revision => fields[-4],
114 :name => fields[-4],
114 :name => fields[-4],
115 :time => Time.parse(fields[-3]),
115 :time => Time.parse(fields[-3]),
116 :author => ''
116 :author => ''
117 })
117 })
118 })
118 })
119 else
119 else
120 entries << Entry.new({:name => fields[1],
120 entries << Entry.new({:name => fields[1],
121 :path => "#{path}/#{fields[1]}",
121 :path => "#{path}/#{fields[1]}",
122 :kind => 'dir',
122 :kind => 'dir',
123 :size => nil,
123 :size => nil,
124 :lastrev => nil
124 :lastrev => nil
125 })
125 })
126 end
126 end
127 }
127 }
128 end
128 end
129 return nil if $? && $?.exitstatus != 0
129 return nil if $? && $?.exitstatus != 0
130 entries.sort_by_name
130 entries.sort_by_name
131 end
131 end
132
132
133 STARTLOG="----------------------------"
133 STARTLOG="----------------------------"
134 ENDLOG ="============================================================================="
134 ENDLOG ="============================================================================="
135
135
136 # Returns all revisions found between identifier_from and identifier_to
136 # Returns all revisions found between identifier_from and identifier_to
137 # in the repository. both identifier have to be dates or nil.
137 # in the repository. both identifier have to be dates or nil.
138 # these method returns nothing but yield every result in block
138 # these method returns nothing but yield every result in block
139 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
139 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
140 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
140 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
141
141
142 path_with_project="#{url}#{with_leading_slash(path)}"
142 path_with_project="#{url}#{with_leading_slash(path)}"
143 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rlog"
143 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rlog"
144 cmd << " -d\">#{time_to_cvstime_rlog(identifier_from)}\"" if identifier_from
144 cmd << " -d\">#{time_to_cvstime_rlog(identifier_from)}\"" if identifier_from
145 cmd << " #{shell_quote path_with_project}"
145 cmd << " #{shell_quote path_with_project}"
146 shellout(cmd) do |io|
146 shellout(cmd) do |io|
147 state="entry_start"
147 state="entry_start"
148
148
149 commit_log=String.new
149 commit_log=String.new
150 revision=nil
150 revision=nil
151 date=nil
151 date=nil
152 author=nil
152 author=nil
153 entry_path=nil
153 entry_path=nil
154 entry_name=nil
154 entry_name=nil
155 file_state=nil
155 file_state=nil
156 branch_map=nil
156 branch_map=nil
157
157
158 io.each_line() do |line|
158 io.each_line() do |line|
159
159
160 if state!="revision" && /^#{ENDLOG}/ =~ line
160 if state!="revision" && /^#{ENDLOG}/ =~ line
161 commit_log=String.new
161 commit_log=String.new
162 revision=nil
162 revision=nil
163 state="entry_start"
163 state="entry_start"
164 end
164 end
165
165
166 if state=="entry_start"
166 if state=="entry_start"
167 branch_map=Hash.new
167 branch_map=Hash.new
168 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
168 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
169 entry_path = normalize_cvs_path($1)
169 entry_path = normalize_cvs_path($1)
170 entry_name = normalize_path(File.basename($1))
170 entry_name = normalize_path(File.basename($1))
171 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
171 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
172 elsif /^head: (.+)$/ =~ line
172 elsif /^head: (.+)$/ =~ line
173 entry_headRev = $1 #unless entry.nil?
173 entry_headRev = $1 #unless entry.nil?
174 elsif /^symbolic names:/ =~ line
174 elsif /^symbolic names:/ =~ line
175 state="symbolic" #unless entry.nil?
175 state="symbolic" #unless entry.nil?
176 elsif /^#{STARTLOG}/ =~ line
176 elsif /^#{STARTLOG}/ =~ line
177 commit_log=String.new
177 commit_log=String.new
178 state="revision"
178 state="revision"
179 end
179 end
180 next
180 next
181 elsif state=="symbolic"
181 elsif state=="symbolic"
182 if /^(.*):\s(.*)/ =~ (line.strip)
182 if /^(.*):\s(.*)/ =~ (line.strip)
183 branch_map[$1]=$2
183 branch_map[$1]=$2
184 else
184 else
185 state="tags"
185 state="tags"
186 next
186 next
187 end
187 end
188 elsif state=="tags"
188 elsif state=="tags"
189 if /^#{STARTLOG}/ =~ line
189 if /^#{STARTLOG}/ =~ line
190 commit_log = ""
190 commit_log = ""
191 state="revision"
191 state="revision"
192 elsif /^#{ENDLOG}/ =~ line
192 elsif /^#{ENDLOG}/ =~ line
193 state="head"
193 state="head"
194 end
194 end
195 next
195 next
196 elsif state=="revision"
196 elsif state=="revision"
197 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
197 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
198 if revision
198 if revision
199
199
200 revHelper=CvsRevisionHelper.new(revision)
200 revHelper=CvsRevisionHelper.new(revision)
201 revBranch="HEAD"
201 revBranch="HEAD"
202
202
203 branch_map.each() do |branch_name,branch_point|
203 branch_map.each() do |branch_name,branch_point|
204 if revHelper.is_in_branch_with_symbol(branch_point)
204 if revHelper.is_in_branch_with_symbol(branch_point)
205 revBranch=branch_name
205 revBranch=branch_name
206 end
206 end
207 end
207 end
208
208
209 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
209 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
210
210
211 yield Revision.new({
211 yield Revision.new({
212 :time => date,
212 :time => date,
213 :author => author,
213 :author => author,
214 :message=>commit_log.chomp,
214 :message=>commit_log.chomp,
215 :paths => [{
215 :paths => [{
216 :revision => revision,
216 :revision => revision,
217 :branch=> revBranch,
217 :branch=> revBranch,
218 :path=>entry_path,
218 :path=>entry_path,
219 :name=>entry_name,
219 :name=>entry_name,
220 :kind=>'file',
220 :kind=>'file',
221 :action=>file_state
221 :action=>file_state
222 }]
222 }]
223 })
223 })
224 end
224 end
225
225
226 commit_log=String.new
226 commit_log=String.new
227 revision=nil
227 revision=nil
228
228
229 if /^#{ENDLOG}/ =~ line
229 if /^#{ENDLOG}/ =~ line
230 state="entry_start"
230 state="entry_start"
231 end
231 end
232 next
232 next
233 end
233 end
234
234
235 if /^branches: (.+)$/ =~ line
235 if /^branches: (.+)$/ =~ line
236 #TODO: version.branch = $1
236 #TODO: version.branch = $1
237 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
237 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
238 revision = $1
238 revision = $1
239 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
239 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
240 date = Time.parse($1)
240 date = Time.parse($1)
241 author = /author: ([^;]+)/.match(line)[1]
241 author = /author: ([^;]+)/.match(line)[1]
242 file_state = /state: ([^;]+)/.match(line)[1]
242 file_state = /state: ([^;]+)/.match(line)[1]
243 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
243 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
244 # useful for stats or something else
244 # useful for stats or something else
245 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
245 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
246 # unless linechanges.nil?
246 # unless linechanges.nil?
247 # version.line_plus = linechanges[1]
247 # version.line_plus = linechanges[1]
248 # version.line_minus = linechanges[2]
248 # version.line_minus = linechanges[2]
249 # else
249 # else
250 # version.line_plus = 0
250 # version.line_plus = 0
251 # version.line_minus = 0
251 # version.line_minus = 0
252 # end
252 # end
253 else
253 else
254 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
254 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
255 end
255 end
256 end
256 end
257 end
257 end
258 end
258 end
259 end
259 end
260
260
261 def diff(path, identifier_from, identifier_to=nil)
261 def diff(path, identifier_from, identifier_to=nil)
262 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
262 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
263 path_with_project="#{url}#{with_leading_slash(path)}"
263 path_with_project="#{url}#{with_leading_slash(path)}"
264 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
264 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
265 diff = []
265 diff = []
266 shellout(cmd) do |io|
266 shellout(cmd) do |io|
267 io.each_line do |line|
267 io.each_line do |line|
268 diff << line
268 diff << line
269 end
269 end
270 end
270 end
271 return nil if $? && $?.exitstatus != 0
271 return nil if $? && $?.exitstatus != 0
272 diff
272 diff
273 end
273 end
274
274
275 def cat(path, identifier=nil)
275 def cat(path, identifier=nil)
276 identifier = (identifier) ? identifier : "HEAD"
276 identifier = (identifier) ? identifier : "HEAD"
277 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
277 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
278 path_with_project="#{url}#{with_leading_slash(path)}"
278 path_with_project="#{url}#{with_leading_slash(path)}"
279 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} co"
279 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} co"
280 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
280 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
281 cmd << " -p #{shell_quote path_with_project}"
281 cmd << " -p #{shell_quote path_with_project}"
282 cat = nil
282 cat = nil
283 shellout(cmd) do |io|
283 shellout(cmd) do |io|
284 io.binmode
284 io.binmode
285 cat = io.read
285 cat = io.read
286 end
286 end
287 return nil if $? && $?.exitstatus != 0
287 return nil if $? && $?.exitstatus != 0
288 cat
288 cat
289 end
289 end
290
290
291 def annotate(path, identifier=nil)
291 def annotate(path, identifier=nil)
292 identifier = (identifier) ? identifier.to_i : "HEAD"
292 identifier = (identifier) ? identifier.to_i : "HEAD"
293 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
293 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
294 path_with_project="#{url}#{with_leading_slash(path)}"
294 path_with_project="#{url}#{with_leading_slash(path)}"
295 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
295 cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
296 blame = Annotate.new
296 blame = Annotate.new
297 shellout(cmd) do |io|
297 shellout(cmd) do |io|
298 io.each_line do |line|
298 io.each_line do |line|
299 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
299 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
300 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
300 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
301 end
301 end
302 end
302 end
303 return nil if $? && $?.exitstatus != 0
303 return nil if $? && $?.exitstatus != 0
304 blame
304 blame
305 end
305 end
306
306
307 private
307 private
308
308
309 # Returns the root url without the connexion string
309 # Returns the root url without the connexion string
310 # :pserver:anonymous@foo.bar:/path => /path
310 # :pserver:anonymous@foo.bar:/path => /path
311 # :ext:cvsservername:/path => /path
311 # :ext:cvsservername:/path => /path
312 def root_url_path
312 def root_url_path
313 root_url.to_s.gsub(/^:.+:\d*/, '')
313 root_url.to_s.gsub(/^:.+:\d*/, '')
314 end
314 end
315
315
316 # convert a date/time into the CVS-format
316 # convert a date/time into the CVS-format
317 def time_to_cvstime(time)
317 def time_to_cvstime(time)
318 return nil if time.nil?
318 return nil if time.nil?
319 return Time.now if time == 'HEAD'
319 return Time.now if time == 'HEAD'
320
320
321 unless time.kind_of? Time
321 unless time.kind_of? Time
322 time = Time.parse(time)
322 time = Time.parse(time)
323 end
323 end
324 return time.strftime("%Y-%m-%d %H:%M:%S")
324 return time.strftime("%Y-%m-%d %H:%M:%S")
325 end
325 end
326
326
327 def time_to_cvstime_rlog(time)
327 def time_to_cvstime_rlog(time)
328 return nil if time.nil?
328 return nil if time.nil?
329 t1 = time.clone.localtime
329 t1 = time.clone.localtime
330 return t1.strftime("%Y-%m-%d %H:%M:%S")
330 return t1.strftime("%Y-%m-%d %H:%M:%S")
331 end
331 end
332
332
333 def normalize_cvs_path(path)
333 def normalize_cvs_path(path)
334 normalize_path(path.gsub(/Attic\//,''))
334 normalize_path(path.gsub(/Attic\//,''))
335 end
335 end
336
336
337 def normalize_path(path)
337 def normalize_path(path)
338 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
338 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
339 end
339 end
340 end
340 end
341
341
342 class CvsRevisionHelper
342 class CvsRevisionHelper
343 attr_accessor :complete_rev, :revision, :base, :branchid
343 attr_accessor :complete_rev, :revision, :base, :branchid
344
344
345 def initialize(complete_rev)
345 def initialize(complete_rev)
346 @complete_rev = complete_rev
346 @complete_rev = complete_rev
347 parseRevision()
347 parseRevision()
348 end
348 end
349
349
350 def branchPoint
350 def branchPoint
351 return @base
351 return @base
352 end
352 end
353
353
354 def branchVersion
354 def branchVersion
355 if isBranchRevision
355 if isBranchRevision
356 return @base+"."+@branchid
356 return @base+"."+@branchid
357 end
357 end
358 return @base
358 return @base
359 end
359 end
360
360
361 def isBranchRevision
361 def isBranchRevision
362 !@branchid.nil?
362 !@branchid.nil?
363 end
363 end
364
364
365 def prevRev
365 def prevRev
366 unless @revision==0
366 unless @revision==0
367 return buildRevision(@revision-1)
367 return buildRevision(@revision-1)
368 end
368 end
369 return buildRevision(@revision)
369 return buildRevision(@revision)
370 end
370 end
371
371
372 def is_in_branch_with_symbol(branch_symbol)
372 def is_in_branch_with_symbol(branch_symbol)
373 bpieces=branch_symbol.split(".")
373 bpieces=branch_symbol.split(".")
374 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
374 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
375 return (branchVersion==branch_start)
375 return (branchVersion==branch_start)
376 end
376 end
377
377
378 private
378 private
379 def buildRevision(rev)
379 def buildRevision(rev)
380 if rev== 0
380 if rev== 0
381 if @branchid.nil?
382 @base+".0"
383 else
381 @base
384 @base
385 end
382 elsif @branchid.nil?
386 elsif @branchid.nil?
383 @base+"."+rev.to_s
387 @base+"."+rev.to_s
384 else
388 else
385 @base+"."+@branchid+"."+rev.to_s
389 @base+"."+@branchid+"."+rev.to_s
386 end
390 end
387 end
391 end
388
392
389 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
393 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
390 def parseRevision()
394 def parseRevision()
391 pieces=@complete_rev.split(".")
395 pieces=@complete_rev.split(".")
392 @revision=pieces.last.to_i
396 @revision=pieces.last.to_i
393 baseSize=1
397 baseSize=1
394 baseSize+=(pieces.size/2)
398 baseSize+=(pieces.size/2)
395 @base=pieces[0..-baseSize].join(".")
399 @base=pieces[0..-baseSize].join(".")
396 if baseSize > 2
400 if baseSize > 2
397 @branchid=pieces[-2]
401 @branchid=pieces[-2]
398 end
402 end
399 end
403 end
400 end
404 end
401 end
405 end
402 end
406 end
403 end
407 end
@@ -1,187 +1,205
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 require 'repositories_controller'
19 require 'repositories_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class RepositoriesController; def rescue_action(e) raise e end; end
22 class RepositoriesController; def rescue_action(e) raise e end; end
23
23
24 class RepositoriesCvsControllerTest < ActionController::TestCase
24 class RepositoriesCvsControllerTest < ActionController::TestCase
25 fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :enabled_modules
25 fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :enabled_modules
26
26
27 # No '..' in the repository path
27 # No '..' in the repository path
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
29 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
29 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
30 # CVS module
30 # CVS module
31 MODULE_NAME = 'test'
31 MODULE_NAME = 'test'
32 PRJ_ID = 3
32 PRJ_ID = 3
33
33
34 def setup
34 def setup
35 @controller = RepositoriesController.new
35 @controller = RepositoriesController.new
36 @request = ActionController::TestRequest.new
36 @request = ActionController::TestRequest.new
37 @response = ActionController::TestResponse.new
37 @response = ActionController::TestResponse.new
38 Setting.default_language = 'en'
38 Setting.default_language = 'en'
39 User.current = nil
39 User.current = nil
40
40
41 @project = Project.find(PRJ_ID)
41 @project = Project.find(PRJ_ID)
42 @repository = Repository::Cvs.create(:project => Project.find(PRJ_ID),
42 @repository = Repository::Cvs.create(:project => Project.find(PRJ_ID),
43 :root_url => REPOSITORY_PATH,
43 :root_url => REPOSITORY_PATH,
44 :url => MODULE_NAME)
44 :url => MODULE_NAME)
45 assert @repository
45 assert @repository
46 end
46 end
47
47
48 if File.directory?(REPOSITORY_PATH)
48 if File.directory?(REPOSITORY_PATH)
49 def test_show
49 def test_show
50 @repository.fetch_changesets
50 @repository.fetch_changesets
51 @repository.reload
51 @repository.reload
52 get :show, :id => PRJ_ID
52 get :show, :id => PRJ_ID
53 assert_response :success
53 assert_response :success
54 assert_template 'show'
54 assert_template 'show'
55 assert_not_nil assigns(:entries)
55 assert_not_nil assigns(:entries)
56 assert_not_nil assigns(:changesets)
56 assert_not_nil assigns(:changesets)
57 end
57 end
58
58
59 def test_browse_root
59 def test_browse_root
60 @repository.fetch_changesets
60 @repository.fetch_changesets
61 @repository.reload
61 @repository.reload
62 get :show, :id => PRJ_ID
62 get :show, :id => PRJ_ID
63 assert_response :success
63 assert_response :success
64 assert_template 'show'
64 assert_template 'show'
65 assert_not_nil assigns(:entries)
65 assert_not_nil assigns(:entries)
66 assert_equal 3, assigns(:entries).size
66 assert_equal 3, assigns(:entries).size
67
67
68 entry = assigns(:entries).detect {|e| e.name == 'images'}
68 entry = assigns(:entries).detect {|e| e.name == 'images'}
69 assert_equal 'dir', entry.kind
69 assert_equal 'dir', entry.kind
70
70
71 entry = assigns(:entries).detect {|e| e.name == 'README'}
71 entry = assigns(:entries).detect {|e| e.name == 'README'}
72 assert_equal 'file', entry.kind
72 assert_equal 'file', entry.kind
73 end
73 end
74
74
75 def test_browse_directory
75 def test_browse_directory
76 @repository.fetch_changesets
76 @repository.fetch_changesets
77 @repository.reload
77 @repository.reload
78 get :show, :id => PRJ_ID, :path => ['images']
78 get :show, :id => PRJ_ID, :path => ['images']
79 assert_response :success
79 assert_response :success
80 assert_template 'show'
80 assert_template 'show'
81 assert_not_nil assigns(:entries)
81 assert_not_nil assigns(:entries)
82 assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name)
82 assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name)
83 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
83 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
84 assert_not_nil entry
84 assert_not_nil entry
85 assert_equal 'file', entry.kind
85 assert_equal 'file', entry.kind
86 assert_equal 'images/edit.png', entry.path
86 assert_equal 'images/edit.png', entry.path
87 end
87 end
88
88
89 def test_browse_at_given_revision
89 def test_browse_at_given_revision
90 @repository.fetch_changesets
90 @repository.fetch_changesets
91 @repository.reload
91 @repository.reload
92 get :show, :id => PRJ_ID, :path => ['images'], :rev => 1
92 get :show, :id => PRJ_ID, :path => ['images'], :rev => 1
93 assert_response :success
93 assert_response :success
94 assert_template 'show'
94 assert_template 'show'
95 assert_not_nil assigns(:entries)
95 assert_not_nil assigns(:entries)
96 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
96 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
97 end
97 end
98
98
99 def test_entry
99 def test_entry
100 @repository.fetch_changesets
100 @repository.fetch_changesets
101 @repository.reload
101 @repository.reload
102 get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb']
102 get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb']
103 assert_response :success
103 assert_response :success
104 assert_template 'entry'
104 assert_template 'entry'
105 assert_no_tag :tag => 'td', :attributes => { :class => /line-code/},
105 assert_no_tag :tag => 'td', :attributes => { :class => /line-code/},
106 :content => /before_filter/
106 :content => /before_filter/
107 end
107 end
108
108
109 def test_entry_at_given_revision
109 def test_entry_at_given_revision
110 # changesets must be loaded
110 # changesets must be loaded
111 @repository.fetch_changesets
111 @repository.fetch_changesets
112 @repository.reload
112 @repository.reload
113 get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], :rev => 2
113 get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], :rev => 2
114 assert_response :success
114 assert_response :success
115 assert_template 'entry'
115 assert_template 'entry'
116 # this line was removed in r3
116 # this line was removed in r3
117 assert_tag :tag => 'td', :attributes => { :class => /line-code/},
117 assert_tag :tag => 'td', :attributes => { :class => /line-code/},
118 :content => /before_filter/
118 :content => /before_filter/
119 end
119 end
120
120
121 def test_entry_not_found
121 def test_entry_not_found
122 @repository.fetch_changesets
122 @repository.fetch_changesets
123 @repository.reload
123 @repository.reload
124 get :entry, :id => PRJ_ID, :path => ['sources', 'zzz.c']
124 get :entry, :id => PRJ_ID, :path => ['sources', 'zzz.c']
125 assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
125 assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
126 :content => /The entry or revision was not found in the repository/
126 :content => /The entry or revision was not found in the repository/
127 end
127 end
128
128
129 def test_entry_download
129 def test_entry_download
130 @repository.fetch_changesets
130 @repository.fetch_changesets
131 @repository.reload
131 @repository.reload
132 get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
132 get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
133 assert_response :success
133 assert_response :success
134 end
134 end
135
135
136 def test_directory_entry
136 def test_directory_entry
137 @repository.fetch_changesets
137 @repository.fetch_changesets
138 @repository.reload
138 @repository.reload
139 get :entry, :id => PRJ_ID, :path => ['sources']
139 get :entry, :id => PRJ_ID, :path => ['sources']
140 assert_response :success
140 assert_response :success
141 assert_template 'show'
141 assert_template 'show'
142 assert_not_nil assigns(:entry)
142 assert_not_nil assigns(:entry)
143 assert_equal 'sources', assigns(:entry).name
143 assert_equal 'sources', assigns(:entry).name
144 end
144 end
145
145
146 def test_diff
146 def test_diff
147 @repository.fetch_changesets
147 @repository.fetch_changesets
148 @repository.reload
148 @repository.reload
149 get :diff, :id => PRJ_ID, :rev => 3, :type => 'inline'
149 get :diff, :id => PRJ_ID, :rev => 3, :type => 'inline'
150 assert_response :success
150 assert_response :success
151 assert_template 'diff'
151 assert_template 'diff'
152 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' },
152 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' },
153 :content => /watched.remove_watcher/
153 :content => /watched.remove_watcher/
154 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' },
154 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' },
155 :content => /watched.remove_all_watcher/
155 :content => /watched.remove_all_watcher/
156 end
156 end
157
157
158 def test_diff_new_files
159 @repository.fetch_changesets
160 @repository.reload
161 get :diff, :id => PRJ_ID, :rev => 1, :type => 'inline'
162 assert_response :success
163 assert_template 'diff'
164 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' },
165 :content => /watched.remove_watcher/
166 assert_tag :tag => 'th', :attributes => { :class => 'filename' },
167 :content => /test\/README/
168 assert_tag :tag => 'th', :attributes => { :class => 'filename' },
169 :content => /test\/images\/delete.png /
170 assert_tag :tag => 'th', :attributes => { :class => 'filename' },
171 :content => /test\/images\/edit.png/
172 assert_tag :tag => 'th', :attributes => { :class => 'filename' },
173 :content => /test\/sources\/watchers_controller.rb/
174 end
175
158 def test_annotate
176 def test_annotate
159 @repository.fetch_changesets
177 @repository.fetch_changesets
160 @repository.reload
178 @repository.reload
161 get :annotate, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb']
179 get :annotate, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb']
162 assert_response :success
180 assert_response :success
163 assert_template 'annotate'
181 assert_template 'annotate'
164 # 1.1 line
182 # 1.1 line
165 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
183 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
166 :content => '18',
184 :content => '18',
167 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
185 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
168 :content => /1.1/,
186 :content => /1.1/,
169 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
187 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
170 :content => /LANG/
188 :content => /LANG/
171 }
189 }
172 }
190 }
173 # 1.2 line
191 # 1.2 line
174 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
192 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
175 :content => '32',
193 :content => '32',
176 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
194 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
177 :content => /1.2/,
195 :content => /1.2/,
178 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
196 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
179 :content => /LANG/
197 :content => /LANG/
180 }
198 }
181 }
199 }
182 end
200 end
183 else
201 else
184 puts "CVS test repository NOT FOUND. Skipping functional tests !!!"
202 puts "CVS test repository NOT FOUND. Skipping functional tests !!!"
185 def test_fake; assert true end
203 def test_fake; assert true end
186 end
204 end
187 end
205 end
General Comments 0
You need to be logged in to leave comments. Login now