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