##// END OF EJS Templates
Fixed: view file at given revision with CVS....
Jean-Philippe Lang -
r1539:ca6e69ec247e
parent child
Show More
@@ -1,324 +1,324
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 'SVG/Graph/Bar'
19 19 require 'SVG/Graph/BarHorizontal'
20 20 require 'digest/sha1'
21 21
22 22 class ChangesetNotFound < Exception; end
23 23 class InvalidRevisionParam < Exception; end
24 24
25 25 class RepositoriesController < ApplicationController
26 26 layout 'base'
27 27 menu_item :repository
28 28 before_filter :find_repository, :except => :edit
29 29 before_filter :find_project, :only => :edit
30 30 before_filter :authorize
31 31 accept_key_auth :revisions
32 32
33 33 def edit
34 34 @repository = @project.repository
35 35 if !@repository
36 36 @repository = Repository.factory(params[:repository_scm])
37 37 @repository.project = @project if @repository
38 38 end
39 39 if request.post? && @repository
40 40 @repository.attributes = params[:repository]
41 41 @repository.save
42 42 end
43 43 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
44 44 end
45 45
46 46 def destroy
47 47 @repository.destroy
48 48 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
49 49 end
50 50
51 51 def show
52 52 # check if new revisions have been committed in the repository
53 53 @repository.fetch_changesets if Setting.autofetch_changesets?
54 54 # root entries
55 55 @entries = @repository.entries('', @rev)
56 56 # latest changesets
57 57 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
58 58 show_error_not_found unless @entries || @changesets.any?
59 59 rescue Redmine::Scm::Adapters::CommandFailed => e
60 60 show_error_command_failed(e.message)
61 61 end
62 62
63 63 def browse
64 64 @entries = @repository.entries(@path, @rev)
65 65 if request.xhr?
66 66 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
67 67 else
68 68 show_error_not_found and return unless @entries
69 69 render :action => 'browse'
70 70 end
71 71 rescue Redmine::Scm::Adapters::CommandFailed => e
72 72 show_error_command_failed(e.message)
73 73 end
74 74
75 75 def changes
76 @entry = @repository.scm.entry(@path, @rev)
76 @entry = @repository.entry(@path, @rev)
77 77 show_error_not_found and return unless @entry
78 78 @changesets = @repository.changesets_for_path(@path)
79 79 rescue Redmine::Scm::Adapters::CommandFailed => e
80 80 show_error_command_failed(e.message)
81 81 end
82 82
83 83 def revisions
84 84 @changeset_count = @repository.changesets.count
85 85 @changeset_pages = Paginator.new self, @changeset_count,
86 86 per_page_option,
87 87 params['page']
88 88 @changesets = @repository.changesets.find(:all,
89 89 :limit => @changeset_pages.items_per_page,
90 90 :offset => @changeset_pages.current.offset)
91 91
92 92 respond_to do |format|
93 93 format.html { render :layout => false if request.xhr? }
94 94 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
95 95 end
96 96 end
97 97
98 98 def entry
99 @entry = @repository.scm.entry(@path, @rev)
99 @entry = @repository.entry(@path, @rev)
100 100 show_error_not_found and return unless @entry
101 101
102 102 # If the entry is a dir, show the browser
103 103 browse and return if @entry.is_dir?
104 104
105 @content = @repository.scm.cat(@path, @rev)
105 @content = @repository.cat(@path, @rev)
106 106 show_error_not_found and return unless @content
107 107 if 'raw' == params[:format] || @content.is_binary_data?
108 108 # Force the download if it's a binary file
109 109 send_data @content, :filename => @path.split('/').last
110 110 else
111 111 # Prevent empty lines when displaying a file with Windows style eol
112 112 @content.gsub!("\r\n", "\n")
113 113 end
114 114 rescue Redmine::Scm::Adapters::CommandFailed => e
115 115 show_error_command_failed(e.message)
116 116 end
117 117
118 118 def annotate
119 119 @annotate = @repository.scm.annotate(@path, @rev)
120 120 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
121 121 rescue Redmine::Scm::Adapters::CommandFailed => e
122 122 show_error_command_failed(e.message)
123 123 end
124 124
125 125 def revision
126 126 @changeset = @repository.changesets.find_by_revision(@rev)
127 127 raise ChangesetNotFound unless @changeset
128 128 @changes_count = @changeset.changes.size
129 129 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
130 130 @changes = @changeset.changes.find(:all,
131 131 :limit => @changes_pages.items_per_page,
132 132 :offset => @changes_pages.current.offset)
133 133
134 134 respond_to do |format|
135 135 format.html
136 136 format.js {render :layout => false}
137 137 end
138 138 rescue ChangesetNotFound
139 139 show_error_not_found
140 140 rescue Redmine::Scm::Adapters::CommandFailed => e
141 141 show_error_command_failed(e.message)
142 142 end
143 143
144 144 def diff
145 145 if params[:format] == 'diff'
146 146 @diff = @repository.diff(@path, @rev, @rev_to)
147 147 show_error_not_found and return unless @diff
148 148 filename = "changeset_r#{@rev}"
149 149 filename << "_r#{@rev_to}" if @rev_to
150 150 send_data @diff.join, :filename => "#{filename}.diff",
151 151 :type => 'text/x-patch',
152 152 :disposition => 'attachment'
153 153 else
154 154 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
155 155 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
156 156
157 157 # Save diff type as user preference
158 158 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
159 159 User.current.pref[:diff_type] = @diff_type
160 160 User.current.preference.save
161 161 end
162 162
163 163 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
164 164 unless read_fragment(@cache_key)
165 165 @diff = @repository.diff(@path, @rev, @rev_to)
166 166 show_error_not_found unless @diff
167 167 end
168 168 end
169 169 rescue Redmine::Scm::Adapters::CommandFailed => e
170 170 show_error_command_failed(e.message)
171 171 end
172 172
173 173 def stats
174 174 end
175 175
176 176 def graph
177 177 data = nil
178 178 case params[:graph]
179 179 when "commits_per_month"
180 180 data = graph_commits_per_month(@repository)
181 181 when "commits_per_author"
182 182 data = graph_commits_per_author(@repository)
183 183 end
184 184 if data
185 185 headers["Content-Type"] = "image/svg+xml"
186 186 send_data(data, :type => "image/svg+xml", :disposition => "inline")
187 187 else
188 188 render_404
189 189 end
190 190 end
191 191
192 192 private
193 193 def find_project
194 194 @project = Project.find(params[:id])
195 195 rescue ActiveRecord::RecordNotFound
196 196 render_404
197 197 end
198 198
199 199 REV_PARAM_RE = %r{^[a-f0-9]*$}
200 200
201 201 def find_repository
202 202 @project = Project.find(params[:id])
203 203 @repository = @project.repository
204 204 render_404 and return false unless @repository
205 205 @path = params[:path].join('/') unless params[:path].nil?
206 206 @path ||= ''
207 207 @rev = params[:rev]
208 208 @rev_to = params[:rev_to]
209 209 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
210 210 rescue ActiveRecord::RecordNotFound
211 211 render_404
212 212 rescue InvalidRevisionParam
213 213 show_error_not_found
214 214 end
215 215
216 216 def show_error_not_found
217 217 render_error l(:error_scm_not_found)
218 218 end
219 219
220 220 def show_error_command_failed(msg)
221 221 render_error l(:error_scm_command_failed, msg)
222 222 end
223 223
224 224 def graph_commits_per_month(repository)
225 225 @date_to = Date.today
226 226 @date_from = @date_to << 11
227 227 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
228 228 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
229 229 commits_by_month = [0] * 12
230 230 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
231 231
232 232 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
233 233 changes_by_month = [0] * 12
234 234 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
235 235
236 236 fields = []
237 237 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
238 238 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
239 239
240 240 graph = SVG::Graph::Bar.new(
241 241 :height => 300,
242 242 :width => 500,
243 243 :fields => fields.reverse,
244 244 :stack => :side,
245 245 :scale_integers => true,
246 246 :step_x_labels => 2,
247 247 :show_data_values => false,
248 248 :graph_title => l(:label_commits_per_month),
249 249 :show_graph_title => true
250 250 )
251 251
252 252 graph.add_data(
253 253 :data => commits_by_month[0..11].reverse,
254 254 :title => l(:label_revision_plural)
255 255 )
256 256
257 257 graph.add_data(
258 258 :data => changes_by_month[0..11].reverse,
259 259 :title => l(:label_change_plural)
260 260 )
261 261
262 262 graph.burn
263 263 end
264 264
265 265 def graph_commits_per_author(repository)
266 266 commits_by_author = repository.changesets.count(:all, :group => :committer)
267 267 commits_by_author.sort! {|x, y| x.last <=> y.last}
268 268
269 269 changes_by_author = repository.changes.count(:all, :group => :committer)
270 270 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
271 271
272 272 fields = commits_by_author.collect {|r| r.first}
273 273 commits_data = commits_by_author.collect {|r| r.last}
274 274 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
275 275
276 276 fields = fields + [""]*(10 - fields.length) if fields.length<10
277 277 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
278 278 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
279 279
280 280 # Remove email adress in usernames
281 281 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
282 282
283 283 graph = SVG::Graph::BarHorizontal.new(
284 284 :height => 300,
285 285 :width => 500,
286 286 :fields => fields,
287 287 :stack => :side,
288 288 :scale_integers => true,
289 289 :show_data_values => false,
290 290 :rotate_y_labels => false,
291 291 :graph_title => l(:label_commits_per_author),
292 292 :show_graph_title => true
293 293 )
294 294
295 295 graph.add_data(
296 296 :data => commits_data,
297 297 :title => l(:label_revision_plural)
298 298 )
299 299
300 300 graph.add_data(
301 301 :data => changes_data,
302 302 :title => l(:label_change_plural)
303 303 )
304 304
305 305 graph.burn
306 306 end
307 307
308 308 end
309 309
310 310 class Date
311 311 def months_ago(date = Date.today)
312 312 (date.year - self.year)*12 + (date.month - self.month)
313 313 end
314 314
315 315 def weeks_ago(date = Date.today)
316 316 (date.year - self.year)*52 + (date.cweek - self.cweek)
317 317 end
318 318 end
319 319
320 320 class String
321 321 def with_leading_slash
322 322 starts_with?('/') ? self : "/#{self}"
323 323 end
324 324 end
@@ -1,118 +1,126
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 class Repository < ActiveRecord::Base
19 19 belongs_to :project
20 20 has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
21 21 has_many :changes, :through => :changesets
22 22
23 23 # Checks if the SCM is enabled when creating a repository
24 24 validate_on_create { |r| r.errors.add(:type, :activerecord_error_invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
25 25
26 26 # Removes leading and trailing whitespace
27 27 def url=(arg)
28 28 write_attribute(:url, arg ? arg.to_s.strip : nil)
29 29 end
30 30
31 31 # Removes leading and trailing whitespace
32 32 def root_url=(arg)
33 33 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
34 34 end
35 35
36 36 def scm
37 37 @scm ||= self.scm_adapter.new url, root_url, login, password
38 38 update_attribute(:root_url, @scm.root_url) if root_url.blank?
39 39 @scm
40 40 end
41 41
42 42 def scm_name
43 43 self.class.scm_name
44 44 end
45 45
46 46 def supports_cat?
47 47 scm.supports_cat?
48 48 end
49 49
50 50 def supports_annotate?
51 51 scm.supports_annotate?
52 52 end
53 53
54 def entry(path=nil, identifier=nil)
55 scm.entry(path, identifier)
56 end
57
54 58 def entries(path=nil, identifier=nil)
55 59 scm.entries(path, identifier)
56 60 end
57 61
62 def cat(path, identifier=nil)
63 scm.cat(path, identifier)
64 end
65
58 66 def diff(path, rev, rev_to)
59 67 scm.diff(path, rev, rev_to)
60 68 end
61 69
62 70 # Default behaviour: we search in cached changesets
63 71 def changesets_for_path(path)
64 72 path = "/#{path}" unless path.starts_with?('/')
65 73 Change.find(:all, :include => :changeset,
66 74 :conditions => ["repository_id = ? AND path = ?", id, path],
67 75 :order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset)
68 76 end
69 77
70 78 # Returns a path relative to the url of the repository
71 79 def relative_path(path)
72 80 path
73 81 end
74 82
75 83 def latest_changeset
76 84 @latest_changeset ||= changesets.find(:first)
77 85 end
78 86
79 87 def scan_changesets_for_issue_ids
80 88 self.changesets.each(&:scan_comment_for_issue_ids)
81 89 end
82 90
83 91 # fetch new changesets for all repositories
84 92 # can be called periodically by an external script
85 93 # eg. ruby script/runner "Repository.fetch_changesets"
86 94 def self.fetch_changesets
87 95 find(:all).each(&:fetch_changesets)
88 96 end
89 97
90 98 # scan changeset comments to find related and fixed issues for all repositories
91 99 def self.scan_changesets_for_issue_ids
92 100 find(:all).each(&:scan_changesets_for_issue_ids)
93 101 end
94 102
95 103 def self.scm_name
96 104 'Abstract'
97 105 end
98 106
99 107 def self.available_scm
100 108 subclasses.collect {|klass| [klass.scm_name, klass.name]}
101 109 end
102 110
103 111 def self.factory(klass_name, *args)
104 112 klass = "Repository::#{klass_name}".constantize
105 113 klass.new(*args)
106 114 rescue
107 115 nil
108 116 end
109 117
110 118 private
111 119
112 120 def before_save
113 121 # Strips url and root_url
114 122 url.strip!
115 123 root_url.strip!
116 124 true
117 125 end
118 126 end
@@ -1,156 +1,161
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/cvs_adapter'
19 19 require 'digest/sha1'
20 20
21 21 class Repository::Cvs < Repository
22 22 validates_presence_of :url, :root_url
23 23
24 24 def scm_adapter
25 25 Redmine::Scm::Adapters::CvsAdapter
26 26 end
27 27
28 28 def self.scm_name
29 29 'CVS'
30 30 end
31 31
32 def entry(path, identifier)
33 e = entries(path, identifier)
34 e ? e.first : nil
32 def entry(path=nil, identifier=nil)
33 rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
34 scm.entry(path, rev.nil? ? nil : rev.committed_on)
35 35 end
36 36
37 37 def entries(path=nil, identifier=nil)
38 38 rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
39 39 entries = scm.entries(path, rev.nil? ? nil : rev.committed_on)
40 40 if entries
41 41 entries.each() do |entry|
42 42 unless entry.lastrev.nil? || entry.lastrev.identifier
43 43 change=changes.find_by_revision_and_path( entry.lastrev.revision, scm.with_leading_slash(entry.path) )
44 44 if change
45 45 entry.lastrev.identifier=change.changeset.revision
46 46 entry.lastrev.author=change.changeset.committer
47 47 entry.lastrev.revision=change.revision
48 48 entry.lastrev.branch=change.branch
49 49 end
50 50 end
51 51 end
52 52 end
53 53 entries
54 54 end
55 55
56 def cat(path, identifier=nil)
57 rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
58 scm.cat(path, rev.nil? ? nil : rev.committed_on)
59 end
60
56 61 def diff(path, rev, rev_to)
57 62 #convert rev to revision. CVS can't handle changesets here
58 63 diff=[]
59 64 changeset_from=changesets.find_by_revision(rev)
60 65 if rev_to.to_i > 0
61 66 changeset_to=changesets.find_by_revision(rev_to)
62 67 end
63 68 changeset_from.changes.each() do |change_from|
64 69
65 70 revision_from=nil
66 71 revision_to=nil
67 72
68 73 revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
69 74
70 75 if revision_from
71 76 if changeset_to
72 77 changeset_to.changes.each() do |change_to|
73 78 revision_to=change_to.revision if change_to.path==change_from.path
74 79 end
75 80 end
76 81 unless revision_to
77 82 revision_to=scm.get_previous_revision(revision_from)
78 83 end
79 84 file_diff = scm.diff(change_from.path, revision_from, revision_to)
80 85 diff = diff + file_diff unless file_diff.nil?
81 86 end
82 87 end
83 88 return diff
84 89 end
85 90
86 91 def fetch_changesets
87 92 # some nifty bits to introduce a commit-id with cvs
88 93 # natively cvs doesn't provide any kind of changesets, there is only a revision per file.
89 94 # we now take a guess using the author, the commitlog and the commit-date.
90 95
91 96 # last one is the next step to take. the commit-date is not equal for all
92 97 # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
93 98 # we use a small delta here, to merge all changes belonging to _one_ changeset
94 99 time_delta=10.seconds
95 100
96 101 fetch_since = latest_changeset ? latest_changeset.committed_on : nil
97 102 transaction do
98 103 tmp_rev_num = 1
99 104 scm.revisions('', fetch_since, nil, :with_paths => true) do |revision|
100 105 # only add the change to the database, if it doen't exists. the cvs log
101 106 # is not exclusive at all.
102 107 unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
103 108 revision
104 109 cs = changesets.find(:first, :conditions=>{
105 110 :committed_on=>revision.time-time_delta..revision.time+time_delta,
106 111 :committer=>revision.author,
107 112 :comments=>revision.message
108 113 })
109 114
110 115 # create a new changeset....
111 116 unless cs
112 117 # we use a temporaray revision number here (just for inserting)
113 118 # later on, we calculate a continous positive number
114 119 latest = changesets.find(:first, :order => 'id DESC')
115 120 cs = Changeset.create(:repository => self,
116 121 :revision => "_#{tmp_rev_num}",
117 122 :committer => revision.author,
118 123 :committed_on => revision.time,
119 124 :comments => revision.message)
120 125 tmp_rev_num += 1
121 126 end
122 127
123 128 #convert CVS-File-States to internal Action-abbrevations
124 129 #default action is (M)odified
125 130 action="M"
126 131 if revision.paths[0][:action]=="Exp" && revision.paths[0][:revision]=="1.1"
127 132 action="A" #add-action always at first revision (= 1.1)
128 133 elsif revision.paths[0][:action]=="dead"
129 134 action="D" #dead-state is similar to Delete
130 135 end
131 136
132 137 Change.create(:changeset => cs,
133 138 :action => action,
134 139 :path => scm.with_leading_slash(revision.paths[0][:path]),
135 140 :revision => revision.paths[0][:revision],
136 141 :branch => revision.paths[0][:branch]
137 142 )
138 143 end
139 144 end
140 145
141 146 # Renumber new changesets in chronological order
142 147 changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset|
143 148 changeset.update_attribute :revision, next_revision_number
144 149 end
145 150 end # transaction
146 151 end
147 152
148 153 private
149 154
150 155 # Returns the next revision number to assign to a CVS changeset
151 156 def next_revision_number
152 157 # Need to retrieve existing revision numbers to sort them as integers
153 158 @current_revision_number ||= (connection.select_values("SELECT revision FROM #{Changeset.table_name} WHERE repository_id = #{id} AND revision NOT LIKE '_%'").collect(&:to_i).max || 0)
154 159 @current_revision_number += 1
155 160 end
156 161 end
@@ -1,354 +1,356
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 #{root_url} rls -ed"
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 #{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 # gsub(/^:.*@[^:]+:\d*/, '') is here to remove :pserver:anonymous@foo.bar: string if present in the url
137 137 if /^RCS file: #{Regexp.escape(root_url.gsub(/^:.*@[^:]+:\d*/, ''))}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
138 138 entry_path = normalize_cvs_path($1)
139 139 entry_name = normalize_path(File.basename($1))
140 140 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
141 141 elsif /^head: (.+)$/ =~ line
142 142 entry_headRev = $1 #unless entry.nil?
143 143 elsif /^symbolic names:/ =~ line
144 144 state="symbolic" #unless entry.nil?
145 145 elsif /^#{STARTLOG}/ =~ line
146 146 commit_log=String.new
147 147 state="revision"
148 148 end
149 149 next
150 150 elsif state=="symbolic"
151 151 if /^(.*):\s(.*)/ =~ (line.strip)
152 152 branch_map[$1]=$2
153 153 else
154 154 state="tags"
155 155 next
156 156 end
157 157 elsif state=="tags"
158 158 if /^#{STARTLOG}/ =~ line
159 159 commit_log = ""
160 160 state="revision"
161 161 elsif /^#{ENDLOG}/ =~ line
162 162 state="head"
163 163 end
164 164 next
165 165 elsif state=="revision"
166 166 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
167 167 if revision
168 168
169 169 revHelper=CvsRevisionHelper.new(revision)
170 170 revBranch="HEAD"
171 171
172 172 branch_map.each() do |branch_name,branch_point|
173 173 if revHelper.is_in_branch_with_symbol(branch_point)
174 174 revBranch=branch_name
175 175 end
176 176 end
177 177
178 178 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
179 179
180 180 yield Revision.new({
181 181 :time => date,
182 182 :author => author,
183 183 :message=>commit_log.chomp,
184 184 :paths => [{
185 185 :revision => revision,
186 186 :branch=> revBranch,
187 187 :path=>entry_path,
188 188 :name=>entry_name,
189 189 :kind=>'file',
190 190 :action=>file_state
191 191 }]
192 192 })
193 193 end
194 194
195 195 commit_log=String.new
196 196 revision=nil
197 197
198 198 if /^#{ENDLOG}/ =~ line
199 199 state="entry_start"
200 200 end
201 201 next
202 202 end
203 203
204 204 if /^branches: (.+)$/ =~ line
205 205 #TODO: version.branch = $1
206 206 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
207 207 revision = $1
208 208 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
209 209 date = Time.parse($1)
210 210 author = /author: ([^;]+)/.match(line)[1]
211 211 file_state = /state: ([^;]+)/.match(line)[1]
212 212 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
213 213 # useful for stats or something else
214 214 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
215 215 # unless linechanges.nil?
216 216 # version.line_plus = linechanges[1]
217 217 # version.line_minus = linechanges[2]
218 218 # else
219 219 # version.line_plus = 0
220 220 # version.line_minus = 0
221 221 # end
222 222 else
223 223 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
224 224 end
225 225 end
226 226 end
227 227 end
228 228 end
229 229
230 230 def diff(path, identifier_from, identifier_to=nil)
231 231 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
232 232 path_with_project="#{url}#{with_leading_slash(path)}"
233 233 cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
234 234 diff = []
235 235 shellout(cmd) do |io|
236 236 io.each_line do |line|
237 237 diff << line
238 238 end
239 239 end
240 240 return nil if $? && $?.exitstatus != 0
241 241 diff
242 242 end
243 243
244 244 def cat(path, identifier=nil)
245 245 identifier = (identifier) ? identifier : "HEAD"
246 246 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
247 247 path_with_project="#{url}#{with_leading_slash(path)}"
248 cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{shell_quote path_with_project}"
248 cmd = "#{CVS_BIN} -d #{root_url} co"
249 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
250 cmd << " -p #{shell_quote path_with_project}"
249 251 cat = nil
250 252 shellout(cmd) do |io|
251 253 cat = io.read
252 254 end
253 255 return nil if $? && $?.exitstatus != 0
254 256 cat
255 257 end
256 258
257 259 def annotate(path, identifier=nil)
258 260 identifier = (identifier) ? identifier : "HEAD"
259 261 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
260 262 path_with_project="#{url}#{with_leading_slash(path)}"
261 263 cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
262 264 blame = Annotate.new
263 265 shellout(cmd) do |io|
264 266 io.each_line do |line|
265 267 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
266 268 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
267 269 end
268 270 end
269 271 return nil if $? && $?.exitstatus != 0
270 272 blame
271 273 end
272 274
273 275 private
274 276
275 277 # convert a date/time into the CVS-format
276 278 def time_to_cvstime(time)
277 279 return nil if time.nil?
278 280 unless time.kind_of? Time
279 281 time = Time.parse(time)
280 282 end
281 283 return time.strftime("%Y-%m-%d %H:%M:%S")
282 284 end
283 285
284 286 def normalize_cvs_path(path)
285 287 normalize_path(path.gsub(/Attic\//,''))
286 288 end
287 289
288 290 def normalize_path(path)
289 291 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
290 292 end
291 293 end
292 294
293 295 class CvsRevisionHelper
294 296 attr_accessor :complete_rev, :revision, :base, :branchid
295 297
296 298 def initialize(complete_rev)
297 299 @complete_rev = complete_rev
298 300 parseRevision()
299 301 end
300 302
301 303 def branchPoint
302 304 return @base
303 305 end
304 306
305 307 def branchVersion
306 308 if isBranchRevision
307 309 return @base+"."+@branchid
308 310 end
309 311 return @base
310 312 end
311 313
312 314 def isBranchRevision
313 315 !@branchid.nil?
314 316 end
315 317
316 318 def prevRev
317 319 unless @revision==0
318 320 return buildRevision(@revision-1)
319 321 end
320 322 return buildRevision(@revision)
321 323 end
322 324
323 325 def is_in_branch_with_symbol(branch_symbol)
324 326 bpieces=branch_symbol.split(".")
325 327 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
326 328 return (branchVersion==branch_start)
327 329 end
328 330
329 331 private
330 332 def buildRevision(rev)
331 333 if rev== 0
332 334 @base
333 335 elsif @branchid.nil?
334 336 @base+"."+rev.to_s
335 337 else
336 338 @base+"."+@branchid+"."+rev.to_s
337 339 end
338 340 end
339 341
340 342 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
341 343 def parseRevision()
342 344 pieces=@complete_rev.split(".")
343 345 @revision=pieces.last.to_i
344 346 baseSize=1
345 347 baseSize+=(pieces.size/2)
346 348 @base=pieces[0..-baseSize].join(".")
347 349 if baseSize > 2
348 350 @branchid=pieces[-2]
349 351 end
350 352 end
351 353 end
352 354 end
353 355 end
354 356 end
@@ -1,152 +1,165
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.dirname(__FILE__) + '/../test_helper'
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 < Test::Unit::TestCase
25 25
26 26 # No '..' in the repository path
27 27 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
28 28 REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
29 29 # CVS module
30 30 MODULE_NAME = 'test'
31 31
32 32 def setup
33 33 @controller = RepositoriesController.new
34 34 @request = ActionController::TestRequest.new
35 35 @response = ActionController::TestResponse.new
36 36 Setting.default_language = 'en'
37 37 User.current = nil
38 38
39 39 @project = Project.find(1)
40 40 @project.repository = Repository::Cvs.create(:root_url => REPOSITORY_PATH,
41 41 :url => MODULE_NAME)
42 42 end
43 43
44 44 if File.directory?(REPOSITORY_PATH)
45 45 def test_show
46 46 get :show, :id => 1
47 47 assert_response :success
48 48 assert_template 'show'
49 49 assert_not_nil assigns(:entries)
50 50 assert_not_nil assigns(:changesets)
51 51 end
52 52
53 53 def test_browse_root
54 54 get :browse, :id => 1
55 55 assert_response :success
56 56 assert_template 'browse'
57 57 assert_not_nil assigns(:entries)
58 58 assert_equal 3, assigns(:entries).size
59 59
60 60 entry = assigns(:entries).detect {|e| e.name == 'images'}
61 61 assert_equal 'dir', entry.kind
62 62
63 63 entry = assigns(:entries).detect {|e| e.name == 'README'}
64 64 assert_equal 'file', entry.kind
65 65 end
66 66
67 67 def test_browse_directory
68 68 get :browse, :id => 1, :path => ['images']
69 69 assert_response :success
70 70 assert_template 'browse'
71 71 assert_not_nil assigns(:entries)
72 72 assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name)
73 73 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
74 74 assert_not_nil entry
75 75 assert_equal 'file', entry.kind
76 76 assert_equal 'images/edit.png', entry.path
77 77 end
78 78
79 79 def test_browse_at_given_revision
80 80 Project.find(1).repository.fetch_changesets
81 81 get :browse, :id => 1, :path => ['images'], :rev => 1
82 82 assert_response :success
83 83 assert_template 'browse'
84 84 assert_not_nil assigns(:entries)
85 85 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
86 86 end
87 87
88 88 def test_entry
89 89 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb']
90 90 assert_response :success
91 91 assert_template 'entry'
92 assert_no_tag :tag => 'td', :attributes => { :class => /line-code/},
93 :content => /before_filter/
94 end
95
96 def test_entry_at_given_revision
97 # changesets must be loaded
98 Project.find(1).repository.fetch_changesets
99 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb'], :rev => 2
100 assert_response :success
101 assert_template 'entry'
102 # this line was removed in r3
103 assert_tag :tag => 'td', :attributes => { :class => /line-code/},
104 :content => /before_filter/
92 105 end
93 106
94 107 def test_entry_not_found
95 108 get :entry, :id => 1, :path => ['sources', 'zzz.c']
96 109 assert_tag :tag => 'div', :attributes => { :class => /error/ },
97 110 :content => /The entry or revision was not found in the repository/
98 111 end
99 112
100 113 def test_entry_download
101 114 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
102 115 assert_response :success
103 116 end
104 117
105 118 def test_directory_entry
106 119 get :entry, :id => 1, :path => ['sources']
107 120 assert_response :success
108 121 assert_template 'browse'
109 122 assert_not_nil assigns(:entry)
110 123 assert_equal 'sources', assigns(:entry).name
111 124 end
112 125
113 126 def test_diff
114 127 Project.find(1).repository.fetch_changesets
115 128 get :diff, :id => 1, :rev => 3, :type => 'inline'
116 129 assert_response :success
117 130 assert_template 'diff'
118 131 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' },
119 132 :content => /watched.remove_watcher/
120 133 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' },
121 134 :content => /watched.remove_all_watcher/
122 135 end
123 136
124 137 def test_annotate
125 138 Project.find(1).repository.fetch_changesets
126 139 get :annotate, :id => 1, :path => ['sources', 'watchers_controller.rb']
127 140 assert_response :success
128 141 assert_template 'annotate'
129 142 # 1.1 line
130 143 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
131 144 :content => '18',
132 145 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
133 146 :content => /1.1/,
134 147 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
135 148 :content => /LANG/
136 149 }
137 150 }
138 151 # 1.2 line
139 152 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
140 153 :content => '32',
141 154 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
142 155 :content => /1.2/,
143 156 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
144 157 :content => /LANG/
145 158 }
146 159 }
147 160 end
148 161 else
149 162 puts "CVS test repository NOT FOUND. Skipping functional tests !!!"
150 163 def test_fake; assert true end
151 164 end
152 165 end
@@ -1,146 +1,155
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.dirname(__FILE__) + '/../test_helper'
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 RepositoriesSubversionControllerTest < Test::Unit::TestCase
25 25 fixtures :projects, :users, :roles, :members, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
26 26
27 27 # No '..' in the repository path for svn
28 28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/subversion_repository'
29 29
30 30 def setup
31 31 @controller = RepositoriesController.new
32 32 @request = ActionController::TestRequest.new
33 33 @response = ActionController::TestResponse.new
34 34 Setting.default_language = 'en'
35 35 User.current = nil
36 36 end
37 37
38 38 if File.directory?(REPOSITORY_PATH)
39 39 def test_show
40 40 get :show, :id => 1
41 41 assert_response :success
42 42 assert_template 'show'
43 43 assert_not_nil assigns(:entries)
44 44 assert_not_nil assigns(:changesets)
45 45 end
46 46
47 47 def test_browse_root
48 48 get :browse, :id => 1
49 49 assert_response :success
50 50 assert_template 'browse'
51 51 assert_not_nil assigns(:entries)
52 52 entry = assigns(:entries).detect {|e| e.name == 'subversion_test'}
53 53 assert_equal 'dir', entry.kind
54 54 end
55 55
56 56 def test_browse_directory
57 57 get :browse, :id => 1, :path => ['subversion_test']
58 58 assert_response :success
59 59 assert_template 'browse'
60 60 assert_not_nil assigns(:entries)
61 61 assert_equal ['folder', '.project', 'helloworld.c', 'textfile.txt'], assigns(:entries).collect(&:name)
62 62 entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'}
63 63 assert_equal 'file', entry.kind
64 64 assert_equal 'subversion_test/helloworld.c', entry.path
65 65 end
66 66
67 67 def test_browse_at_given_revision
68 68 get :browse, :id => 1, :path => ['subversion_test'], :rev => 4
69 69 assert_response :success
70 70 assert_template 'browse'
71 71 assert_not_nil assigns(:entries)
72 72 assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], assigns(:entries).collect(&:name)
73 73 end
74 74
75 75 def test_entry
76 76 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c']
77 77 assert_response :success
78 78 assert_template 'entry'
79 79 end
80 80
81 def test_entry_at_given_revision
82 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.rb'], :rev => 2
83 assert_response :success
84 assert_template 'entry'
85 # this line was removed in r3 and file was moved in r6
86 assert_tag :tag => 'td', :attributes => { :class => /line-code/},
87 :content => /Here's the code/
88 end
89
81 90 def test_entry_not_found
82 91 get :entry, :id => 1, :path => ['subversion_test', 'zzz.c']
83 92 assert_tag :tag => 'div', :attributes => { :class => /error/ },
84 93 :content => /The entry or revision was not found in the repository/
85 94 end
86 95
87 96 def test_entry_download
88 97 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'], :format => 'raw'
89 98 assert_response :success
90 99 end
91 100
92 101 def test_directory_entry
93 102 get :entry, :id => 1, :path => ['subversion_test', 'folder']
94 103 assert_response :success
95 104 assert_template 'browse'
96 105 assert_not_nil assigns(:entry)
97 106 assert_equal 'folder', assigns(:entry).name
98 107 end
99 108
100 109 def test_revision
101 110 get :revision, :id => 1, :rev => 2
102 111 assert_response :success
103 112 assert_template 'revision'
104 113 assert_tag :tag => 'tr',
105 114 :child => { :tag => 'td',
106 115 # link to the entry at rev 2
107 116 :child => { :tag => 'a', :attributes => {:href => 'repositories/entry/ecookbook/test/some/path/in/the/repo?rev=2'},
108 117 :content => %r{/test/some/path/in/the/repo} }
109 118 },
110 119 :child => { :tag => 'td',
111 120 # link to partial diff
112 121 :child => { :tag => 'a', :attributes => { :href => '/repositories/diff/ecookbook/test/some/path/in/the/repo?rev=2' } }
113 122 }
114 123 end
115 124
116 125 def test_revision_with_repository_pointing_to_a_subdirectory
117 126 r = Project.find(1).repository
118 127 # Changes repository url to a subdirectory
119 128 r.update_attribute :url, (r.url + '/test/some')
120 129
121 130 get :revision, :id => 1, :rev => 2
122 131 assert_response :success
123 132 assert_template 'revision'
124 133 assert_tag :tag => 'tr',
125 134 :child => { :tag => 'td', :content => %r{/test/some/path/in/the/repo} },
126 135 :child => { :tag => 'td',
127 136 :child => { :tag => 'a', :attributes => { :href => '/repositories/diff/ecookbook/path/in/the/repo?rev=2' } }
128 137 }
129 138 end
130 139
131 140 def test_diff
132 141 get :diff, :id => 1, :rev => 3
133 142 assert_response :success
134 143 assert_template 'diff'
135 144 end
136 145
137 146 def test_annotate
138 147 get :annotate, :id => 1, :path => ['subversion_test', 'helloworld.c']
139 148 assert_response :success
140 149 assert_template 'annotate'
141 150 end
142 151 else
143 152 puts "Subversion test repository NOT FOUND. Skipping functional tests !!!"
144 153 def test_fake; assert true end
145 154 end
146 155 end
General Comments 0
You need to be logged in to leave comments. Login now