Mercurial > hg > soundsoftware-site
comparison app/controllers/repositories_controller.rb @ 1115:433d4f72a19b redmine-2.2
Update to Redmine SVN revision 11137 on 2.2-stable branch
author | Chris Cannam |
---|---|
date | Mon, 07 Jan 2013 12:01:42 +0000 |
parents | cbb26bc654de |
children | bb32da3bea34 622f24f53b42 261b3d9a4903 |
comparison
equal
deleted
inserted
replaced
929:5f33065ddc4b | 1115:433d4f72a19b |
---|---|
1 # Redmine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006-2011 Jean-Philippe Lang | 2 # Copyright (C) 2006-2012 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. |
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 require 'redmine/scm/adapters/abstract_adapter' | |
21 | 22 |
22 class ChangesetNotFound < Exception; end | 23 class ChangesetNotFound < Exception; end |
23 class InvalidRevisionParam < Exception; end | 24 class InvalidRevisionParam < Exception; end |
24 | 25 |
25 class RepositoriesController < ApplicationController | 26 class RepositoriesController < ApplicationController |
26 menu_item :repository | 27 menu_item :repository |
27 menu_item :settings, :only => :edit | 28 menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers] |
28 default_search_scope :changesets | 29 default_search_scope :changesets |
29 | 30 |
30 before_filter :find_repository, :except => :edit | 31 before_filter :find_project_by_project_id, :only => [:new, :create] |
31 before_filter :find_project, :only => :edit | 32 before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] |
33 before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers] | |
34 before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue] | |
32 before_filter :authorize | 35 before_filter :authorize |
33 accept_rss_auth :revisions | 36 accept_rss_auth :revisions |
34 | 37 |
35 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed | 38 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed |
36 | 39 |
40 def new | |
41 scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first | |
42 @repository = Repository.factory(scm) | |
43 @repository.is_default = @project.repository.nil? | |
44 @repository.project = @project | |
45 end | |
46 | |
47 def create | |
48 attrs = pickup_extra_info | |
49 @repository = Repository.factory(params[:repository_scm]) | |
50 @repository.safe_attributes = params[:repository] | |
51 if attrs[:attrs_extra].keys.any? | |
52 @repository.merge_extra_info(attrs[:attrs_extra]) | |
53 end | |
54 @repository.project = @project | |
55 if request.post? && @repository.save | |
56 redirect_to settings_project_path(@project, :tab => 'repositories') | |
57 else | |
58 render :action => 'new' | |
59 end | |
60 end | |
61 | |
37 def edit | 62 def edit |
38 @repository = @project.repository | 63 end |
39 if !@repository && !params[:repository_scm].blank? | 64 |
40 @repository = Repository.factory(params[:repository_scm]) | 65 def update |
41 @repository.project = @project if @repository | 66 attrs = pickup_extra_info |
42 end | 67 @repository.safe_attributes = attrs[:attrs] |
43 if request.post? && @repository | 68 if attrs[:attrs_extra].keys.any? |
44 p1 = params[:repository] | 69 @repository.merge_extra_info(attrs[:attrs_extra]) |
45 p = {} | 70 end |
46 p_extra = {} | 71 @repository.project = @project |
47 p1.each do |k, v| | 72 if request.put? && @repository.save |
48 if k =~ /^extra_/ | 73 redirect_to settings_project_path(@project, :tab => 'repositories') |
49 p_extra[k] = v | 74 else |
50 else | 75 render :action => 'edit' |
51 p[k] = v | 76 end |
52 end | 77 end |
78 | |
79 def pickup_extra_info | |
80 p = {} | |
81 p_extra = {} | |
82 params[:repository].each do |k, v| | |
83 if k =~ /^extra_/ | |
84 p_extra[k] = v | |
85 else | |
86 p[k] = v | |
53 end | 87 end |
54 @repository.attributes = p | 88 end |
55 @repository.merge_extra_info(p_extra) | 89 {:attrs => p, :attrs_extra => p_extra} |
56 @repository.save | 90 end |
57 end | 91 private :pickup_extra_info |
58 render(:update) do |page| | |
59 page.replace_html "tab-content-repository", | |
60 :partial => 'projects/settings/repository' | |
61 if @repository && !@project.repository | |
62 @project.reload # needed to reload association | |
63 page.replace_html "main-menu", render_main_menu(@project) | |
64 end | |
65 end | |
66 end | |
67 | 92 |
68 def committers | 93 def committers |
69 @committers = @repository.committers | 94 @committers = @repository.committers |
70 @users = @project.users | 95 @users = @project.users |
71 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id) | 96 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id) |
74 @users.sort! | 99 @users.sort! |
75 if request.post? && params[:committers].is_a?(Hash) | 100 if request.post? && params[:committers].is_a?(Hash) |
76 # Build a hash with repository usernames as keys and corresponding user ids as values | 101 # Build a hash with repository usernames as keys and corresponding user ids as values |
77 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} | 102 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} |
78 flash[:notice] = l(:notice_successful_update) | 103 flash[:notice] = l(:notice_successful_update) |
79 redirect_to :action => 'committers', :id => @project | 104 redirect_to settings_project_path(@project, :tab => 'repositories') |
80 end | 105 end |
81 end | 106 end |
82 | 107 |
83 def destroy | 108 def destroy |
84 @repository.destroy | 109 @repository.destroy if request.delete? |
85 redirect_to :controller => 'projects', | 110 redirect_to settings_project_path(@project, :tab => 'repositories') |
86 :action => 'settings', | |
87 :id => @project, | |
88 :tab => 'repository' | |
89 end | 111 end |
90 | 112 |
91 def show | 113 def show |
92 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? | 114 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? |
93 | 115 |
97 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) | 119 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) |
98 else | 120 else |
99 (show_error_not_found; return) unless @entries | 121 (show_error_not_found; return) unless @entries |
100 @changesets = @repository.latest_changesets(@path, @rev) | 122 @changesets = @repository.latest_changesets(@path, @rev) |
101 @properties = @repository.properties(@path, @rev) | 123 @properties = @repository.properties(@path, @rev) |
124 @repositories = @project.repositories | |
102 render :action => 'show' | 125 render :action => 'show' |
103 end | 126 end |
104 end | 127 end |
105 | 128 |
106 alias_method :browse, :show | 129 alias_method :browse, :show |
127 format.html { render :layout => false if request.xhr? } | 150 format.html { render :layout => false if request.xhr? } |
128 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } | 151 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } |
129 end | 152 end |
130 end | 153 end |
131 | 154 |
155 def raw | |
156 entry_and_raw(true) | |
157 end | |
158 | |
132 def entry | 159 def entry |
160 entry_and_raw(false) | |
161 end | |
162 | |
163 def entry_and_raw(is_raw) | |
133 @entry = @repository.entry(@path, @rev) | 164 @entry = @repository.entry(@path, @rev) |
134 (show_error_not_found; return) unless @entry | 165 (show_error_not_found; return) unless @entry |
135 | 166 |
136 # If the entry is a dir, show the browser | 167 # If the entry is a dir, show the browser |
137 (show; return) if @entry.is_dir? | 168 (show; return) if @entry.is_dir? |
138 | 169 |
139 @content = @repository.cat(@path, @rev) | 170 @content = @repository.cat(@path, @rev) |
140 (show_error_not_found; return) unless @content | 171 (show_error_not_found; return) unless @content |
141 if 'raw' == params[:format] || | 172 if is_raw || |
142 (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || | 173 (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || |
143 ! is_entry_text_data?(@content, @path) | 174 ! is_entry_text_data?(@content, @path) |
144 # Force the download | 175 # Force the download |
145 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } | 176 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } |
146 send_type = Redmine::MimeType.of(@path) | 177 send_type = Redmine::MimeType.of(@path) |
147 send_opt[:type] = send_type.to_s if send_type | 178 send_opt[:type] = send_type.to_s if send_type |
179 send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment') | |
148 send_data @content, send_opt | 180 send_data @content, send_opt |
149 else | 181 else |
150 # Prevent empty lines when displaying a file with Windows style eol | 182 # Prevent empty lines when displaying a file with Windows style eol |
151 # TODO: UTF-16 | 183 # TODO: UTF-16 |
152 # Is this needs? AttachmentsController reads file simply. | 184 # Is this needs? AttachmentsController reads file simply. |
153 @content.gsub!("\r\n", "\n") | 185 @content.gsub!("\r\n", "\n") |
154 @changeset = @repository.find_changeset_by_name(@rev) | 186 @changeset = @repository.find_changeset_by_name(@rev) |
155 end | 187 end |
156 end | 188 end |
189 private :entry_and_raw | |
157 | 190 |
158 def is_entry_text_data?(ent, path) | 191 def is_entry_text_data?(ent, path) |
159 # UTF-16 contains "\x00". | 192 # UTF-16 contains "\x00". |
160 # It is very strict that file contains less than 30% of ascii symbols | 193 # It is very strict that file contains less than 30% of ascii symbols |
161 # in non Western Europe. | 194 # in non Western Europe. |
184 end | 217 end |
185 @changeset = @repository.find_changeset_by_name(@rev) | 218 @changeset = @repository.find_changeset_by_name(@rev) |
186 end | 219 end |
187 | 220 |
188 def revision | 221 def revision |
189 raise ChangesetNotFound if @rev.blank? | |
190 @changeset = @repository.find_changeset_by_name(@rev) | |
191 raise ChangesetNotFound unless @changeset | |
192 | |
193 respond_to do |format| | 222 respond_to do |format| |
194 format.html | 223 format.html |
195 format.js {render :layout => false} | 224 format.js {render :layout => false} |
196 end | 225 end |
197 rescue ChangesetNotFound | 226 end |
198 show_error_not_found | 227 |
228 # Adds a related issue to a changeset | |
229 # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues | |
230 def add_related_issue | |
231 @issue = @changeset.find_referenced_issue_by_id(params[:issue_id]) | |
232 if @issue && (!@issue.visible? || @changeset.issues.include?(@issue)) | |
233 @issue = nil | |
234 end | |
235 | |
236 if @issue | |
237 @changeset.issues << @issue | |
238 end | |
239 end | |
240 | |
241 # Removes a related issue from a changeset | |
242 # DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id | |
243 def remove_related_issue | |
244 @issue = Issue.visible.find_by_id(params[:issue_id]) | |
245 if @issue | |
246 @changeset.issues.delete(@issue) | |
247 end | |
199 end | 248 end |
200 | 249 |
201 def diff | 250 def diff |
202 if params[:format] == 'diff' | 251 if params[:format] == 'diff' |
203 @diff = @repository.diff(@path, @rev, @rev_to) | 252 @diff = @repository.diff(@path, @rev, @rev_to) |
248 end | 297 end |
249 end | 298 end |
250 | 299 |
251 private | 300 private |
252 | 301 |
302 def find_repository | |
303 @repository = Repository.find(params[:id]) | |
304 @project = @repository.project | |
305 rescue ActiveRecord::RecordNotFound | |
306 render_404 | |
307 end | |
308 | |
253 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i | 309 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i |
254 | 310 |
255 def find_repository | 311 def find_project_repository |
256 @project = Project.find(params[:id]) | 312 @project = Project.find(params[:id]) |
257 @repository = @project.repository | 313 if params[:repository_id].present? |
314 @repository = @project.repositories.find_by_identifier_param(params[:repository_id]) | |
315 else | |
316 @repository = @project.repository | |
317 end | |
258 (render_404; return false) unless @repository | 318 (render_404; return false) unless @repository |
259 @path = params[:path].join('/') unless params[:path].nil? | 319 @path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s |
260 @path ||= '' | |
261 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip | 320 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip |
262 @rev_to = params[:rev_to] | 321 @rev_to = params[:rev_to] |
263 | 322 |
264 unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) | 323 unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) |
265 if @repository.branches.blank? | 324 if @repository.branches.blank? |
270 render_404 | 329 render_404 |
271 rescue InvalidRevisionParam | 330 rescue InvalidRevisionParam |
272 show_error_not_found | 331 show_error_not_found |
273 end | 332 end |
274 | 333 |
334 def find_changeset | |
335 if @rev.present? | |
336 @changeset = @repository.find_changeset_by_name(@rev) | |
337 end | |
338 show_error_not_found unless @changeset | |
339 end | |
340 | |
275 def show_error_not_found | 341 def show_error_not_found |
276 render_error :message => l(:error_scm_not_found), :status => 404 | 342 render_error :message => l(:error_scm_not_found), :status => 404 |
277 end | 343 end |
278 | 344 |
279 # Handler for Redmine::Scm::Adapters::CommandFailed exception | 345 # Handler for Redmine::Scm::Adapters::CommandFailed exception |
283 | 349 |
284 def graph_commits_per_month(repository) | 350 def graph_commits_per_month(repository) |
285 @date_to = Date.today | 351 @date_to = Date.today |
286 @date_from = @date_to << 11 | 352 @date_from = @date_to << 11 |
287 @date_from = Date.civil(@date_from.year, @date_from.month, 1) | 353 @date_from = Date.civil(@date_from.year, @date_from.month, 1) |
288 commits_by_day = repository.changesets.count( | 354 commits_by_day = Changeset.count( |
289 :all, :group => :commit_date, | 355 :all, :group => :commit_date, |
290 :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to]) | 356 :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) |
291 commits_by_month = [0] * 12 | 357 commits_by_month = [0] * 12 |
292 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last } | 358 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } |
293 | 359 |
294 changes_by_day = repository.changes.count( | 360 changes_by_day = Change.count( |
295 :all, :group => :commit_date, | 361 :all, :group => :commit_date, :include => :changeset, |
296 :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to]) | 362 :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) |
297 changes_by_month = [0] * 12 | 363 changes_by_month = [0] * 12 |
298 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last } | 364 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } |
299 | 365 |
300 fields = [] | 366 fields = [] |
301 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)} | 367 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)} |
302 | 368 |
303 graph = SVG::Graph::Bar.new( | 369 graph = SVG::Graph::Bar.new( |
324 | 390 |
325 graph.burn | 391 graph.burn |
326 end | 392 end |
327 | 393 |
328 def graph_commits_per_author(repository) | 394 def graph_commits_per_author(repository) |
329 commits_by_author = repository.changesets.count(:all, :group => :committer) | 395 commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id]) |
330 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last} | 396 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last} |
331 | 397 |
332 changes_by_author = repository.changes.count(:all, :group => :committer) | 398 changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id]) |
333 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} | 399 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} |
334 | 400 |
335 fields = commits_by_author.collect {|r| r.first} | 401 fields = commits_by_author.collect {|r| r.first} |
336 commits_data = commits_by_author.collect {|r| r.last} | 402 commits_data = commits_by_author.collect {|r| r.last} |
337 changes_data = commits_by_author.collect {|r| h[r.first] || 0} | 403 changes_data = commits_by_author.collect {|r| h[r.first] || 0} |
363 :title => l(:label_change_plural) | 429 :title => l(:label_change_plural) |
364 ) | 430 ) |
365 graph.burn | 431 graph.burn |
366 end | 432 end |
367 end | 433 end |
368 | |
369 class Date | |
370 def months_ago(date = Date.today) | |
371 (date.year - self.year)*12 + (date.month - self.month) | |
372 end | |
373 | |
374 def weeks_ago(date = Date.today) | |
375 (date.year - self.year)*52 + (date.cweek - self.cweek) | |
376 end | |
377 end | |
378 | |
379 class String | |
380 def with_leading_slash | |
381 starts_with?('/') ? self : "/#{self}" | |
382 end | |
383 end |