##// END OF EJS Templates
Fixes valid revision regexp....
Jean-Philippe Lang -
r4435:03397f605c7e
parent child
Show More
@@ -1,331 +1,331
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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 menu_item :repository
26 menu_item :repository
27 menu_item :settings, :only => :edit
27 menu_item :settings, :only => :edit
28 default_search_scope :changesets
28 default_search_scope :changesets
29
29
30 before_filter :find_repository, :except => :edit
30 before_filter :find_repository, :except => :edit
31 before_filter :find_project, :only => :edit
31 before_filter :find_project, :only => :edit
32 before_filter :authorize
32 before_filter :authorize
33 accept_key_auth :revisions
33 accept_key_auth :revisions
34
34
35 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
35 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
36
36
37 def edit
37 def edit
38 @repository = @project.repository
38 @repository = @project.repository
39 if !@repository
39 if !@repository
40 @repository = Repository.factory(params[:repository_scm])
40 @repository = Repository.factory(params[:repository_scm])
41 @repository.project = @project if @repository
41 @repository.project = @project if @repository
42 end
42 end
43 if request.post? && @repository
43 if request.post? && @repository
44 @repository.attributes = params[:repository]
44 @repository.attributes = params[:repository]
45 @repository.save
45 @repository.save
46 end
46 end
47 render(:update) do |page|
47 render(:update) do |page|
48 page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'
48 page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'
49 if @repository && !@project.repository
49 if @repository && !@project.repository
50 @project.reload #needed to reload association
50 @project.reload #needed to reload association
51 page.replace_html "main-menu", render_main_menu(@project)
51 page.replace_html "main-menu", render_main_menu(@project)
52 end
52 end
53 end
53 end
54 end
54 end
55
55
56 def committers
56 def committers
57 @committers = @repository.committers
57 @committers = @repository.committers
58 @users = @project.users
58 @users = @project.users
59 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
59 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
60 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
60 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
61 @users.compact!
61 @users.compact!
62 @users.sort!
62 @users.sort!
63 if request.post? && params[:committers].is_a?(Hash)
63 if request.post? && params[:committers].is_a?(Hash)
64 # Build a hash with repository usernames as keys and corresponding user ids as values
64 # Build a hash with repository usernames as keys and corresponding user ids as values
65 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
65 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
66 flash[:notice] = l(:notice_successful_update)
66 flash[:notice] = l(:notice_successful_update)
67 redirect_to :action => 'committers', :id => @project
67 redirect_to :action => 'committers', :id => @project
68 end
68 end
69 end
69 end
70
70
71 def destroy
71 def destroy
72 @repository.destroy
72 @repository.destroy
73 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
73 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
74 end
74 end
75
75
76 def show
76 def show
77 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
77 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
78
78
79 @entries = @repository.entries(@path, @rev)
79 @entries = @repository.entries(@path, @rev)
80 if request.xhr?
80 if request.xhr?
81 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
81 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
82 else
82 else
83 (show_error_not_found; return) unless @entries
83 (show_error_not_found; return) unless @entries
84 @changesets = @repository.latest_changesets(@path, @rev)
84 @changesets = @repository.latest_changesets(@path, @rev)
85 @properties = @repository.properties(@path, @rev)
85 @properties = @repository.properties(@path, @rev)
86 render :action => 'show'
86 render :action => 'show'
87 end
87 end
88 end
88 end
89
89
90 alias_method :browse, :show
90 alias_method :browse, :show
91
91
92 def changes
92 def changes
93 @entry = @repository.entry(@path, @rev)
93 @entry = @repository.entry(@path, @rev)
94 (show_error_not_found; return) unless @entry
94 (show_error_not_found; return) unless @entry
95 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
95 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
96 @properties = @repository.properties(@path, @rev)
96 @properties = @repository.properties(@path, @rev)
97 end
97 end
98
98
99 def revisions
99 def revisions
100 @changeset_count = @repository.changesets.count
100 @changeset_count = @repository.changesets.count
101 @changeset_pages = Paginator.new self, @changeset_count,
101 @changeset_pages = Paginator.new self, @changeset_count,
102 per_page_option,
102 per_page_option,
103 params['page']
103 params['page']
104 @changesets = @repository.changesets.find(:all,
104 @changesets = @repository.changesets.find(:all,
105 :limit => @changeset_pages.items_per_page,
105 :limit => @changeset_pages.items_per_page,
106 :offset => @changeset_pages.current.offset,
106 :offset => @changeset_pages.current.offset,
107 :include => [:user, :repository])
107 :include => [:user, :repository])
108
108
109 respond_to do |format|
109 respond_to do |format|
110 format.html { render :layout => false if request.xhr? }
110 format.html { render :layout => false if request.xhr? }
111 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
111 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
112 end
112 end
113 end
113 end
114
114
115 def entry
115 def entry
116 @entry = @repository.entry(@path, @rev)
116 @entry = @repository.entry(@path, @rev)
117 (show_error_not_found; return) unless @entry
117 (show_error_not_found; return) unless @entry
118
118
119 # If the entry is a dir, show the browser
119 # If the entry is a dir, show the browser
120 (show; return) if @entry.is_dir?
120 (show; return) if @entry.is_dir?
121
121
122 @content = @repository.cat(@path, @rev)
122 @content = @repository.cat(@path, @rev)
123 (show_error_not_found; return) unless @content
123 (show_error_not_found; return) unless @content
124 if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
124 if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
125 # Force the download
125 # Force the download
126 send_data @content, :filename => @path.split('/').last
126 send_data @content, :filename => @path.split('/').last
127 else
127 else
128 # Prevent empty lines when displaying a file with Windows style eol
128 # Prevent empty lines when displaying a file with Windows style eol
129 @content.gsub!("\r\n", "\n")
129 @content.gsub!("\r\n", "\n")
130 end
130 end
131 end
131 end
132
132
133 def annotate
133 def annotate
134 @entry = @repository.entry(@path, @rev)
134 @entry = @repository.entry(@path, @rev)
135 (show_error_not_found; return) unless @entry
135 (show_error_not_found; return) unless @entry
136
136
137 @annotate = @repository.scm.annotate(@path, @rev)
137 @annotate = @repository.scm.annotate(@path, @rev)
138 (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
138 (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
139 end
139 end
140
140
141 def revision
141 def revision
142 @changeset = @repository.find_changeset_by_name(@rev)
142 @changeset = @repository.find_changeset_by_name(@rev)
143 raise ChangesetNotFound unless @changeset
143 raise ChangesetNotFound unless @changeset
144
144
145 respond_to do |format|
145 respond_to do |format|
146 format.html
146 format.html
147 format.js {render :layout => false}
147 format.js {render :layout => false}
148 end
148 end
149 rescue ChangesetNotFound
149 rescue ChangesetNotFound
150 show_error_not_found
150 show_error_not_found
151 end
151 end
152
152
153 def diff
153 def diff
154 if params[:format] == 'diff'
154 if params[:format] == 'diff'
155 @diff = @repository.diff(@path, @rev, @rev_to)
155 @diff = @repository.diff(@path, @rev, @rev_to)
156 (show_error_not_found; return) unless @diff
156 (show_error_not_found; return) unless @diff
157 filename = "changeset_r#{@rev}"
157 filename = "changeset_r#{@rev}"
158 filename << "_r#{@rev_to}" if @rev_to
158 filename << "_r#{@rev_to}" if @rev_to
159 send_data @diff.join, :filename => "#{filename}.diff",
159 send_data @diff.join, :filename => "#{filename}.diff",
160 :type => 'text/x-patch',
160 :type => 'text/x-patch',
161 :disposition => 'attachment'
161 :disposition => 'attachment'
162 else
162 else
163 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
163 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
164 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
164 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
165
165
166 # Save diff type as user preference
166 # Save diff type as user preference
167 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
167 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
168 User.current.pref[:diff_type] = @diff_type
168 User.current.pref[:diff_type] = @diff_type
169 User.current.preference.save
169 User.current.preference.save
170 end
170 end
171
171
172 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
172 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
173 unless read_fragment(@cache_key)
173 unless read_fragment(@cache_key)
174 @diff = @repository.diff(@path, @rev, @rev_to)
174 @diff = @repository.diff(@path, @rev, @rev_to)
175 show_error_not_found unless @diff
175 show_error_not_found unless @diff
176 end
176 end
177 end
177 end
178 end
178 end
179
179
180 def stats
180 def stats
181 end
181 end
182
182
183 def graph
183 def graph
184 data = nil
184 data = nil
185 case params[:graph]
185 case params[:graph]
186 when "commits_per_month"
186 when "commits_per_month"
187 data = graph_commits_per_month(@repository)
187 data = graph_commits_per_month(@repository)
188 when "commits_per_author"
188 when "commits_per_author"
189 data = graph_commits_per_author(@repository)
189 data = graph_commits_per_author(@repository)
190 end
190 end
191 if data
191 if data
192 headers["Content-Type"] = "image/svg+xml"
192 headers["Content-Type"] = "image/svg+xml"
193 send_data(data, :type => "image/svg+xml", :disposition => "inline")
193 send_data(data, :type => "image/svg+xml", :disposition => "inline")
194 else
194 else
195 render_404
195 render_404
196 end
196 end
197 end
197 end
198
198
199 private
199 private
200
200
201 REV_PARAM_RE = %r{^[a-f0-9]*$}i
201 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
202
202
203 def find_repository
203 def find_repository
204 @project = Project.find(params[:id])
204 @project = Project.find(params[:id])
205 @repository = @project.repository
205 @repository = @project.repository
206 (render_404; return false) unless @repository
206 (render_404; return false) unless @repository
207 @path = params[:path].join('/') unless params[:path].nil?
207 @path = params[:path].join('/') unless params[:path].nil?
208 @path ||= ''
208 @path ||= ''
209 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
209 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
210 @rev_to = params[:rev_to]
210 @rev_to = params[:rev_to]
211
211
212 unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
212 unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
213 if @repository.branches.blank?
213 if @repository.branches.blank?
214 raise InvalidRevisionParam
214 raise InvalidRevisionParam
215 end
215 end
216 end
216 end
217 rescue ActiveRecord::RecordNotFound
217 rescue ActiveRecord::RecordNotFound
218 render_404
218 render_404
219 rescue InvalidRevisionParam
219 rescue InvalidRevisionParam
220 show_error_not_found
220 show_error_not_found
221 end
221 end
222
222
223 def show_error_not_found
223 def show_error_not_found
224 render_error l(:error_scm_not_found)
224 render_error l(:error_scm_not_found)
225 end
225 end
226
226
227 # Handler for Redmine::Scm::Adapters::CommandFailed exception
227 # Handler for Redmine::Scm::Adapters::CommandFailed exception
228 def show_error_command_failed(exception)
228 def show_error_command_failed(exception)
229 render_error l(:error_scm_command_failed, exception.message)
229 render_error l(:error_scm_command_failed, exception.message)
230 end
230 end
231
231
232 def graph_commits_per_month(repository)
232 def graph_commits_per_month(repository)
233 @date_to = Date.today
233 @date_to = Date.today
234 @date_from = @date_to << 11
234 @date_from = @date_to << 11
235 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
235 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
236 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
236 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
237 commits_by_month = [0] * 12
237 commits_by_month = [0] * 12
238 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
238 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
239
239
240 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
240 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
241 changes_by_month = [0] * 12
241 changes_by_month = [0] * 12
242 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
242 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
243
243
244 fields = []
244 fields = []
245 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
245 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
246
246
247 graph = SVG::Graph::Bar.new(
247 graph = SVG::Graph::Bar.new(
248 :height => 300,
248 :height => 300,
249 :width => 800,
249 :width => 800,
250 :fields => fields.reverse,
250 :fields => fields.reverse,
251 :stack => :side,
251 :stack => :side,
252 :scale_integers => true,
252 :scale_integers => true,
253 :step_x_labels => 2,
253 :step_x_labels => 2,
254 :show_data_values => false,
254 :show_data_values => false,
255 :graph_title => l(:label_commits_per_month),
255 :graph_title => l(:label_commits_per_month),
256 :show_graph_title => true
256 :show_graph_title => true
257 )
257 )
258
258
259 graph.add_data(
259 graph.add_data(
260 :data => commits_by_month[0..11].reverse,
260 :data => commits_by_month[0..11].reverse,
261 :title => l(:label_revision_plural)
261 :title => l(:label_revision_plural)
262 )
262 )
263
263
264 graph.add_data(
264 graph.add_data(
265 :data => changes_by_month[0..11].reverse,
265 :data => changes_by_month[0..11].reverse,
266 :title => l(:label_change_plural)
266 :title => l(:label_change_plural)
267 )
267 )
268
268
269 graph.burn
269 graph.burn
270 end
270 end
271
271
272 def graph_commits_per_author(repository)
272 def graph_commits_per_author(repository)
273 commits_by_author = repository.changesets.count(:all, :group => :committer)
273 commits_by_author = repository.changesets.count(:all, :group => :committer)
274 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
274 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
275
275
276 changes_by_author = repository.changes.count(:all, :group => :committer)
276 changes_by_author = repository.changes.count(:all, :group => :committer)
277 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
277 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
278
278
279 fields = commits_by_author.collect {|r| r.first}
279 fields = commits_by_author.collect {|r| r.first}
280 commits_data = commits_by_author.collect {|r| r.last}
280 commits_data = commits_by_author.collect {|r| r.last}
281 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
281 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
282
282
283 fields = fields + [""]*(10 - fields.length) if fields.length<10
283 fields = fields + [""]*(10 - fields.length) if fields.length<10
284 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
284 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
285 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
285 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
286
286
287 # Remove email adress in usernames
287 # Remove email adress in usernames
288 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
288 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
289
289
290 graph = SVG::Graph::BarHorizontal.new(
290 graph = SVG::Graph::BarHorizontal.new(
291 :height => 400,
291 :height => 400,
292 :width => 800,
292 :width => 800,
293 :fields => fields,
293 :fields => fields,
294 :stack => :side,
294 :stack => :side,
295 :scale_integers => true,
295 :scale_integers => true,
296 :show_data_values => false,
296 :show_data_values => false,
297 :rotate_y_labels => false,
297 :rotate_y_labels => false,
298 :graph_title => l(:label_commits_per_author),
298 :graph_title => l(:label_commits_per_author),
299 :show_graph_title => true
299 :show_graph_title => true
300 )
300 )
301
301
302 graph.add_data(
302 graph.add_data(
303 :data => commits_data,
303 :data => commits_data,
304 :title => l(:label_revision_plural)
304 :title => l(:label_revision_plural)
305 )
305 )
306
306
307 graph.add_data(
307 graph.add_data(
308 :data => changes_data,
308 :data => changes_data,
309 :title => l(:label_change_plural)
309 :title => l(:label_change_plural)
310 )
310 )
311
311
312 graph.burn
312 graph.burn
313 end
313 end
314
314
315 end
315 end
316
316
317 class Date
317 class Date
318 def months_ago(date = Date.today)
318 def months_ago(date = Date.today)
319 (date.year - self.year)*12 + (date.month - self.month)
319 (date.year - self.year)*12 + (date.month - self.month)
320 end
320 end
321
321
322 def weeks_ago(date = Date.today)
322 def weeks_ago(date = Date.today)
323 (date.year - self.year)*52 + (date.cweek - self.cweek)
323 (date.year - self.year)*52 + (date.cweek - self.cweek)
324 end
324 end
325 end
325 end
326
326
327 class String
327 class String
328 def with_leading_slash
328 def with_leading_slash
329 starts_with?('/') ? self : "/#{self}"
329 starts_with?('/') ? self : "/#{self}"
330 end
330 end
331 end
331 end
General Comments 0
You need to be logged in to leave comments. Login now