annotate app/controllers/.svn/text-base/repositories_controller.rb.svn-base @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children af80e5618e9b 8661b858af72
rev   line source
Chris@0 1 # Redmine - project management software
Chris@0 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 require 'SVG/Graph/Bar'
Chris@0 19 require 'SVG/Graph/BarHorizontal'
Chris@0 20 require 'digest/sha1'
Chris@0 21
Chris@0 22 class ChangesetNotFound < Exception; end
Chris@0 23 class InvalidRevisionParam < Exception; end
Chris@0 24
Chris@0 25 class RepositoriesController < ApplicationController
Chris@0 26 menu_item :repository
Chris@0 27 menu_item :settings, :only => :edit
Chris@0 28 default_search_scope :changesets
Chris@0 29
Chris@0 30 before_filter :find_repository, :except => :edit
Chris@0 31 before_filter :find_project, :only => :edit
Chris@0 32 before_filter :authorize
Chris@0 33 accept_key_auth :revisions
Chris@0 34
Chris@0 35 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
Chris@0 36
Chris@0 37 def edit
Chris@0 38 @repository = @project.repository
Chris@0 39 if !@repository
Chris@0 40 @repository = Repository.factory(params[:repository_scm])
Chris@0 41 @repository.project = @project if @repository
Chris@0 42 end
Chris@0 43 if request.post? && @repository
Chris@0 44 @repository.attributes = params[:repository]
Chris@0 45 @repository.save
Chris@0 46 end
Chris@0 47 render(:update) do |page|
Chris@0 48 page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'
Chris@0 49 if @repository && !@project.repository
Chris@0 50 @project.reload #needed to reload association
Chris@0 51 page.replace_html "main-menu", render_main_menu(@project)
Chris@0 52 end
Chris@0 53 end
Chris@0 54 end
Chris@0 55
Chris@0 56 def committers
Chris@0 57 @committers = @repository.committers
Chris@0 58 @users = @project.users
Chris@0 59 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
Chris@0 60 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
Chris@0 61 @users.compact!
Chris@0 62 @users.sort!
Chris@0 63 if request.post? && params[:committers].is_a?(Hash)
Chris@0 64 # Build a hash with repository usernames as keys and corresponding user ids as values
Chris@0 65 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
Chris@0 66 flash[:notice] = l(:notice_successful_update)
Chris@0 67 redirect_to :action => 'committers', :id => @project
Chris@0 68 end
Chris@0 69 end
Chris@0 70
Chris@0 71 def destroy
Chris@0 72 @repository.destroy
Chris@0 73 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
Chris@0 74 end
Chris@0 75
Chris@0 76 def show
Chris@0 77 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
Chris@0 78
Chris@0 79 @entries = @repository.entries(@path, @rev)
Chris@0 80 if request.xhr?
Chris@0 81 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
Chris@0 82 else
Chris@0 83 (show_error_not_found; return) unless @entries
Chris@0 84 @changesets = @repository.latest_changesets(@path, @rev)
Chris@0 85 @properties = @repository.properties(@path, @rev)
Chris@0 86 render :action => 'show'
Chris@0 87 end
Chris@0 88 end
Chris@0 89
Chris@0 90 alias_method :browse, :show
Chris@0 91
Chris@0 92 def changes
Chris@0 93 @entry = @repository.entry(@path, @rev)
Chris@0 94 (show_error_not_found; return) unless @entry
Chris@0 95 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
Chris@0 96 @properties = @repository.properties(@path, @rev)
Chris@0 97 end
Chris@0 98
Chris@0 99 def revisions
Chris@0 100 @changeset_count = @repository.changesets.count
Chris@0 101 @changeset_pages = Paginator.new self, @changeset_count,
Chris@0 102 per_page_option,
Chris@0 103 params['page']
Chris@0 104 @changesets = @repository.changesets.find(:all,
Chris@0 105 :limit => @changeset_pages.items_per_page,
Chris@0 106 :offset => @changeset_pages.current.offset,
Chris@0 107 :include => [:user, :repository])
Chris@0 108
Chris@0 109 respond_to do |format|
Chris@0 110 format.html { render :layout => false if request.xhr? }
Chris@0 111 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
Chris@0 112 end
Chris@0 113 end
Chris@0 114
Chris@0 115 def entry
Chris@0 116 @entry = @repository.entry(@path, @rev)
Chris@0 117 (show_error_not_found; return) unless @entry
Chris@0 118
Chris@0 119 # If the entry is a dir, show the browser
Chris@0 120 (show; return) if @entry.is_dir?
Chris@0 121
Chris@0 122 @content = @repository.cat(@path, @rev)
Chris@0 123 (show_error_not_found; return) unless @content
Chris@0 124 if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
Chris@0 125 # Force the download
Chris@0 126 send_data @content, :filename => @path.split('/').last
Chris@0 127 else
Chris@0 128 # Prevent empty lines when displaying a file with Windows style eol
Chris@0 129 @content.gsub!("\r\n", "\n")
Chris@0 130 end
Chris@0 131 end
Chris@0 132
Chris@0 133 def annotate
Chris@0 134 @entry = @repository.entry(@path, @rev)
Chris@0 135 (show_error_not_found; return) unless @entry
Chris@0 136
Chris@0 137 @annotate = @repository.scm.annotate(@path, @rev)
Chris@0 138 (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
Chris@0 139 end
Chris@0 140
Chris@0 141 def revision
Chris@0 142 @changeset = @repository.find_changeset_by_name(@rev)
Chris@0 143 raise ChangesetNotFound unless @changeset
Chris@0 144
Chris@0 145 respond_to do |format|
Chris@0 146 format.html
Chris@0 147 format.js {render :layout => false}
Chris@0 148 end
Chris@0 149 rescue ChangesetNotFound
Chris@0 150 show_error_not_found
Chris@0 151 end
Chris@0 152
Chris@0 153 def diff
Chris@0 154 if params[:format] == 'diff'
Chris@0 155 @diff = @repository.diff(@path, @rev, @rev_to)
Chris@0 156 (show_error_not_found; return) unless @diff
Chris@0 157 filename = "changeset_r#{@rev}"
Chris@0 158 filename << "_r#{@rev_to}" if @rev_to
Chris@0 159 send_data @diff.join, :filename => "#{filename}.diff",
Chris@0 160 :type => 'text/x-patch',
Chris@0 161 :disposition => 'attachment'
Chris@0 162 else
Chris@0 163 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
Chris@0 164 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
Chris@0 165
Chris@0 166 # Save diff type as user preference
Chris@0 167 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
Chris@0 168 User.current.pref[:diff_type] = @diff_type
Chris@0 169 User.current.preference.save
Chris@0 170 end
Chris@0 171
Chris@0 172 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
Chris@0 173 unless read_fragment(@cache_key)
Chris@0 174 @diff = @repository.diff(@path, @rev, @rev_to)
Chris@0 175 show_error_not_found unless @diff
Chris@0 176 end
Chris@0 177 end
Chris@0 178 end
Chris@0 179
Chris@0 180 def stats
Chris@0 181 end
Chris@0 182
Chris@0 183 def graph
Chris@0 184 data = nil
Chris@0 185 case params[:graph]
Chris@0 186 when "commits_per_month"
Chris@0 187 data = graph_commits_per_month(@repository)
Chris@0 188 when "commits_per_author"
Chris@0 189 data = graph_commits_per_author(@repository)
Chris@0 190 end
Chris@0 191 if data
Chris@0 192 headers["Content-Type"] = "image/svg+xml"
Chris@0 193 send_data(data, :type => "image/svg+xml", :disposition => "inline")
Chris@0 194 else
Chris@0 195 render_404
Chris@0 196 end
Chris@0 197 end
Chris@0 198
Chris@0 199 private
Chris@0 200 def find_repository
Chris@0 201 @project = Project.find(params[:id])
Chris@0 202 @repository = @project.repository
Chris@0 203 (render_404; return false) unless @repository
Chris@0 204 @path = params[:path].join('/') unless params[:path].nil?
Chris@0 205 @path ||= ''
Chris@0 206 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
Chris@0 207 @rev_to = params[:rev_to]
Chris@0 208 rescue ActiveRecord::RecordNotFound
Chris@0 209 render_404
Chris@0 210 rescue InvalidRevisionParam
Chris@0 211 show_error_not_found
Chris@0 212 end
Chris@0 213
Chris@0 214 def show_error_not_found
Chris@0 215 render_error l(:error_scm_not_found)
Chris@0 216 end
Chris@0 217
Chris@0 218 # Handler for Redmine::Scm::Adapters::CommandFailed exception
Chris@0 219 def show_error_command_failed(exception)
Chris@0 220 render_error l(:error_scm_command_failed, exception.message)
Chris@0 221 end
Chris@0 222
Chris@0 223 def graph_commits_per_month(repository)
Chris@0 224 @date_to = Date.today
Chris@0 225 @date_from = @date_to << 11
Chris@0 226 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
Chris@0 227 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
Chris@0 228 commits_by_month = [0] * 12
Chris@0 229 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
Chris@0 230
Chris@0 231 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
Chris@0 232 changes_by_month = [0] * 12
Chris@0 233 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
Chris@0 234
Chris@0 235 fields = []
Chris@0 236 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
Chris@0 237
Chris@0 238 graph = SVG::Graph::Bar.new(
Chris@0 239 :height => 300,
Chris@0 240 :width => 800,
Chris@0 241 :fields => fields.reverse,
Chris@0 242 :stack => :side,
Chris@0 243 :scale_integers => true,
Chris@0 244 :step_x_labels => 2,
Chris@0 245 :show_data_values => false,
Chris@0 246 :graph_title => l(:label_commits_per_month),
Chris@0 247 :show_graph_title => true
Chris@0 248 )
Chris@0 249
Chris@0 250 graph.add_data(
Chris@0 251 :data => commits_by_month[0..11].reverse,
Chris@0 252 :title => l(:label_revision_plural)
Chris@0 253 )
Chris@0 254
Chris@0 255 graph.add_data(
Chris@0 256 :data => changes_by_month[0..11].reverse,
Chris@0 257 :title => l(:label_change_plural)
Chris@0 258 )
Chris@0 259
Chris@0 260 graph.burn
Chris@0 261 end
Chris@0 262
Chris@0 263 def graph_commits_per_author(repository)
Chris@0 264 commits_by_author = repository.changesets.count(:all, :group => :committer)
Chris@0 265 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
Chris@0 266
Chris@0 267 changes_by_author = repository.changes.count(:all, :group => :committer)
Chris@0 268 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
Chris@0 269
Chris@0 270 fields = commits_by_author.collect {|r| r.first}
Chris@0 271 commits_data = commits_by_author.collect {|r| r.last}
Chris@0 272 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
Chris@0 273
Chris@0 274 fields = fields + [""]*(10 - fields.length) if fields.length<10
Chris@0 275 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
Chris@0 276 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
Chris@0 277
Chris@0 278 # Remove email adress in usernames
Chris@0 279 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
Chris@0 280
Chris@0 281 graph = SVG::Graph::BarHorizontal.new(
Chris@0 282 :height => 400,
Chris@0 283 :width => 800,
Chris@0 284 :fields => fields,
Chris@0 285 :stack => :side,
Chris@0 286 :scale_integers => true,
Chris@0 287 :show_data_values => false,
Chris@0 288 :rotate_y_labels => false,
Chris@0 289 :graph_title => l(:label_commits_per_author),
Chris@0 290 :show_graph_title => true
Chris@0 291 )
Chris@0 292
Chris@0 293 graph.add_data(
Chris@0 294 :data => commits_data,
Chris@0 295 :title => l(:label_revision_plural)
Chris@0 296 )
Chris@0 297
Chris@0 298 graph.add_data(
Chris@0 299 :data => changes_data,
Chris@0 300 :title => l(:label_change_plural)
Chris@0 301 )
Chris@0 302
Chris@0 303 graph.burn
Chris@0 304 end
Chris@0 305
Chris@0 306 end
Chris@0 307
Chris@0 308 class Date
Chris@0 309 def months_ago(date = Date.today)
Chris@0 310 (date.year - self.year)*12 + (date.month - self.month)
Chris@0 311 end
Chris@0 312
Chris@0 313 def weeks_ago(date = Date.today)
Chris@0 314 (date.year - self.year)*52 + (date.cweek - self.cweek)
Chris@0 315 end
Chris@0 316 end
Chris@0 317
Chris@0 318 class String
Chris@0 319 def with_leading_slash
Chris@0 320 starts_with?('/') ? self : "/#{self}"
Chris@0 321 end
Chris@0 322 end