##// END OF EJS Templates
Fixed: Links to repository directories don't work (#1119)....
Jean-Philippe Lang -
r1350:a73f68a185df
parent child
Show More
@@ -1,307 +1,314
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 'SVG/Graph/Bar'
18 require 'SVG/Graph/Bar'
19 require 'SVG/Graph/BarHorizontal'
19 require 'SVG/Graph/BarHorizontal'
20 require 'digest/sha1'
20 require 'digest/sha1'
21
21
22 class ChangesetNotFound < Exception; end
22 class ChangesetNotFound < Exception; end
23 class InvalidRevisionParam < Exception; end
23 class InvalidRevisionParam < Exception; end
24
24
25 class RepositoriesController < ApplicationController
25 class RepositoriesController < ApplicationController
26 layout 'base'
26 layout 'base'
27 menu_item :repository
27 menu_item :repository
28 before_filter :find_repository, :except => :edit
28 before_filter :find_repository, :except => :edit
29 before_filter :find_project, :only => :edit
29 before_filter :find_project, :only => :edit
30 before_filter :authorize
30 before_filter :authorize
31 accept_key_auth :revisions
31 accept_key_auth :revisions
32
32
33 def edit
33 def edit
34 @repository = @project.repository
34 @repository = @project.repository
35 if !@repository
35 if !@repository
36 @repository = Repository.factory(params[:repository_scm])
36 @repository = Repository.factory(params[:repository_scm])
37 @repository.project = @project
37 @repository.project = @project
38 end
38 end
39 if request.post?
39 if request.post?
40 @repository.attributes = params[:repository]
40 @repository.attributes = params[:repository]
41 @repository.save
41 @repository.save
42 end
42 end
43 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
43 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
44 end
44 end
45
45
46 def destroy
46 def destroy
47 @repository.destroy
47 @repository.destroy
48 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
48 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
49 end
49 end
50
50
51 def show
51 def show
52 # check if new revisions have been committed in the repository
52 # check if new revisions have been committed in the repository
53 @repository.fetch_changesets if Setting.autofetch_changesets?
53 @repository.fetch_changesets if Setting.autofetch_changesets?
54 # root entries
54 # root entries
55 @entries = @repository.entries('', @rev)
55 @entries = @repository.entries('', @rev)
56 # latest changesets
56 # latest changesets
57 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
57 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
58 show_error_not_found unless @entries || @changesets.any?
58 show_error_not_found unless @entries || @changesets.any?
59 rescue Redmine::Scm::Adapters::CommandFailed => e
59 rescue Redmine::Scm::Adapters::CommandFailed => e
60 show_error_command_failed(e.message)
60 show_error_command_failed(e.message)
61 end
61 end
62
62
63 def browse
63 def browse
64 @entries = @repository.entries(@path, @rev)
64 @entries = @repository.entries(@path, @rev)
65 if request.xhr?
65 if request.xhr?
66 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
66 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
67 else
67 else
68 show_error_not_found unless @entries
68 show_error_not_found and return unless @entries
69 render :action => 'browse'
69 end
70 end
70 rescue Redmine::Scm::Adapters::CommandFailed => e
71 rescue Redmine::Scm::Adapters::CommandFailed => e
71 show_error_command_failed(e.message)
72 show_error_command_failed(e.message)
72 end
73 end
73
74
74 def changes
75 def changes
75 @entry = @repository.scm.entry(@path, @rev)
76 @entry = @repository.scm.entry(@path, @rev)
76 show_error_not_found and return unless @entry
77 show_error_not_found and return unless @entry
77 @changesets = @repository.changesets_for_path(@path)
78 @changesets = @repository.changesets_for_path(@path)
78 rescue Redmine::Scm::Adapters::CommandFailed => e
79 rescue Redmine::Scm::Adapters::CommandFailed => e
79 show_error_command_failed(e.message)
80 show_error_command_failed(e.message)
80 end
81 end
81
82
82 def revisions
83 def revisions
83 @changeset_count = @repository.changesets.count
84 @changeset_count = @repository.changesets.count
84 @changeset_pages = Paginator.new self, @changeset_count,
85 @changeset_pages = Paginator.new self, @changeset_count,
85 per_page_option,
86 per_page_option,
86 params['page']
87 params['page']
87 @changesets = @repository.changesets.find(:all,
88 @changesets = @repository.changesets.find(:all,
88 :limit => @changeset_pages.items_per_page,
89 :limit => @changeset_pages.items_per_page,
89 :offset => @changeset_pages.current.offset)
90 :offset => @changeset_pages.current.offset)
90
91
91 respond_to do |format|
92 respond_to do |format|
92 format.html { render :layout => false if request.xhr? }
93 format.html { render :layout => false if request.xhr? }
93 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
94 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
94 end
95 end
95 end
96 end
96
97
97 def entry
98 def entry
99 @entry = @repository.scm.entry(@path, @rev)
100 show_error_not_found and return unless @entry
101
102 # If the entry is a dir, show the browser
103 browse and return if @entry.is_dir?
104
98 @content = @repository.scm.cat(@path, @rev)
105 @content = @repository.scm.cat(@path, @rev)
99 show_error_not_found and return unless @content
106 show_error_not_found and return unless @content
100 if 'raw' == params[:format] || @content.is_binary_data?
107 if 'raw' == params[:format] || @content.is_binary_data?
101 # Force the download if it's a binary file
108 # Force the download if it's a binary file
102 send_data @content, :filename => @path.split('/').last
109 send_data @content, :filename => @path.split('/').last
103 else
110 else
104 # Prevent empty lines when displaying a file with Windows style eol
111 # Prevent empty lines when displaying a file with Windows style eol
105 @content.gsub!("\r\n", "\n")
112 @content.gsub!("\r\n", "\n")
106 end
113 end
107 rescue Redmine::Scm::Adapters::CommandFailed => e
114 rescue Redmine::Scm::Adapters::CommandFailed => e
108 show_error_command_failed(e.message)
115 show_error_command_failed(e.message)
109 end
116 end
110
117
111 def annotate
118 def annotate
112 @annotate = @repository.scm.annotate(@path, @rev)
119 @annotate = @repository.scm.annotate(@path, @rev)
113 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
120 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
114 rescue Redmine::Scm::Adapters::CommandFailed => e
121 rescue Redmine::Scm::Adapters::CommandFailed => e
115 show_error_command_failed(e.message)
122 show_error_command_failed(e.message)
116 end
123 end
117
124
118 def revision
125 def revision
119 @changeset = @repository.changesets.find_by_revision(@rev)
126 @changeset = @repository.changesets.find_by_revision(@rev)
120 raise ChangesetNotFound unless @changeset
127 raise ChangesetNotFound unless @changeset
121 @changes_count = @changeset.changes.size
128 @changes_count = @changeset.changes.size
122 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
129 @changes_pages = Paginator.new self, @changes_count, 150, params['page']
123 @changes = @changeset.changes.find(:all,
130 @changes = @changeset.changes.find(:all,
124 :limit => @changes_pages.items_per_page,
131 :limit => @changes_pages.items_per_page,
125 :offset => @changes_pages.current.offset)
132 :offset => @changes_pages.current.offset)
126
133
127 respond_to do |format|
134 respond_to do |format|
128 format.html
135 format.html
129 format.js {render :layout => false}
136 format.js {render :layout => false}
130 end
137 end
131 rescue ChangesetNotFound
138 rescue ChangesetNotFound
132 show_error_not_found
139 show_error_not_found
133 rescue Redmine::Scm::Adapters::CommandFailed => e
140 rescue Redmine::Scm::Adapters::CommandFailed => e
134 show_error_command_failed(e.message)
141 show_error_command_failed(e.message)
135 end
142 end
136
143
137 def diff
144 def diff
138 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
145 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
139 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
146 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
140
147
141 # Save diff type as user preference
148 # Save diff type as user preference
142 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
149 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
143 User.current.pref[:diff_type] = @diff_type
150 User.current.pref[:diff_type] = @diff_type
144 User.current.preference.save
151 User.current.preference.save
145 end
152 end
146
153
147 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
154 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
148 unless read_fragment(@cache_key)
155 unless read_fragment(@cache_key)
149 @diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
156 @diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
150 show_error_not_found unless @diff
157 show_error_not_found unless @diff
151 end
158 end
152 rescue Redmine::Scm::Adapters::CommandFailed => e
159 rescue Redmine::Scm::Adapters::CommandFailed => e
153 show_error_command_failed(e.message)
160 show_error_command_failed(e.message)
154 end
161 end
155
162
156 def stats
163 def stats
157 end
164 end
158
165
159 def graph
166 def graph
160 data = nil
167 data = nil
161 case params[:graph]
168 case params[:graph]
162 when "commits_per_month"
169 when "commits_per_month"
163 data = graph_commits_per_month(@repository)
170 data = graph_commits_per_month(@repository)
164 when "commits_per_author"
171 when "commits_per_author"
165 data = graph_commits_per_author(@repository)
172 data = graph_commits_per_author(@repository)
166 end
173 end
167 if data
174 if data
168 headers["Content-Type"] = "image/svg+xml"
175 headers["Content-Type"] = "image/svg+xml"
169 send_data(data, :type => "image/svg+xml", :disposition => "inline")
176 send_data(data, :type => "image/svg+xml", :disposition => "inline")
170 else
177 else
171 render_404
178 render_404
172 end
179 end
173 end
180 end
174
181
175 private
182 private
176 def find_project
183 def find_project
177 @project = Project.find(params[:id])
184 @project = Project.find(params[:id])
178 rescue ActiveRecord::RecordNotFound
185 rescue ActiveRecord::RecordNotFound
179 render_404
186 render_404
180 end
187 end
181
188
182 REV_PARAM_RE = %r{^[a-f0-9]*$}
189 REV_PARAM_RE = %r{^[a-f0-9]*$}
183
190
184 def find_repository
191 def find_repository
185 @project = Project.find(params[:id])
192 @project = Project.find(params[:id])
186 @repository = @project.repository
193 @repository = @project.repository
187 render_404 and return false unless @repository
194 render_404 and return false unless @repository
188 @path = params[:path].join('/') unless params[:path].nil?
195 @path = params[:path].join('/') unless params[:path].nil?
189 @path ||= ''
196 @path ||= ''
190 @rev = params[:rev]
197 @rev = params[:rev]
191 @rev_to = params[:rev_to]
198 @rev_to = params[:rev_to]
192 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
199 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
193 rescue ActiveRecord::RecordNotFound
200 rescue ActiveRecord::RecordNotFound
194 render_404
201 render_404
195 rescue InvalidRevisionParam
202 rescue InvalidRevisionParam
196 show_error_not_found
203 show_error_not_found
197 end
204 end
198
205
199 def show_error_not_found
206 def show_error_not_found
200 render_error l(:error_scm_not_found)
207 render_error l(:error_scm_not_found)
201 end
208 end
202
209
203 def show_error_command_failed(msg)
210 def show_error_command_failed(msg)
204 render_error l(:error_scm_command_failed, msg)
211 render_error l(:error_scm_command_failed, msg)
205 end
212 end
206
213
207 def graph_commits_per_month(repository)
214 def graph_commits_per_month(repository)
208 @date_to = Date.today
215 @date_to = Date.today
209 @date_from = @date_to << 11
216 @date_from = @date_to << 11
210 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
217 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
211 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
218 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
212 commits_by_month = [0] * 12
219 commits_by_month = [0] * 12
213 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
220 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
214
221
215 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
222 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
216 changes_by_month = [0] * 12
223 changes_by_month = [0] * 12
217 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
224 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
218
225
219 fields = []
226 fields = []
220 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
227 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
221 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
228 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
222
229
223 graph = SVG::Graph::Bar.new(
230 graph = SVG::Graph::Bar.new(
224 :height => 300,
231 :height => 300,
225 :width => 500,
232 :width => 500,
226 :fields => fields.reverse,
233 :fields => fields.reverse,
227 :stack => :side,
234 :stack => :side,
228 :scale_integers => true,
235 :scale_integers => true,
229 :step_x_labels => 2,
236 :step_x_labels => 2,
230 :show_data_values => false,
237 :show_data_values => false,
231 :graph_title => l(:label_commits_per_month),
238 :graph_title => l(:label_commits_per_month),
232 :show_graph_title => true
239 :show_graph_title => true
233 )
240 )
234
241
235 graph.add_data(
242 graph.add_data(
236 :data => commits_by_month[0..11].reverse,
243 :data => commits_by_month[0..11].reverse,
237 :title => l(:label_revision_plural)
244 :title => l(:label_revision_plural)
238 )
245 )
239
246
240 graph.add_data(
247 graph.add_data(
241 :data => changes_by_month[0..11].reverse,
248 :data => changes_by_month[0..11].reverse,
242 :title => l(:label_change_plural)
249 :title => l(:label_change_plural)
243 )
250 )
244
251
245 graph.burn
252 graph.burn
246 end
253 end
247
254
248 def graph_commits_per_author(repository)
255 def graph_commits_per_author(repository)
249 commits_by_author = repository.changesets.count(:all, :group => :committer)
256 commits_by_author = repository.changesets.count(:all, :group => :committer)
250 commits_by_author.sort! {|x, y| x.last <=> y.last}
257 commits_by_author.sort! {|x, y| x.last <=> y.last}
251
258
252 changes_by_author = repository.changes.count(:all, :group => :committer)
259 changes_by_author = repository.changes.count(:all, :group => :committer)
253 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
260 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
254
261
255 fields = commits_by_author.collect {|r| r.first}
262 fields = commits_by_author.collect {|r| r.first}
256 commits_data = commits_by_author.collect {|r| r.last}
263 commits_data = commits_by_author.collect {|r| r.last}
257 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
264 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
258
265
259 fields = fields + [""]*(10 - fields.length) if fields.length<10
266 fields = fields + [""]*(10 - fields.length) if fields.length<10
260 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
267 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
261 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
268 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
262
269
263 # Remove email adress in usernames
270 # Remove email adress in usernames
264 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
271 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
265
272
266 graph = SVG::Graph::BarHorizontal.new(
273 graph = SVG::Graph::BarHorizontal.new(
267 :height => 300,
274 :height => 300,
268 :width => 500,
275 :width => 500,
269 :fields => fields,
276 :fields => fields,
270 :stack => :side,
277 :stack => :side,
271 :scale_integers => true,
278 :scale_integers => true,
272 :show_data_values => false,
279 :show_data_values => false,
273 :rotate_y_labels => false,
280 :rotate_y_labels => false,
274 :graph_title => l(:label_commits_per_author),
281 :graph_title => l(:label_commits_per_author),
275 :show_graph_title => true
282 :show_graph_title => true
276 )
283 )
277
284
278 graph.add_data(
285 graph.add_data(
279 :data => commits_data,
286 :data => commits_data,
280 :title => l(:label_revision_plural)
287 :title => l(:label_revision_plural)
281 )
288 )
282
289
283 graph.add_data(
290 graph.add_data(
284 :data => changes_data,
291 :data => changes_data,
285 :title => l(:label_change_plural)
292 :title => l(:label_change_plural)
286 )
293 )
287
294
288 graph.burn
295 graph.burn
289 end
296 end
290
297
291 end
298 end
292
299
293 class Date
300 class Date
294 def months_ago(date = Date.today)
301 def months_ago(date = Date.today)
295 (date.year - self.year)*12 + (date.month - self.month)
302 (date.year - self.year)*12 + (date.month - self.month)
296 end
303 end
297
304
298 def weeks_ago(date = Date.today)
305 def weeks_ago(date = Date.today)
299 (date.year - self.year)*52 + (date.cweek - self.cweek)
306 (date.year - self.year)*52 + (date.cweek - self.cweek)
300 end
307 end
301 end
308 end
302
309
303 class String
310 class String
304 def with_leading_slash
311 def with_leading_slash
305 starts_with?('/') ? self : "/#{self}"
312 starts_with?('/') ? self : "/#{self}"
306 end
313 end
307 end
314 end
@@ -1,386 +1,395
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 'cgi'
18 require 'cgi'
19
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class CommandFailed < StandardError #:nodoc:
23 class CommandFailed < StandardError #:nodoc:
24 end
24 end
25
25
26 class AbstractAdapter #:nodoc:
26 class AbstractAdapter #:nodoc:
27 def initialize(url, root_url=nil, login=nil, password=nil)
27 def initialize(url, root_url=nil, login=nil, password=nil)
28 @url = url
28 @url = url
29 @login = login if login && !login.empty?
29 @login = login if login && !login.empty?
30 @password = (password || "") if @login
30 @password = (password || "") if @login
31 @root_url = root_url.blank? ? retrieve_root_url : root_url
31 @root_url = root_url.blank? ? retrieve_root_url : root_url
32 end
32 end
33
33
34 def adapter_name
34 def adapter_name
35 'Abstract'
35 'Abstract'
36 end
36 end
37
37
38 def supports_cat?
38 def supports_cat?
39 true
39 true
40 end
40 end
41
41
42 def supports_annotate?
42 def supports_annotate?
43 respond_to?('annotate')
43 respond_to?('annotate')
44 end
44 end
45
45
46 def root_url
46 def root_url
47 @root_url
47 @root_url
48 end
48 end
49
49
50 def url
50 def url
51 @url
51 @url
52 end
52 end
53
53
54 # get info about the svn repository
54 # get info about the svn repository
55 def info
55 def info
56 return nil
56 return nil
57 end
57 end
58
58
59 # Returns the entry identified by path and revision identifier
59 # Returns the entry identified by path and revision identifier
60 # or nil if entry doesn't exist in the repository
60 # or nil if entry doesn't exist in the repository
61 def entry(path=nil, identifier=nil)
61 def entry(path=nil, identifier=nil)
62 e = entries(path, identifier)
62 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
63 e ? e.first : nil
63 search_path = parts[0..-2].join('/')
64 search_name = parts[-1]
65 if search_path.blank? && search_name.blank?
66 # Root entry
67 Entry.new(:path => '', :kind => 'dir')
68 else
69 # Search for the entry in the parent directory
70 es = entries(search_path, identifier)
71 es ? es.detect {|e| e.name == search_name} : nil
72 end
64 end
73 end
65
74
66 # Returns an Entries collection
75 # Returns an Entries collection
67 # or nil if the given path doesn't exist in the repository
76 # or nil if the given path doesn't exist in the repository
68 def entries(path=nil, identifier=nil)
77 def entries(path=nil, identifier=nil)
69 return nil
78 return nil
70 end
79 end
71
80
72 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
81 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
73 return nil
82 return nil
74 end
83 end
75
84
76 def diff(path, identifier_from, identifier_to=nil, type="inline")
85 def diff(path, identifier_from, identifier_to=nil, type="inline")
77 return nil
86 return nil
78 end
87 end
79
88
80 def cat(path, identifier=nil)
89 def cat(path, identifier=nil)
81 return nil
90 return nil
82 end
91 end
83
92
84 def with_leading_slash(path)
93 def with_leading_slash(path)
85 path ||= ''
94 path ||= ''
86 (path[0,1]!="/") ? "/#{path}" : path
95 (path[0,1]!="/") ? "/#{path}" : path
87 end
96 end
88
97
89 def shell_quote(str)
98 def shell_quote(str)
90 if RUBY_PLATFORM =~ /mswin/
99 if RUBY_PLATFORM =~ /mswin/
91 '"' + str.gsub(/"/, '\\"') + '"'
100 '"' + str.gsub(/"/, '\\"') + '"'
92 else
101 else
93 "'" + str.gsub(/'/, "'\"'\"'") + "'"
102 "'" + str.gsub(/'/, "'\"'\"'") + "'"
94 end
103 end
95 end
104 end
96
105
97 private
106 private
98 def retrieve_root_url
107 def retrieve_root_url
99 info = self.info
108 info = self.info
100 info ? info.root_url : nil
109 info ? info.root_url : nil
101 end
110 end
102
111
103 def target(path)
112 def target(path)
104 path ||= ''
113 path ||= ''
105 base = path.match(/^\//) ? root_url : url
114 base = path.match(/^\//) ? root_url : url
106 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
115 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
107 end
116 end
108
117
109 def logger
118 def logger
110 RAILS_DEFAULT_LOGGER
119 RAILS_DEFAULT_LOGGER
111 end
120 end
112
121
113 def shellout(cmd, &block)
122 def shellout(cmd, &block)
114 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
123 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
115 begin
124 begin
116 IO.popen(cmd, "r+") do |io|
125 IO.popen(cmd, "r+") do |io|
117 io.close_write
126 io.close_write
118 block.call(io) if block_given?
127 block.call(io) if block_given?
119 end
128 end
120 rescue Errno::ENOENT => e
129 rescue Errno::ENOENT => e
121 # The command failed, log it and re-raise
130 # The command failed, log it and re-raise
122 logger.error("SCM command failed: #{cmd}\n with: #{e.message}")
131 logger.error("SCM command failed: #{cmd}\n with: #{e.message}")
123 raise CommandFailed.new(e.message)
132 raise CommandFailed.new(e.message)
124 end
133 end
125 end
134 end
126 end
135 end
127
136
128 class Entries < Array
137 class Entries < Array
129 def sort_by_name
138 def sort_by_name
130 sort {|x,y|
139 sort {|x,y|
131 if x.kind == y.kind
140 if x.kind == y.kind
132 x.name <=> y.name
141 x.name <=> y.name
133 else
142 else
134 x.kind <=> y.kind
143 x.kind <=> y.kind
135 end
144 end
136 }
145 }
137 end
146 end
138
147
139 def revisions
148 def revisions
140 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
149 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
141 end
150 end
142 end
151 end
143
152
144 class Info
153 class Info
145 attr_accessor :root_url, :lastrev
154 attr_accessor :root_url, :lastrev
146 def initialize(attributes={})
155 def initialize(attributes={})
147 self.root_url = attributes[:root_url] if attributes[:root_url]
156 self.root_url = attributes[:root_url] if attributes[:root_url]
148 self.lastrev = attributes[:lastrev]
157 self.lastrev = attributes[:lastrev]
149 end
158 end
150 end
159 end
151
160
152 class Entry
161 class Entry
153 attr_accessor :name, :path, :kind, :size, :lastrev
162 attr_accessor :name, :path, :kind, :size, :lastrev
154 def initialize(attributes={})
163 def initialize(attributes={})
155 self.name = attributes[:name] if attributes[:name]
164 self.name = attributes[:name] if attributes[:name]
156 self.path = attributes[:path] if attributes[:path]
165 self.path = attributes[:path] if attributes[:path]
157 self.kind = attributes[:kind] if attributes[:kind]
166 self.kind = attributes[:kind] if attributes[:kind]
158 self.size = attributes[:size].to_i if attributes[:size]
167 self.size = attributes[:size].to_i if attributes[:size]
159 self.lastrev = attributes[:lastrev]
168 self.lastrev = attributes[:lastrev]
160 end
169 end
161
170
162 def is_file?
171 def is_file?
163 'file' == self.kind
172 'file' == self.kind
164 end
173 end
165
174
166 def is_dir?
175 def is_dir?
167 'dir' == self.kind
176 'dir' == self.kind
168 end
177 end
169
178
170 def is_text?
179 def is_text?
171 Redmine::MimeType.is_type?('text', name)
180 Redmine::MimeType.is_type?('text', name)
172 end
181 end
173 end
182 end
174
183
175 class Revisions < Array
184 class Revisions < Array
176 def latest
185 def latest
177 sort {|x,y|
186 sort {|x,y|
178 unless x.time.nil? or y.time.nil?
187 unless x.time.nil? or y.time.nil?
179 x.time <=> y.time
188 x.time <=> y.time
180 else
189 else
181 0
190 0
182 end
191 end
183 }.last
192 }.last
184 end
193 end
185 end
194 end
186
195
187 class Revision
196 class Revision
188 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
197 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
189 def initialize(attributes={})
198 def initialize(attributes={})
190 self.identifier = attributes[:identifier]
199 self.identifier = attributes[:identifier]
191 self.scmid = attributes[:scmid]
200 self.scmid = attributes[:scmid]
192 self.name = attributes[:name] || self.identifier
201 self.name = attributes[:name] || self.identifier
193 self.author = attributes[:author]
202 self.author = attributes[:author]
194 self.time = attributes[:time]
203 self.time = attributes[:time]
195 self.message = attributes[:message] || ""
204 self.message = attributes[:message] || ""
196 self.paths = attributes[:paths]
205 self.paths = attributes[:paths]
197 self.revision = attributes[:revision]
206 self.revision = attributes[:revision]
198 self.branch = attributes[:branch]
207 self.branch = attributes[:branch]
199 end
208 end
200
209
201 end
210 end
202
211
203 # A line of Diff
212 # A line of Diff
204 class Diff
213 class Diff
205 attr_accessor :nb_line_left
214 attr_accessor :nb_line_left
206 attr_accessor :line_left
215 attr_accessor :line_left
207 attr_accessor :nb_line_right
216 attr_accessor :nb_line_right
208 attr_accessor :line_right
217 attr_accessor :line_right
209 attr_accessor :type_diff_right
218 attr_accessor :type_diff_right
210 attr_accessor :type_diff_left
219 attr_accessor :type_diff_left
211
220
212 def initialize ()
221 def initialize ()
213 self.nb_line_left = ''
222 self.nb_line_left = ''
214 self.nb_line_right = ''
223 self.nb_line_right = ''
215 self.line_left = ''
224 self.line_left = ''
216 self.line_right = ''
225 self.line_right = ''
217 self.type_diff_right = ''
226 self.type_diff_right = ''
218 self.type_diff_left = ''
227 self.type_diff_left = ''
219 end
228 end
220
229
221 def inspect
230 def inspect
222 puts '### Start Line Diff ###'
231 puts '### Start Line Diff ###'
223 puts self.nb_line_left
232 puts self.nb_line_left
224 puts self.line_left
233 puts self.line_left
225 puts self.nb_line_right
234 puts self.nb_line_right
226 puts self.line_right
235 puts self.line_right
227 end
236 end
228 end
237 end
229
238
230 class DiffTableList < Array
239 class DiffTableList < Array
231 def initialize (diff, type="inline")
240 def initialize (diff, type="inline")
232 diff_table = DiffTable.new type
241 diff_table = DiffTable.new type
233 diff.each do |line|
242 diff.each do |line|
234 if line =~ /^(---|\+\+\+) (.*)$/
243 if line =~ /^(---|\+\+\+) (.*)$/
235 self << diff_table if diff_table.length > 1
244 self << diff_table if diff_table.length > 1
236 diff_table = DiffTable.new type
245 diff_table = DiffTable.new type
237 end
246 end
238 a = diff_table.add_line line
247 a = diff_table.add_line line
239 end
248 end
240 self << diff_table unless diff_table.empty?
249 self << diff_table unless diff_table.empty?
241 self
250 self
242 end
251 end
243 end
252 end
244
253
245 # Class for create a Diff
254 # Class for create a Diff
246 class DiffTable < Hash
255 class DiffTable < Hash
247 attr_reader :file_name, :line_num_l, :line_num_r
256 attr_reader :file_name, :line_num_l, :line_num_r
248
257
249 # Initialize with a Diff file and the type of Diff View
258 # Initialize with a Diff file and the type of Diff View
250 # The type view must be inline or sbs (side_by_side)
259 # The type view must be inline or sbs (side_by_side)
251 def initialize(type="inline")
260 def initialize(type="inline")
252 @parsing = false
261 @parsing = false
253 @nb_line = 1
262 @nb_line = 1
254 @start = false
263 @start = false
255 @before = 'same'
264 @before = 'same'
256 @second = true
265 @second = true
257 @type = type
266 @type = type
258 end
267 end
259
268
260 # Function for add a line of this Diff
269 # Function for add a line of this Diff
261 def add_line(line)
270 def add_line(line)
262 unless @parsing
271 unless @parsing
263 if line =~ /^(---|\+\+\+) (.*)$/
272 if line =~ /^(---|\+\+\+) (.*)$/
264 @file_name = $2
273 @file_name = $2
265 return false
274 return false
266 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
275 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
267 @line_num_l = $5.to_i
276 @line_num_l = $5.to_i
268 @line_num_r = $2.to_i
277 @line_num_r = $2.to_i
269 @parsing = true
278 @parsing = true
270 end
279 end
271 else
280 else
272 if line =~ /^[^\+\-\s@\\]/
281 if line =~ /^[^\+\-\s@\\]/
273 self.delete(self.keys.sort.last)
282 self.delete(self.keys.sort.last)
274 @parsing = false
283 @parsing = false
275 return false
284 return false
276 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
285 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
277 @line_num_l = $5.to_i
286 @line_num_l = $5.to_i
278 @line_num_r = $2.to_i
287 @line_num_r = $2.to_i
279 else
288 else
280 @nb_line += 1 if parse_line(line, @type)
289 @nb_line += 1 if parse_line(line, @type)
281 end
290 end
282 end
291 end
283 return true
292 return true
284 end
293 end
285
294
286 def inspect
295 def inspect
287 puts '### DIFF TABLE ###'
296 puts '### DIFF TABLE ###'
288 puts "file : #{file_name}"
297 puts "file : #{file_name}"
289 self.each do |d|
298 self.each do |d|
290 d.inspect
299 d.inspect
291 end
300 end
292 end
301 end
293
302
294 private
303 private
295 # Test if is a Side By Side type
304 # Test if is a Side By Side type
296 def sbs?(type, func)
305 def sbs?(type, func)
297 if @start and type == "sbs"
306 if @start and type == "sbs"
298 if @before == func and @second
307 if @before == func and @second
299 tmp_nb_line = @nb_line
308 tmp_nb_line = @nb_line
300 self[tmp_nb_line] = Diff.new
309 self[tmp_nb_line] = Diff.new
301 else
310 else
302 @second = false
311 @second = false
303 tmp_nb_line = @start
312 tmp_nb_line = @start
304 @start += 1
313 @start += 1
305 @nb_line -= 1
314 @nb_line -= 1
306 end
315 end
307 else
316 else
308 tmp_nb_line = @nb_line
317 tmp_nb_line = @nb_line
309 @start = @nb_line
318 @start = @nb_line
310 self[tmp_nb_line] = Diff.new
319 self[tmp_nb_line] = Diff.new
311 @second = true
320 @second = true
312 end
321 end
313 unless self[tmp_nb_line]
322 unless self[tmp_nb_line]
314 @nb_line += 1
323 @nb_line += 1
315 self[tmp_nb_line] = Diff.new
324 self[tmp_nb_line] = Diff.new
316 else
325 else
317 self[tmp_nb_line]
326 self[tmp_nb_line]
318 end
327 end
319 end
328 end
320
329
321 # Escape the HTML for the diff
330 # Escape the HTML for the diff
322 def escapeHTML(line)
331 def escapeHTML(line)
323 CGI.escapeHTML(line)
332 CGI.escapeHTML(line)
324 end
333 end
325
334
326 def parse_line(line, type="inline")
335 def parse_line(line, type="inline")
327 if line[0, 1] == "+"
336 if line[0, 1] == "+"
328 diff = sbs? type, 'add'
337 diff = sbs? type, 'add'
329 @before = 'add'
338 @before = 'add'
330 diff.line_left = escapeHTML line[1..-1]
339 diff.line_left = escapeHTML line[1..-1]
331 diff.nb_line_left = @line_num_l
340 diff.nb_line_left = @line_num_l
332 diff.type_diff_left = 'diff_in'
341 diff.type_diff_left = 'diff_in'
333 @line_num_l += 1
342 @line_num_l += 1
334 true
343 true
335 elsif line[0, 1] == "-"
344 elsif line[0, 1] == "-"
336 diff = sbs? type, 'remove'
345 diff = sbs? type, 'remove'
337 @before = 'remove'
346 @before = 'remove'
338 diff.line_right = escapeHTML line[1..-1]
347 diff.line_right = escapeHTML line[1..-1]
339 diff.nb_line_right = @line_num_r
348 diff.nb_line_right = @line_num_r
340 diff.type_diff_right = 'diff_out'
349 diff.type_diff_right = 'diff_out'
341 @line_num_r += 1
350 @line_num_r += 1
342 true
351 true
343 elsif line[0, 1] =~ /\s/
352 elsif line[0, 1] =~ /\s/
344 @before = 'same'
353 @before = 'same'
345 @start = false
354 @start = false
346 diff = Diff.new
355 diff = Diff.new
347 diff.line_right = escapeHTML line[1..-1]
356 diff.line_right = escapeHTML line[1..-1]
348 diff.nb_line_right = @line_num_r
357 diff.nb_line_right = @line_num_r
349 diff.line_left = escapeHTML line[1..-1]
358 diff.line_left = escapeHTML line[1..-1]
350 diff.nb_line_left = @line_num_l
359 diff.nb_line_left = @line_num_l
351 self[@nb_line] = diff
360 self[@nb_line] = diff
352 @line_num_l += 1
361 @line_num_l += 1
353 @line_num_r += 1
362 @line_num_r += 1
354 true
363 true
355 elsif line[0, 1] = "\\"
364 elsif line[0, 1] = "\\"
356 true
365 true
357 else
366 else
358 false
367 false
359 end
368 end
360 end
369 end
361 end
370 end
362
371
363 class Annotate
372 class Annotate
364 attr_reader :lines, :revisions
373 attr_reader :lines, :revisions
365
374
366 def initialize
375 def initialize
367 @lines = []
376 @lines = []
368 @revisions = []
377 @revisions = []
369 end
378 end
370
379
371 def add_line(line, revision)
380 def add_line(line, revision)
372 @lines << line
381 @lines << line
373 @revisions << revision
382 @revisions << revision
374 end
383 end
375
384
376 def content
385 def content
377 content = lines.join("\n")
386 content = lines.join("\n")
378 end
387 end
379
388
380 def empty?
389 def empty?
381 lines.empty?
390 lines.empty?
382 end
391 end
383 end
392 end
384 end
393 end
385 end
394 end
386 end
395 end
@@ -1,197 +1,185
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 BazaarAdapter < AbstractAdapter
23 class BazaarAdapter < AbstractAdapter
24
24
25 # Bazaar executable name
25 # Bazaar executable name
26 BZR_BIN = "bzr"
26 BZR_BIN = "bzr"
27
27
28 # Get info about the repository
28 # Get info about the repository
29 def info
29 def info
30 cmd = "#{BZR_BIN} revno #{target('')}"
30 cmd = "#{BZR_BIN} revno #{target('')}"
31 info = nil
31 info = nil
32 shellout(cmd) do |io|
32 shellout(cmd) do |io|
33 if io.read =~ %r{^(\d+)$}
33 if io.read =~ %r{^(\d+)$}
34 info = Info.new({:root_url => url,
34 info = Info.new({:root_url => url,
35 :lastrev => Revision.new({
35 :lastrev => Revision.new({
36 :identifier => $1
36 :identifier => $1
37 })
37 })
38 })
38 })
39 end
39 end
40 end
40 end
41 return nil if $? && $?.exitstatus != 0
41 return nil if $? && $?.exitstatus != 0
42 info
42 info
43 rescue CommandFailed
43 rescue CommandFailed
44 return nil
44 return nil
45 end
45 end
46
46
47 # Returns the entry identified by path and revision identifier
48 # or nil if entry doesn't exist in the repository
49 def entry(path=nil, identifier=nil)
50 path ||= ''
51 parts = path.split(%r{[\/\\]}).select {|p| !p.blank?}
52 if parts.size > 0
53 parent = parts[0..-2].join('/')
54 entries = entries(parent, identifier)
55 entries ? entries.detect {|e| e.name == parts.last} : nil
56 end
57 end
58
59 # Returns an Entries collection
47 # Returns an Entries collection
60 # or nil if the given path doesn't exist in the repository
48 # or nil if the given path doesn't exist in the repository
61 def entries(path=nil, identifier=nil)
49 def entries(path=nil, identifier=nil)
62 path ||= ''
50 path ||= ''
63 entries = Entries.new
51 entries = Entries.new
64 cmd = "#{BZR_BIN} ls -v --show-ids"
52 cmd = "#{BZR_BIN} ls -v --show-ids"
65 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
53 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
66 cmd << " #{target(path)}"
54 cmd << " #{target(path)}"
67 shellout(cmd) do |io|
55 shellout(cmd) do |io|
68 prefix = "#{url}/#{path}".gsub('\\', '/')
56 prefix = "#{url}/#{path}".gsub('\\', '/')
69 logger.debug "PREFIX: #{prefix}"
57 logger.debug "PREFIX: #{prefix}"
70 re = %r{^V\s+#{Regexp.escape(prefix)}(\/?)([^\/]+)(\/?)\s+(\S+)$}
58 re = %r{^V\s+#{Regexp.escape(prefix)}(\/?)([^\/]+)(\/?)\s+(\S+)$}
71 io.each_line do |line|
59 io.each_line do |line|
72 next unless line =~ re
60 next unless line =~ re
73 entries << Entry.new({:name => $2.strip,
61 entries << Entry.new({:name => $2.strip,
74 :path => ((path.empty? ? "" : "#{path}/") + $2.strip),
62 :path => ((path.empty? ? "" : "#{path}/") + $2.strip),
75 :kind => ($3.blank? ? 'file' : 'dir'),
63 :kind => ($3.blank? ? 'file' : 'dir'),
76 :size => nil,
64 :size => nil,
77 :lastrev => Revision.new(:revision => $4.strip)
65 :lastrev => Revision.new(:revision => $4.strip)
78 })
66 })
79 end
67 end
80 end
68 end
81 return nil if $? && $?.exitstatus != 0
69 return nil if $? && $?.exitstatus != 0
82 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
70 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
83 entries.sort_by_name
71 entries.sort_by_name
84 end
72 end
85
73
86 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
74 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
87 path ||= ''
75 path ||= ''
88 identifier_from = 'last:1' unless identifier_from and identifier_from.to_i > 0
76 identifier_from = 'last:1' unless identifier_from and identifier_from.to_i > 0
89 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
77 identifier_to = 1 unless identifier_to and identifier_to.to_i > 0
90 revisions = Revisions.new
78 revisions = Revisions.new
91 cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to.to_i}..#{identifier_from} #{target(path)}"
79 cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to.to_i}..#{identifier_from} #{target(path)}"
92 shellout(cmd) do |io|
80 shellout(cmd) do |io|
93 revision = nil
81 revision = nil
94 parsing = nil
82 parsing = nil
95 io.each_line do |line|
83 io.each_line do |line|
96 if line =~ /^----/
84 if line =~ /^----/
97 revisions << revision if revision
85 revisions << revision if revision
98 revision = Revision.new(:paths => [], :message => '')
86 revision = Revision.new(:paths => [], :message => '')
99 parsing = nil
87 parsing = nil
100 else
88 else
101 next unless revision
89 next unless revision
102
90
103 if line =~ /^revno: (\d+)$/
91 if line =~ /^revno: (\d+)$/
104 revision.identifier = $1.to_i
92 revision.identifier = $1.to_i
105 elsif line =~ /^committer: (.+)$/
93 elsif line =~ /^committer: (.+)$/
106 revision.author = $1.strip
94 revision.author = $1.strip
107 elsif line =~ /^revision-id:(.+)$/
95 elsif line =~ /^revision-id:(.+)$/
108 revision.scmid = $1.strip
96 revision.scmid = $1.strip
109 elsif line =~ /^timestamp: (.+)$/
97 elsif line =~ /^timestamp: (.+)$/
110 revision.time = Time.parse($1).localtime
98 revision.time = Time.parse($1).localtime
111 elsif line =~ /^ -----/
99 elsif line =~ /^ -----/
112 # partial revisions
100 # partial revisions
113 parsing = nil unless parsing == 'message'
101 parsing = nil unless parsing == 'message'
114 elsif line =~ /^(message|added|modified|removed|renamed):/
102 elsif line =~ /^(message|added|modified|removed|renamed):/
115 parsing = $1
103 parsing = $1
116 elsif line =~ /^ (.*)$/
104 elsif line =~ /^ (.*)$/
117 if parsing == 'message'
105 if parsing == 'message'
118 revision.message << "#{$1}\n"
106 revision.message << "#{$1}\n"
119 else
107 else
120 if $1 =~ /^(.*)\s+(\S+)$/
108 if $1 =~ /^(.*)\s+(\S+)$/
121 path = $1.strip
109 path = $1.strip
122 revid = $2
110 revid = $2
123 case parsing
111 case parsing
124 when 'added'
112 when 'added'
125 revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid}
113 revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid}
126 when 'modified'
114 when 'modified'
127 revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid}
115 revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid}
128 when 'removed'
116 when 'removed'
129 revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid}
117 revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid}
130 when 'renamed'
118 when 'renamed'
131 new_path = path.split('=>').last
119 new_path = path.split('=>').last
132 revision.paths << {:action => 'M', :path => "/#{new_path.strip}", :revision => revid} if new_path
120 revision.paths << {:action => 'M', :path => "/#{new_path.strip}", :revision => revid} if new_path
133 end
121 end
134 end
122 end
135 end
123 end
136 else
124 else
137 parsing = nil
125 parsing = nil
138 end
126 end
139 end
127 end
140 end
128 end
141 revisions << revision if revision
129 revisions << revision if revision
142 end
130 end
143 return nil if $? && $?.exitstatus != 0
131 return nil if $? && $?.exitstatus != 0
144 revisions
132 revisions
145 end
133 end
146
134
147 def diff(path, identifier_from, identifier_to=nil, type="inline")
135 def diff(path, identifier_from, identifier_to=nil, type="inline")
148 path ||= ''
136 path ||= ''
149 if identifier_to
137 if identifier_to
150 identifier_to = identifier_to.to_i
138 identifier_to = identifier_to.to_i
151 else
139 else
152 identifier_to = identifier_from.to_i - 1
140 identifier_to = identifier_from.to_i - 1
153 end
141 end
154 cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
142 cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
155 diff = []
143 diff = []
156 shellout(cmd) do |io|
144 shellout(cmd) do |io|
157 io.each_line do |line|
145 io.each_line do |line|
158 diff << line
146 diff << line
159 end
147 end
160 end
148 end
161 #return nil if $? && $?.exitstatus != 0
149 #return nil if $? && $?.exitstatus != 0
162 DiffTableList.new diff, type
150 DiffTableList.new diff, type
163 end
151 end
164
152
165 def cat(path, identifier=nil)
153 def cat(path, identifier=nil)
166 cmd = "#{BZR_BIN} cat"
154 cmd = "#{BZR_BIN} cat"
167 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
155 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
168 cmd << " #{target(path)}"
156 cmd << " #{target(path)}"
169 cat = nil
157 cat = nil
170 shellout(cmd) do |io|
158 shellout(cmd) do |io|
171 io.binmode
159 io.binmode
172 cat = io.read
160 cat = io.read
173 end
161 end
174 return nil if $? && $?.exitstatus != 0
162 return nil if $? && $?.exitstatus != 0
175 cat
163 cat
176 end
164 end
177
165
178 def annotate(path, identifier=nil)
166 def annotate(path, identifier=nil)
179 cmd = "#{BZR_BIN} annotate --all"
167 cmd = "#{BZR_BIN} annotate --all"
180 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
168 cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
181 cmd << " #{target(path)}"
169 cmd << " #{target(path)}"
182 blame = Annotate.new
170 blame = Annotate.new
183 shellout(cmd) do |io|
171 shellout(cmd) do |io|
184 author = nil
172 author = nil
185 identifier = nil
173 identifier = nil
186 io.each_line do |line|
174 io.each_line do |line|
187 next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$}
175 next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$}
188 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
176 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
189 end
177 end
190 end
178 end
191 return nil if $? && $?.exitstatus != 0
179 return nil if $? && $?.exitstatus != 0
192 blame
180 blame
193 end
181 end
194 end
182 end
195 end
183 end
196 end
184 end
197 end
185 end
@@ -1,363 +1,354
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 = "cvs"
26 CVS_BIN = "cvs"
27
27
28 # Guidelines for the input:
28 # Guidelines for the input:
29 # url -> the project-path, relative to the cvsroot (eg. module name)
29 # url -> the project-path, relative to the cvsroot (eg. module name)
30 # root_url -> the good old, sometimes damned, CVSROOT
30 # root_url -> the good old, sometimes damned, CVSROOT
31 # login -> unnecessary
31 # login -> unnecessary
32 # password -> unnecessary too
32 # password -> unnecessary too
33 def initialize(url, root_url=nil, login=nil, password=nil)
33 def initialize(url, root_url=nil, login=nil, password=nil)
34 @url = url
34 @url = url
35 @login = login if login && !login.empty?
35 @login = login if login && !login.empty?
36 @password = (password || "") if @login
36 @password = (password || "") if @login
37 #TODO: better Exception here (IllegalArgumentException)
37 #TODO: better Exception here (IllegalArgumentException)
38 raise CommandFailed if root_url.blank?
38 raise CommandFailed if root_url.blank?
39 @root_url = root_url
39 @root_url = root_url
40 end
40 end
41
41
42 def root_url
42 def root_url
43 @root_url
43 @root_url
44 end
44 end
45
45
46 def url
46 def url
47 @url
47 @url
48 end
48 end
49
49
50 def info
50 def info
51 logger.debug "<cvs> info"
51 logger.debug "<cvs> info"
52 Info.new({:root_url => @root_url, :lastrev => nil})
52 Info.new({:root_url => @root_url, :lastrev => nil})
53 end
53 end
54
54
55 def get_previous_revision(revision)
55 def get_previous_revision(revision)
56 CvsRevisionHelper.new(revision).prevRev
56 CvsRevisionHelper.new(revision).prevRev
57 end
57 end
58
59 # Returns the entry identified by path and revision identifier
60 # or nil if entry doesn't exist in the repository
61 # this method returns all revisions from one single SCM-Entry
62 def entry(path=nil, identifier="HEAD")
63 e = entries(path, identifier)
64 logger.debug("<cvs-result> #{e.first.inspect}") if e
65 e ? e.first : nil
66 end
67
58
68 # Returns an Entries collection
59 # Returns an Entries collection
69 # or nil if the given path doesn't exist in the repository
60 # or nil if the given path doesn't exist in the repository
70 # this method is used by the repository-browser (aka LIST)
61 # this method is used by the repository-browser (aka LIST)
71 def entries(path=nil, identifier=nil)
62 def entries(path=nil, identifier=nil)
72 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
63 logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
73 path_with_project="#{url}#{with_leading_slash(path)}"
64 path_with_project="#{url}#{with_leading_slash(path)}"
74 entries = Entries.new
65 entries = Entries.new
75 cmd = "#{CVS_BIN} -d #{root_url} rls -ed"
66 cmd = "#{CVS_BIN} -d #{root_url} rls -ed"
76 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
67 cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
77 cmd << " #{path_with_project}"
68 cmd << " #{path_with_project}"
78 shellout(cmd) do |io|
69 shellout(cmd) do |io|
79 io.each_line(){|line|
70 io.each_line(){|line|
80 fields=line.chop.split('/',-1)
71 fields=line.chop.split('/',-1)
81 logger.debug(">>InspectLine #{fields.inspect}")
72 logger.debug(">>InspectLine #{fields.inspect}")
82
73
83 if fields[0]!="D"
74 if fields[0]!="D"
84 entries << Entry.new({:name => fields[-5],
75 entries << Entry.new({:name => fields[-5],
85 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
76 #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
86 :path => "#{path}/#{fields[-5]}",
77 :path => "#{path}/#{fields[-5]}",
87 :kind => 'file',
78 :kind => 'file',
88 :size => nil,
79 :size => nil,
89 :lastrev => Revision.new({
80 :lastrev => Revision.new({
90 :revision => fields[-4],
81 :revision => fields[-4],
91 :name => fields[-4],
82 :name => fields[-4],
92 :time => Time.parse(fields[-3]),
83 :time => Time.parse(fields[-3]),
93 :author => ''
84 :author => ''
94 })
85 })
95 })
86 })
96 else
87 else
97 entries << Entry.new({:name => fields[1],
88 entries << Entry.new({:name => fields[1],
98 :path => "#{path}/#{fields[1]}",
89 :path => "#{path}/#{fields[1]}",
99 :kind => 'dir',
90 :kind => 'dir',
100 :size => nil,
91 :size => nil,
101 :lastrev => nil
92 :lastrev => nil
102 })
93 })
103 end
94 end
104 }
95 }
105 end
96 end
106 return nil if $? && $?.exitstatus != 0
97 return nil if $? && $?.exitstatus != 0
107 entries.sort_by_name
98 entries.sort_by_name
108 end
99 end
109
100
110 STARTLOG="----------------------------"
101 STARTLOG="----------------------------"
111 ENDLOG ="============================================================================="
102 ENDLOG ="============================================================================="
112
103
113 # Returns all revisions found between identifier_from and identifier_to
104 # Returns all revisions found between identifier_from and identifier_to
114 # in the repository. both identifier have to be dates or nil.
105 # in the repository. both identifier have to be dates or nil.
115 # these method returns nothing but yield every result in block
106 # these method returns nothing but yield every result in block
116 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
107 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
117 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
108 logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
118
109
119 path_with_project="#{url}#{with_leading_slash(path)}"
110 path_with_project="#{url}#{with_leading_slash(path)}"
120 cmd = "#{CVS_BIN} -d #{root_url} rlog"
111 cmd = "#{CVS_BIN} -d #{root_url} rlog"
121 cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from
112 cmd << " -d\">#{time_to_cvstime(identifier_from)}\"" if identifier_from
122 cmd << " #{path_with_project}"
113 cmd << " #{path_with_project}"
123 shellout(cmd) do |io|
114 shellout(cmd) do |io|
124 state="entry_start"
115 state="entry_start"
125
116
126 commit_log=String.new
117 commit_log=String.new
127 revision=nil
118 revision=nil
128 date=nil
119 date=nil
129 author=nil
120 author=nil
130 entry_path=nil
121 entry_path=nil
131 entry_name=nil
122 entry_name=nil
132 file_state=nil
123 file_state=nil
133 branch_map=nil
124 branch_map=nil
134
125
135 io.each_line() do |line|
126 io.each_line() do |line|
136
127
137 if state!="revision" && /^#{ENDLOG}/ =~ line
128 if state!="revision" && /^#{ENDLOG}/ =~ line
138 commit_log=String.new
129 commit_log=String.new
139 revision=nil
130 revision=nil
140 state="entry_start"
131 state="entry_start"
141 end
132 end
142
133
143 if state=="entry_start"
134 if state=="entry_start"
144 branch_map=Hash.new
135 branch_map=Hash.new
145 # gsub(/^:.*@[^:]+:\d*/, '') is here to remove :pserver:anonymous@foo.bar: string if present in the url
136 # gsub(/^:.*@[^:]+:\d*/, '') is here to remove :pserver:anonymous@foo.bar: string if present in the url
146 if /^RCS file: #{Regexp.escape(root_url.gsub(/^:.*@[^:]+:\d*/, ''))}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
137 if /^RCS file: #{Regexp.escape(root_url.gsub(/^:.*@[^:]+:\d*/, ''))}\/#{Regexp.escape(path_with_project)}(.+),v$/ =~ line
147 entry_path = normalize_cvs_path($1)
138 entry_path = normalize_cvs_path($1)
148 entry_name = normalize_path(File.basename($1))
139 entry_name = normalize_path(File.basename($1))
149 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
140 logger.debug("Path #{entry_path} <=> Name #{entry_name}")
150 elsif /^head: (.+)$/ =~ line
141 elsif /^head: (.+)$/ =~ line
151 entry_headRev = $1 #unless entry.nil?
142 entry_headRev = $1 #unless entry.nil?
152 elsif /^symbolic names:/ =~ line
143 elsif /^symbolic names:/ =~ line
153 state="symbolic" #unless entry.nil?
144 state="symbolic" #unless entry.nil?
154 elsif /^#{STARTLOG}/ =~ line
145 elsif /^#{STARTLOG}/ =~ line
155 commit_log=String.new
146 commit_log=String.new
156 state="revision"
147 state="revision"
157 end
148 end
158 next
149 next
159 elsif state=="symbolic"
150 elsif state=="symbolic"
160 if /^(.*):\s(.*)/ =~ (line.strip)
151 if /^(.*):\s(.*)/ =~ (line.strip)
161 branch_map[$1]=$2
152 branch_map[$1]=$2
162 else
153 else
163 state="tags"
154 state="tags"
164 next
155 next
165 end
156 end
166 elsif state=="tags"
157 elsif state=="tags"
167 if /^#{STARTLOG}/ =~ line
158 if /^#{STARTLOG}/ =~ line
168 commit_log = ""
159 commit_log = ""
169 state="revision"
160 state="revision"
170 elsif /^#{ENDLOG}/ =~ line
161 elsif /^#{ENDLOG}/ =~ line
171 state="head"
162 state="head"
172 end
163 end
173 next
164 next
174 elsif state=="revision"
165 elsif state=="revision"
175 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
166 if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
176 if revision
167 if revision
177
168
178 revHelper=CvsRevisionHelper.new(revision)
169 revHelper=CvsRevisionHelper.new(revision)
179 revBranch="HEAD"
170 revBranch="HEAD"
180
171
181 branch_map.each() do |branch_name,branch_point|
172 branch_map.each() do |branch_name,branch_point|
182 if revHelper.is_in_branch_with_symbol(branch_point)
173 if revHelper.is_in_branch_with_symbol(branch_point)
183 revBranch=branch_name
174 revBranch=branch_name
184 end
175 end
185 end
176 end
186
177
187 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
178 logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
188
179
189 yield Revision.new({
180 yield Revision.new({
190 :time => date,
181 :time => date,
191 :author => author,
182 :author => author,
192 :message=>commit_log.chomp,
183 :message=>commit_log.chomp,
193 :paths => [{
184 :paths => [{
194 :revision => revision,
185 :revision => revision,
195 :branch=> revBranch,
186 :branch=> revBranch,
196 :path=>entry_path,
187 :path=>entry_path,
197 :name=>entry_name,
188 :name=>entry_name,
198 :kind=>'file',
189 :kind=>'file',
199 :action=>file_state
190 :action=>file_state
200 }]
191 }]
201 })
192 })
202 end
193 end
203
194
204 commit_log=String.new
195 commit_log=String.new
205 revision=nil
196 revision=nil
206
197
207 if /^#{ENDLOG}/ =~ line
198 if /^#{ENDLOG}/ =~ line
208 state="entry_start"
199 state="entry_start"
209 end
200 end
210 next
201 next
211 end
202 end
212
203
213 if /^branches: (.+)$/ =~ line
204 if /^branches: (.+)$/ =~ line
214 #TODO: version.branch = $1
205 #TODO: version.branch = $1
215 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
206 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
216 revision = $1
207 revision = $1
217 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
208 elsif /^date:\s+(\d+.\d+.\d+\s+\d+:\d+:\d+)/ =~ line
218 date = Time.parse($1)
209 date = Time.parse($1)
219 author = /author: ([^;]+)/.match(line)[1]
210 author = /author: ([^;]+)/.match(line)[1]
220 file_state = /state: ([^;]+)/.match(line)[1]
211 file_state = /state: ([^;]+)/.match(line)[1]
221 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
212 #TODO: linechanges only available in CVS.... maybe a feature our SVN implementation. i'm sure, they are
222 # useful for stats or something else
213 # useful for stats or something else
223 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
214 # linechanges =/lines: \+(\d+) -(\d+)/.match(line)
224 # unless linechanges.nil?
215 # unless linechanges.nil?
225 # version.line_plus = linechanges[1]
216 # version.line_plus = linechanges[1]
226 # version.line_minus = linechanges[2]
217 # version.line_minus = linechanges[2]
227 # else
218 # else
228 # version.line_plus = 0
219 # version.line_plus = 0
229 # version.line_minus = 0
220 # version.line_minus = 0
230 # end
221 # end
231 else
222 else
232 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
223 commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
233 end
224 end
234 end
225 end
235 end
226 end
236 end
227 end
237 end
228 end
238
229
239 def diff(path, identifier_from, identifier_to=nil, type="inline")
230 def diff(path, identifier_from, identifier_to=nil, type="inline")
240 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
231 logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
241 path_with_project="#{url}#{with_leading_slash(path)}"
232 path_with_project="#{url}#{with_leading_slash(path)}"
242 cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{path_with_project}"
233 cmd = "#{CVS_BIN} -d #{root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{path_with_project}"
243 diff = []
234 diff = []
244 shellout(cmd) do |io|
235 shellout(cmd) do |io|
245 io.each_line do |line|
236 io.each_line do |line|
246 diff << line
237 diff << line
247 end
238 end
248 end
239 end
249 return nil if $? && $?.exitstatus != 0
240 return nil if $? && $?.exitstatus != 0
250 DiffTableList.new diff, type
241 DiffTableList.new diff, type
251 end
242 end
252
243
253 def cat(path, identifier=nil)
244 def cat(path, identifier=nil)
254 identifier = (identifier) ? identifier : "HEAD"
245 identifier = (identifier) ? identifier : "HEAD"
255 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
246 logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
256 path_with_project="#{url}#{with_leading_slash(path)}"
247 path_with_project="#{url}#{with_leading_slash(path)}"
257 cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{path_with_project}"
248 cmd = "#{CVS_BIN} -d #{root_url} co -r#{identifier} -p #{path_with_project}"
258 cat = nil
249 cat = nil
259 shellout(cmd) do |io|
250 shellout(cmd) do |io|
260 cat = io.read
251 cat = io.read
261 end
252 end
262 return nil if $? && $?.exitstatus != 0
253 return nil if $? && $?.exitstatus != 0
263 cat
254 cat
264 end
255 end
265
256
266 def annotate(path, identifier=nil)
257 def annotate(path, identifier=nil)
267 identifier = (identifier) ? identifier : "HEAD"
258 identifier = (identifier) ? identifier : "HEAD"
268 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
259 logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
269 path_with_project="#{url}#{with_leading_slash(path)}"
260 path_with_project="#{url}#{with_leading_slash(path)}"
270 cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{path_with_project}"
261 cmd = "#{CVS_BIN} -d #{root_url} rannotate -r#{identifier} #{path_with_project}"
271 blame = Annotate.new
262 blame = Annotate.new
272 shellout(cmd) do |io|
263 shellout(cmd) do |io|
273 io.each_line do |line|
264 io.each_line do |line|
274 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
265 next unless line =~ %r{^([\d\.]+)\s+\(([^\)]+)\s+[^\)]+\):\s(.*)$}
275 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
266 blame.add_line($3.rstrip, Revision.new(:revision => $1, :author => $2.strip))
276 end
267 end
277 end
268 end
278 return nil if $? && $?.exitstatus != 0
269 return nil if $? && $?.exitstatus != 0
279 blame
270 blame
280 end
271 end
281
272
282 private
273 private
283
274
284 # convert a date/time into the CVS-format
275 # convert a date/time into the CVS-format
285 def time_to_cvstime(time)
276 def time_to_cvstime(time)
286 return nil if time.nil?
277 return nil if time.nil?
287 unless time.kind_of? Time
278 unless time.kind_of? Time
288 time = Time.parse(time)
279 time = Time.parse(time)
289 end
280 end
290 return time.strftime("%Y-%m-%d %H:%M:%S")
281 return time.strftime("%Y-%m-%d %H:%M:%S")
291 end
282 end
292
283
293 def normalize_cvs_path(path)
284 def normalize_cvs_path(path)
294 normalize_path(path.gsub(/Attic\//,''))
285 normalize_path(path.gsub(/Attic\//,''))
295 end
286 end
296
287
297 def normalize_path(path)
288 def normalize_path(path)
298 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
289 path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1')
299 end
290 end
300 end
291 end
301
292
302 class CvsRevisionHelper
293 class CvsRevisionHelper
303 attr_accessor :complete_rev, :revision, :base, :branchid
294 attr_accessor :complete_rev, :revision, :base, :branchid
304
295
305 def initialize(complete_rev)
296 def initialize(complete_rev)
306 @complete_rev = complete_rev
297 @complete_rev = complete_rev
307 parseRevision()
298 parseRevision()
308 end
299 end
309
300
310 def branchPoint
301 def branchPoint
311 return @base
302 return @base
312 end
303 end
313
304
314 def branchVersion
305 def branchVersion
315 if isBranchRevision
306 if isBranchRevision
316 return @base+"."+@branchid
307 return @base+"."+@branchid
317 end
308 end
318 return @base
309 return @base
319 end
310 end
320
311
321 def isBranchRevision
312 def isBranchRevision
322 !@branchid.nil?
313 !@branchid.nil?
323 end
314 end
324
315
325 def prevRev
316 def prevRev
326 unless @revision==0
317 unless @revision==0
327 return buildRevision(@revision-1)
318 return buildRevision(@revision-1)
328 end
319 end
329 return buildRevision(@revision)
320 return buildRevision(@revision)
330 end
321 end
331
322
332 def is_in_branch_with_symbol(branch_symbol)
323 def is_in_branch_with_symbol(branch_symbol)
333 bpieces=branch_symbol.split(".")
324 bpieces=branch_symbol.split(".")
334 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
325 branch_start="#{bpieces[0..-3].join(".")}.#{bpieces[-1]}"
335 return (branchVersion==branch_start)
326 return (branchVersion==branch_start)
336 end
327 end
337
328
338 private
329 private
339 def buildRevision(rev)
330 def buildRevision(rev)
340 if rev== 0
331 if rev== 0
341 @base
332 @base
342 elsif @branchid.nil?
333 elsif @branchid.nil?
343 @base+"."+rev.to_s
334 @base+"."+rev.to_s
344 else
335 else
345 @base+"."+@branchid+"."+rev.to_s
336 @base+"."+@branchid+"."+rev.to_s
346 end
337 end
347 end
338 end
348
339
349 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
340 # Interpretiert die cvs revisionsnummern wie z.b. 1.14 oder 1.3.0.15
350 def parseRevision()
341 def parseRevision()
351 pieces=@complete_rev.split(".")
342 pieces=@complete_rev.split(".")
352 @revision=pieces.last.to_i
343 @revision=pieces.last.to_i
353 baseSize=1
344 baseSize=1
354 baseSize+=(pieces.size/2)
345 baseSize+=(pieces.size/2)
355 @base=pieces[0..-baseSize].join(".")
346 @base=pieces[0..-baseSize].join(".")
356 if baseSize > 2
347 if baseSize > 2
357 @branchid=pieces[-2]
348 @branchid=pieces[-2]
358 end
349 end
359 end
350 end
360 end
351 end
361 end
352 end
362 end
353 end
363 end
354 end
@@ -1,163 +1,156
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 require 'rexml/document'
19 require 'rexml/document'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class DarcsAdapter < AbstractAdapter
24 class DarcsAdapter < AbstractAdapter
25 # Darcs executable name
25 # Darcs executable name
26 DARCS_BIN = "darcs"
26 DARCS_BIN = "darcs"
27
27
28 def initialize(url, root_url=nil, login=nil, password=nil)
28 def initialize(url, root_url=nil, login=nil, password=nil)
29 @url = url
29 @url = url
30 @root_url = url
30 @root_url = url
31 end
31 end
32
32
33 def supports_cat?
33 def supports_cat?
34 false
34 false
35 end
35 end
36
36
37 # Get info about the svn repository
37 # Get info about the svn repository
38 def info
38 def info
39 rev = revisions(nil,nil,nil,{:limit => 1})
39 rev = revisions(nil,nil,nil,{:limit => 1})
40 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
40 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
41 end
41 end
42
42
43 # Returns the entry identified by path and revision identifier
44 # or nil if entry doesn't exist in the repository
45 def entry(path=nil, identifier=nil)
46 e = entries(path, identifier)
47 e ? e.first : nil
48 end
49
50 # Returns an Entries collection
43 # Returns an Entries collection
51 # or nil if the given path doesn't exist in the repository
44 # or nil if the given path doesn't exist in the repository
52 def entries(path=nil, identifier=nil)
45 def entries(path=nil, identifier=nil)
53 path_prefix = (path.blank? ? '' : "#{path}/")
46 path_prefix = (path.blank? ? '' : "#{path}/")
54 path = '.' if path.blank?
47 path = '.' if path.blank?
55 entries = Entries.new
48 entries = Entries.new
56 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
49 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
57 cmd << " --match \"hash #{identifier}\"" if identifier
50 cmd << " --match \"hash #{identifier}\"" if identifier
58 cmd << " #{path}"
51 cmd << " #{path}"
59 shellout(cmd) do |io|
52 shellout(cmd) do |io|
60 begin
53 begin
61 doc = REXML::Document.new(io)
54 doc = REXML::Document.new(io)
62 if doc.root.name == 'directory'
55 if doc.root.name == 'directory'
63 doc.elements.each('directory/*') do |element|
56 doc.elements.each('directory/*') do |element|
64 next unless ['file', 'directory'].include? element.name
57 next unless ['file', 'directory'].include? element.name
65 entries << entry_from_xml(element, path_prefix)
58 entries << entry_from_xml(element, path_prefix)
66 end
59 end
67 elsif doc.root.name == 'file'
60 elsif doc.root.name == 'file'
68 entries << entry_from_xml(doc.root, path_prefix)
61 entries << entry_from_xml(doc.root, path_prefix)
69 end
62 end
70 rescue
63 rescue
71 end
64 end
72 end
65 end
73 return nil if $? && $?.exitstatus != 0
66 return nil if $? && $?.exitstatus != 0
74 entries.sort_by_name
67 entries.sort_by_name
75 end
68 end
76
69
77 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
70 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
78 path = '.' if path.blank?
71 path = '.' if path.blank?
79 revisions = Revisions.new
72 revisions = Revisions.new
80 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
73 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
81 cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
74 cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
82 cmd << " --last #{options[:limit].to_i}" if options[:limit]
75 cmd << " --last #{options[:limit].to_i}" if options[:limit]
83 shellout(cmd) do |io|
76 shellout(cmd) do |io|
84 begin
77 begin
85 doc = REXML::Document.new(io)
78 doc = REXML::Document.new(io)
86 doc.elements.each("changelog/patch") do |patch|
79 doc.elements.each("changelog/patch") do |patch|
87 message = patch.elements['name'].text
80 message = patch.elements['name'].text
88 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
81 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
89 revisions << Revision.new({:identifier => nil,
82 revisions << Revision.new({:identifier => nil,
90 :author => patch.attributes['author'],
83 :author => patch.attributes['author'],
91 :scmid => patch.attributes['hash'],
84 :scmid => patch.attributes['hash'],
92 :time => Time.parse(patch.attributes['local_date']),
85 :time => Time.parse(patch.attributes['local_date']),
93 :message => message,
86 :message => message,
94 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
87 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
95 })
88 })
96 end
89 end
97 rescue
90 rescue
98 end
91 end
99 end
92 end
100 return nil if $? && $?.exitstatus != 0
93 return nil if $? && $?.exitstatus != 0
101 revisions
94 revisions
102 end
95 end
103
96
104 def diff(path, identifier_from, identifier_to=nil, type="inline")
97 def diff(path, identifier_from, identifier_to=nil, type="inline")
105 path = '*' if path.blank?
98 path = '*' if path.blank?
106 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
99 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
107 if identifier_to.nil?
100 if identifier_to.nil?
108 cmd << " --match \"hash #{identifier_from}\""
101 cmd << " --match \"hash #{identifier_from}\""
109 else
102 else
110 cmd << " --to-match \"hash #{identifier_from}\""
103 cmd << " --to-match \"hash #{identifier_from}\""
111 cmd << " --from-match \"hash #{identifier_to}\""
104 cmd << " --from-match \"hash #{identifier_to}\""
112 end
105 end
113 cmd << " -u #{path}"
106 cmd << " -u #{path}"
114 diff = []
107 diff = []
115 shellout(cmd) do |io|
108 shellout(cmd) do |io|
116 io.each_line do |line|
109 io.each_line do |line|
117 diff << line
110 diff << line
118 end
111 end
119 end
112 end
120 return nil if $? && $?.exitstatus != 0
113 return nil if $? && $?.exitstatus != 0
121 DiffTableList.new diff, type
114 DiffTableList.new diff, type
122 end
115 end
123
116
124 private
117 private
125
118
126 def entry_from_xml(element, path_prefix)
119 def entry_from_xml(element, path_prefix)
127 Entry.new({:name => element.attributes['name'],
120 Entry.new({:name => element.attributes['name'],
128 :path => path_prefix + element.attributes['name'],
121 :path => path_prefix + element.attributes['name'],
129 :kind => element.name == 'file' ? 'file' : 'dir',
122 :kind => element.name == 'file' ? 'file' : 'dir',
130 :size => nil,
123 :size => nil,
131 :lastrev => Revision.new({
124 :lastrev => Revision.new({
132 :identifier => nil,
125 :identifier => nil,
133 :scmid => element.elements['modified'].elements['patch'].attributes['hash']
126 :scmid => element.elements['modified'].elements['patch'].attributes['hash']
134 })
127 })
135 })
128 })
136 end
129 end
137
130
138 # Retrieve changed paths for a single patch
131 # Retrieve changed paths for a single patch
139 def get_paths_for_patch(hash)
132 def get_paths_for_patch(hash)
140 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
133 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
141 cmd << " --match \"hash #{hash}\" "
134 cmd << " --match \"hash #{hash}\" "
142 paths = []
135 paths = []
143 shellout(cmd) do |io|
136 shellout(cmd) do |io|
144 begin
137 begin
145 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
138 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
146 # A root element is added so that REXML doesn't raise an error
139 # A root element is added so that REXML doesn't raise an error
147 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
140 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
148 doc.elements.each('fake_root/summary/*') do |modif|
141 doc.elements.each('fake_root/summary/*') do |modif|
149 paths << {:action => modif.name[0,1].upcase,
142 paths << {:action => modif.name[0,1].upcase,
150 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
143 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
151 }
144 }
152 end
145 end
153 rescue
146 rescue
154 end
147 end
155 end
148 end
156 paths
149 paths
157 rescue CommandFailed
150 rescue CommandFailed
158 paths
151 paths
159 end
152 end
160 end
153 end
161 end
154 end
162 end
155 end
163 end
156 end
@@ -1,264 +1,256
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 GitAdapter < AbstractAdapter
23 class GitAdapter < AbstractAdapter
24
24
25 # Git executable name
25 # Git executable name
26 GIT_BIN = "git"
26 GIT_BIN = "git"
27
27
28 # Get the revision of a particuliar file
28 # Get the revision of a particuliar file
29 def get_rev (rev,path)
29 def get_rev (rev,path)
30 cmd="git --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}" if rev!='latest' and (! rev.nil?)
30 cmd="git --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}" if rev!='latest' and (! rev.nil?)
31 cmd="git --git-dir #{target('')} log -1 master -- #{shell_quote path}" if
31 cmd="git --git-dir #{target('')} log -1 master -- #{shell_quote path}" if
32 rev=='latest' or rev.nil?
32 rev=='latest' or rev.nil?
33 rev=[]
33 rev=[]
34 i=0
34 i=0
35 shellout(cmd) do |io|
35 shellout(cmd) do |io|
36 files=[]
36 files=[]
37 changeset = {}
37 changeset = {}
38 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
38 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
39
39
40 io.each_line do |line|
40 io.each_line do |line|
41 if line =~ /^commit ([0-9a-f]{40})$/
41 if line =~ /^commit ([0-9a-f]{40})$/
42 key = "commit"
42 key = "commit"
43 value = $1
43 value = $1
44 if (parsing_descr == 1 || parsing_descr == 2)
44 if (parsing_descr == 1 || parsing_descr == 2)
45 parsing_descr = 0
45 parsing_descr = 0
46 rev = Revision.new({:identifier => changeset[:commit],
46 rev = Revision.new({:identifier => changeset[:commit],
47 :scmid => changeset[:commit],
47 :scmid => changeset[:commit],
48 :author => changeset[:author],
48 :author => changeset[:author],
49 :time => Time.parse(changeset[:date]),
49 :time => Time.parse(changeset[:date]),
50 :message => changeset[:description],
50 :message => changeset[:description],
51 :paths => files
51 :paths => files
52 })
52 })
53 changeset = {}
53 changeset = {}
54 files = []
54 files = []
55 end
55 end
56 changeset[:commit] = $1
56 changeset[:commit] = $1
57 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
57 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
58 key = $1
58 key = $1
59 value = $2
59 value = $2
60 if key == "Author"
60 if key == "Author"
61 changeset[:author] = value
61 changeset[:author] = value
62 elsif key == "Date"
62 elsif key == "Date"
63 changeset[:date] = value
63 changeset[:date] = value
64 end
64 end
65 elsif (parsing_descr == 0) && line.chomp.to_s == ""
65 elsif (parsing_descr == 0) && line.chomp.to_s == ""
66 parsing_descr = 1
66 parsing_descr = 1
67 changeset[:description] = ""
67 changeset[:description] = ""
68 elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
68 elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
69 parsing_descr = 2
69 parsing_descr = 2
70 fileaction = $1
70 fileaction = $1
71 filepath = $2
71 filepath = $2
72 files << {:action => fileaction, :path => filepath}
72 files << {:action => fileaction, :path => filepath}
73 elsif (parsing_descr == 1) && line.chomp.to_s == ""
73 elsif (parsing_descr == 1) && line.chomp.to_s == ""
74 parsing_descr = 2
74 parsing_descr = 2
75 elsif (parsing_descr == 1)
75 elsif (parsing_descr == 1)
76 changeset[:description] << line
76 changeset[:description] << line
77 end
77 end
78 end
78 end
79 rev = Revision.new({:identifier => changeset[:commit],
79 rev = Revision.new({:identifier => changeset[:commit],
80 :scmid => changeset[:commit],
80 :scmid => changeset[:commit],
81 :author => changeset[:author],
81 :author => changeset[:author],
82 :time => (changeset[:date] ? Time.parse(changeset[:date]) : nil),
82 :time => (changeset[:date] ? Time.parse(changeset[:date]) : nil),
83 :message => changeset[:description],
83 :message => changeset[:description],
84 :paths => files
84 :paths => files
85 })
85 })
86
86
87 end
87 end
88
88
89 get_rev('latest',path) if rev == []
89 get_rev('latest',path) if rev == []
90
90
91 return nil if $? && $?.exitstatus != 0
91 return nil if $? && $?.exitstatus != 0
92 return rev
92 return rev
93 end
93 end
94
94
95
95
96 def info
96 def info
97 revs = revisions(url,nil,nil,{:limit => 1})
97 revs = revisions(url,nil,nil,{:limit => 1})
98 if revs && revs.any?
98 if revs && revs.any?
99 Info.new(:root_url => url, :lastrev => revs.first)
99 Info.new(:root_url => url, :lastrev => revs.first)
100 else
100 else
101 nil
101 nil
102 end
102 end
103 rescue Errno::ENOENT => e
103 rescue Errno::ENOENT => e
104 return nil
104 return nil
105 end
105 end
106
106
107 def entries(path=nil, identifier=nil)
107 def entries(path=nil, identifier=nil)
108 path ||= ''
108 path ||= ''
109 entries = Entries.new
109 entries = Entries.new
110 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
110 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
111 cmd << shell_quote("HEAD:" + path) if identifier.nil?
111 cmd << shell_quote("HEAD:" + path) if identifier.nil?
112 cmd << shell_quote(identifier + ":" + path) if identifier
112 cmd << shell_quote(identifier + ":" + path) if identifier
113 shellout(cmd) do |io|
113 shellout(cmd) do |io|
114 io.each_line do |line|
114 io.each_line do |line|
115 e = line.chomp.to_s
115 e = line.chomp.to_s
116 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
116 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
117 type = $1
117 type = $1
118 sha = $2
118 sha = $2
119 size = $3
119 size = $3
120 name = $4
120 name = $4
121 entries << Entry.new({:name => name,
121 entries << Entry.new({:name => name,
122 :path => (path.empty? ? name : "#{path}/#{name}"),
122 :path => (path.empty? ? name : "#{path}/#{name}"),
123 :kind => ((type == "tree") ? 'dir' : 'file'),
123 :kind => ((type == "tree") ? 'dir' : 'file'),
124 :size => ((type == "tree") ? nil : size),
124 :size => ((type == "tree") ? nil : size),
125 :lastrev => get_rev(identifier,(path.empty? ? name : "#{path}/#{name}"))
125 :lastrev => get_rev(identifier,(path.empty? ? name : "#{path}/#{name}"))
126
126
127 }) unless entries.detect{|entry| entry.name == name}
127 }) unless entries.detect{|entry| entry.name == name}
128 end
128 end
129 end
129 end
130 end
130 end
131 return nil if $? && $?.exitstatus != 0
131 return nil if $? && $?.exitstatus != 0
132 entries.sort_by_name
132 entries.sort_by_name
133 end
133 end
134
134
135 def entry(path=nil, identifier=nil)
136 path ||= ''
137 search_path = path.split('/')[0..-2].join('/')
138 entry_name = path.split('/').last
139 e = entries(search_path, identifier)
140 e ? e.detect{|entry| entry.name == entry_name} : nil
141 end
142
143 def revisions(path, identifier_from, identifier_to, options={})
135 def revisions(path, identifier_from, identifier_to, options={})
144 revisions = Revisions.new
136 revisions = Revisions.new
145 cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw "
137 cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw "
146 cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit]
138 cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit]
147 cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
139 cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
148 cmd << " #{shell_quote identifier_to} " if identifier_to
140 cmd << " #{shell_quote identifier_to} " if identifier_to
149 #cmd << " HEAD " if !identifier_to
141 #cmd << " HEAD " if !identifier_to
150 shellout(cmd) do |io|
142 shellout(cmd) do |io|
151 files=[]
143 files=[]
152 changeset = {}
144 changeset = {}
153 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
145 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
154 revno = 1
146 revno = 1
155
147
156 io.each_line do |line|
148 io.each_line do |line|
157 if line =~ /^commit ([0-9a-f]{40})$/
149 if line =~ /^commit ([0-9a-f]{40})$/
158 key = "commit"
150 key = "commit"
159 value = $1
151 value = $1
160 if (parsing_descr == 1 || parsing_descr == 2)
152 if (parsing_descr == 1 || parsing_descr == 2)
161 parsing_descr = 0
153 parsing_descr = 0
162 revisions << Revision.new({:identifier => changeset[:commit],
154 revisions << Revision.new({:identifier => changeset[:commit],
163 :scmid => changeset[:commit],
155 :scmid => changeset[:commit],
164 :author => changeset[:author],
156 :author => changeset[:author],
165 :time => Time.parse(changeset[:date]),
157 :time => Time.parse(changeset[:date]),
166 :message => changeset[:description],
158 :message => changeset[:description],
167 :paths => files
159 :paths => files
168 })
160 })
169 changeset = {}
161 changeset = {}
170 files = []
162 files = []
171 revno = revno + 1
163 revno = revno + 1
172 end
164 end
173 changeset[:commit] = $1
165 changeset[:commit] = $1
174 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
166 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
175 key = $1
167 key = $1
176 value = $2
168 value = $2
177 if key == "Author"
169 if key == "Author"
178 changeset[:author] = value
170 changeset[:author] = value
179 elsif key == "Date"
171 elsif key == "Date"
180 changeset[:date] = value
172 changeset[:date] = value
181 end
173 end
182 elsif (parsing_descr == 0) && line.chomp.to_s == ""
174 elsif (parsing_descr == 0) && line.chomp.to_s == ""
183 parsing_descr = 1
175 parsing_descr = 1
184 changeset[:description] = ""
176 changeset[:description] = ""
185 elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
177 elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
186 parsing_descr = 2
178 parsing_descr = 2
187 fileaction = $1
179 fileaction = $1
188 filepath = $2
180 filepath = $2
189 files << {:action => fileaction, :path => filepath}
181 files << {:action => fileaction, :path => filepath}
190 elsif (parsing_descr == 1) && line.chomp.to_s == ""
182 elsif (parsing_descr == 1) && line.chomp.to_s == ""
191 parsing_descr = 2
183 parsing_descr = 2
192 elsif (parsing_descr == 1)
184 elsif (parsing_descr == 1)
193 changeset[:description] << line[4..-1]
185 changeset[:description] << line[4..-1]
194 end
186 end
195 end
187 end
196
188
197 revisions << Revision.new({:identifier => changeset[:commit],
189 revisions << Revision.new({:identifier => changeset[:commit],
198 :scmid => changeset[:commit],
190 :scmid => changeset[:commit],
199 :author => changeset[:author],
191 :author => changeset[:author],
200 :time => Time.parse(changeset[:date]),
192 :time => Time.parse(changeset[:date]),
201 :message => changeset[:description],
193 :message => changeset[:description],
202 :paths => files
194 :paths => files
203 }) if changeset[:commit]
195 }) if changeset[:commit]
204
196
205 end
197 end
206
198
207 return nil if $? && $?.exitstatus != 0
199 return nil if $? && $?.exitstatus != 0
208 revisions
200 revisions
209 end
201 end
210
202
211 def diff(path, identifier_from, identifier_to=nil, type="inline")
203 def diff(path, identifier_from, identifier_to=nil, type="inline")
212 path ||= ''
204 path ||= ''
213 if !identifier_to
205 if !identifier_to
214 identifier_to = nil
206 identifier_to = nil
215 end
207 end
216
208
217 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}" if identifier_to.nil?
209 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}" if identifier_to.nil?
218 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}" if !identifier_to.nil?
210 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}" if !identifier_to.nil?
219 cmd << " -- #{shell_quote path}" unless path.empty?
211 cmd << " -- #{shell_quote path}" unless path.empty?
220 diff = []
212 diff = []
221 shellout(cmd) do |io|
213 shellout(cmd) do |io|
222 io.each_line do |line|
214 io.each_line do |line|
223 diff << line
215 diff << line
224 end
216 end
225 end
217 end
226 return nil if $? && $?.exitstatus != 0
218 return nil if $? && $?.exitstatus != 0
227 DiffTableList.new diff, type
219 DiffTableList.new diff, type
228 end
220 end
229
221
230 def annotate(path, identifier=nil)
222 def annotate(path, identifier=nil)
231 identifier = 'HEAD' if identifier.blank?
223 identifier = 'HEAD' if identifier.blank?
232 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -l #{shell_quote identifier} -- #{shell_quote path}"
224 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -l #{shell_quote identifier} -- #{shell_quote path}"
233 blame = Annotate.new
225 blame = Annotate.new
234 content = nil
226 content = nil
235 shellout(cmd) { |io| io.binmode; content = io.read }
227 shellout(cmd) { |io| io.binmode; content = io.read }
236 return nil if $? && $?.exitstatus != 0
228 return nil if $? && $?.exitstatus != 0
237 # git annotates binary files
229 # git annotates binary files
238 return nil if content.is_binary_data?
230 return nil if content.is_binary_data?
239 content.split("\n").each do |line|
231 content.split("\n").each do |line|
240 next unless line =~ /([0-9a-f]{39,40})\s\((\w*)[^\)]*\)(.*)/
232 next unless line =~ /([0-9a-f]{39,40})\s\((\w*)[^\)]*\)(.*)/
241 blame.add_line($3.rstrip, Revision.new(:identifier => $1, :author => $2.strip))
233 blame.add_line($3.rstrip, Revision.new(:identifier => $1, :author => $2.strip))
242 end
234 end
243 blame
235 blame
244 end
236 end
245
237
246 def cat(path, identifier=nil)
238 def cat(path, identifier=nil)
247 if identifier.nil?
239 if identifier.nil?
248 identifier = 'HEAD'
240 identifier = 'HEAD'
249 end
241 end
250 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
242 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
251 cat = nil
243 cat = nil
252 shellout(cmd) do |io|
244 shellout(cmd) do |io|
253 io.binmode
245 io.binmode
254 cat = io.read
246 cat = io.read
255 end
247 end
256 return nil if $? && $?.exitstatus != 0
248 return nil if $? && $?.exitstatus != 0
257 cat
249 cat
258 end
250 end
259 end
251 end
260 end
252 end
261 end
253 end
262
254
263 end
255 end
264
256
@@ -1,207 +1,199
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 MercurialAdapter < AbstractAdapter
23 class MercurialAdapter < AbstractAdapter
24
24
25 # Mercurial executable name
25 # Mercurial executable name
26 HG_BIN = "hg"
26 HG_BIN = "hg"
27
27
28 def info
28 def info
29 cmd = "#{HG_BIN} -R #{target('')} root"
29 cmd = "#{HG_BIN} -R #{target('')} root"
30 root_url = nil
30 root_url = nil
31 shellout(cmd) do |io|
31 shellout(cmd) do |io|
32 root_url = io.gets
32 root_url = io.gets
33 end
33 end
34 return nil if $? && $?.exitstatus != 0
34 return nil if $? && $?.exitstatus != 0
35 info = Info.new({:root_url => root_url.chomp,
35 info = Info.new({:root_url => root_url.chomp,
36 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
36 :lastrev => revisions(nil,nil,nil,{:limit => 1}).last
37 })
37 })
38 info
38 info
39 rescue CommandFailed
39 rescue CommandFailed
40 return nil
40 return nil
41 end
41 end
42
42
43 def entries(path=nil, identifier=nil)
43 def entries(path=nil, identifier=nil)
44 path ||= ''
44 path ||= ''
45 entries = Entries.new
45 entries = Entries.new
46 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate"
46 cmd = "#{HG_BIN} -R #{target('')} --cwd #{target(path)} locate"
47 cmd << " -r #{identifier.to_i}" if identifier
47 cmd << " -r #{identifier.to_i}" if identifier
48 cmd << " " + shell_quote('glob:**')
48 cmd << " " + shell_quote('glob:**')
49 shellout(cmd) do |io|
49 shellout(cmd) do |io|
50 io.each_line do |line|
50 io.each_line do |line|
51 e = line.chomp.split(%r{[\/\\]})
51 e = line.chomp.split(%r{[\/\\]})
52 entries << Entry.new({:name => e.first,
52 entries << Entry.new({:name => e.first,
53 :path => (path.empty? ? e.first : "#{path}/#{e.first}"),
53 :path => (path.empty? ? e.first : "#{path}/#{e.first}"),
54 :kind => (e.size > 1 ? 'dir' : 'file'),
54 :kind => (e.size > 1 ? 'dir' : 'file'),
55 :lastrev => Revision.new
55 :lastrev => Revision.new
56 }) unless entries.detect{|entry| entry.name == e.first}
56 }) unless entries.detect{|entry| entry.name == e.first}
57 end
57 end
58 end
58 end
59 return nil if $? && $?.exitstatus != 0
59 return nil if $? && $?.exitstatus != 0
60 entries.sort_by_name
60 entries.sort_by_name
61 end
61 end
62
63 def entry(path=nil, identifier=nil)
64 path ||= ''
65 search_path = path.split('/')[0..-2].join('/')
66 entry_name = path.split('/').last
67 e = entries(search_path, identifier)
68 e ? e.detect{|entry| entry.name == entry_name} : nil
69 end
70
62
71 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
63 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
72 revisions = Revisions.new
64 revisions = Revisions.new
73 cmd = "#{HG_BIN} -v --encoding utf8 -R #{target('')} log"
65 cmd = "#{HG_BIN} -v --encoding utf8 -R #{target('')} log"
74 if identifier_from && identifier_to
66 if identifier_from && identifier_to
75 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
67 cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}"
76 elsif identifier_from
68 elsif identifier_from
77 cmd << " -r #{identifier_from.to_i}:"
69 cmd << " -r #{identifier_from.to_i}:"
78 end
70 end
79 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
71 cmd << " --limit #{options[:limit].to_i}" if options[:limit]
80 shellout(cmd) do |io|
72 shellout(cmd) do |io|
81 changeset = {}
73 changeset = {}
82 parsing_descr = false
74 parsing_descr = false
83 line_feeds = 0
75 line_feeds = 0
84
76
85 io.each_line do |line|
77 io.each_line do |line|
86 if line =~ /^(\w+):\s*(.*)$/
78 if line =~ /^(\w+):\s*(.*)$/
87 key = $1
79 key = $1
88 value = $2
80 value = $2
89 if parsing_descr && line_feeds > 1
81 if parsing_descr && line_feeds > 1
90 parsing_descr = false
82 parsing_descr = false
91 revisions << build_revision_from_changeset(changeset)
83 revisions << build_revision_from_changeset(changeset)
92 changeset = {}
84 changeset = {}
93 end
85 end
94 if !parsing_descr
86 if !parsing_descr
95 changeset.store key.to_sym, value
87 changeset.store key.to_sym, value
96 if $1 == "description"
88 if $1 == "description"
97 parsing_descr = true
89 parsing_descr = true
98 line_feeds = 0
90 line_feeds = 0
99 next
91 next
100 end
92 end
101 end
93 end
102 end
94 end
103 if parsing_descr
95 if parsing_descr
104 changeset[:description] << line
96 changeset[:description] << line
105 line_feeds += 1 if line.chomp.empty?
97 line_feeds += 1 if line.chomp.empty?
106 end
98 end
107 end
99 end
108 # Add the last changeset if there is one left
100 # Add the last changeset if there is one left
109 revisions << build_revision_from_changeset(changeset) if changeset[:date]
101 revisions << build_revision_from_changeset(changeset) if changeset[:date]
110 end
102 end
111 return nil if $? && $?.exitstatus != 0
103 return nil if $? && $?.exitstatus != 0
112 revisions
104 revisions
113 end
105 end
114
106
115 def diff(path, identifier_from, identifier_to=nil, type="inline")
107 def diff(path, identifier_from, identifier_to=nil, type="inline")
116 path ||= ''
108 path ||= ''
117 if identifier_to
109 if identifier_to
118 identifier_to = identifier_to.to_i
110 identifier_to = identifier_to.to_i
119 else
111 else
120 identifier_to = identifier_from.to_i - 1
112 identifier_to = identifier_from.to_i - 1
121 end
113 end
122 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
114 cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates"
123 cmd << " -I #{target(path)}" unless path.empty?
115 cmd << " -I #{target(path)}" unless path.empty?
124 diff = []
116 diff = []
125 shellout(cmd) do |io|
117 shellout(cmd) do |io|
126 io.each_line do |line|
118 io.each_line do |line|
127 diff << line
119 diff << line
128 end
120 end
129 end
121 end
130 return nil if $? && $?.exitstatus != 0
122 return nil if $? && $?.exitstatus != 0
131 DiffTableList.new diff, type
123 DiffTableList.new diff, type
132 end
124 end
133
125
134 def cat(path, identifier=nil)
126 def cat(path, identifier=nil)
135 cmd = "#{HG_BIN} -R #{target('')} cat"
127 cmd = "#{HG_BIN} -R #{target('')} cat"
136 cmd << " -r #{identifier.to_i}" if identifier
128 cmd << " -r #{identifier.to_i}" if identifier
137 cmd << " #{target(path)}"
129 cmd << " #{target(path)}"
138 cat = nil
130 cat = nil
139 shellout(cmd) do |io|
131 shellout(cmd) do |io|
140 io.binmode
132 io.binmode
141 cat = io.read
133 cat = io.read
142 end
134 end
143 return nil if $? && $?.exitstatus != 0
135 return nil if $? && $?.exitstatus != 0
144 cat
136 cat
145 end
137 end
146
138
147 def annotate(path, identifier=nil)
139 def annotate(path, identifier=nil)
148 path ||= ''
140 path ||= ''
149 cmd = "#{HG_BIN} -R #{target('')}"
141 cmd = "#{HG_BIN} -R #{target('')}"
150 cmd << " annotate -n -u"
142 cmd << " annotate -n -u"
151 cmd << " -r #{identifier.to_i}" if identifier
143 cmd << " -r #{identifier.to_i}" if identifier
152 cmd << " #{target(path)}"
144 cmd << " #{target(path)}"
153 blame = Annotate.new
145 blame = Annotate.new
154 shellout(cmd) do |io|
146 shellout(cmd) do |io|
155 io.each_line do |line|
147 io.each_line do |line|
156 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
148 next unless line =~ %r{^([^:]+)\s(\d+):(.*)$}
157 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
149 blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip))
158 end
150 end
159 end
151 end
160 return nil if $? && $?.exitstatus != 0
152 return nil if $? && $?.exitstatus != 0
161 blame
153 blame
162 end
154 end
163
155
164 private
156 private
165
157
166 # Builds a revision objet from the changeset returned by hg command
158 # Builds a revision objet from the changeset returned by hg command
167 def build_revision_from_changeset(changeset)
159 def build_revision_from_changeset(changeset)
168 rev_id = changeset[:changeset].to_s.split(':').first.to_i
160 rev_id = changeset[:changeset].to_s.split(':').first.to_i
169
161
170 # Changes
162 # Changes
171 paths = (rev_id == 0) ?
163 paths = (rev_id == 0) ?
172 # Can't get changes for revision 0 with hg status
164 # Can't get changes for revision 0 with hg status
173 changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} :
165 changeset[:files].to_s.split.collect{|path| {:action => 'A', :path => "/#{path}"}} :
174 status(rev_id)
166 status(rev_id)
175
167
176 Revision.new({:identifier => rev_id,
168 Revision.new({:identifier => rev_id,
177 :scmid => changeset[:changeset].to_s.split(':').last,
169 :scmid => changeset[:changeset].to_s.split(':').last,
178 :author => changeset[:user],
170 :author => changeset[:user],
179 :time => Time.parse(changeset[:date]),
171 :time => Time.parse(changeset[:date]),
180 :message => changeset[:description],
172 :message => changeset[:description],
181 :paths => paths
173 :paths => paths
182 })
174 })
183 end
175 end
184
176
185 # Returns the file changes for a given revision
177 # Returns the file changes for a given revision
186 def status(rev_id)
178 def status(rev_id)
187 cmd = "#{HG_BIN} -R #{target('')} status --rev #{rev_id.to_i - 1}:#{rev_id.to_i}"
179 cmd = "#{HG_BIN} -R #{target('')} status --rev #{rev_id.to_i - 1}:#{rev_id.to_i}"
188 result = []
180 result = []
189 shellout(cmd) do |io|
181 shellout(cmd) do |io|
190 io.each_line do |line|
182 io.each_line do |line|
191 action, file = line.chomp.split
183 action, file = line.chomp.split
192 next unless action && file
184 next unless action && file
193 file.gsub!("\\", "/")
185 file.gsub!("\\", "/")
194 case action
186 case action
195 when 'R'
187 when 'R'
196 result << { :action => 'D' , :path => "/#{file}" }
188 result << { :action => 'D' , :path => "/#{file}" }
197 else
189 else
198 result << { :action => action, :path => "/#{file}" }
190 result << { :action => action, :path => "/#{file}" }
199 end
191 end
200 end
192 end
201 end
193 end
202 result
194 result
203 end
195 end
204 end
196 end
205 end
197 end
206 end
198 end
207 end
199 end
@@ -1,191 +1,184
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 require 'rexml/document'
19 require 'rexml/document'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class SubversionAdapter < AbstractAdapter
24 class SubversionAdapter < AbstractAdapter
25
25
26 # SVN executable name
26 # SVN executable name
27 SVN_BIN = "svn"
27 SVN_BIN = "svn"
28
28
29 # Get info about the svn repository
29 # Get info about the svn repository
30 def info
30 def info
31 cmd = "#{SVN_BIN} info --xml #{target('')}"
31 cmd = "#{SVN_BIN} info --xml #{target('')}"
32 cmd << credentials_string
32 cmd << credentials_string
33 info = nil
33 info = nil
34 shellout(cmd) do |io|
34 shellout(cmd) do |io|
35 begin
35 begin
36 doc = REXML::Document.new(io)
36 doc = REXML::Document.new(io)
37 #root_url = doc.elements["info/entry/repository/root"].text
37 #root_url = doc.elements["info/entry/repository/root"].text
38 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
38 info = Info.new({:root_url => doc.elements["info/entry/repository/root"].text,
39 :lastrev => Revision.new({
39 :lastrev => Revision.new({
40 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
40 :identifier => doc.elements["info/entry/commit"].attributes['revision'],
41 :time => Time.parse(doc.elements["info/entry/commit/date"].text).localtime,
41 :time => Time.parse(doc.elements["info/entry/commit/date"].text).localtime,
42 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
42 :author => (doc.elements["info/entry/commit/author"] ? doc.elements["info/entry/commit/author"].text : "")
43 })
43 })
44 })
44 })
45 rescue
45 rescue
46 end
46 end
47 end
47 end
48 return nil if $? && $?.exitstatus != 0
48 return nil if $? && $?.exitstatus != 0
49 info
49 info
50 rescue CommandFailed
50 rescue CommandFailed
51 return nil
51 return nil
52 end
52 end
53
53
54 # Returns the entry identified by path and revision identifier
55 # or nil if entry doesn't exist in the repository
56 def entry(path=nil, identifier=nil)
57 e = entries(path, identifier)
58 e ? e.first : nil
59 end
60
61 # Returns an Entries collection
54 # Returns an Entries collection
62 # or nil if the given path doesn't exist in the repository
55 # or nil if the given path doesn't exist in the repository
63 def entries(path=nil, identifier=nil)
56 def entries(path=nil, identifier=nil)
64 path ||= ''
57 path ||= ''
65 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
58 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
66 entries = Entries.new
59 entries = Entries.new
67 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
60 cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
68 cmd << credentials_string
61 cmd << credentials_string
69 shellout(cmd) do |io|
62 shellout(cmd) do |io|
70 output = io.read
63 output = io.read
71 begin
64 begin
72 doc = REXML::Document.new(output)
65 doc = REXML::Document.new(output)
73 doc.elements.each("lists/list/entry") do |entry|
66 doc.elements.each("lists/list/entry") do |entry|
74 entries << Entry.new({:name => entry.elements['name'].text,
67 entries << Entry.new({:name => entry.elements['name'].text,
75 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
68 :path => ((path.empty? ? "" : "#{path}/") + entry.elements['name'].text),
76 :kind => entry.attributes['kind'],
69 :kind => entry.attributes['kind'],
77 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
70 :size => (entry.elements['size'] and entry.elements['size'].text).to_i,
78 :lastrev => Revision.new({
71 :lastrev => Revision.new({
79 :identifier => entry.elements['commit'].attributes['revision'],
72 :identifier => entry.elements['commit'].attributes['revision'],
80 :time => Time.parse(entry.elements['commit'].elements['date'].text).localtime,
73 :time => Time.parse(entry.elements['commit'].elements['date'].text).localtime,
81 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
74 :author => (entry.elements['commit'].elements['author'] ? entry.elements['commit'].elements['author'].text : "")
82 })
75 })
83 })
76 })
84 end
77 end
85 rescue Exception => e
78 rescue Exception => e
86 logger.error("Error parsing svn output: #{e.message}")
79 logger.error("Error parsing svn output: #{e.message}")
87 logger.error("Output was:\n #{output}")
80 logger.error("Output was:\n #{output}")
88 end
81 end
89 end
82 end
90 return nil if $? && $?.exitstatus != 0
83 return nil if $? && $?.exitstatus != 0
91 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
84 logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
92 entries.sort_by_name
85 entries.sort_by_name
93 end
86 end
94
87
95 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
88 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
96 path ||= ''
89 path ||= ''
97 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
90 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
98 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
91 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
99 revisions = Revisions.new
92 revisions = Revisions.new
100 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
93 cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
101 cmd << credentials_string
94 cmd << credentials_string
102 cmd << " --verbose " if options[:with_paths]
95 cmd << " --verbose " if options[:with_paths]
103 cmd << ' ' + target(path)
96 cmd << ' ' + target(path)
104 shellout(cmd) do |io|
97 shellout(cmd) do |io|
105 begin
98 begin
106 doc = REXML::Document.new(io)
99 doc = REXML::Document.new(io)
107 doc.elements.each("log/logentry") do |logentry|
100 doc.elements.each("log/logentry") do |logentry|
108 paths = []
101 paths = []
109 logentry.elements.each("paths/path") do |path|
102 logentry.elements.each("paths/path") do |path|
110 paths << {:action => path.attributes['action'],
103 paths << {:action => path.attributes['action'],
111 :path => path.text,
104 :path => path.text,
112 :from_path => path.attributes['copyfrom-path'],
105 :from_path => path.attributes['copyfrom-path'],
113 :from_revision => path.attributes['copyfrom-rev']
106 :from_revision => path.attributes['copyfrom-rev']
114 }
107 }
115 end
108 end
116 paths.sort! { |x,y| x[:path] <=> y[:path] }
109 paths.sort! { |x,y| x[:path] <=> y[:path] }
117
110
118 revisions << Revision.new({:identifier => logentry.attributes['revision'],
111 revisions << Revision.new({:identifier => logentry.attributes['revision'],
119 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
112 :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""),
120 :time => Time.parse(logentry.elements['date'].text).localtime,
113 :time => Time.parse(logentry.elements['date'].text).localtime,
121 :message => logentry.elements['msg'].text,
114 :message => logentry.elements['msg'].text,
122 :paths => paths
115 :paths => paths
123 })
116 })
124 end
117 end
125 rescue
118 rescue
126 end
119 end
127 end
120 end
128 return nil if $? && $?.exitstatus != 0
121 return nil if $? && $?.exitstatus != 0
129 revisions
122 revisions
130 end
123 end
131
124
132 def diff(path, identifier_from, identifier_to=nil, type="inline")
125 def diff(path, identifier_from, identifier_to=nil, type="inline")
133 path ||= ''
126 path ||= ''
134 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
127 identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
135 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
128 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
136
129
137 cmd = "#{SVN_BIN} diff -r "
130 cmd = "#{SVN_BIN} diff -r "
138 cmd << "#{identifier_to}:"
131 cmd << "#{identifier_to}:"
139 cmd << "#{identifier_from}"
132 cmd << "#{identifier_from}"
140 cmd << " #{target(path)}@#{identifier_from}"
133 cmd << " #{target(path)}@#{identifier_from}"
141 cmd << credentials_string
134 cmd << credentials_string
142 diff = []
135 diff = []
143 shellout(cmd) do |io|
136 shellout(cmd) do |io|
144 io.each_line do |line|
137 io.each_line do |line|
145 diff << line
138 diff << line
146 end
139 end
147 end
140 end
148 return nil if $? && $?.exitstatus != 0
141 return nil if $? && $?.exitstatus != 0
149 DiffTableList.new diff, type
142 DiffTableList.new diff, type
150 end
143 end
151
144
152 def cat(path, identifier=nil)
145 def cat(path, identifier=nil)
153 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
146 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
154 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
147 cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
155 cmd << credentials_string
148 cmd << credentials_string
156 cat = nil
149 cat = nil
157 shellout(cmd) do |io|
150 shellout(cmd) do |io|
158 io.binmode
151 io.binmode
159 cat = io.read
152 cat = io.read
160 end
153 end
161 return nil if $? && $?.exitstatus != 0
154 return nil if $? && $?.exitstatus != 0
162 cat
155 cat
163 end
156 end
164
157
165 def annotate(path, identifier=nil)
158 def annotate(path, identifier=nil)
166 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
159 identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
167 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
160 cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
168 cmd << credentials_string
161 cmd << credentials_string
169 blame = Annotate.new
162 blame = Annotate.new
170 shellout(cmd) do |io|
163 shellout(cmd) do |io|
171 io.each_line do |line|
164 io.each_line do |line|
172 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
165 next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
173 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
166 blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
174 end
167 end
175 end
168 end
176 return nil if $? && $?.exitstatus != 0
169 return nil if $? && $?.exitstatus != 0
177 blame
170 blame
178 end
171 end
179
172
180 private
173 private
181
174
182 def credentials_string
175 def credentials_string
183 str = ''
176 str = ''
184 str << " --username #{shell_quote(@login)}" unless @login.blank?
177 str << " --username #{shell_quote(@login)}" unless @login.blank?
185 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
178 str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
186 str
179 str
187 end
180 end
188 end
181 end
189 end
182 end
190 end
183 end
191 end
184 end
@@ -1,129 +1,137
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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
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 RepositoriesBazaarControllerTest < Test::Unit::TestCase
24 class RepositoriesBazaarControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules
25 fixtures :projects, :users, :roles, :members, :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/bazaar_repository'
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/bazaar_repository'
29
29
30 def setup
30 def setup
31 @controller = RepositoriesController.new
31 @controller = RepositoriesController.new
32 @request = ActionController::TestRequest.new
32 @request = ActionController::TestRequest.new
33 @response = ActionController::TestResponse.new
33 @response = ActionController::TestResponse.new
34 User.current = nil
34 User.current = nil
35 Repository::Bazaar.create(:project => Project.find(3), :url => REPOSITORY_PATH)
35 Repository::Bazaar.create(:project => Project.find(3), :url => REPOSITORY_PATH)
36 end
36 end
37
37
38 if File.directory?(REPOSITORY_PATH)
38 if File.directory?(REPOSITORY_PATH)
39 def test_show
39 def test_show
40 get :show, :id => 3
40 get :show, :id => 3
41 assert_response :success
41 assert_response :success
42 assert_template 'show'
42 assert_template 'show'
43 assert_not_nil assigns(:entries)
43 assert_not_nil assigns(:entries)
44 assert_not_nil assigns(:changesets)
44 assert_not_nil assigns(:changesets)
45 end
45 end
46
46
47 def test_browse_root
47 def test_browse_root
48 get :browse, :id => 3
48 get :browse, :id => 3
49 assert_response :success
49 assert_response :success
50 assert_template 'browse'
50 assert_template 'browse'
51 assert_not_nil assigns(:entries)
51 assert_not_nil assigns(:entries)
52 assert_equal 2, assigns(:entries).size
52 assert_equal 2, assigns(:entries).size
53 assert assigns(:entries).detect {|e| e.name == 'directory' && e.kind == 'dir'}
53 assert assigns(:entries).detect {|e| e.name == 'directory' && e.kind == 'dir'}
54 assert assigns(:entries).detect {|e| e.name == 'doc-mkdir.txt' && e.kind == 'file'}
54 assert assigns(:entries).detect {|e| e.name == 'doc-mkdir.txt' && e.kind == 'file'}
55 end
55 end
56
56
57 def test_browse_directory
57 def test_browse_directory
58 get :browse, :id => 3, :path => ['directory']
58 get :browse, :id => 3, :path => ['directory']
59 assert_response :success
59 assert_response :success
60 assert_template 'browse'
60 assert_template 'browse'
61 assert_not_nil assigns(:entries)
61 assert_not_nil assigns(:entries)
62 assert_equal ['doc-ls.txt', 'document.txt', 'edit.png'], assigns(:entries).collect(&:name)
62 assert_equal ['doc-ls.txt', 'document.txt', 'edit.png'], assigns(:entries).collect(&:name)
63 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
63 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
64 assert_not_nil entry
64 assert_not_nil entry
65 assert_equal 'file', entry.kind
65 assert_equal 'file', entry.kind
66 assert_equal 'directory/edit.png', entry.path
66 assert_equal 'directory/edit.png', entry.path
67 end
67 end
68
68
69 def test_browse_at_given_revision
69 def test_browse_at_given_revision
70 get :browse, :id => 3, :path => [], :rev => 3
70 get :browse, :id => 3, :path => [], :rev => 3
71 assert_response :success
71 assert_response :success
72 assert_template 'browse'
72 assert_template 'browse'
73 assert_not_nil assigns(:entries)
73 assert_not_nil assigns(:entries)
74 assert_equal ['directory', 'doc-deleted.txt', 'doc-ls.txt', 'doc-mkdir.txt'], assigns(:entries).collect(&:name)
74 assert_equal ['directory', 'doc-deleted.txt', 'doc-ls.txt', 'doc-mkdir.txt'], assigns(:entries).collect(&:name)
75 end
75 end
76
76
77 def test_changes
77 def test_changes
78 get :changes, :id => 3, :path => ['doc-mkdir.txt']
78 get :changes, :id => 3, :path => ['doc-mkdir.txt']
79 assert_response :success
79 assert_response :success
80 assert_template 'changes'
80 assert_template 'changes'
81 assert_tag :tag => 'h2', :content => 'doc-mkdir.txt'
81 assert_tag :tag => 'h2', :content => 'doc-mkdir.txt'
82 end
82 end
83
83
84 def test_entry_show
84 def test_entry_show
85 get :entry, :id => 3, :path => ['directory', 'doc-ls.txt']
85 get :entry, :id => 3, :path => ['directory', 'doc-ls.txt']
86 assert_response :success
86 assert_response :success
87 assert_template 'entry'
87 assert_template 'entry'
88 # Line 19
88 # Line 19
89 assert_tag :tag => 'th',
89 assert_tag :tag => 'th',
90 :content => /29/,
90 :content => /29/,
91 :attributes => { :class => /line-num/ },
91 :attributes => { :class => /line-num/ },
92 :sibling => { :tag => 'td', :content => /Show help message/ }
92 :sibling => { :tag => 'td', :content => /Show help message/ }
93 end
93 end
94
94
95 def test_entry_download
95 def test_entry_download
96 get :entry, :id => 3, :path => ['directory', 'doc-ls.txt'], :format => 'raw'
96 get :entry, :id => 3, :path => ['directory', 'doc-ls.txt'], :format => 'raw'
97 assert_response :success
97 assert_response :success
98 # File content
98 # File content
99 assert @response.body.include?('Show help message')
99 assert @response.body.include?('Show help message')
100 end
100 end
101
101
102 def test_directory_entry
103 get :entry, :id => 3, :path => ['directory']
104 assert_response :success
105 assert_template 'browse'
106 assert_not_nil assigns(:entry)
107 assert_equal 'directory', assigns(:entry).name
108 end
109
102 def test_diff
110 def test_diff
103 # Full diff of changeset 3
111 # Full diff of changeset 3
104 get :diff, :id => 3, :rev => 3
112 get :diff, :id => 3, :rev => 3
105 assert_response :success
113 assert_response :success
106 assert_template 'diff'
114 assert_template 'diff'
107 # Line 22 removed
115 # Line 22 removed
108 assert_tag :tag => 'th',
116 assert_tag :tag => 'th',
109 :content => /2/,
117 :content => /2/,
110 :sibling => { :tag => 'td',
118 :sibling => { :tag => 'td',
111 :attributes => { :class => /diff_in/ },
119 :attributes => { :class => /diff_in/ },
112 :content => /Main purpose/ }
120 :content => /Main purpose/ }
113 end
121 end
114
122
115 def test_annotate
123 def test_annotate
116 get :annotate, :id => 3, :path => ['doc-mkdir.txt']
124 get :annotate, :id => 3, :path => ['doc-mkdir.txt']
117 assert_response :success
125 assert_response :success
118 assert_template 'annotate'
126 assert_template 'annotate'
119 # Line 2, revision 3
127 # Line 2, revision 3
120 assert_tag :tag => 'th', :content => /2/,
128 assert_tag :tag => 'th', :content => /2/,
121 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /3/ } },
129 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /3/ } },
122 :sibling => { :tag => 'td', :content => /jsmith/ },
130 :sibling => { :tag => 'td', :content => /jsmith/ },
123 :sibling => { :tag => 'td', :content => /Main purpose/ }
131 :sibling => { :tag => 'td', :content => /Main purpose/ }
124 end
132 end
125 else
133 else
126 puts "Bazaar test repository NOT FOUND. Skipping functional tests !!!"
134 puts "Bazaar test repository NOT FOUND. Skipping functional tests !!!"
127 def test_fake; assert true end
135 def test_fake; assert true end
128 end
136 end
129 end
137 end
@@ -1,144 +1,152
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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
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 < Test::Unit::TestCase
24 class RepositoriesCvsControllerTest < Test::Unit::TestCase
25
25
26 # No '..' in the repository path
26 # No '..' in the repository path
27 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
27 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
28 REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
28 REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
29 # CVS module
29 # CVS module
30 MODULE_NAME = 'test'
30 MODULE_NAME = 'test'
31
31
32 def setup
32 def setup
33 @controller = RepositoriesController.new
33 @controller = RepositoriesController.new
34 @request = ActionController::TestRequest.new
34 @request = ActionController::TestRequest.new
35 @response = ActionController::TestResponse.new
35 @response = ActionController::TestResponse.new
36 Setting.default_language = 'en'
36 Setting.default_language = 'en'
37 User.current = nil
37 User.current = nil
38
38
39 @project = Project.find(1)
39 @project = Project.find(1)
40 @project.repository = Repository::Cvs.create(:root_url => REPOSITORY_PATH,
40 @project.repository = Repository::Cvs.create(:root_url => REPOSITORY_PATH,
41 :url => MODULE_NAME)
41 :url => MODULE_NAME)
42 end
42 end
43
43
44 if File.directory?(REPOSITORY_PATH)
44 if File.directory?(REPOSITORY_PATH)
45 def test_show
45 def test_show
46 get :show, :id => 1
46 get :show, :id => 1
47 assert_response :success
47 assert_response :success
48 assert_template 'show'
48 assert_template 'show'
49 assert_not_nil assigns(:entries)
49 assert_not_nil assigns(:entries)
50 assert_not_nil assigns(:changesets)
50 assert_not_nil assigns(:changesets)
51 end
51 end
52
52
53 def test_browse_root
53 def test_browse_root
54 get :browse, :id => 1
54 get :browse, :id => 1
55 assert_response :success
55 assert_response :success
56 assert_template 'browse'
56 assert_template 'browse'
57 assert_not_nil assigns(:entries)
57 assert_not_nil assigns(:entries)
58 assert_equal 3, assigns(:entries).size
58 assert_equal 3, assigns(:entries).size
59
59
60 entry = assigns(:entries).detect {|e| e.name == 'images'}
60 entry = assigns(:entries).detect {|e| e.name == 'images'}
61 assert_equal 'dir', entry.kind
61 assert_equal 'dir', entry.kind
62
62
63 entry = assigns(:entries).detect {|e| e.name == 'README'}
63 entry = assigns(:entries).detect {|e| e.name == 'README'}
64 assert_equal 'file', entry.kind
64 assert_equal 'file', entry.kind
65 end
65 end
66
66
67 def test_browse_directory
67 def test_browse_directory
68 get :browse, :id => 1, :path => ['images']
68 get :browse, :id => 1, :path => ['images']
69 assert_response :success
69 assert_response :success
70 assert_template 'browse'
70 assert_template 'browse'
71 assert_not_nil assigns(:entries)
71 assert_not_nil assigns(:entries)
72 assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name)
72 assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name)
73 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
73 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
74 assert_not_nil entry
74 assert_not_nil entry
75 assert_equal 'file', entry.kind
75 assert_equal 'file', entry.kind
76 assert_equal 'images/edit.png', entry.path
76 assert_equal 'images/edit.png', entry.path
77 end
77 end
78
78
79 def test_browse_at_given_revision
79 def test_browse_at_given_revision
80 Project.find(1).repository.fetch_changesets
80 Project.find(1).repository.fetch_changesets
81 get :browse, :id => 1, :path => ['images'], :rev => 1
81 get :browse, :id => 1, :path => ['images'], :rev => 1
82 assert_response :success
82 assert_response :success
83 assert_template 'browse'
83 assert_template 'browse'
84 assert_not_nil assigns(:entries)
84 assert_not_nil assigns(:entries)
85 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
85 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
86 end
86 end
87
87
88 def test_entry
88 def test_entry
89 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb']
89 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb']
90 assert_response :success
90 assert_response :success
91 assert_template 'entry'
91 assert_template 'entry'
92 end
92 end
93
93
94 def test_entry_not_found
94 def test_entry_not_found
95 get :entry, :id => 1, :path => ['sources', 'zzz.c']
95 get :entry, :id => 1, :path => ['sources', 'zzz.c']
96 assert_tag :tag => 'div', :attributes => { :class => /error/ },
96 assert_tag :tag => 'div', :attributes => { :class => /error/ },
97 :content => /The entry or revision was not found in the repository/
97 :content => /The entry or revision was not found in the repository/
98 end
98 end
99
99
100 def test_entry_download
100 def test_entry_download
101 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
101 get :entry, :id => 1, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
102 assert_response :success
102 assert_response :success
103 end
103 end
104
105 def test_directory_entry
106 get :entry, :id => 1, :path => ['sources']
107 assert_response :success
108 assert_template 'browse'
109 assert_not_nil assigns(:entry)
110 assert_equal 'sources', assigns(:entry).name
111 end
104
112
105 def test_diff
113 def test_diff
106 Project.find(1).repository.fetch_changesets
114 Project.find(1).repository.fetch_changesets
107 get :diff, :id => 1, :rev => 3, :type => 'inline'
115 get :diff, :id => 1, :rev => 3, :type => 'inline'
108 assert_response :success
116 assert_response :success
109 assert_template 'diff'
117 assert_template 'diff'
110 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' },
118 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' },
111 :content => /watched.remove_watcher/
119 :content => /watched.remove_watcher/
112 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' },
120 assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' },
113 :content => /watched.remove_all_watcher/
121 :content => /watched.remove_all_watcher/
114 end
122 end
115
123
116 def test_annotate
124 def test_annotate
117 Project.find(1).repository.fetch_changesets
125 Project.find(1).repository.fetch_changesets
118 get :annotate, :id => 1, :path => ['sources', 'watchers_controller.rb']
126 get :annotate, :id => 1, :path => ['sources', 'watchers_controller.rb']
119 assert_response :success
127 assert_response :success
120 assert_template 'annotate'
128 assert_template 'annotate'
121 # 1.1 line
129 # 1.1 line
122 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
130 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
123 :content => '18',
131 :content => '18',
124 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
132 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
125 :content => /1.1/,
133 :content => /1.1/,
126 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
134 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
127 :content => /LANG/
135 :content => /LANG/
128 }
136 }
129 }
137 }
130 # 1.2 line
138 # 1.2 line
131 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
139 assert_tag :tag => 'th', :attributes => { :class => 'line-num' },
132 :content => '32',
140 :content => '32',
133 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
141 :sibling => { :tag => 'td', :attributes => { :class => 'revision' },
134 :content => /1.2/,
142 :content => /1.2/,
135 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
143 :sibling => { :tag => 'td', :attributes => { :class => 'author' },
136 :content => /LANG/
144 :content => /LANG/
137 }
145 }
138 }
146 }
139 end
147 end
140 else
148 else
141 puts "CVS test repository NOT FOUND. Skipping functional tests !!!"
149 puts "CVS test repository NOT FOUND. Skipping functional tests !!!"
142 def test_fake; assert true end
150 def test_fake; assert true end
143 end
151 end
144 end
152 end
@@ -1,138 +1,146
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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
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 RepositoriesGitControllerTest < Test::Unit::TestCase
24 class RepositoriesGitControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules
25 fixtures :projects, :users, :roles, :members, :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/git_repository'
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
29 REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
29 REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
30
30
31 def setup
31 def setup
32 @controller = RepositoriesController.new
32 @controller = RepositoriesController.new
33 @request = ActionController::TestRequest.new
33 @request = ActionController::TestRequest.new
34 @response = ActionController::TestResponse.new
34 @response = ActionController::TestResponse.new
35 User.current = nil
35 User.current = nil
36 Repository::Git.create(:project => Project.find(3), :url => REPOSITORY_PATH)
36 Repository::Git.create(:project => Project.find(3), :url => REPOSITORY_PATH)
37 end
37 end
38
38
39 if File.directory?(REPOSITORY_PATH)
39 if File.directory?(REPOSITORY_PATH)
40 def test_show
40 def test_show
41 get :show, :id => 3
41 get :show, :id => 3
42 assert_response :success
42 assert_response :success
43 assert_template 'show'
43 assert_template 'show'
44 assert_not_nil assigns(:entries)
44 assert_not_nil assigns(:entries)
45 assert_not_nil assigns(:changesets)
45 assert_not_nil assigns(:changesets)
46 end
46 end
47
47
48 def test_browse_root
48 def test_browse_root
49 get :browse, :id => 3
49 get :browse, :id => 3
50 assert_response :success
50 assert_response :success
51 assert_template 'browse'
51 assert_template 'browse'
52 assert_not_nil assigns(:entries)
52 assert_not_nil assigns(:entries)
53 assert_equal 3, assigns(:entries).size
53 assert_equal 3, assigns(:entries).size
54 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
54 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
55 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
55 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
56 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
56 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
57 end
57 end
58
58
59 def test_browse_directory
59 def test_browse_directory
60 get :browse, :id => 3, :path => ['images']
60 get :browse, :id => 3, :path => ['images']
61 assert_response :success
61 assert_response :success
62 assert_template 'browse'
62 assert_template 'browse'
63 assert_not_nil assigns(:entries)
63 assert_not_nil assigns(:entries)
64 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
64 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
65 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
65 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
66 assert_not_nil entry
66 assert_not_nil entry
67 assert_equal 'file', entry.kind
67 assert_equal 'file', entry.kind
68 assert_equal 'images/edit.png', entry.path
68 assert_equal 'images/edit.png', entry.path
69 end
69 end
70
70
71 def test_browse_at_given_revision
71 def test_browse_at_given_revision
72 get :browse, :id => 3, :path => ['images'], :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
72 get :browse, :id => 3, :path => ['images'], :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
73 assert_response :success
73 assert_response :success
74 assert_template 'browse'
74 assert_template 'browse'
75 assert_not_nil assigns(:entries)
75 assert_not_nil assigns(:entries)
76 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
76 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
77 end
77 end
78
78
79 def test_changes
79 def test_changes
80 get :changes, :id => 3, :path => ['images', 'edit.png']
80 get :changes, :id => 3, :path => ['images', 'edit.png']
81 assert_response :success
81 assert_response :success
82 assert_template 'changes'
82 assert_template 'changes'
83 assert_tag :tag => 'h2', :content => 'edit.png'
83 assert_tag :tag => 'h2', :content => 'edit.png'
84 end
84 end
85
85
86 def test_entry_show
86 def test_entry_show
87 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
87 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
88 assert_response :success
88 assert_response :success
89 assert_template 'entry'
89 assert_template 'entry'
90 # Line 19
90 # Line 19
91 assert_tag :tag => 'th',
91 assert_tag :tag => 'th',
92 :content => /10/,
92 :content => /10/,
93 :attributes => { :class => /line-num/ },
93 :attributes => { :class => /line-num/ },
94 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
94 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
95 end
95 end
96
96
97 def test_entry_download
97 def test_entry_download
98 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
98 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
99 assert_response :success
99 assert_response :success
100 # File content
100 # File content
101 assert @response.body.include?('WITHOUT ANY WARRANTY')
101 assert @response.body.include?('WITHOUT ANY WARRANTY')
102 end
102 end
103
103
104 def test_directory_entry
105 get :entry, :id => 3, :path => ['sources']
106 assert_response :success
107 assert_template 'browse'
108 assert_not_nil assigns(:entry)
109 assert_equal 'sources', assigns(:entry).name
110 end
111
104 def test_diff
112 def test_diff
105 # Full diff of changeset 2f9c0091
113 # Full diff of changeset 2f9c0091
106 get :diff, :id => 3, :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
114 get :diff, :id => 3, :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
107 assert_response :success
115 assert_response :success
108 assert_template 'diff'
116 assert_template 'diff'
109 # Line 22 removed
117 # Line 22 removed
110 assert_tag :tag => 'th',
118 assert_tag :tag => 'th',
111 :content => /22/,
119 :content => /22/,
112 :sibling => { :tag => 'td',
120 :sibling => { :tag => 'td',
113 :attributes => { :class => /diff_out/ },
121 :attributes => { :class => /diff_out/ },
114 :content => /def remove/ }
122 :content => /def remove/ }
115 end
123 end
116
124
117 def test_annotate
125 def test_annotate
118 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
126 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
119 assert_response :success
127 assert_response :success
120 assert_template 'annotate'
128 assert_template 'annotate'
121 # Line 23, changeset 2f9c0091
129 # Line 23, changeset 2f9c0091
122 assert_tag :tag => 'th', :content => /23/,
130 assert_tag :tag => 'th', :content => /23/,
123 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /2f9c0091/ } },
131 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /2f9c0091/ } },
124 :sibling => { :tag => 'td', :content => /jsmith/ },
132 :sibling => { :tag => 'td', :content => /jsmith/ },
125 :sibling => { :tag => 'td', :content => /watcher =/ }
133 :sibling => { :tag => 'td', :content => /watcher =/ }
126 end
134 end
127
135
128 def test_annotate_binary_file
136 def test_annotate_binary_file
129 get :annotate, :id => 3, :path => ['images', 'delete.png']
137 get :annotate, :id => 3, :path => ['images', 'delete.png']
130 assert_response 500
138 assert_response 500
131 assert_tag :tag => 'div', :attributes => { :class => /error/ },
139 assert_tag :tag => 'div', :attributes => { :class => /error/ },
132 :content => /can not be annotated/
140 :content => /can not be annotated/
133 end
141 end
134 else
142 else
135 puts "Git test repository NOT FOUND. Skipping functional tests !!!"
143 puts "Git test repository NOT FOUND. Skipping functional tests !!!"
136 def test_fake; assert true end
144 def test_fake; assert true end
137 end
145 end
138 end
146 end
@@ -1,130 +1,138
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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
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 RepositoriesMercurialControllerTest < Test::Unit::TestCase
24 class RepositoriesMercurialControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules
25 fixtures :projects, :users, :roles, :members, :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/mercurial_repository'
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
29
29
30 def setup
30 def setup
31 @controller = RepositoriesController.new
31 @controller = RepositoriesController.new
32 @request = ActionController::TestRequest.new
32 @request = ActionController::TestRequest.new
33 @response = ActionController::TestResponse.new
33 @response = ActionController::TestResponse.new
34 User.current = nil
34 User.current = nil
35 Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH)
35 Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH)
36 end
36 end
37
37
38 if File.directory?(REPOSITORY_PATH)
38 if File.directory?(REPOSITORY_PATH)
39 def test_show
39 def test_show
40 get :show, :id => 3
40 get :show, :id => 3
41 assert_response :success
41 assert_response :success
42 assert_template 'show'
42 assert_template 'show'
43 assert_not_nil assigns(:entries)
43 assert_not_nil assigns(:entries)
44 assert_not_nil assigns(:changesets)
44 assert_not_nil assigns(:changesets)
45 end
45 end
46
46
47 def test_browse_root
47 def test_browse_root
48 get :browse, :id => 3
48 get :browse, :id => 3
49 assert_response :success
49 assert_response :success
50 assert_template 'browse'
50 assert_template 'browse'
51 assert_not_nil assigns(:entries)
51 assert_not_nil assigns(:entries)
52 assert_equal 3, assigns(:entries).size
52 assert_equal 3, assigns(:entries).size
53 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
53 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
54 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
54 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
55 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
55 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
56 end
56 end
57
57
58 def test_browse_directory
58 def test_browse_directory
59 get :browse, :id => 3, :path => ['images']
59 get :browse, :id => 3, :path => ['images']
60 assert_response :success
60 assert_response :success
61 assert_template 'browse'
61 assert_template 'browse'
62 assert_not_nil assigns(:entries)
62 assert_not_nil assigns(:entries)
63 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
63 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
64 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
64 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
65 assert_not_nil entry
65 assert_not_nil entry
66 assert_equal 'file', entry.kind
66 assert_equal 'file', entry.kind
67 assert_equal 'images/edit.png', entry.path
67 assert_equal 'images/edit.png', entry.path
68 end
68 end
69
69
70 def test_browse_at_given_revision
70 def test_browse_at_given_revision
71 get :browse, :id => 3, :path => ['images'], :rev => 0
71 get :browse, :id => 3, :path => ['images'], :rev => 0
72 assert_response :success
72 assert_response :success
73 assert_template 'browse'
73 assert_template 'browse'
74 assert_not_nil assigns(:entries)
74 assert_not_nil assigns(:entries)
75 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
75 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
76 end
76 end
77
77
78 def test_changes
78 def test_changes
79 get :changes, :id => 3, :path => ['images', 'edit.png']
79 get :changes, :id => 3, :path => ['images', 'edit.png']
80 assert_response :success
80 assert_response :success
81 assert_template 'changes'
81 assert_template 'changes'
82 assert_tag :tag => 'h2', :content => 'edit.png'
82 assert_tag :tag => 'h2', :content => 'edit.png'
83 end
83 end
84
84
85 def test_entry_show
85 def test_entry_show
86 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
86 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
87 assert_response :success
87 assert_response :success
88 assert_template 'entry'
88 assert_template 'entry'
89 # Line 19
89 # Line 19
90 assert_tag :tag => 'th',
90 assert_tag :tag => 'th',
91 :content => /10/,
91 :content => /10/,
92 :attributes => { :class => /line-num/ },
92 :attributes => { :class => /line-num/ },
93 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
93 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
94 end
94 end
95
95
96 def test_entry_download
96 def test_entry_download
97 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
97 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
98 assert_response :success
98 assert_response :success
99 # File content
99 # File content
100 assert @response.body.include?('WITHOUT ANY WARRANTY')
100 assert @response.body.include?('WITHOUT ANY WARRANTY')
101 end
101 end
102
102
103 def test_directory_entry
104 get :entry, :id => 3, :path => ['sources']
105 assert_response :success
106 assert_template 'browse'
107 assert_not_nil assigns(:entry)
108 assert_equal 'sources', assigns(:entry).name
109 end
110
103 def test_diff
111 def test_diff
104 # Full diff of changeset 4
112 # Full diff of changeset 4
105 get :diff, :id => 3, :rev => 4
113 get :diff, :id => 3, :rev => 4
106 assert_response :success
114 assert_response :success
107 assert_template 'diff'
115 assert_template 'diff'
108 # Line 22 removed
116 # Line 22 removed
109 assert_tag :tag => 'th',
117 assert_tag :tag => 'th',
110 :content => /22/,
118 :content => /22/,
111 :sibling => { :tag => 'td',
119 :sibling => { :tag => 'td',
112 :attributes => { :class => /diff_out/ },
120 :attributes => { :class => /diff_out/ },
113 :content => /def remove/ }
121 :content => /def remove/ }
114 end
122 end
115
123
116 def test_annotate
124 def test_annotate
117 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
125 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
118 assert_response :success
126 assert_response :success
119 assert_template 'annotate'
127 assert_template 'annotate'
120 # Line 23, revision 4
128 # Line 23, revision 4
121 assert_tag :tag => 'th', :content => /23/,
129 assert_tag :tag => 'th', :content => /23/,
122 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /4/ } },
130 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /4/ } },
123 :sibling => { :tag => 'td', :content => /jsmith/ },
131 :sibling => { :tag => 'td', :content => /jsmith/ },
124 :sibling => { :tag => 'td', :content => /watcher =/ }
132 :sibling => { :tag => 'td', :content => /watcher =/ }
125 end
133 end
126 else
134 else
127 puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!"
135 puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!"
128 def test_fake; assert true end
136 def test_fake; assert true end
129 end
137 end
130 end
138 end
@@ -1,107 +1,115
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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
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 RepositoriesSubversionControllerTest < Test::Unit::TestCase
24 class RepositoriesSubversionControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
25 fixtures :projects, :users, :roles, :members, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
26
26
27 # No '..' in the repository path for svn
27 # No '..' in the repository path for svn
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/subversion_repository'
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/subversion_repository'
29
29
30 def setup
30 def setup
31 @controller = RepositoriesController.new
31 @controller = RepositoriesController.new
32 @request = ActionController::TestRequest.new
32 @request = ActionController::TestRequest.new
33 @response = ActionController::TestResponse.new
33 @response = ActionController::TestResponse.new
34 Setting.default_language = 'en'
34 Setting.default_language = 'en'
35 User.current = nil
35 User.current = nil
36 end
36 end
37
37
38 if File.directory?(REPOSITORY_PATH)
38 if File.directory?(REPOSITORY_PATH)
39 def test_show
39 def test_show
40 get :show, :id => 1
40 get :show, :id => 1
41 assert_response :success
41 assert_response :success
42 assert_template 'show'
42 assert_template 'show'
43 assert_not_nil assigns(:entries)
43 assert_not_nil assigns(:entries)
44 assert_not_nil assigns(:changesets)
44 assert_not_nil assigns(:changesets)
45 end
45 end
46
46
47 def test_browse_root
47 def test_browse_root
48 get :browse, :id => 1
48 get :browse, :id => 1
49 assert_response :success
49 assert_response :success
50 assert_template 'browse'
50 assert_template 'browse'
51 assert_not_nil assigns(:entries)
51 assert_not_nil assigns(:entries)
52 entry = assigns(:entries).detect {|e| e.name == 'subversion_test'}
52 entry = assigns(:entries).detect {|e| e.name == 'subversion_test'}
53 assert_equal 'dir', entry.kind
53 assert_equal 'dir', entry.kind
54 end
54 end
55
55
56 def test_browse_directory
56 def test_browse_directory
57 get :browse, :id => 1, :path => ['subversion_test']
57 get :browse, :id => 1, :path => ['subversion_test']
58 assert_response :success
58 assert_response :success
59 assert_template 'browse'
59 assert_template 'browse'
60 assert_not_nil assigns(:entries)
60 assert_not_nil assigns(:entries)
61 assert_equal ['folder', '.project', 'helloworld.c', 'textfile.txt'], assigns(:entries).collect(&:name)
61 assert_equal ['folder', '.project', 'helloworld.c', 'textfile.txt'], assigns(:entries).collect(&:name)
62 entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'}
62 entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'}
63 assert_equal 'file', entry.kind
63 assert_equal 'file', entry.kind
64 assert_equal 'subversion_test/helloworld.c', entry.path
64 assert_equal 'subversion_test/helloworld.c', entry.path
65 end
65 end
66
66
67 def test_browse_at_given_revision
67 def test_browse_at_given_revision
68 get :browse, :id => 1, :path => ['subversion_test'], :rev => 4
68 get :browse, :id => 1, :path => ['subversion_test'], :rev => 4
69 assert_response :success
69 assert_response :success
70 assert_template 'browse'
70 assert_template 'browse'
71 assert_not_nil assigns(:entries)
71 assert_not_nil assigns(:entries)
72 assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], assigns(:entries).collect(&:name)
72 assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], assigns(:entries).collect(&:name)
73 end
73 end
74
74
75 def test_entry
75 def test_entry
76 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c']
76 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c']
77 assert_response :success
77 assert_response :success
78 assert_template 'entry'
78 assert_template 'entry'
79 end
79 end
80
80
81 def test_entry_not_found
81 def test_entry_not_found
82 get :entry, :id => 1, :path => ['subversion_test', 'zzz.c']
82 get :entry, :id => 1, :path => ['subversion_test', 'zzz.c']
83 assert_tag :tag => 'div', :attributes => { :class => /error/ },
83 assert_tag :tag => 'div', :attributes => { :class => /error/ },
84 :content => /The entry or revision was not found in the repository/
84 :content => /The entry or revision was not found in the repository/
85 end
85 end
86
86
87 def test_entry_download
87 def test_entry_download
88 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'], :format => 'raw'
88 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'], :format => 'raw'
89 assert_response :success
89 assert_response :success
90 end
90 end
91
91
92 def test_directory_entry
93 get :entry, :id => 1, :path => ['subversion_test', 'folder']
94 assert_response :success
95 assert_template 'browse'
96 assert_not_nil assigns(:entry)
97 assert_equal 'folder', assigns(:entry).name
98 end
99
92 def test_diff
100 def test_diff
93 get :diff, :id => 1, :rev => 3
101 get :diff, :id => 1, :rev => 3
94 assert_response :success
102 assert_response :success
95 assert_template 'diff'
103 assert_template 'diff'
96 end
104 end
97
105
98 def test_annotate
106 def test_annotate
99 get :annotate, :id => 1, :path => ['subversion_test', 'helloworld.c']
107 get :annotate, :id => 1, :path => ['subversion_test', 'helloworld.c']
100 assert_response :success
108 assert_response :success
101 assert_template 'annotate'
109 assert_template 'annotate'
102 end
110 end
103 else
111 else
104 puts "Subversion test repository NOT FOUND. Skipping functional tests !!!"
112 puts "Subversion test repository NOT FOUND. Skipping functional tests !!!"
105 def test_fake; assert true end
113 def test_fake; assert true end
106 end
114 end
107 end
115 end
General Comments 0
You need to be logged in to leave comments. Login now