# Redmine - project management software # Copyright (C) 2006-2013 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'SVG/Graph/Bar' require 'SVG/Graph/BarHorizontal' require 'digest/sha1' require 'redmine/scm/adapters/abstract_adapter' require 'tempfile' require 'json' require 'open-uri' class ChangesetNotFound < Exception; end class InvalidRevisionParam < Exception; end class RepositoriesController < ApplicationController include ApplicationHelper menu_item :repository menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers] default_search_scope :changesets before_filter :find_project_by_project_id, :only => [:new, :create, :newrepo, :stats, :quality_analysis] before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] before_filter :find_project_repository, :except => [:new, :create, :newcreate, :edit, :update, :destroy, :committers, :newrepo, :to_gitlab, :forked, :export_rep_static, :training_project_extend] # 连接gitlab # before_filter :connect_gitlab, :only => [:quality_analysis, :commit_diff] before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue] # before_filter :authorize , :except => [:newrepo,:newcreate,:fork, :to_gitlab, :forked, :project_archive, :quality_analysis, :commit_diff] before_filter :authorize_visible , :except => [:newrepo,:newcreate,:fork, :to_gitlab, :forked, :project_archive, :quality_analysis, :commit_diff] # 版本库新增权限 # before_filter :show_rep, :only => [:show, :stats, :revisions, :revision, :diff, :commit_diff ] accept_rss_auth :revisions # hidden repositories filter // 隐藏代码过滤器 # before_filter :check_hidden_repo, :only => [:stats, :revisions, :revision, :diff ] helper :repositories include RepositoriesHelper helper :project_score require 'ostruct' #@root_path = RepositoriesHelper::ROOT_PATH # require 'net/ssh' rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed def new if @project.repositories.count == 0 scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first @repository = Repository.factory(scm) @repository.is_default = @project.repository.nil? @repository.project = @project @course_tag = params[:course] if @course_tag == 1 render :layout => 'base_courses' else render :layout => 'base_projects' end else render_403 end end def export_rep_static # 管理员界面导出所有项目 @project = Project.find(params[:id]) gpid = @project.gpid rev = params[:rev] cycle = params[:cycle] respond_to do |format| format.html format.xls{ filename = "#{@project.name.to_s}_#{l(:label_rep_xls)}.xls" send_data(export_rep_xls(gpid, :rev => rev, :cycle => cycle), :type => 'application/octet-stream', :filename => filename_for_content_disposition(filename)) } end end def forked @project = Project.find(params[:id]) @repository = Repository.where("project_id =? and type =?", @project.id, "Repository::Gitlab") # 如果当前用户已经fork过该项目,不会新fork项目,则跳至已fork的项 unless has_forked?(@project, User.current) project = project_from_current_project(@project.id, User.current.id) redirect_to project_path(project) else # 自己不能fork自己的项目 if User.current.id == @project.user_id flash[:notice] = l(:project_gitlab_fork_own) redirect_to repository_url(@repository) else g = Gitlab.client if User.current.gid.nil? begin s = Trustie::Gitlab::Sync.new s.sync_user(User.current) ensure logger.error "Synv user failed ==>#{User.current.id}" end end gproject = g.fork(@project.gpid, User.current.gid) if gproject copy_project(@project, gproject) forked_count = @project.forked_count.to_i + 1 @project.update_attributes(:forked_count => forked_count) end end end end # 一键ZIP下载 def project_archive token = Gitlab.private_token token = aes_encrypt(token, "abcd") # g = Gitlab.client # g.project_archive(@project.gpid, @rev) # 'git archive --format zip --output /path/to/file.zip master' # 将 master 以zip格式打包到指定文件 # # zip_path = Gitlab.endpoint.to_s + "/projects/" + @project.gpid.to_s + "/repository/archive?&private_token=" + Gitlab.private_token # f = open(zip_path).read # send_file "/path/to/file.zip" end # 开启实训项目,学生会fork一个项目并自动发送任务 def training_project_extend @project = Project.find(params[:id]) @repository = Repository.where("project_id =? and type =?", @project.id, "Repository::Gitlab") # 如果当前用户已经fork过该项目,不会新fork项目,则跳至已fork的项 unless has_forked?(@project, User.current) project = project_from_current_project(@project.id, User.current.id) redirect_to project_path(project) else ActiveRecord::Base.transaction do g = Gitlab.client if User.current.gid.nil? begin s = Trustie::Gitlab::Sync.new s.sync_user(User.current) ensure logger.error "Syn user failed ==>#{User.current.id}" end end gproject = g.fork(@project.gpid, User.current.gid) if gproject new_training_project = copu_project_and_module(@project, gproject) forked_count = @project.forked_count.to_i + 1 @project.update_attributes(:forked_count => forked_count) # 发布实训任务,只发布实训任务的第一个 publish_training_tasks(@project, new_training_project) end end end end def copu_project_and_module tproject, gproject project = Project.new project.name = tproject.name project.is_public = tproject.is_public project.status = tproject.status project.description = tproject.description project.hidden_repo = tproject.hidden_repo project.user_id = User.current.id project.project_type = 0 project.project_new_type = tproject.project_new_type project.gpid = gproject.id project.forked_from_project_id = tproject.id project.enabled_module_names = tproject.enabled_module_names if project.save project.update_attribute(:training_status,1) r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first m = Member.new(:user => User.current, :roles => [r]) if ProjectScore.where("project_id=?", project.id).first.nil? ProjectScore.create(:project_id => project.id, :score => false) end project_info = ProjectInfo.new(:user_id => User.current.id, :project_id => project.id) user_grades = UserGrade.create(:user_id => User.current.id, :project_id => project.id) Rails.logger.debug "UserGrade created: #{user_grades.to_json}" project_status = ProjectStatus.create(:project_id => @project.id, :watchers_count => 0, :changesets_count => 0, :project_type => @project.project_type,:grade => 0) Rails.logger.debug "ProjectStatus created: #{project_status.to_json}" project.members << m project.project_infos << project_info copy_repository(project, gproject) return project else respond_to do |format| format.html { render :action => 'forked', :layout => 'base_projects'} format.api { render_validation_errors(@project) } end end end # REDO: 如果实训项目还没有创建任务的时候应该跳出 def publish_training_tasks original_project, new_training_project original_task = TrainingTask.where(:project_id => original_project.id, :position => 1).first training_task = TrainingTask.new training_task.save_attachments(params[:attachments] || (params[:training_task] && params[:training_task][:uploads])) training_task.subject = original_task.subject training_task.description = original_task.description training_task.position = original_task.position training_task.project_id = new_training_project.id training_task.author_id = User.current.id if training_task.save respond_to do |format| format.html{redirect_to project_training_tasks_url(:project_id => new_training_project.id)} end else raise "create task failed" end end # 判断用户是否已经fork过该项目 def has_forked?(project, user) projects = Project.where("user_id =?", user) projects.map(&:forked_from_project_id).detect{|s| s == @project.id}.nil? ? true : false end # 获取当前用户fork过的项目 def project_from_current_project(project, user) project = Project.where("user_id =? and forked_from_project_id =?",user, project).first end # copy a project for fork def copy_project(tproject, gproject) project = Project.new project.name = tproject.name project.is_public = tproject.is_public project.status = tproject.status project.description = tproject.description project.hidden_repo = tproject.hidden_repo project.user_id = User.current.id project.project_type = 0 project.project_new_type = tproject.project_new_type project.gpid = gproject.id project.forked_from_project_id = tproject.id if project.save r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first m = Member.new(:user => User.current, :roles => [r]) if ProjectScore.where("project_id=?", project.id).first.nil? ProjectScore.create(:project_id => project.id, :score => false) end project_info = ProjectInfo.new(:user_id => User.current.id, :project_id => project.id) user_grades = UserGrade.create(:user_id => User.current.id, :project_id => project.id) Rails.logger.debug "UserGrade created: #{user_grades.to_json}" project_status = ProjectStatus.create(:project_id => @project.id, :watchers_count => 0, :changesets_count => 0, :project_type => @project.project_type,:grade => 0) Rails.logger.debug "ProjectStatus created: #{project_status.to_json}" project.members << m project.project_infos << project_info copy_repository(project, gproject) respond_to do |format| format.html { flash[:notice] = l(:notice_successful_create) if params[:continue] attrs = {:parent_id => project.parent_id}.reject {|k,v| v.nil?} redirect_to new_project_url(attrs, :course => '0') else redirect_to project_path(project) end } format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => project.id) } format.js end return project else respond_to do |format| format.html { render :action => 'forked', :layout => 'base_projects'} format.api { render_validation_errors(@project) } end end end def copy_repository(project, gproject) # 避免 # if is_sigle_identifier?(project.user_id, gproject.name) repository = Repository.factory('Git') repository.project_id = project.id repository.type = 'Repository::Gitlab' repository.url = gproject.name repository.identifier = gproject.name repository = repository.save # else # flash[:notice] = l(:project_gitlab_create_double_message) # end end def newrepo scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first @repository = Repository.factory(scm) @repository.is_default = @project.repository.nil? @repository.project = @project @course_tag = params[:course] if @course_tag == 1 render :layout => 'base_courses' else render :layout => 'base_projects' end end def fork @repository_url = params[:repository_url] # @repository.url # system "htpasswd -mb "+@root_path+"user.passwd "+params[:repository][:identifier]+" "+@upasswd # system "echo -e '"+params[:project_id]+"-"+params[:repository][:identifier]+"-write:"+ # " "+params[:repository][:identifier]+"' >> "+@root_path+"group.passwd" system "git clone --bare "+@repository_url # system "mv "+@project_path+"/hooks/post-update{.sample,}" # system "chmod a+x "+@project_path+"/hooks/post-update" # system "."+@project_path+"/hooks/post-update" # system "echo -e 'Allow from all \n Order Deny,Allow \n "+ # " \n"+ # "Require group "+params[:project_id]+"-"+params[:repository][:identifier]+"-write \n "+ # " \n ' >>"+ # @project_path+"/.htaccess" flash[:notice] = l(:label_notice_fork_successed) @repositories = @project.repositories render :action => 'show', :layout => 'base_projects' end HOOK_TEMPLATE = %Q{#!/bin/sh exec sh -c ' function update() { CMD_PATH=`dirname $0`; cd $CMD_PATH; PY_PATH=$PWD/../../git_refresh_changes.py; [[ -s "$PY_PATH" ]] && $(which python) $PY_PATH $PWD; cd -; } git update-server-info update ' } def create # 判断版本库创建者是否有同名版本库,避免版本库路径一致问题 unless is_sigle_identifier?(@project.user_id, params[:repository].first[1]) flash[:notice] = l(:project_gitlab_create_double_message) redirect_to settings_project_url(@project, :tab => 'repositories') else attrs = pickup_extra_info @repository = Repository.factory('Git') @repository.safe_attributes = params[:repository] if attrs[:attrs_extra].keys.any? @repository.merge_extra_info(attrs[:attrs_extra]) end @repository.project = @project @repository.type = 'Repository::Gitlab' @repository.identifier = @repository.identifier.downcase @repository.url = @repository.identifier ActiveRecord::Base.transaction do begin if request.post? && @repository.save s = Trustie::Gitlab::Sync.new s.create_project(@project, @repository) raise "sync failed" if @project.gpid.blank? redirect_to(:controller => 'repositories', :action => 'show', :id => @project, :repository_id => gitlab_repository(@project).try(:identifier)) else redirect_to settings_project_url(@project, :tab => 'repositories',:repository_error_message=>@repository.errors.full_messages) end rescue Gitlab::Error::Forbidden => e @message = l(:label_pull_request_forbidden) rescue Gitlab::Error::NotFound => e @message = l(:label_pull_request_notfound) rescue Exception => e puts e end end end end def edit end def update attrs = pickup_extra_info @repository.safe_attributes = attrs[:attrs] if attrs[:attrs_extra].keys.any? @repository.merge_extra_info(attrs[:attrs_extra]) end @repository.project = @project if request.put? && @repository.save redirect_to settings_project_url(@project, :tab => 'repositories') else render :action => 'edit' end end def pickup_extra_info p = {} p_extra = {} params[:repository].each do |k, v| if k =~ /^extra_/ p_extra[k] = v else p[k] = v end end {:attrs => p, :attrs_extra => p_extra} end private :pickup_extra_info def committers @committers = @repository.committers @users = @project.users additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id) @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty? @users.compact! @users.sort! if request.post? && params[:committers].is_a?(Hash) # Build a hash with repository usernames as keys and corresponding user ids as values @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h} flash[:notice] = l(:notice_successful_update) respond_to do |format| format.html{ render :layout => "base_projects" } end elsif request.get? respond_to do |format| format.html{ render :layout => "base_projects" } end end end def quality_analysis gitlab_branches = @g.branches(@project.gpid) @branch_names = gitlab_branches.map{|b| b.name} @gitlab_default_branch = @g_project.default_branch # language = params[:language] # branch = params[:branch] # path = params[:path] # user_name = User.find(@project.user_id).try(:login) # rep_name = params[:repository_id] # host = "192.168.0.200" # port = "1125" # username = "git" # password = "123123" # server_cmd1 = "sh gitclone.sh" + " " + user_name + " " + rep_name # # 连接到远程主机 foobar # ssh = Net::SSH.start(host, username, :port => port, :password => password) do |ssh| # result = ssh.exec!(server_cmd1) # path = "/home/git/repo/" + user_name + "/" + rep_name # # sonar 分析 # # server_cmd2 # # 删除clone的版本库 # # server_cmd3 # end respond_to do |format| format.js format.html{ render :layout => "base_projects" } end end def destroy DestroyRepositoryTask.new.destroy(User.current.id, @repository.id) @repository.hidden = true @repository.save redirect_to settings_project_url(@project, :tab => 'repositories') end def to_gitlab @project = Project.find(params[:project_id]) @repository = Repository.find(params[:id]) s = Trustie::Gitlab::Sync.new s.sync_project(@project, path: params[:repo_name], import_url: @repository.url) @repository.type = 'Repository::Gitlab' @repository.save redirect_to :controller => 'repositories', :action => 'show', :id => @project.id, to: 'gitlab' end # 权限: # 如果项目隐藏了版本库,则非项目成员及项目报告人员不能够访问版本库 # 如果没有隐藏版本库,只要项目公开,其它成员都可以看到版本库 # 项目关联了课程,课程的老师是可以看到版本库的 # 超级管理员可以看到项目版本库 def show # 顶部导航 @project_menu_type = 5 # TODO: the below will move to filter, done. # 获取版本库目录结构 @entries = @repository.entries(@path, @rev) if request.xhr? @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) else @changesets_latest_coimmit = @g.rep_last_changes(@project.gpid, :rev => @rev, :path => @path) # @g.rep_last_changes(@project.gpid, :rev => @rev, :path => @path) # 总的提交数 @changesets_all_count = @g.user_static(@project.gpid, :rev => @rev).count # 获取默认分支 @g_default_branch = @g_project.default_branch.nil? ? "master" : @g_project.default_branch @creator = @project.owner.to_s gitlab_address = Redmine::Configuration['gitlab_address'] gitlab_token = Gitlab.private_token # token值加密解密 token = aes_encrypt("priEn3UwXfJs3Pmy", gitlab_token) # token值解密 # gitlab_token = aes_dicrypt("priEn3UwXfJs3Pmy", token) @zip_path = Gitlab.endpoint.to_s + "/projects/" + @project.gpid.to_s + "/repository/archive?&private_token=" + token @creator = @project.owner.to_s gitlab_address = Redmine::Configuration['gitlab_address'] @repos_url = gitlab_address.to_s+"/" + @creator + "/" + @repository.identifier+"."+"git" # 一些数据的异步同步更新 # 访问版本庫后更新project_score表数据;changeset_num为提交总数 project_score = @project.project_score if project_score.nil? ProjectScore.create(:project_id => @project.id, :score => false) else project_score.update_column(:changeset_num, @changesets_all_count) end # 更新提交时间,用于课程 unless @changesets_latest_coimmit.blank? update_commits_date(@project, @changesets_latest_coimmit) end render :layout => 'base_projects' end end alias_method :browse, :show # 获取版本文件目录的 def tree_head_message end # 注:由于考虑到性能所以commits api每次返回20条记录 def changes # 顶部导航 @project_menu_type = 5 @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry g = Gitlab.client limit = 10 # 每次页面的换回值从1开始,但是gitlab的页面查询是从0开始,所以先改变page的类型减一在改回来 @commits = g.commits(@project.gpid, page:(params[:page].to_i - 1).to_s, ref_name:@rev) @commits_count = params[:commit_count].nil? ? @g.user_static(@project.gpid, :rev => @rev).count : params[:commit_count].to_i @commits_pages = Paginator.new @commits_count, limit, params[:page] # @offset ||= @commits_pages.offset # @commits = paginateHelper @commits, limit render :layout => 'base_projects' end def revisions @changeset_count = @repository.changesets.count @changeset_pages = Paginator.new @changeset_count, per_page_option, params['page'] @changesets = @repository.changesets. limit(@changeset_pages.per_page). offset(@changeset_pages.offset). includes(:user, :repository, :parents). all respond_to do |format| format.html { render :layout => 'base_projects' } format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } end end def raw entry_and_raw(true) end def entry # 顶部导航 @project_menu_type = 5 entry_and_raw(false) @content = @repository.cat(@path, @rev) # @changesets_latest_coimmit = @g.commit(@project.gpid, @entry.try(:lastrev)) @changesets_latest_coimmit = @g.rep_last_changes(@project.gpid, :rev => @rev, :path => @path) # 总的提交数 @changesets_all_count = @g.user_static(@project.gpid, :rev => @rev).count if is_entry_text_data?(@content, @path) render :layout => 'base_projects' end end def entry_edit entry_and_raw(false) @content = @repository.cat(@path, @rev) # @changesets_latest_coimmit = @g.commit(@project.gpid, @entry.try(:lastrev)) @changesets_latest_coimmit = @g.rep_last_changes(@project.gpid, :rev => @rev, :path => @path) # 总的提交数 @changesets_all_count = @g.user_static(@project.gpid, :rev => @rev).count if is_entry_text_data?(@content, @path) render :layout => 'base_projects' end end def entry_update g = Gitlab.client @path = params[:path] @rev = params[:rev] @content = params[:content] code_file = g.edit_file(@project.gpid, :content => params[:content], :file_path => @path, :branch_name => @rev, :commit_message => "...") # if @shixun.try(:status).to_i < 2 # shixun_modify_status_without_publish(@shixun, 1) # end respond_to do |format| format.js end end def entry_and_raw(is_raw) @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry # If the entry is a dir, show the browser (show; return) if @entry.is_dir? @content = @repository.cat(@path, @rev) (show_error_not_found; return) unless @content if is_raw || (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || !is_entry_text_data?(@content, @path) # Force the download send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } send_type = Redmine::MimeType.of(@path) send_opt[:type] = send_type.to_s if send_type send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment') send_data @content, send_opt else # Prevent empty lines when displaying a file with Windows style eol # TODO: UTF-16 # Is this needs? AttachmentsController reads file simply. @content.gsub!("\r\n", "\n") @changeset = @repository.find_changeset_by_name(@rev) end end private :entry_and_raw def is_entry_text_data?(ent, path) # UTF-16 contains "\x00". # It is very strict that file contains less than 30% of ascii symbols # in non Western Europe. return true if Redmine::MimeType.is_type?('text', path) # Ruby 1.8.6 has a bug of integer divisions. # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F return false if ent.is_binary_data? true end private :is_entry_text_data? def annotate @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry @annotate = @repository.scm.annotate(@path, @rev) if @annotate.nil? || @annotate.empty? (render_error l(:error_scm_annotate); return) end ann_buf_size = 0 @annotate.lines.each do |buf| ann_buf_size += buf.size end if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte (render_error l(:error_scm_annotate_big_text_file); return) end @changeset = @repository.find_changeset_by_name(@rev) end def revision respond_to do |format| format.html{render :layout => 'base_projects'} format.js {render :layout => false} end end # Adds a related issue to a changeset # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues def add_related_issue @issue = @changeset.find_referenced_issue_by_id(params[:issue_id]) if @issue && (!@issue.visible? || @changeset.issues.include?(@issue)) @issue = nil end if @issue @changeset.issues << @issue end end # Removes a related issue from a changeset # DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id def remove_related_issue @issue = Issue.visible.find_by_id(params[:issue_id]) if @issue @changeset.issues.delete(@issue) end end # 每次提交对应的文件差异 def commit_diff # 顶部导航 @project_menu_type = 5 @commit_diff = @g.commit_diff(@project.gpid, params[:changeset]) diff = ActiveSupport::JSON.decode(@commit_diff.to_json).first diff = OpenStruct.new(diff) @diff_file = Trustie::Gitlab::Diff::File.new(diff) @commit_details = @g.commit(@project.gpid, params[:changeset]) render :layout => 'base_projects' end def diff if params[:format] == 'diff' @diff = @repository.diff(@path, @rev, @rev_to) (show_error_not_found; return) unless @diff filename = "changeset_r#{@rev}" filename << "_r#{@rev_to}" if @rev_to send_data @diff.join, :filename => "#{filename}.diff", :type => 'text/x-patch', :disposition => 'attachment' else @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) # Save diff type as user preference if User.current.logged? && @diff_type != User.current.pref[:diff_type] User.current.pref[:diff_type] = @diff_type User.current.preference.save end @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}") unless read_fragment(@cache_key) @diff = @repository.diff(@path, @rev, @rev_to) unless @diff show_error_not_found return end end @changeset = @repository.find_changeset_by_name(@rev) @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to) end render :layout => 'base_projects' end def stats if @project.gpid.nil? render 404 return end project_id = @project.gpid # @repository_id = @repository.identifier # creator = params[:creator] rev = params[:rev] g = Gitlab.client begin @static_total_per_user = g.rep_stats(project_id, :rev => rev) # 更新rep_statics统计数 @static_total_per_user.each do |static| rep_static = RepStatics.where("project_id =? and email =?", @project.id, static.email.to_s).first if rep_static.nil? RepStatics.create(:project_id => @project.id, :uname => static.uname, :commits_num => static.commits_num, :email => static.email, :add => static.add, :del => static.del, :changeset => static.changes) else if @rev == params[:default_branch] rep_static.update_attributes(:uname => static.uname, :commits_num => static.commits_num, :email => static.email, :add => static.add, :del => static.del, :changeset => static.changes) end end end rescue render_404 return end render :layout => 'base_projects' end def graph data = nil case params[:graph] when "commits_per_month" data = graph_commits_per_month(@repository) when "commits_per_author" data = graph_commits_per_author(@repository) when "author_commits_per_month" data = graph_author_commits_per_month(@repository) when "author_commits_six_month" data = author_commits_six_month(@repository) when "author_code_six_months" data = author_code_six_month(@repository) end if data headers["Content-Type"] = "image/svg+xml" send_data(data, :type => "image/svg+xml", :disposition => "inline") else render_404 end end def authorize_visible allowed = authorize_allowed(params[:controller], params[:action], global = false) if allowed || User.current.admin? || (@project.hidden_repo && User.current.member_of?(@project) && !role_of_members_in_project(@project.id, User.current.id) == "Reporter") true else if @project && @project.archived? render_403 :message => :notice_not_authorized_archived_project else deny_access end end end private # 更新项目统计数 def update_commits_count project, count project.project_score.update_attribute(:changeset_num, count) end # 更新项目提交次数时间 def update_commits_date project, date project.project_score.update_attribute(:commit_time, date.created_at) end # 链接gitlab def connect_gitlab begin @g = Gitlab.client unless @project.gpid.nil? @g_project = @g.project(@project.gpid) end rescue => e logger.error("connect gitlab failed ==> #{e}") end end def show_rep visible_repository?(@project) end def find_repository @repository = Repository.find(params[:id]) @project = @repository.project rescue ActiveRecord::RecordNotFound render_404 end REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i REP_TYPE = "Repository::Gitlab" # 获取项目、版本库、路劲、默认分支 def find_project_repository @project = Project.find(params[:id]) @repository = Repository.where(:type => REP_TYPE, :project_id => @project).first (render_404; return false) unless (@repository || @project.gpid) @path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s @g = Gitlab.client @g_project = @g.project(@project.gpid) @g_default_branch = @g_project.default_branch # gitlab端获取默认分支 @rev = params[:rev].blank? ? @g_default_branch : params[:rev].to_s.strip @rev_to = params[:rev_to] unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) if @repository.branches.blank? raise InvalidRevisionParam end end rescue ActiveRecord::RecordNotFound render_404 rescue InvalidRevisionParam show_error_not_found end def find_changeset if @rev.present? @changeset = @repository.find_changeset_by_name(@rev) end show_error_not_found unless @changeset end def show_error_not_found render_error :message => l(:error_scm_not_found), :status => 404 end def show_error_forbidden render_error :status => 403 end # Handler for Redmine::Scm::Adapters::CommandFailed exception def show_error_command_failed(exception) render_error l(:error_scm_command_failed, exception.message) end def graph_commits_per_month(repository) @date_to = Date.today @date_from = @date_to << 11 @date_from = Date.civil(@date_from.year, @date_from.month, 1) commits_by_day = Changeset.count( :all, :group => :commit_date, :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) commits_by_month = [0] * 12 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } changes_by_day = Change.count( :all, :group => :commit_date, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) changes_by_month = [0] * 12 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last } fields = [] 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)} graph = SVG::Graph::Bar.new( :height => 300, :width => 600, :fields => fields.reverse, :stack => :side, :scale_integers => true, :step_x_labels => 2, :show_data_values => true, :graph_title => l(:label_commits_per_month), :show_graph_title => true ) # 具状图 graph.add_data( :data => commits_by_month[0..11].reverse, :title => l(:label_revision_plural) ) graph.add_data( :data => changes_by_month[0..11].reverse, :title => l(:label_change_plural) ) graph.burn end def graph_commits_per_author(repository) commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id]) commits_by_author = commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}.last(25) changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id]) h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} fields = commits_by_author.collect {|r| r.first} commits_data = commits_by_author.collect {|r| r.last} changes_data = commits_by_author.collect {|r| h[r.first] || 0} fields = fields + [""]*(10 - fields.length) if fields.length<10 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10 # Remove email adress in usernames fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } graph = SVG::Graph::BarHorizontal.new( :height => 400, :width => 600, :fields => fields, :stack => :side, :scale_integers => true, :show_data_values => true, :rotate_y_labels => false, :graph_title => l(:label_commits_per_author), :show_graph_title => true ) graph.add_data( :data => commits_data, :title => l(:label_revision_plural) ) graph.add_data( :data => changes_data, :title => l(:label_change_plural) ) graph.burn end # 用户最近一年的提交次数 def graph_author_commits_per_month(repository) @date_to = Date.today @date_from = @date_to << 12 @date_from = Date.civil(@date_from.year, @date_from.month, @date_from.day) commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) commits_by_author = commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}.last(25) fields = commits_by_author.collect {|r| r.first} commits_data = commits_by_author.collect {|r| r.last} fields = fields + [""]*(10 - fields.length) if fields.length<10 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 # Remove email adress in usernames fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } graph = SVG::Graph::BarHorizontal.new( :height => 400, :width => 600, :fields => fields, :stack => :side, :scale_integers => true, :show_data_values => true, :rotate_y_labels => false, :graph_title => l(:label_author_commits_year), :show_graph_title => true, :no_css => true ) graph.add_data( :data => commits_data, :title => l(:label_revision_commit_count) ) graph.burn end # 用户最近六个月的提交次数 def author_commits_six_month(repository) @date_to = Date.today @date_from = @date_to << 6 @date_from = Date.civil(@date_from.year, @date_from.month, @date_from.day) commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) commits_by_author = commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}.last(25) fields = commits_by_author.collect {|r| r.first} commits_data = commits_by_author.collect {|r| r.last} fields = fields + [""]*(10 - fields.length) if fields.length<10 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 # Remove email adress in usernames fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } graph = SVG::Graph::BarHorizontal.new( :height => 400, :width => 600, :fields => fields, :stack => :side, :scale_integers => true, :show_data_values => true, :rotate_y_labels => false, :graph_title => l(:label_author_commits_six_month), :show_graph_title => true ) graph.add_data( :data => commits_data, :title => l(:label_revision_commit_count) ) graph.burn end # 最近六个月代码量统计 def author_code_six_month(repository) @date_to = Date.today @date_from = @date_to << 6 @date_from = Date.civil(@date_from.year, @date_from.month, @date_from.day) commits_by_author = Changeset.count(:group => :committer, :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to]) commits_by_author = commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}.last(40) all_author = [] commits_by_author.each do |cba| all_author << cba.first end # all_author = all_author.collect {|c| c.gsub(%r{/ /<.+@.+>}, '') } all_author = all_author.collect {|c| c.split.first } commits_by_author = repository.commits(all_author, "#{@date_from}", "#{@date_to}", repository.id == 150 ? "szzh" : 'master') fields = commits_by_author.collect {|r| r.first} commits_data = commits_by_author.collect {|r| r.last} fields = fields + [""]*(10 - fields.length) if fields.length<10 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 # Remove email adress in usernames fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } graph = SVG::Graph::BarHorizontal.new( :height => 400, :width => 600, :fields => fields, :stack => :side, :scale_integers => true, :show_data_values => true, :rotate_y_labels => false, :graph_title => l(:label_author_code_six_month), :show_graph_title => true ) graph.add_data( :data => commits_data, :title => l(:lable_revision_code_count) ) graph.burn end end