##// END OF EJS Templates
Fixed: cvs diff broken by r4539 (#7176)....
Jean-Philippe Lang -
r4463:0025a6620087
parent child
Show More
@@ -1,362 +1,362
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class CvsAdapter < AbstractAdapter
24 24
25 25 # CVS executable name
26 26 CVS_BIN = "cvs"
27 27
28 28 # Guidelines for the input:
29 29 # url -> the project-path, relative to the cvsroot (eg. module name)
30 30 # root_url -> the good old, sometimes damned, CVSROOT
31 31 # login -> unnecessary
32 32 # password -> unnecessary too
33 33 def initialize(url, root_url=nil, login=nil, password=nil)
34 34 @url = url
35 35 @login = login if login && !login.empty?
36 36 @password = (password || "") if @login
37 37 #TODO: better Exception here (IllegalArgumentException)
38 38 raise CommandFailed if root_url.blank?
39 39 @root_url = root_url
40 40 end
41 41
42 42 def root_url
43 43 @root_url
44 44 end
45 45
46 46 def url
47 47 @url
48 48 end
49 49
50 50 def info
51 51 logger.debug "<cvs> info"
52 52 Info.new({:root_url => @root_url, :lastrev => nil})
53 53 end
54 54
55 55 def get_previous_revision(revision)
56 56 CvsRevisionHelper.new(revision).prevRev
57 57 end
58 58
59 59 # Returns an Entries collection
60 60 # or nil if the given path doesn't exist in the repository
61 61 # this method is used by the repository-browser (aka LIST)
62 62 def entries(path=nil, identifier=nil)
63 63 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
64 64 path_with_project="#{url}#{with_leading_slash(path)}"
65 65 entries = Entries.new
66 66 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rls -e"
67 67 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
68 68 cmd << " #{shell_quote path_with_project}"
69 69 shellout(cmd) do |io|
70 70 io.each_line(){|line|
71 71 fields=line.chop.split('/',-1)
72 72 logger.debug(">>InspectLine #{fields.inspect}")
73 73
74 74 if fields[0]!="D"
75 75 entries << Entry.new({:name => fields[-5],
76 76 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
77 77 :path => "#{path}/#{fields[-5]}",
78 78 :kind => 'file',
79 79 :size => nil,
80 80 :lastrev => Revision.new({
81 81 :revision => fields[-4],
82 82 :name => fields[-4],
83 83 :time => Time.parse(fields[-3]),
84 84 :author => ''
85 85 })
86 86 })
87 87 else
88 88 entries << Entry.new({:name => fields[1],
89 89 :path => "#{path}/#{fields[1]}",
90 90 :kind => 'dir',
91 91 :size => nil,
92 92 :lastrev => nil
93 93 })
94 94 end
95 95 }
96 96 end
97 97 return nil if $? && $?.exitstatus != 0
98 98 entries.sort_by_name
99 99 end
100 100
101 101 STARTLOG="----------------------------"
102 102 ENDLOG ="============================================================================="
103 103
104 104 # Returns all revisions found between identifier_from and identifier_to
105 105 # in the repository. both identifier have to be dates or nil.
106 106 # these method returns nothing but yield every result in block
107 107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
108 108 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
109 109
110 110 path_with_project="#{url}#{with_leading_slash(path)}"
111 111 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rlog"
112 112 cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from
113 113 cmd << " #{shell_quote path_with_project}"
114 114 shellout(cmd) do |io|
115 115 state="entry_start"
116 116
117 117 commit_log=String.new
118 118 revision=nil
119 119 date=nil
120 120 author=nil
121 121 entry_path=nil
122 122 entry_name=nil
123 123 file_state=nil
124 124 branch_map=nil
125 125
126 126 io.each_line() do |line|
127 127
128 128 if state!="revision" && /^#{ENDLOG}/ =~ line
129 129 commit_log=String.new
130 130 revision=nil
131 131 state="entry_start"
132 132 end
133 133
134 134 if state=="entry_start"
135 135 branch_map=Hash.new
136 136 if /^RCS file: #{Regexp.escape(root_url_path)}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
137 137 entry_path = normalize_cvs_path($1)
138 138 entry_name = normalize_path(File.basename($1))
139 139 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
140 140 elsif /^head: (.+)$/ =~ line
141 141 entry_headRev = $1 #unless entry.nil?
142 142 elsif /^symbolic names:/ =~ line
143 143 state="symbolic" #unless entry.nil?
144 144 elsif /^#{STARTLOG}/ =~ line
145 145 commit_log=String.new
146 146 state="revision"
147 147 end
148 148 next
149 149 elsif state=="symbolic"
150 150 if /^(.*):\s(.*)/ =~ (line.strip)
151 151 branch_map[$1]=$2
152 152 else
153 153 state="tags"
154 154 next
155 155 end
156 156 elsif state=="tags"
157 157 if /^#{STARTLOG}/ =~ line
158 158 commit_log = ""
159 159 state="revision"
160 160 elsif /^#{ENDLOG}/ =~ line
161 161 state="head"
162 162 end
163 163 next
164 164 elsif state=="revision"
165 165 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
166 166 if revision
167 167
168 168 revHelper=CvsRevisionHelper.new(revision)
169 169 revBranch="HEAD"
170 170
171 171 branch_map.each() do |branch_name,branch_point|
172 172 if revHelper.is_in_branch_with_symbol(branch_point)
173 173 revBranch=branch_name
174 174 end
175 175 end
176 176
177 177 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
178 178
179 179 yield Revision.new({
180 180 :time => date,
181 181 :author => author,
182 182 :message=>commit_log.chomp,
183 183 :paths => [{
184 184 :revision => revision,
185 185 :branch=> revBranch,
186 186 :path=>entry_path,
187 187 :name=>entry_name,
188 188 :kind=>'file',
189 189 :action=>file_state
190 190 }]
191 191 })
192 192 end
193 193
194 194 commit_log=String.new
195 195 revision=nil
196 196
197 197 if /^#{ENDLOG}/ =~ line
198 198 state="entry_start"
199 199 end
200 200 next
201 201 end
202 202
203 203 if /^branches: (.+)$/ =~ line
204 204 #TODO: version.branch = $1
205 205 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
206 206 revision = $1
207 207 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
208 208 date = Time.parse($1)
209 209 author = /author: ([^;]+)/.match(line)[1]
210 210 file_state = /state: ([^;]+)/.match(line)[1]
211 211 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
212 212 # useful for stats or something else
213 213 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
214 214 # unless linechanges.nil?
215 215 # version.line_plus = linechanges[1]
216 216 # version.line_minus = linechanges[2]
217 217 # else
218 218 # version.line_plus = 0
219 219 # version.line_minus = 0
220 220 # end
221 221 else
222 222 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
223 223 end
224 224 end
225 225 end
226 226 end
227 227 end
228 228
229 229 def diff(path, identifier_from, identifier_to=nil)
230 230 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
231 231 path_with_project="#{url}#{with_leading_slash(path)}"
232 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rdiff -u -r#{identifier_to.to_i} -r#{identifier_from.to_i} #{shell_quote path_with_project}"
232 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
233 233 diff = []
234 234 shellout(cmd) do |io|
235 235 io.each_line do |line|
236 236 diff << line
237 237 end
238 238 end
239 239 return nil if $? && $?.exitstatus != 0
240 240 diff
241 241 end
242 242
243 243 def cat(path, identifier=nil)
244 244 identifier = (identifier) ? identifier : "HEAD"
245 245 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
246 246 path_with_project="#{url}#{with_leading_slash(path)}"
247 247 cmd = "#{CVS_BIN} -d #{shell_quote root_url} co"
248 248 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
249 249 cmd << " -p #{shell_quote path_with_project}"
250 250 cat = nil
251 251 shellout(cmd) do |io|
252 252 cat = io.read
253 253 end
254 254 return nil if $? && $?.exitstatus != 0
255 255 cat
256 256 end
257 257
258 258 def annotate(path, identifier=nil)
259 259 identifier = (identifier) ? identifier.to_i : "HEAD"
260 260 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
261 261 path_with_project="#{url}#{with_leading_slash(path)}"
262 262 cmd = "#{CVS_BIN} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
263 263 blame = Annotate.new
264 264 shellout(cmd) do |io|
265 265 io.each_line do |line|
266 266 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
267 267 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
268 268 end
269 269 end
270 270 return nil if $? && $?.exitstatus != 0
271 271 blame
272 272 end
273 273
274 274 private
275 275
276 276 # Returns the root url without the connexion string
277 277 # :pserver:anonymous@foo.bar:/path => /path
278 278 # :ext:cvsservername:/path => /path
279 279 def root_url_path
280 280 root_url.to_s.gsub(/^:.+:\d*/, '')
281 281 end
282 282
283 283 # convert a date/time into the CVS-format
284 284 def time_to_cvstime(time)
285 285 return nil if time.nil?
286 286 unless time.kind_of? Time
287 287 time = Time.parse(time)
288 288 end
289 289 return time.strftime("%Y-%m-%d %H:%M:%S")
290 290 end
291 291
292 292 def normalize_cvs_path(path)
293 293 normalize_path(path.gsub(/Attic\//,''))
294 294 end
295 295
296 296 def normalize_path(path)
297 297 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
298 298 end
299 299 end
300 300
301 301 class CvsRevisionHelper
302 302 attr_accessor :complete_rev, :revision, :base, :branchid
303 303
304 304 def initialize(complete_rev)
305 305 @complete_rev = complete_rev
306 306 parseRevision()
307 307 end
308 308
309 309 def branchPoint
310 310 return @base
311 311 end
312 312
313 313 def branchVersion
314 314 if isBranchRevision
315 315 return @base+"."+@branchid
316 316 end
317 317 return @base
318 318 end
319 319
320 320 def isBranchRevision
321 321 !@branchid.nil?
322 322 end
323 323
324 324 def prevRev
325 325 unless @revision==0
326 326 return buildRevision(@revision-1)
327 327 end
328 328 return buildRevision(@revision)
329 329 end
330 330
331 331 def is_in_branch_with_symbol(branch_symbol)
332 332 bpieces=branch_symbol.split(".")
333 333 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
334 334 return (branchVersion==branch_start)
335 335 end
336 336
337 337 private
338 338 def buildRevision(rev)
339 339 if rev== 0
340 340 @base
341 341 elsif @branchid.nil?
342 342 @base+"."+rev.to_s
343 343 else
344 344 @base+"."+@branchid+"."+rev.to_s
345 345 end
346 346 end
347 347
348 348 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
349 349 def parseRevision()
350 350 pieces=@complete_rev.split(".")
351 351 @revision=pieces.last.to_i
352 352 baseSize=1
353 353 baseSize+=(pieces.size/2)
354 354 @base=pieces[0..-baseSize].join(".")
355 355 if baseSize > 2
356 356 @branchid=pieces[-2]
357 357 end
358 358 end
359 359 end
360 360 end
361 361 end
362 362 end
@@ -1,165 +1,166
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19 require 'repositories_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class RepositoriesController; def rescue_action(e) raise e end; end
23 23
24 24 class RepositoriesCvsControllerTest < ActionController::TestCase
25 fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :enabled_modules
25 26
26 27 # No '..' in the repository path
27 28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
28 29 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
29 30 # CVS module
30 31 MODULE_NAME = 'test'
31 32
32 33 def setup
33 34 @controller = RepositoriesController.new
34 35 @request = ActionController::TestRequest.new
35 36 @response = ActionController::TestResponse.new
36 37 Setting.default_language = 'en'
37 38 User.current = nil
38 39
39 40 @project = Project.find(1)
40 41 @project.repository = Repository::Cvs.create(:root_url => REPOSITORY_PATH,
41 42 :url => MODULE_NAME)
42 43 end
43 44
44 45 if File.directory?(REPOSITORY_PATH)
45 46 def test_show
46 47 get :show, :id => 1
47 48 assert_response :success
48 49 assert_template 'show'
49 50 assert_not_nil assigns(:entries)
50 51 assert_not_nil assigns(:changesets)
51 52 end
52 53
53 54 def test_browse_root
54 55 get :show, :id => 1
55 56 assert_response :success
56 57 assert_template 'show'
57 58 assert_not_nil assigns(:entries)
58 59 assert_equal 3, assigns(:entries).size
59 60
60 61 entry = assigns(:entries).detect {|e| e.name == 'images'}
61 62 assert_equal 'dir', entry.kind
62 63
63 64 entry = assigns(:entries).detect {|e| e.name == 'README'}
64 65 assert_equal 'file', entry.kind
65 66 end
66 67
67 68 def test_browse_directory
68 69 get :show, :id => 1, :path => ['images']
69 70 assert_response :success
70 71 assert_template 'show'
71 72 assert_not_nil assigns(:entries)
72 73 assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name)
73 74 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
74 75 assert_not_nil entry
75 76 assert_equal 'file', entry.kind
76 77 assert_equal 'images/edit.png', entry.path
77 78 end
78 79
79 80 def test_browse_at_given_revision
80 81 Project.find(1).repository.fetch_changesets
81 82 get :show, :id => 1, :path => ['images'], :rev => 1
82 83 assert_response :success
83 84 assert_template 'show'
84 85 assert_not_nil assigns(:entries)
85 86 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
86 87 end
87 88
88 89 def test_entry
89 90 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb']
90 91 assert_response :success
91 92 assert_template 'entry'
92 93 assert_no_tag :tag => 'td', :attributes => { :class => /line-code/},
93 94 :content => /before_filter/
94 95 end
95 96
96 97 def test_entry_at_given_revision
97 98 # changesets must be loaded
98 99 Project.find(1).repository.fetch_changesets
99 100 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb'], :rev => 2
100 101 assert_response :success
101 102 assert_template 'entry'
102 103 # this line was removed in r3
103 104 assert_tag :tag => 'td', :attributes => { :class => /line-code/},
104 105 :content => /before_filter/
105 106 end
106 107
107 108 def test_entry_not_found
108 109 get :entry, :id => 1, :path => ['sources', 'zzz.c']
109 110 assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
110 111 :content => /The entry or revision was not found in the repository/
111 112 end
112 113
113 114 def test_entry_download
114 115 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
115 116 assert_response :success
116 117 end
117 118
118 119 def test_directory_entry
119 120 get :entry, :id => 1, :path => ['sources']
120 121 assert_response :success
121 122 assert_template 'show'
122 123 assert_not_nil assigns(:entry)
123 124 assert_equal 'sources', assigns(:entry).name
124 125 end
125 126
126 127 def test_diff
127 128 Project.find(1).repository.fetch_changesets
128 129 get :diff, :id => 1, :rev => 3, :type => 'inline'
129 130 assert_response :success
130 131 assert_template 'diff'
131 132 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' },
132 133 :content => /watched.remove_watcher/
133 134 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' },
134 135 :content => /watched.remove_all_watcher/
135 136 end
136 137
137 138 def test_annotate
138 139 Project.find(1).repository.fetch_changesets
139 140 get :annotate, :id => 1, :path => ['sources', 'watchers_controller.rb']
140 141 assert_response :success
141 142 assert_template 'annotate'
142 143 # 1.1 line
143 144 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
144 145 :content => '18',
145 146 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
146 147 :content => /1.1/,
147 148 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
148 149 :content => /LANG/
149 150 }
150 151 }
151 152 # 1.2 line
152 153 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
153 154 :content => '32',
154 155 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
155 156 :content => /1.2/,
156 157 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
157 158 :content => /LANG/
158 159 }
159 160 }
160 161 end
161 162 else
162 163 puts "CVS test repository NOT FOUND. Skipping functional tests !!!"
163 164 def test_fake; assert true end
164 165 end
165 166 end
General Comments 0
You need to be logged in to leave comments. Login now