##// END OF EJS Templates
Merged r4816 from trunk....
Toshi MARUYAMA -
r4699:008d38d6b45f
parent child
Show More
@@ -1,335 +1,335
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 => filename_for_content_disposition(@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 raise ChangesetNotFound if @rev.nil? || @rev.empty?
142 raise ChangesetNotFound if @rev.nil? || @rev.empty?
143 @changeset = @repository.find_changeset_by_name(@rev)
143 @changeset = @repository.find_changeset_by_name(@rev)
144 raise ChangesetNotFound unless @changeset
144 raise ChangesetNotFound unless @changeset
145
145
146 respond_to do |format|
146 respond_to do |format|
147 format.html
147 format.html
148 format.js {render :layout => false}
148 format.js {render :layout => false}
149 end
149 end
150 rescue ChangesetNotFound
150 rescue ChangesetNotFound
151 show_error_not_found
151 show_error_not_found
152 end
152 end
153
153
154 def diff
154 def diff
155 if params[:format] == 'diff'
155 if params[:format] == 'diff'
156 @diff = @repository.diff(@path, @rev, @rev_to)
156 @diff = @repository.diff(@path, @rev, @rev_to)
157 (show_error_not_found; return) unless @diff
157 (show_error_not_found; return) unless @diff
158 filename = "changeset_r#{@rev}"
158 filename = "changeset_r#{@rev}"
159 filename << "_r#{@rev_to}" if @rev_to
159 filename << "_r#{@rev_to}" if @rev_to
160 send_data @diff.join, :filename => "#{filename}.diff",
160 send_data @diff.join, :filename => "#{filename}.diff",
161 :type => 'text/x-patch',
161 :type => 'text/x-patch',
162 :disposition => 'attachment'
162 :disposition => 'attachment'
163 else
163 else
164 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
164 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
165 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
165 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
166
166
167 # Save diff type as user preference
167 # Save diff type as user preference
168 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
168 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
169 User.current.pref[:diff_type] = @diff_type
169 User.current.pref[:diff_type] = @diff_type
170 User.current.preference.save
170 User.current.preference.save
171 end
171 end
172
172
173 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
173 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
174 unless read_fragment(@cache_key)
174 unless read_fragment(@cache_key)
175 @diff = @repository.diff(@path, @rev, @rev_to)
175 @diff = @repository.diff(@path, @rev, @rev_to)
176 show_error_not_found unless @diff
176 show_error_not_found unless @diff
177 end
177 end
178
178
179 @changeset = @repository.find_changeset_by_name(@rev)
179 @changeset = @repository.find_changeset_by_name(@rev)
180 @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
180 @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
181 end
181 end
182 end
182 end
183
183
184 def stats
184 def stats
185 end
185 end
186
186
187 def graph
187 def graph
188 data = nil
188 data = nil
189 case params[:graph]
189 case params[:graph]
190 when "commits_per_month"
190 when "commits_per_month"
191 data = graph_commits_per_month(@repository)
191 data = graph_commits_per_month(@repository)
192 when "commits_per_author"
192 when "commits_per_author"
193 data = graph_commits_per_author(@repository)
193 data = graph_commits_per_author(@repository)
194 end
194 end
195 if data
195 if data
196 headers["Content-Type"] = "image/svg+xml"
196 headers["Content-Type"] = "image/svg+xml"
197 send_data(data, :type => "image/svg+xml", :disposition => "inline")
197 send_data(data, :type => "image/svg+xml", :disposition => "inline")
198 else
198 else
199 render_404
199 render_404
200 end
200 end
201 end
201 end
202
202
203 private
203 private
204
204
205 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
205 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
206
206
207 def find_repository
207 def find_repository
208 @project = Project.find(params[:id])
208 @project = Project.find(params[:id])
209 @repository = @project.repository
209 @repository = @project.repository
210 (render_404; return false) unless @repository
210 (render_404; return false) unless @repository
211 @path = params[:path].join('/') unless params[:path].nil?
211 @path = params[:path].join('/') unless params[:path].nil?
212 @path ||= ''
212 @path ||= ''
213 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
213 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
214 @rev_to = params[:rev_to]
214 @rev_to = params[:rev_to]
215
215
216 unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
216 unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
217 if @repository.branches.blank?
217 if @repository.branches.blank?
218 raise InvalidRevisionParam
218 raise InvalidRevisionParam
219 end
219 end
220 end
220 end
221 rescue ActiveRecord::RecordNotFound
221 rescue ActiveRecord::RecordNotFound
222 render_404
222 render_404
223 rescue InvalidRevisionParam
223 rescue InvalidRevisionParam
224 show_error_not_found
224 show_error_not_found
225 end
225 end
226
226
227 def show_error_not_found
227 def show_error_not_found
228 render_error :message => l(:error_scm_not_found), :status => 404
228 render_error :message => l(:error_scm_not_found), :status => 404
229 end
229 end
230
230
231 # Handler for Redmine::Scm::Adapters::CommandFailed exception
231 # Handler for Redmine::Scm::Adapters::CommandFailed exception
232 def show_error_command_failed(exception)
232 def show_error_command_failed(exception)
233 render_error l(:error_scm_command_failed, exception.message)
233 render_error l(:error_scm_command_failed, exception.message)
234 end
234 end
235
235
236 def graph_commits_per_month(repository)
236 def graph_commits_per_month(repository)
237 @date_to = Date.today
237 @date_to = Date.today
238 @date_from = @date_to << 11
238 @date_from = @date_to << 11
239 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
239 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
240 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
240 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
241 commits_by_month = [0] * 12
241 commits_by_month = [0] * 12
242 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
242 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
243
243
244 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
244 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
245 changes_by_month = [0] * 12
245 changes_by_month = [0] * 12
246 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
246 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
247
247
248 fields = []
248 fields = []
249 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
249 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
250
250
251 graph = SVG::Graph::Bar.new(
251 graph = SVG::Graph::Bar.new(
252 :height => 300,
252 :height => 300,
253 :width => 800,
253 :width => 800,
254 :fields => fields.reverse,
254 :fields => fields.reverse,
255 :stack => :side,
255 :stack => :side,
256 :scale_integers => true,
256 :scale_integers => true,
257 :step_x_labels => 2,
257 :step_x_labels => 2,
258 :show_data_values => false,
258 :show_data_values => false,
259 :graph_title => l(:label_commits_per_month),
259 :graph_title => l(:label_commits_per_month),
260 :show_graph_title => true
260 :show_graph_title => true
261 )
261 )
262
262
263 graph.add_data(
263 graph.add_data(
264 :data => commits_by_month[0..11].reverse,
264 :data => commits_by_month[0..11].reverse,
265 :title => l(:label_revision_plural)
265 :title => l(:label_revision_plural)
266 )
266 )
267
267
268 graph.add_data(
268 graph.add_data(
269 :data => changes_by_month[0..11].reverse,
269 :data => changes_by_month[0..11].reverse,
270 :title => l(:label_change_plural)
270 :title => l(:label_change_plural)
271 )
271 )
272
272
273 graph.burn
273 graph.burn
274 end
274 end
275
275
276 def graph_commits_per_author(repository)
276 def graph_commits_per_author(repository)
277 commits_by_author = repository.changesets.count(:all, :group => :committer)
277 commits_by_author = repository.changesets.count(:all, :group => :committer)
278 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
278 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
279
279
280 changes_by_author = repository.changes.count(:all, :group => :committer)
280 changes_by_author = repository.changes.count(:all, :group => :committer)
281 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
281 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
282
282
283 fields = commits_by_author.collect {|r| r.first}
283 fields = commits_by_author.collect {|r| r.first}
284 commits_data = commits_by_author.collect {|r| r.last}
284 commits_data = commits_by_author.collect {|r| r.last}
285 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
285 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
286
286
287 fields = fields + [""]*(10 - fields.length) if fields.length<10
287 fields = fields + [""]*(10 - fields.length) if fields.length<10
288 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
288 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
289 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
289 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
290
290
291 # Remove email adress in usernames
291 # Remove email adress in usernames
292 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
292 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
293
293
294 graph = SVG::Graph::BarHorizontal.new(
294 graph = SVG::Graph::BarHorizontal.new(
295 :height => 400,
295 :height => 400,
296 :width => 800,
296 :width => 800,
297 :fields => fields,
297 :fields => fields,
298 :stack => :side,
298 :stack => :side,
299 :scale_integers => true,
299 :scale_integers => true,
300 :show_data_values => false,
300 :show_data_values => false,
301 :rotate_y_labels => false,
301 :rotate_y_labels => false,
302 :graph_title => l(:label_commits_per_author),
302 :graph_title => l(:label_commits_per_author),
303 :show_graph_title => true
303 :show_graph_title => true
304 )
304 )
305
305
306 graph.add_data(
306 graph.add_data(
307 :data => commits_data,
307 :data => commits_data,
308 :title => l(:label_revision_plural)
308 :title => l(:label_revision_plural)
309 )
309 )
310
310
311 graph.add_data(
311 graph.add_data(
312 :data => changes_data,
312 :data => changes_data,
313 :title => l(:label_change_plural)
313 :title => l(:label_change_plural)
314 )
314 )
315
315
316 graph.burn
316 graph.burn
317 end
317 end
318
318
319 end
319 end
320
320
321 class Date
321 class Date
322 def months_ago(date = Date.today)
322 def months_ago(date = Date.today)
323 (date.year - self.year)*12 + (date.month - self.month)
323 (date.year - self.year)*12 + (date.month - self.month)
324 end
324 end
325
325
326 def weeks_ago(date = Date.today)
326 def weeks_ago(date = Date.today)
327 (date.year - self.year)*52 + (date.cweek - self.cweek)
327 (date.year - self.year)*52 + (date.cweek - self.cweek)
328 end
328 end
329 end
329 end
330
330
331 class String
331 class String
332 def with_leading_slash
332 def with_leading_slash
333 starts_with?('/') ? self : "/#{self}"
333 starts_with?('/') ? self : "/#{self}"
334 end
334 end
335 end
335 end
General Comments 0
You need to be logged in to leave comments. Login now