"Fossies" - the Fresh Open Source Software Archive

Member "redmine-4.1.1/app/controllers/repositories_controller.rb" (6 Apr 2020, 13803 Bytes) of package /linux/www/redmine-4.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "repositories_controller.rb": 4.1.0_vs_4.1.1.

    1 # frozen_string_literal: true
    2 
    3 # Redmine - project management software
    4 # Copyright (C) 2006-2019  Jean-Philippe Lang
    5 #
    6 # This program is free software; you can redistribute it and/or
    7 # modify it under the terms of the GNU General Public License
    8 # as published by the Free Software Foundation; either version 2
    9 # of the License, or (at your option) any later version.
   10 #
   11 # This program is distributed in the hope that it will be useful,
   12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 # GNU General Public License for more details.
   15 #
   16 # You should have received a copy of the GNU General Public License
   17 # along with this program; if not, write to the Free Software
   18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   19 
   20 require 'digest/sha1'
   21 require 'redmine/scm/adapters'
   22 
   23 class ChangesetNotFound < StandardError; end
   24 class InvalidRevisionParam < StandardError; end
   25 
   26 class RepositoriesController < ApplicationController
   27   menu_item :repository
   28   menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
   29   default_search_scope :changesets
   30 
   31   before_action :find_project_by_project_id, :only => [:new, :create]
   32   before_action :build_new_repository_from_params, :only => [:new, :create]
   33   before_action :find_repository, :only => [:edit, :update, :destroy, :committers]
   34   before_action :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
   35   before_action :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
   36   before_action :authorize
   37   accept_rss_auth :revisions
   38 
   39   rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
   40 
   41   def new
   42     @repository.is_default = @project.repository.nil?
   43   end
   44 
   45   def create
   46     if @repository.save
   47       redirect_to settings_project_path(@project, :tab => 'repositories')
   48     else
   49       render :action => 'new'
   50     end
   51   end
   52 
   53   def edit
   54   end
   55 
   56   def update
   57     @repository.safe_attributes = params[:repository]
   58     if @repository.save
   59       redirect_to settings_project_path(@project, :tab => 'repositories')
   60     else
   61       render :action => 'edit'
   62     end
   63   end
   64 
   65   def committers
   66     @committers = @repository.committers
   67     @users = @project.users.to_a
   68     additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
   69     @users += User.where(:id => additional_user_ids).to_a unless additional_user_ids.empty?
   70     @users.compact!
   71     @users.sort!
   72     if request.post? && params[:committers].present?
   73       # Build a hash with repository usernames as keys and corresponding user ids as values
   74       @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
   75       flash[:notice] = l(:notice_successful_update)
   76       redirect_to settings_project_path(@project, :tab => 'repositories')
   77     end
   78   end
   79 
   80   def destroy
   81     @repository.destroy if request.delete?
   82     redirect_to settings_project_path(@project, :tab => 'repositories')
   83   end
   84 
   85   def show
   86     @repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty?
   87 
   88     @entries = @repository.entries(@path, @rev)
   89     @changeset = @repository.find_changeset_by_name(@rev)
   90     if request.xhr?
   91       @entries ? render(:partial => 'dir_list_content') : head(200)
   92     else
   93       (show_error_not_found; return) unless @entries
   94       @changesets = @repository.latest_changesets(@path, @rev)
   95       @properties = @repository.properties(@path, @rev)
   96       @repositories = @project.repositories
   97       render :action => 'show'
   98     end
   99   end
  100 
  101   alias_method :browse, :show
  102 
  103   def changes
  104     @entry = @repository.entry(@path, @rev)
  105     (show_error_not_found; return) unless @entry
  106     @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
  107     @properties = @repository.properties(@path, @rev)
  108     @changeset = @repository.find_changeset_by_name(@rev)
  109   end
  110 
  111   def revisions
  112     @changeset_count = @repository.changesets.count
  113     @changeset_pages = Paginator.new @changeset_count,
  114                                      per_page_option,
  115                                      params['page']
  116     @changesets = @repository.changesets.
  117       limit(@changeset_pages.per_page).
  118       offset(@changeset_pages.offset).
  119       includes(:user, :repository, :parents).
  120       to_a
  121 
  122     respond_to do |format|
  123       format.html { render :layout => false if request.xhr? }
  124       format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
  125     end
  126   end
  127 
  128   def raw
  129     entry_and_raw(true)
  130   end
  131 
  132   def entry
  133     entry_and_raw(false)
  134   end
  135 
  136   def entry_and_raw(is_raw)
  137     @entry = @repository.entry(@path, @rev)
  138     (show_error_not_found; return) unless @entry
  139 
  140     # If the entry is a dir, show the browser
  141     (show; return) if @entry.is_dir?
  142 
  143     if is_raw
  144       # Force the download
  145       send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
  146       send_type = Redmine::MimeType.of(@path)
  147       send_opt[:type] = send_type.to_s if send_type
  148       send_opt[:disposition] = disposition(@path)
  149       send_data @repository.cat(@path, @rev), send_opt
  150     else
  151       # set up pagination from entry to entry
  152       parent_path = @path.split('/')[0...-1].join('/')
  153       @entries = @repository.entries(parent_path, @rev).reject(&:is_dir?)
  154       if index = @entries.index{|e| e.name == @entry.name}
  155         @paginator = Redmine::Pagination::Paginator.new(@entries.size, 1, index+1)
  156       end
  157 
  158       if !@entry.size || @entry.size <= Setting.file_max_size_displayed.to_i.kilobyte
  159         content = @repository.cat(@path, @rev)
  160         (show_error_not_found; return) unless content
  161 
  162         if content.size <= Setting.file_max_size_displayed.to_i.kilobyte &&
  163            is_entry_text_data?(content, @path)
  164           # TODO: UTF-16
  165           # Prevent empty lines when displaying a file with Windows style eol
  166           # Is this needed? AttachmentsController simply reads file.
  167           @content = content.gsub("\r\n", "\n")
  168         end
  169       end
  170       @changeset = @repository.find_changeset_by_name(@rev)
  171     end
  172   end
  173   private :entry_and_raw
  174 
  175   def is_entry_text_data?(ent, path)
  176     # UTF-16 contains "\x00".
  177     # It is very strict that file contains less than 30% of ascii symbols
  178     # in non Western Europe.
  179     return true if Redmine::MimeType.is_type?('text', path)
  180     # Ruby 1.8.6 has a bug of integer divisions.
  181     # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
  182     return false if Redmine::Scm::Adapters::ScmData.binary?(ent)
  183     true
  184   end
  185   private :is_entry_text_data?
  186 
  187   def annotate
  188     @entry = @repository.entry(@path, @rev)
  189     (show_error_not_found; return) unless @entry
  190 
  191     @annotate = @repository.scm.annotate(@path, @rev)
  192     if @annotate.nil? || @annotate.empty?
  193       @annotate = nil
  194       @error_message = l(:error_scm_annotate)
  195     else
  196       ann_buf_size = 0
  197       @annotate.lines.each do |buf|
  198         ann_buf_size += buf.size
  199       end
  200       if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
  201         @annotate = nil
  202         @error_message = l(:error_scm_annotate_big_text_file)
  203       end
  204     end
  205     @changeset = @repository.find_changeset_by_name(@rev)
  206   end
  207 
  208   def revision
  209     respond_to do |format|
  210       format.html
  211       format.js {render :layout => false}
  212     end
  213   end
  214 
  215   # Adds a related issue to a changeset
  216   # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
  217   def add_related_issue
  218     issue_id = params[:issue_id].to_s.sub(/^#/,'')
  219     @issue = @changeset.find_referenced_issue_by_id(issue_id)
  220     if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
  221       @issue = nil
  222     end
  223 
  224     if @issue
  225       @changeset.issues << @issue
  226     end
  227   end
  228 
  229   # Removes a related issue from a changeset
  230   # DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
  231   def remove_related_issue
  232     @issue = Issue.visible.find_by_id(params[:issue_id])
  233     if @issue
  234       @changeset.issues.delete(@issue)
  235     end
  236   end
  237 
  238   def diff
  239     if params[:format] == 'diff'
  240       @diff = @repository.diff(@path, @rev, @rev_to)
  241       (show_error_not_found; return) unless @diff
  242       filename = +"changeset_r#{@rev}"
  243       filename << "_r#{@rev_to}" if @rev_to
  244       send_data @diff.join, :filename => "#{filename}.diff",
  245                             :type => 'text/x-patch',
  246                             :disposition => 'attachment'
  247     else
  248       @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
  249       @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
  250 
  251       # Save diff type as user preference
  252       if User.current.logged? && @diff_type != User.current.pref[:diff_type]
  253         User.current.pref[:diff_type] = @diff_type
  254         User.current.preference.save
  255       end
  256       @cache_key = "repositories/diff/#{@repository.id}/" +
  257                       Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
  258       unless read_fragment(@cache_key)
  259         @diff = @repository.diff(@path, @rev, @rev_to)
  260         (show_error_not_found; return) unless @diff
  261       end
  262 
  263       @changeset = @repository.find_changeset_by_name(@rev)
  264       @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
  265       @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
  266       render :diff, :formats => :html, :layout => 'base.html.erb'
  267     end
  268   end
  269 
  270   def stats
  271   end
  272 
  273   # Returns JSON data for repository graphs
  274   def graph
  275     data = nil
  276     case params[:graph]
  277     when "commits_per_month"
  278       data = graph_commits_per_month(@repository)
  279     when "commits_per_author"
  280       data = graph_commits_per_author(@repository)
  281     end
  282     if data
  283       render :json => data
  284     else
  285       render_404
  286     end
  287   end
  288 
  289   private
  290 
  291   def build_new_repository_from_params
  292     scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
  293     unless @repository = Repository.factory(scm)
  294       render_404
  295       return
  296     end
  297 
  298     @repository.project = @project
  299     @repository.safe_attributes = params[:repository]
  300     @repository
  301   end
  302 
  303   def find_repository
  304     @repository = Repository.find(params[:id])
  305     @project = @repository.project
  306   rescue ActiveRecord::RecordNotFound
  307     render_404
  308   end
  309 
  310   REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
  311 
  312   def find_project_repository
  313     @project = Project.find(params[:id])
  314     if params[:repository_id].present?
  315       @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
  316     else
  317       @repository = @project.repository
  318     end
  319     (render_404; return false) unless @repository
  320     @path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
  321     @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
  322     @rev_to = params[:rev_to]
  323 
  324     unless REV_PARAM_RE.match?(@rev.to_s) && REV_PARAM_RE.match?(@rev_to.to_s)
  325       if @repository.branches.blank?
  326         raise InvalidRevisionParam
  327       end
  328     end
  329   rescue ActiveRecord::RecordNotFound
  330     render_404
  331   rescue InvalidRevisionParam
  332     show_error_not_found
  333   end
  334 
  335   def find_changeset
  336     if @rev.present?
  337       @changeset = @repository.find_changeset_by_name(@rev)
  338     end
  339     show_error_not_found unless @changeset
  340   end
  341 
  342   def show_error_not_found
  343     render_error :message => l(:error_scm_not_found), :status => 404
  344   end
  345 
  346   # Handler for Redmine::Scm::Adapters::CommandFailed exception
  347   def show_error_command_failed(exception)
  348     render_error l(:error_scm_command_failed, exception.message)
  349   end
  350 
  351   def graph_commits_per_month(repository)
  352     date_to = User.current.today
  353     date_from = date_to << 11
  354     date_from = Date.civil(date_from.year, date_from.month, 1)
  355     commits_by_day = Changeset.
  356       where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, date_from, date_to).
  357       group(:commit_date).
  358       count
  359     commits_by_month = [0] * 12
  360     commits_by_day.each {|c| commits_by_month[(date_to.month - c.first.to_date.month) % 12] += c.last }
  361 
  362     changes_by_day = Change.
  363       joins(:changeset).
  364       where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, date_from, date_to).
  365       group(:commit_date).
  366       count
  367     changes_by_month = [0] * 12
  368     changes_by_day.each {|c| changes_by_month[(date_to.month - c.first.to_date.month) % 12] += c.last }
  369 
  370     fields = []
  371     today = User.current.today
  372     12.times {|m| fields << month_name(((today.month - 1 - m) % 12) + 1)}
  373 
  374     data = {
  375       :labels => fields.reverse,
  376       :commits => commits_by_month[0..11].reverse,
  377       :changes => changes_by_month[0..11].reverse
  378     }
  379   end
  380 
  381   def graph_commits_per_author(repository)
  382     # data
  383     stats = repository.stats_by_author
  384     fields, commits_data, changes_data = [], [], []
  385     stats.each do |name, hsh|
  386       fields << name
  387       commits_data << hsh[:commits_count]
  388       changes_data << hsh[:changes_count]
  389     end
  390 
  391     # expand to 10 values if needed
  392     fields = fields + [""]*(10 - fields.length) if fields.length<10
  393     commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
  394     changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
  395 
  396     # Remove email address in usernames
  397     fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
  398 
  399     data = {
  400       :labels => fields.reverse,
  401       :commits => commits_data.reverse,
  402       :changes => changes_data.reverse
  403     }
  404   end
  405 
  406   def disposition(path)
  407     if Redmine::MimeType.of(@path) == "application/pdf"
  408       'inline'
  409     else
  410       'attachment'
  411     end
  412   end
  413 end