diff --git a/app/controllers/contributions_controller.rb b/app/controllers/contributions_controller.rb index 017970c43..cba11358f 100644 --- a/app/controllers/contributions_controller.rb +++ b/app/controllers/contributions_controller.rb @@ -1,71 +1,1112 @@ -# Redmine - project management software - -class ContributionsController < ApplicationController - - - # 权限: - # 如果项目隐藏了版本库,则非项目成员及项目报告人员不能够访问版本库 - # 如果没有隐藏版本库,只要项目公开,其它成员都可以看到版本库 - # 项目关联了课程,课程的老师是可以看到版本库的 - # 超级管理员可以看到项目版本库 - def show - # 顶部导航 - @project_menu_type = 5 - - Rails.logger.info("!!!!!repository_id is: ") - Rails.logger.info(params[:repository_id]) - - # 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) - @changesets_latest_coimmit = @g.commits(@project.gpid, :rev => @rev, :path => @path).try(:first) - # @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.try(:default_branch).nil? ? "master" : @g_project.try(: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 - @repos_url = @g.project(@project.gpid).try(:http_url_to_repo) - - Rails.logger.info("!!!!!! repos_url is: ") - Rails.logger.info(@repos_url) - - # 一些数据的异步同步更新 - # 访问版本庫后更新project_score表数据;changeset_num为提交总数 - project_score = @project.project_score - - Rails.logger.info("!!!!!! project_score is: ") - Rails.logger.info(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 - - Rails.logger.info("!!!!!! before render base_projects") - - render :layout => 'base_projects' - end - end - - alias_method :browse, :show -end +# encoding=utf-8 +# 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 ContributionsController < 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 Exception => e + logger.info("create repository #{e.message}") + @repo_error= "666" + redirect_to settings_project_url(@project, :tab => 'repositories', :create_error => "版本库创建失败,用户名或版本库名中不允许包含特殊字符") + raise ActiveRecord::Rollback + 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 = 11 + + # TODO: the below will move to filter, done. + # 获取版本库目录结构 + Rails.logger.info("!!!!! this is to show the value of @path and @rev: ") + Rails.logger.info(@path) + Rails.logger.info(@rev) + @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) + @changesets_latest_coimmit = @g.commits(@project.gpid, :rev => @rev, :path => @paht).try(:first) + # @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.try(:default_branch).nil? ? "master" : @g_project.try(: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 + @repos_url = @g.project(@project.gpid).try(:http_url_to_repo) + + # 一些数据的异步同步更新 + # 访问版本庫后更新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.commits(@project.gpid, :rev => @rev, :path => @paht).try(:first) + # @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.commits(@project.gpid, :rev => @rev, :path => @paht).try(:first) + # 总的提交数 + @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