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