Añade plugin Redmine Git Hosting 4.0.2
This commit is contained in:
parent
472cb1ea76
commit
bdd66d941f
494 changed files with 36768 additions and 0 deletions
|
@ -0,0 +1,16 @@
|
|||
class ArchivedRepositoriesController < RepositoriesController
|
||||
skip_before_action :authorize
|
||||
skip_before_action :find_project_repository, only: :index
|
||||
|
||||
before_action :can_view_archived_projects
|
||||
|
||||
def index
|
||||
@archived_projects = Project.where("status = #{Project::STATUS_ARCHIVED}").includes(:repositories)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_view_archived_projects
|
||||
render_403 unless User.current.admin?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
module XitoliteRepositoryFinder
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def find_xitolite_repository
|
||||
begin
|
||||
@repository = Repository::Xitolite.find(find_repository_param)
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
render_404
|
||||
else
|
||||
@project = @repository.project
|
||||
render_404 if @project.nil?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def find_xitolite_repository_by_path
|
||||
repo_path = params[:repo_path] + '.git'
|
||||
repository = Repository::Xitolite.find_by_path(repo_path, loose: true)
|
||||
if repository.nil?
|
||||
RedmineGitHosting.logger.error("GoRedirector : repository not found at path : '#{repo_path}', exiting !")
|
||||
render_404
|
||||
elsif !repository.go_access_available?
|
||||
RedmineGitHosting.logger.error("GoRedirector : GoAccess is disabled for this repository '#{repository.gitolite_repository_name}', exiting !")
|
||||
render_403
|
||||
else
|
||||
RedmineGitHosting.logger.info("GoRedirector : access granted for repository '#{repository.gitolite_repository_name}'")
|
||||
@repository = repository
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,57 @@
|
|||
class DownloadGitRevisionController < ApplicationController
|
||||
|
||||
include XitoliteRepositoryFinder
|
||||
|
||||
before_action :find_xitolite_repository
|
||||
before_action :can_download_git_revision
|
||||
before_action :set_download
|
||||
before_action :validate_download
|
||||
|
||||
helper :bootstrap_kit
|
||||
|
||||
def index
|
||||
begin
|
||||
send_data(@download.content, filename: @download.filename, type: @download.content_type)
|
||||
rescue => e
|
||||
flash.now[:error] = l(:git_archive_timeout, timeout: e.output)
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def find_repository_param
|
||||
params[:id]
|
||||
end
|
||||
|
||||
|
||||
def can_download_git_revision
|
||||
render_403 unless User.current.allowed_to_download?(@repository)
|
||||
end
|
||||
|
||||
|
||||
def set_download
|
||||
@download = Repositories::DownloadRevision.new(@repository, download_revision, download_format)
|
||||
end
|
||||
|
||||
|
||||
def download_revision
|
||||
@download_revision ||= (params[:rev] || 'master')
|
||||
end
|
||||
|
||||
|
||||
def download_format
|
||||
@download_format ||= (params[:download_format] || 'tar')
|
||||
end
|
||||
|
||||
|
||||
def validate_download
|
||||
if !@download.valid_commit?
|
||||
flash.now[:error] = l(:error_download_revision_no_such_commit, commit: download_revision)
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,88 @@
|
|||
class GitolitePublicKeysController < ApplicationController
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
before_action :require_login
|
||||
before_action :find_user
|
||||
before_action :find_gitolite_public_key, only: [:destroy]
|
||||
|
||||
helper :gitolite_public_keys
|
||||
helper :bootstrap_kit
|
||||
|
||||
def index
|
||||
@gitolite_user_keys = @user.gitolite_public_keys.user_key.order('title ASC, created_at ASC')
|
||||
@gitolite_deploy_keys = @user.gitolite_public_keys.deploy_key.order('title ASC, created_at ASC')
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:create_button]
|
||||
@gitolite_public_key = @user.gitolite_public_keys.new
|
||||
@gitolite_public_key.safe_attributes = params[:gitolite_public_key]
|
||||
if @gitolite_public_key.save
|
||||
create_ssh_key(@gitolite_public_key)
|
||||
flash[:notice] = l(:notice_public_key_created, title: view_context.keylabel(@gitolite_public_key))
|
||||
else
|
||||
flash[:error] = @gitolite_public_key.errors.full_messages.to_sentence
|
||||
end
|
||||
redirect_to @redirect_url
|
||||
else
|
||||
redirect_to @cancel_url
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
return unless request.delete?
|
||||
|
||||
if @gitolite_public_key.user == @user || @user.admin?
|
||||
if @gitolite_public_key.destroy
|
||||
destroy_ssh_key(@gitolite_public_key)
|
||||
flash[:notice] = l(:notice_public_key_deleted, title: view_context.keylabel(@gitolite_public_key))
|
||||
end
|
||||
redirect_to @redirect_url
|
||||
else
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user
|
||||
if params[:user_id]
|
||||
set_user_from_params
|
||||
else
|
||||
set_user_from_current_user
|
||||
end
|
||||
end
|
||||
|
||||
def set_user_from_params
|
||||
@user = params[:user_id] == 'current' ? User.current : User.find_by(id: params[:user_id])
|
||||
if @user
|
||||
@cancel_url = @redirect_url = url_for(controller: 'users', action: 'edit', id: params[:user_id], tab: 'keys')
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
def set_user_from_current_user
|
||||
if User.current.allowed_to_create_ssh_keys?
|
||||
@user = User.current
|
||||
@redirect_url = url_for(controller: 'gitolite_public_keys', action: 'index')
|
||||
@cancel_url = url_for(controller: 'my', action: 'account')
|
||||
else
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
def find_gitolite_public_key
|
||||
@gitolite_public_key = @user.gitolite_public_keys.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def create_ssh_key(ssh_key)
|
||||
gitolite_accessor.create_ssh_key(ssh_key)
|
||||
end
|
||||
|
||||
def destroy_ssh_key(ssh_key)
|
||||
gitolite_accessor.destroy_ssh_key(ssh_key)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
class GoRedirectorController < ApplicationController
|
||||
|
||||
include XitoliteRepositoryFinder
|
||||
|
||||
# prevents login action to be filtered by check_if_login_required application scope filter
|
||||
skip_before_action :check_if_login_required, :verify_authenticity_token
|
||||
|
||||
before_action :find_xitolite_repository_by_path
|
||||
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,72 @@
|
|||
class RedmineGitHostingController < ApplicationController
|
||||
include XitoliteRepositoryFinder
|
||||
|
||||
before_action :require_login
|
||||
before_action :find_xitolite_repository
|
||||
before_action :check_required_permissions
|
||||
before_action :set_current_tab
|
||||
|
||||
layout(proc { |controller| controller.request.xhr? ? false : 'base' })
|
||||
|
||||
helper :bootstrap_kit
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.api
|
||||
end
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
private
|
||||
|
||||
def find_repository_param
|
||||
params[:repository_id]
|
||||
end
|
||||
|
||||
def check_required_permissions
|
||||
return render_403 unless @project.module_enabled?(:repository)
|
||||
return true if User.current.admin?
|
||||
return render_403 unless User.current.allowed_to_manage_repository?(@repository)
|
||||
end
|
||||
|
||||
def check_xitolite_permissions
|
||||
case action_name
|
||||
when 'index', 'show'
|
||||
perm = "view_#{controller_name}".to_sym
|
||||
render_403 unless User.current.git_allowed_to?(perm, @repository)
|
||||
when 'new', 'create'
|
||||
perm = "create_#{controller_name}".to_sym
|
||||
render_403 unless User.current.git_allowed_to?(perm, @repository)
|
||||
when 'edit', 'update', 'destroy'
|
||||
perm = "edit_#{controller_name}".to_sym
|
||||
render_403 unless User.current.git_allowed_to?(perm, @repository)
|
||||
end
|
||||
end
|
||||
|
||||
def render_with_api
|
||||
respond_to do |format|
|
||||
format.html { render layout: false }
|
||||
format.api
|
||||
end
|
||||
end
|
||||
|
||||
def render_js_redirect
|
||||
respond_to do |format|
|
||||
format.js { render js: "window.location = #{success_url.to_json};" }
|
||||
format.html do
|
||||
redirect_to success_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def success_url
|
||||
url_for(controller: 'repositories', action: 'edit', id: @repository.id, tab: @tab)
|
||||
end
|
||||
|
||||
def call_use_case_and_redirect(opts = {})
|
||||
# Update Gitolite repository
|
||||
call_use_case(opts)
|
||||
render_js_redirect
|
||||
end
|
||||
end
|
|
@ -0,0 +1,123 @@
|
|||
class RepositoryDeploymentCredentialsController < RedmineGitHostingController
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
before_action :check_xitolite_permissions
|
||||
before_action :find_deployment_credential, only: %i[edit update destroy]
|
||||
before_action :find_key, only: %i[edit update destroy]
|
||||
before_action :find_all_keys, only: %i[index new create]
|
||||
|
||||
helper :gitolite_public_keys
|
||||
|
||||
def index
|
||||
@repository_deployment_credentials = @repository.deployment_credentials.all
|
||||
render layout: false
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def show
|
||||
render_404
|
||||
end
|
||||
|
||||
def new
|
||||
@credential = @repository.deployment_credentials.new
|
||||
end
|
||||
|
||||
def create
|
||||
@credential = build_new_credential
|
||||
return unless @credential.save
|
||||
|
||||
flash[:notice] = l(:notice_deployment_credential_created)
|
||||
call_use_case_and_redirect
|
||||
end
|
||||
|
||||
def update
|
||||
@credential.safe_attributes = params[:repository_deployment_credential]
|
||||
return unless @credential.save
|
||||
|
||||
flash[:notice] = l(:notice_deployment_credential_updated)
|
||||
call_use_case_and_redirect
|
||||
end
|
||||
|
||||
def destroy
|
||||
will_delete_key = @key.deploy_key? && @key.delete_when_unused && @key.repository_deployment_credentials.count == 1
|
||||
@credential.destroy
|
||||
if will_delete_key && @key.repository_deployment_credentials.empty?
|
||||
# Key no longer used -- delete it!
|
||||
@key.destroy
|
||||
flash[:notice] = l(:notice_deployment_credential_deleted_with_key)
|
||||
else
|
||||
flash[:notice] = l(:notice_deployment_credential_deleted)
|
||||
end
|
||||
|
||||
call_use_case_and_redirect
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_tab
|
||||
@tab = 'repository_deployment_credentials'
|
||||
end
|
||||
|
||||
def find_deployment_credential
|
||||
credential = @repository.deployment_credentials.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
else
|
||||
if credential.user && (User.current.admin? || credential.user == User.current)
|
||||
@credential = credential
|
||||
else
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
def find_key
|
||||
key = @credential.gitolite_public_key
|
||||
if key&.user && (User.current.admin? || key.user == User.current)
|
||||
@key = key
|
||||
elsif key
|
||||
render_403
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
def find_all_keys
|
||||
# display create_with_key view. Find preexisting keys to offer to user
|
||||
@user_keys = User.current.gitolite_public_keys.deploy_key.order('title ASC')
|
||||
@disabled_keys = @repository.deployment_credentials.map(&:gitolite_public_key)
|
||||
@other_keys = []
|
||||
# Admin can use other's deploy keys as well
|
||||
@other_keys = other_deployment_keys if User.current.admin?
|
||||
end
|
||||
|
||||
def other_deployment_keys
|
||||
users_allowed_to_create_deployment_keys.map { |user| user.gitolite_public_keys.deploy_key.order('title ASC') }.flatten
|
||||
end
|
||||
|
||||
def users_allowed_to_create_deployment_keys
|
||||
@project.users.select { |user| user != User.current && user.git_allowed_to?(:create_repository_deployment_credentials, @repository) }
|
||||
end
|
||||
|
||||
def call_use_case(opts = {})
|
||||
options = opts.merge(message: "Update deploy keys for repository : '#{@repository.gitolite_repository_name}'")
|
||||
gitolite_accessor.update_repository(@repository, options)
|
||||
end
|
||||
|
||||
def build_new_credential
|
||||
credential = @repository.deployment_credentials.new
|
||||
credential.safe_attributes = params[:repository_deployment_credential]
|
||||
key = GitolitePublicKey.find_by(id: params[:repository_deployment_credential][:gitolite_public_key_id])
|
||||
|
||||
credential.gitolite_public_key = key unless key.nil?
|
||||
|
||||
# If admin, let credential be owned by owner of key...
|
||||
if User.current.admin?
|
||||
credential.user = key.user unless key.nil?
|
||||
else
|
||||
credential.user = User.current
|
||||
end
|
||||
|
||||
credential
|
||||
end
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
class RepositoryGitConfigKeysController < RedmineGitHostingController
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
before_action :check_xitolite_permissions
|
||||
before_action :find_repository_git_config_key, except: %i[index new create]
|
||||
|
||||
accept_api_auth :index, :show
|
||||
|
||||
def index
|
||||
@repository_git_config_keys = @repository.git_config_keys.all
|
||||
@repository_git_option_keys = @repository.git_option_keys.all
|
||||
render_with_api
|
||||
end
|
||||
|
||||
def new
|
||||
@git_config_key = @repository.send(key_type).new
|
||||
end
|
||||
|
||||
def create
|
||||
@git_config_key = @repository.send(key_type).new
|
||||
@git_config_key.safe_attributes = params[:repository_git_config_key]
|
||||
return unless @git_config_key.save
|
||||
|
||||
flash[:notice] = l(:notice_git_config_key_created)
|
||||
call_use_case_and_redirect
|
||||
end
|
||||
|
||||
def update
|
||||
@git_config_key.safe_attributes = params[:repository_git_config_key]
|
||||
return unless @git_config_key.save
|
||||
|
||||
flash[:notice] = l(:notice_git_config_key_updated)
|
||||
options = @git_config_key.key_has_changed? ? { delete_git_config_key: @git_config_key.old_key } : {}
|
||||
call_use_case_and_redirect(options)
|
||||
end
|
||||
|
||||
def destroy
|
||||
return unless @git_config_key.destroy
|
||||
|
||||
flash[:notice] = l(:notice_git_config_key_deleted)
|
||||
options = { delete_git_config_key: @git_config_key.key }
|
||||
call_use_case_and_redirect(options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key_type
|
||||
type = params[:type] || params[:repository_git_config_key][:type]
|
||||
case type
|
||||
when 'RepositoryGitConfigKey::GitConfig', 'git_config'
|
||||
:git_config_keys
|
||||
when 'RepositoryGitConfigKey::Option', 'git_option'
|
||||
:git_option_keys
|
||||
else
|
||||
:git_keys
|
||||
end
|
||||
end
|
||||
|
||||
def set_current_tab
|
||||
@tab = 'repository_git_config_keys'
|
||||
end
|
||||
|
||||
def find_repository_git_config_key
|
||||
@git_config_key = @repository.git_keys.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def call_use_case(opts = {})
|
||||
options = opts.merge(message: "Rebuild Git config keys respository : '#{@repository.gitolite_repository_name}'")
|
||||
gitolite_accessor.update_repository(@repository, options)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
class RepositoryGitExtrasController < RedmineGitHostingController
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
skip_before_action :set_current_tab
|
||||
|
||||
helper :extend_repositories
|
||||
|
||||
def update
|
||||
@git_extra = @repository.extra
|
||||
@git_extra.safe_attributes = params[:repository_git_extra]
|
||||
|
||||
if @git_extra.save
|
||||
flash.now[:notice] = l(:notice_gitolite_extra_updated)
|
||||
gitolite_accessor.update_repository(@repository, update_default_branch: @git_extra.default_branch_has_changed?)
|
||||
else
|
||||
flash.now[:error] = l(:notice_gitolite_extra_update_failed)
|
||||
end
|
||||
end
|
||||
|
||||
def sort_urls
|
||||
@git_extra = @repository.extra
|
||||
return unless request.post?
|
||||
|
||||
if @git_extra.update(urls_order: params[:repository_git_extra])
|
||||
flash.now[:notice] = l(:notice_gitolite_extra_updated)
|
||||
else
|
||||
flash.now[:error] = l(:notice_gitolite_extra_update_failed)
|
||||
end
|
||||
end
|
||||
|
||||
def move
|
||||
@move_repository_form = MoveRepositoryForm.new(@repository)
|
||||
return unless request.post?
|
||||
|
||||
@move_repository_form = MoveRepositoryForm.new(@repository)
|
||||
|
||||
return unless @move_repository_form.submit(params[:repository_mover])
|
||||
|
||||
redirect_to settings_project_path(@repository.project, tab: 'repositories')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_git_extra
|
||||
@git_extra = @repository.extra
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
class RepositoryMirrorsController < RedmineGitHostingController
|
||||
before_action :check_xitolite_permissions
|
||||
before_action :find_repository_mirror, except: %i[index new create]
|
||||
|
||||
accept_api_auth :index, :show
|
||||
|
||||
helper :additionals_clipboardjs
|
||||
|
||||
def index
|
||||
@repository_mirrors = @repository.mirrors.all
|
||||
render_with_api
|
||||
end
|
||||
|
||||
def new
|
||||
@mirror = @repository.mirrors.new
|
||||
end
|
||||
|
||||
def create
|
||||
@mirror = @repository.mirrors.new
|
||||
@mirror.safe_attributes = params[:repository_mirror]
|
||||
return unless @mirror.save
|
||||
|
||||
flash[:notice] = l(:notice_mirror_created)
|
||||
render_js_redirect
|
||||
end
|
||||
|
||||
def update
|
||||
@mirror.safe_attributes = params[:repository_mirror]
|
||||
return unless @mirror.save
|
||||
|
||||
flash[:notice] = l(:notice_mirror_updated)
|
||||
render_js_redirect
|
||||
end
|
||||
|
||||
def destroy
|
||||
return unless @mirror.destroy
|
||||
|
||||
flash[:notice] = l(:notice_mirror_deleted)
|
||||
render_js_redirect
|
||||
end
|
||||
|
||||
def push
|
||||
@push_failed, @shellout = RepositoryMirrors::Push.call(@mirror)
|
||||
render layout: false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_tab
|
||||
@tab = 'repository_mirrors'
|
||||
end
|
||||
|
||||
def find_repository_mirror
|
||||
@mirror = @repository.mirrors.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def check_xitolite_permissions
|
||||
if action_name == 'push'
|
||||
render_403 unless User.current.git_allowed_to?(:push_repository_mirrors, @repository)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
class RepositoryPostReceiveUrlsController < RedmineGitHostingController
|
||||
before_action :check_xitolite_permissions
|
||||
before_action :find_repository_post_receive_url, except: %i[index new create]
|
||||
|
||||
accept_api_auth :index, :show
|
||||
|
||||
def index
|
||||
@repository_post_receive_urls = @repository.post_receive_urls.all
|
||||
render_with_api
|
||||
end
|
||||
|
||||
def new
|
||||
@post_receive_url = @repository.post_receive_urls.new
|
||||
end
|
||||
|
||||
def create
|
||||
@post_receive_url = @repository.post_receive_urls.new
|
||||
@post_receive_url.safe_attributes = params[:repository_post_receive_url]
|
||||
return unless @post_receive_url.save
|
||||
|
||||
flash[:notice] = l(:notice_post_receive_url_created)
|
||||
render_js_redirect
|
||||
end
|
||||
|
||||
def update
|
||||
@post_receive_url.safe_attributes = params[:repository_post_receive_url]
|
||||
return unless @post_receive_url.save
|
||||
|
||||
flash[:notice] = l(:notice_post_receive_url_updated)
|
||||
render_js_redirect
|
||||
end
|
||||
|
||||
def destroy
|
||||
return unless @post_receive_url.destroy
|
||||
|
||||
flash[:notice] = l(:notice_post_receive_url_deleted)
|
||||
render_js_redirect
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_tab
|
||||
@tab = 'repository_post_receive_urls'
|
||||
end
|
||||
|
||||
def find_repository_post_receive_url
|
||||
@post_receive_url = @repository.post_receive_urls.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
|
@ -0,0 +1,80 @@
|
|||
class RepositoryProtectedBranchesController < RedmineGitHostingController
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
before_action :check_xitolite_permissions
|
||||
before_action :find_repository_protected_branch, except: %i[index new create sort]
|
||||
|
||||
accept_api_auth :index, :show
|
||||
|
||||
def index
|
||||
@repository_protected_branches = @repository.protected_branches.all
|
||||
render_with_api
|
||||
end
|
||||
|
||||
def new
|
||||
@protected_branch = @repository.protected_branches.new
|
||||
end
|
||||
|
||||
def create
|
||||
@protected_branch = @repository.protected_branches.new
|
||||
@protected_branch.safe_attributes = params[:repository_protected_branche]
|
||||
return unless @protected_branch.save
|
||||
|
||||
check_members
|
||||
flash[:notice] = l(:notice_protected_branch_created)
|
||||
call_use_case_and_redirect
|
||||
end
|
||||
|
||||
def update
|
||||
@protected_branch.safe_attributes = params[:repository_protected_branche]
|
||||
return unless @protected_branch.save
|
||||
|
||||
check_members
|
||||
flash[:notice] = l(:notice_protected_branch_updated)
|
||||
call_use_case_and_redirect
|
||||
end
|
||||
|
||||
def destroy
|
||||
return unless @protected_branch.destroy
|
||||
|
||||
flash[:notice] = l(:notice_protected_branch_deleted)
|
||||
call_use_case_and_redirect
|
||||
end
|
||||
|
||||
def clone
|
||||
@protected_branch = RepositoryProtectedBranche.clone_from(params[:id])
|
||||
render 'new'
|
||||
end
|
||||
|
||||
def sort
|
||||
params[:protected_branch].each_with_index do |id, index|
|
||||
@repository.protected_branches.where(id: id).update_all(position: index + 1)
|
||||
end
|
||||
# Update Gitolite repository
|
||||
call_use_case
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_tab
|
||||
@tab = 'repository_protected_branches'
|
||||
end
|
||||
|
||||
def find_repository_protected_branch
|
||||
@protected_branch = @repository.protected_branches.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def call_use_case(opts = {})
|
||||
options = opts.merge(message: "Update branch permissions for repository : '#{@repository.gitolite_repository_name}'")
|
||||
gitolite_accessor.update_repository(@repository, options)
|
||||
end
|
||||
|
||||
def check_members
|
||||
member_manager = RepositoryProtectedBranches::MemberManager.new(@protected_branch)
|
||||
member_manager.add_users(params[:user_ids])
|
||||
member_manager.add_groups(params[:group_ids])
|
||||
end
|
||||
end
|
26
plugins/redmine_git_hosting/app/forms/base_form.rb
Normal file
26
plugins/redmine_git_hosting/app/forms/base_form.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
module BaseForm
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ActiveModel::Validations
|
||||
include ActiveModel::Validations::Callbacks
|
||||
include ActiveModel::Conversion
|
||||
extend ActiveModel::Naming
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
|
||||
def submit(attributes = {})
|
||||
attributes.each do |name, value|
|
||||
send("#{name}=", value)
|
||||
end
|
||||
if valid?
|
||||
valid_form_submitted if respond_to?(:valid_form_submitted)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
class MoveRepositoryForm
|
||||
include BaseForm
|
||||
|
||||
attr_reader :repository
|
||||
attr_accessor :project_id
|
||||
|
||||
validates_presence_of :project_id
|
||||
validate :repository_is_movable
|
||||
validate :target_project
|
||||
validate :repository_uniqueness
|
||||
|
||||
def initialize(repository)
|
||||
@repository = repository
|
||||
end
|
||||
|
||||
def project
|
||||
@project ||= Project.find_by_id(project_id)
|
||||
end
|
||||
|
||||
def valid_form_submitted
|
||||
repository.update_attribute(:project_id, project.id)
|
||||
RedmineGitHosting::GitoliteAccessor.move_repository(repository)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repository_is_movable
|
||||
errors.add(:base, :identifier_empty) unless repository.movable?
|
||||
end
|
||||
|
||||
def target_project
|
||||
errors.add(:base, :wrong_target_project) if repository.project == project
|
||||
end
|
||||
|
||||
def repository_uniqueness
|
||||
new_repo = project.repositories.find_by_identifier(repository.identifier)
|
||||
errors.add(:base, :identifier_taken) unless new_repo.nil?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
class PluginSettingsForm
|
||||
class << self
|
||||
def add_accessor(*args)
|
||||
@accessors ||= []
|
||||
args.each do |accessor|
|
||||
@accessors << accessor
|
||||
attr_accessor accessor
|
||||
end
|
||||
end
|
||||
|
||||
def all_accessors
|
||||
@accessors
|
||||
end
|
||||
end
|
||||
|
||||
include BaseForm
|
||||
include PluginSettingsValidation::CacheConfig
|
||||
include PluginSettingsValidation::GitoliteAccessConfig
|
||||
include PluginSettingsValidation::GitoliteConfig
|
||||
include PluginSettingsValidation::HooksConfig
|
||||
include PluginSettingsValidation::MailingListConfig
|
||||
include PluginSettingsValidation::RedmineConfig
|
||||
include PluginSettingsValidation::SshConfig
|
||||
include PluginSettingsValidation::StorageConfig
|
||||
|
||||
attr_reader :plugin
|
||||
|
||||
def initialize(plugin)
|
||||
@plugin = plugin
|
||||
end
|
||||
|
||||
def params
|
||||
Hash[self.class.all_accessors.map { |v| [v, send(v)] }]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_setting(setting)
|
||||
Setting.plugin_redmine_git_hosting[setting]
|
||||
end
|
||||
|
||||
def strip_value(value)
|
||||
return '' if value.nil?
|
||||
|
||||
value.strip
|
||||
end
|
||||
|
||||
def filter_email_list(list)
|
||||
list.select(&:present?).select { |m| valid_email?(m) }
|
||||
end
|
||||
|
||||
def valid_email?(email)
|
||||
RedmineGitHosting::Validators.valid_email?(email)
|
||||
end
|
||||
|
||||
def convert_time(time)
|
||||
(time.to_f * 10).to_i / 10.0
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
module PluginSettingsValidation
|
||||
module CacheConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Gitolite Cache Config
|
||||
add_accessor :gitolite_cache_max_time,
|
||||
:gitolite_cache_max_size,
|
||||
:gitolite_cache_max_elements,
|
||||
:gitolite_cache_adapter
|
||||
|
||||
validates :gitolite_cache_max_time, presence: true, numericality: { only_integer: true }
|
||||
validates :gitolite_cache_max_size, presence: true, numericality: { only_integer: true }
|
||||
validates :gitolite_cache_max_elements, presence: true, numericality: { only_integer: true }
|
||||
validates :gitolite_cache_adapter, presence: true, inclusion: { in: GitCache.adapters }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
module PluginSettingsValidation
|
||||
module GitoliteAccessConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Gitolite Access Config
|
||||
add_accessor :ssh_server_domain,
|
||||
:http_server_domain,
|
||||
:https_server_domain,
|
||||
:http_server_subdir,
|
||||
:show_repositories_url,
|
||||
:gitolite_daemon_by_default,
|
||||
:gitolite_http_by_default
|
||||
|
||||
before_validation do
|
||||
self.ssh_server_domain = strip_value(ssh_server_domain)
|
||||
self.http_server_domain = strip_value(http_server_domain)
|
||||
self.https_server_domain = strip_value(https_server_domain)
|
||||
self.http_server_subdir = strip_value(http_server_subdir)
|
||||
end
|
||||
|
||||
validates :ssh_server_domain, presence: true, format: { with: RedmineGitHosting::Validators::DOMAIN_REGEX }
|
||||
validates :http_server_domain, presence: true, format: { with: RedmineGitHosting::Validators::DOMAIN_REGEX }
|
||||
validates :https_server_domain, format: { with: RedmineGitHosting::Validators::DOMAIN_REGEX }, allow_blank: true
|
||||
|
||||
validates :show_repositories_url, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :gitolite_daemon_by_default, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :gitolite_http_by_default, presence: true, inclusion: { in: %w[0 1 2 3] }, numericality: { only_integer: true }
|
||||
|
||||
validate :http_server_subdir_is_relative
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def http_server_subdir_is_relative
|
||||
errors.add(:http_server_subdir, 'must be relative') if http_server_subdir.starts_with?('/')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
module PluginSettingsValidation
|
||||
module GitoliteConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Gitolite Config File
|
||||
add_accessor :gitolite_config_file,
|
||||
:gitolite_identifier_prefix,
|
||||
:gitolite_identifier_strip_user_id
|
||||
|
||||
# Gitolite Global Config
|
||||
add_accessor :gitolite_temp_dir,
|
||||
:gitolite_recycle_bin_expiration_time,
|
||||
:gitolite_log_level,
|
||||
:git_config_username,
|
||||
:git_config_email
|
||||
|
||||
before_validation do
|
||||
self.gitolite_config_file = strip_value(gitolite_config_file)
|
||||
self.gitolite_identifier_prefix = strip_value(gitolite_identifier_prefix)
|
||||
self.gitolite_temp_dir = strip_value(gitolite_temp_dir)
|
||||
self.git_config_username = strip_value(git_config_username)
|
||||
self.git_config_email = strip_value(git_config_email)
|
||||
self.gitolite_recycle_bin_expiration_time = strip_value(gitolite_recycle_bin_expiration_time)
|
||||
end
|
||||
|
||||
# Validates Gitolite Config File
|
||||
validates :gitolite_identifier_strip_user_id, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
|
||||
# Validates Gitolite Global Config
|
||||
validates :gitolite_temp_dir, presence: true
|
||||
validates :gitolite_recycle_bin_expiration_time, presence: true, numericality: true
|
||||
|
||||
validates :gitolite_log_level, presence: true, inclusion: { in: RedmineGitHosting::Logger::LOG_LEVELS }
|
||||
validates :git_config_username, presence: true
|
||||
validates :git_config_email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||
|
||||
validate :gitolite_config_file_is_relative
|
||||
validate :tmp_dir_is_absolute
|
||||
|
||||
after_validation do
|
||||
self.gitolite_recycle_bin_expiration_time = convert_time(gitolite_recycle_bin_expiration_time)
|
||||
|
||||
if gitolite_config_file == RedmineGitHosting::Config::GITOLITE_DEFAULT_CONFIG_FILE
|
||||
self.gitolite_identifier_strip_user_id = 'false'
|
||||
self.gitolite_identifier_prefix = RedmineGitHosting::Config::GITOLITE_IDENTIFIER_DEFAULT_PREFIX
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gitolite_config_file_is_relative
|
||||
errors.add(:gitolite_config_file, 'must be relative') if gitolite_config_file.starts_with?('/')
|
||||
end
|
||||
|
||||
def tmp_dir_is_absolute
|
||||
errors.add(:gitolite_temp_dir, 'must be absolute') unless gitolite_temp_dir.starts_with?('/')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
module PluginSettingsValidation
|
||||
module HooksConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Gitolite Hooks Config
|
||||
add_accessor :gitolite_overwrite_existing_hooks,
|
||||
:gitolite_hooks_are_asynchronous,
|
||||
:gitolite_hooks_debug,
|
||||
:gitolite_hooks_url
|
||||
|
||||
before_validation do
|
||||
self.gitolite_hooks_url = strip_value(gitolite_hooks_url)
|
||||
end
|
||||
|
||||
validates :gitolite_overwrite_existing_hooks, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :gitolite_hooks_are_asynchronous, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :gitolite_hooks_debug, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :gitolite_hooks_url, presence: true, format: { with: URI::regexp(%w[http https]) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
module PluginSettingsValidation
|
||||
module MailingListConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Git Mailing List Config
|
||||
add_accessor :gitolite_notify_by_default,
|
||||
:gitolite_notify_global_prefix,
|
||||
:gitolite_notify_global_sender_address,
|
||||
:gitolite_notify_global_include,
|
||||
:gitolite_notify_global_exclude
|
||||
|
||||
before_validation do
|
||||
self.gitolite_notify_global_include = filter_email_list(gitolite_notify_global_include)
|
||||
self.gitolite_notify_global_exclude = filter_email_list(gitolite_notify_global_exclude)
|
||||
end
|
||||
|
||||
validates :gitolite_notify_by_default, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :gitolite_notify_global_sender_address, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||
validate :git_notifications_intersection
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Validate intersection of global_include/global_exclude
|
||||
#
|
||||
def git_notifications_intersection
|
||||
intersection = gitolite_notify_global_include & gitolite_notify_global_exclude
|
||||
return unless intersection.count.positive?
|
||||
|
||||
errors.add(:base, 'duplicated entries detected in gitolite_notify_global_include and gitolite_notify_global_exclude')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
module PluginSettingsValidation
|
||||
module RedmineConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Redmine Config
|
||||
add_accessor :redmine_has_rw_access_on_all_repos,
|
||||
:all_projects_use_git,
|
||||
:init_repositories_on_create,
|
||||
:delete_git_repositories,
|
||||
:download_revision_enabled,
|
||||
:gitolite_use_sidekiq
|
||||
|
||||
# This params work together!
|
||||
# When hierarchical_organisation = true, unique_repo_identifier MUST be false
|
||||
# When hierarchical_organisation = false, unique_repo_identifier MUST be true
|
||||
add_accessor :hierarchical_organisation, :unique_repo_identifier
|
||||
|
||||
# hierarchical_organisation and unique_repo_identifier are now combined
|
||||
#
|
||||
before_validation do
|
||||
self.unique_repo_identifier = if Additionals.true? hierarchical_organisation
|
||||
'false'
|
||||
else
|
||||
'true'
|
||||
end
|
||||
|
||||
## If we don't auto-create repository, we cannot create README file
|
||||
self.init_repositories_on_create = 'false' unless Additionals.true? all_projects_use_git
|
||||
end
|
||||
|
||||
validates :redmine_has_rw_access_on_all_repos, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :all_projects_use_git, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :init_repositories_on_create, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :delete_git_repositories, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :download_revision_enabled, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :gitolite_use_sidekiq, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :hierarchical_organisation, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
validates :unique_repo_identifier, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
|
||||
|
||||
validate :check_for_duplicated_repo
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Check duplication if we are switching from a mode to another
|
||||
#
|
||||
def check_for_duplicated_repo
|
||||
return if Additionals.true?(hierarchical_organisation)
|
||||
return unless Repository::Xitolite.have_duplicated_identifier?
|
||||
|
||||
errors.add(:base, 'Detected non-unique repository identifiers. Cannot switch to flat mode')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
module PluginSettingsValidation
|
||||
module SshConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Gitolite SSH Config
|
||||
add_accessor :gitolite_user,
|
||||
:gitolite_server_host,
|
||||
:gitolite_server_port,
|
||||
:gitolite_ssh_private_key,
|
||||
:gitolite_ssh_public_key
|
||||
|
||||
before_validation do
|
||||
self.gitolite_user = strip_value(gitolite_user)
|
||||
self.gitolite_server_host = strip_value(gitolite_server_host)
|
||||
self.gitolite_server_port = strip_value(gitolite_server_port)
|
||||
|
||||
self.gitolite_ssh_private_key = strip_value(gitolite_ssh_private_key)
|
||||
self.gitolite_ssh_public_key = strip_value(gitolite_ssh_public_key)
|
||||
end
|
||||
|
||||
validates :gitolite_user, presence: true
|
||||
validates :gitolite_server_host, presence: true
|
||||
validates :gitolite_server_port, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 65_536 }
|
||||
validates :gitolite_ssh_private_key, presence: true
|
||||
validates :gitolite_ssh_public_key, presence: true
|
||||
|
||||
validates_each :gitolite_ssh_private_key, :gitolite_ssh_public_key do |record, attr, value|
|
||||
record.errors.add(attr, 'must exists on filesystem') unless File.exists?(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
module PluginSettingsValidation
|
||||
module StorageConfig
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
PATHS_TO_VALIDATE = %i[gitolite_global_storage_dir gitolite_redmine_storage_dir gitolite_recycle_bin_dir].freeze
|
||||
|
||||
included do
|
||||
# Gitolite Storage Config
|
||||
add_accessor :gitolite_global_storage_dir,
|
||||
:gitolite_redmine_storage_dir,
|
||||
:gitolite_recycle_bin_dir
|
||||
|
||||
before_validation do
|
||||
self.gitolite_global_storage_dir = strip_value(gitolite_global_storage_dir)
|
||||
self.gitolite_redmine_storage_dir = strip_value(gitolite_redmine_storage_dir)
|
||||
self.gitolite_recycle_bin_dir = strip_value(gitolite_recycle_bin_dir)
|
||||
end
|
||||
|
||||
validates_presence_of :gitolite_global_storage_dir, :gitolite_recycle_bin_dir
|
||||
|
||||
validates_each PATHS_TO_VALIDATE do |record, attr, value|
|
||||
record.errors.add(attr, 'must be relative') if value.starts_with?('/')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
module ArchivedRepositoriesHelper
|
||||
|
||||
def link_to_revision2(revision, repository, options = {})
|
||||
repository = repository.repository if repository.is_a?(Project)
|
||||
text = options.delete(:text) { format_revision(revision) }
|
||||
rev = revision.respond_to?(:identifier) ? revision.identifier : revision
|
||||
link_to(
|
||||
h(text),
|
||||
{ controller: 'archived_repositories', action: 'revision', id: repository.project, repository_id: repository.identifier_param, rev: rev },
|
||||
title: l(:label_revision_id, format_revision(revision))
|
||||
)
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
module BootstrapKit::AjaxHelper
|
||||
def render_flash_messages_as_js(target = '#flash-messages', opts = {})
|
||||
js_render(target, render_flash_messages, opts).html_safe
|
||||
end
|
||||
|
||||
def js_render_template(target, template, opts = {})
|
||||
locals = opts.delete(:locals) { {} }
|
||||
content = render(template: template, locals: locals)
|
||||
js_render(target, content, opts)
|
||||
end
|
||||
|
||||
def js_render_partial(target, partial, opts = {})
|
||||
locals = opts.delete(:locals) { {} }
|
||||
content = render(partial: partial, locals: locals)
|
||||
js_render(target, content, opts)
|
||||
end
|
||||
|
||||
def js_render(target, content, opts = {})
|
||||
method = opts.delete(:method) { :inject }
|
||||
"$('#{target}').#{js_rendering_method(method)}(\"#{escape_javascript(content)}\");\n".html_safe
|
||||
end
|
||||
|
||||
def js_rendering_method(method)
|
||||
case method
|
||||
when :append
|
||||
'append'
|
||||
when :inject
|
||||
'html'
|
||||
when :replace
|
||||
'replaceWith'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
module BootstrapKit::PresenterHelper
|
||||
def present(object, klass = nil, *args)
|
||||
klass ||= "#{object.class.base_class}Presenter".constantize
|
||||
presenter = klass.new(object, self, *args)
|
||||
yield presenter if block_given?
|
||||
presenter
|
||||
end
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
module BootstrapKitHelper
|
||||
include BootstrapKit::AjaxHelper
|
||||
include BootstrapKit::PresenterHelper
|
||||
|
||||
def bootstrap_load_base
|
||||
stylesheet_link_tag('bootstrap_custom', plugin: 'redmine_git_hosting') +
|
||||
bs_include_css('bootstrap_custom')
|
||||
end
|
||||
|
||||
def bootstrap_load_module(bs_module)
|
||||
method = "load_bs_module_#{bs_module}"
|
||||
send(method)
|
||||
end
|
||||
|
||||
def checked_image_with_exclamation(checked = true)
|
||||
checked ? image_tag('toggle_check.png') : image_tag('exclamation.png')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bs_include_js(js)
|
||||
javascript_include_tag "bootstrap/#{js}", plugin: 'redmine_git_hosting'
|
||||
end
|
||||
|
||||
def bs_include_css(css)
|
||||
stylesheet_link_tag "bootstrap/#{css}", plugin: 'redmine_git_hosting'
|
||||
end
|
||||
|
||||
def load_bs_module_alerts
|
||||
bs_include_js('bootstrap_alert') +
|
||||
bs_include_js('bootstrap_alert_helper') +
|
||||
bs_include_js('bootstrap_transitions') +
|
||||
bs_include_css('bootstrap_alert') +
|
||||
bs_include_css('bootstrap_animations') +
|
||||
bs_include_css('bootstrap_close')
|
||||
end
|
||||
|
||||
def load_bs_module_label
|
||||
bs_include_css('bootstrap_label')
|
||||
end
|
||||
|
||||
def load_bs_module_modals
|
||||
bs_include_js('bootstrap_modal')
|
||||
end
|
||||
|
||||
def load_bs_module_sortable
|
||||
bs_include_js('bootstrap_sortable_helper')
|
||||
end
|
||||
|
||||
def load_bs_module_tables
|
||||
bs_include_css('bootstrap_tables')
|
||||
end
|
||||
|
||||
def load_bs_module_tooltip
|
||||
bs_include_js('bootstrap_tooltip') +
|
||||
bs_include_js('bootstrap_tooltip_helper') +
|
||||
bs_include_css('bootstrap_tooltip')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
module ExtendProjectsHelper
|
||||
def render_feature(repository, feature)
|
||||
method = "#{feature}_feature"
|
||||
label, css_class, enabled = send(method, repository)
|
||||
|
||||
# Get css class
|
||||
base_class = ['icon-git']
|
||||
base_class << css_class
|
||||
base_class << 'icon-git-disabled' unless enabled
|
||||
|
||||
# Get label
|
||||
base_label = []
|
||||
base_label << label
|
||||
base_label << "(#{l(:label_disabled)})" unless enabled
|
||||
|
||||
content_tag(:i, '', title: base_label.join(' '), class: base_class)
|
||||
end
|
||||
|
||||
def deployment_credentials_feature(repository)
|
||||
label = l(:label_deployment_credentials)
|
||||
css_class = 'fas fa-lock'
|
||||
enabled = repository.deployment_credentials.active.any?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
|
||||
def post_receive_urls_feature(repository)
|
||||
label = l(:label_post_receive_urls)
|
||||
css_class = 'fas fa-external-link-alt'
|
||||
enabled = repository.post_receive_urls.active.any?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
|
||||
def mirrors_feature(repository)
|
||||
label = l(:label_repository_mirrors)
|
||||
css_class = 'fas fa-cloud-upload-alt'
|
||||
enabled = repository.mirrors.active.any?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
|
||||
def git_daemon_feature(repository)
|
||||
label = l(:label_git_daemon)
|
||||
css_class = 'fab fa-git'
|
||||
enabled = repository.git_access_available?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
|
||||
def git_http_feature(repository)
|
||||
label = l(:label_smart_http)
|
||||
css_class = 'fas fa-cloud-download-alt'
|
||||
enabled = repository.smart_http_enabled?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
|
||||
def git_notify_feature(repository)
|
||||
label = l(:label_git_notify)
|
||||
css_class = 'fas fa-bullhorn'
|
||||
enabled = repository.git_notification_enabled?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
|
||||
def protected_branch_feature(repository)
|
||||
label = l(:label_protected_branch)
|
||||
css_class = 'fas fa-shield-alt'
|
||||
enabled = repository.protected_branches_available?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
|
||||
def git_annex_feature(repository)
|
||||
label = l(:label_git_annex)
|
||||
css_class = 'fas fa-cloud-upload-alt'
|
||||
enabled = repository.git_annex_enabled?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
|
||||
def public_repo_feature(repository)
|
||||
label = l(:label_public_repo)
|
||||
css_class = 'fas fa-users'
|
||||
enabled = repository.public_repo?
|
||||
[label, css_class, enabled]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,67 @@
|
|||
module ExtendRepositoriesHelper
|
||||
def encoding_field(form, repository)
|
||||
content_tag(:p) do
|
||||
form.select(
|
||||
:path_encoding, [nil] + Setting::ENCODINGS,
|
||||
label: l(:field_scm_path_encoding)
|
||||
) + '<br />'.html_safe + l(:text_scm_path_encoding_note)
|
||||
end
|
||||
end
|
||||
|
||||
def available_download_format(repository, rev = nil)
|
||||
%w[zip tar tar.gz].map { |f| [f, download_git_revision_repository_path(repository, rev: rev, download_format: f)] }
|
||||
end
|
||||
|
||||
def create_readme_field(form, repository)
|
||||
return unless repository.new_record?
|
||||
|
||||
content_tag(:p) do
|
||||
hidden_field_tag('repository[create_readme]', 'false', id: '') +
|
||||
content_tag(:label, l(:label_init_repo_with_readme), for: 'repository_create_readme') +
|
||||
check_box_tag('repository[create_readme]', 'true', RedmineGitHosting::Config.init_repositories_on_create?)
|
||||
end
|
||||
end
|
||||
|
||||
def enable_git_annex_field(form, repository)
|
||||
return unless repository.new_record?
|
||||
|
||||
content_tag(:p) do
|
||||
hidden_field_tag('repository[enable_git_annex]', 'false', id: '') +
|
||||
content_tag(:label, l(:label_init_repo_with_git_annex), for: 'repository_enable_git_annex') +
|
||||
check_box_tag('repository[enable_git_annex]', 'true')
|
||||
end
|
||||
end
|
||||
|
||||
def repository_branches_list(branches)
|
||||
options_for_select branches.collect { |b| [b.to_s, b.to_s] }, selected: branches.find(&:is_default).to_s
|
||||
end
|
||||
|
||||
def render_repository_quick_jump(repository)
|
||||
options = repository.project.repositories.map { |r| [r.redmine_name, edit_repository_path(r)] }
|
||||
select_tag('repository_quick_jump_box',
|
||||
options_for_select(options, selected: edit_repository_path(repository)),
|
||||
onchange: 'if (this.value != \'\') { window.location = this.value; }')
|
||||
end
|
||||
|
||||
def link_to_repository(repo, current_repo)
|
||||
css_class = ['repository', (repo == current_repo ? 'selected' : ''), current_repo.type.split('::')[1].downcase].join(' ')
|
||||
link_to h(repo.name),
|
||||
{ controller: 'repositories', action: 'show', id: @project, repository_id: repo.identifier_param, rev: nil, path: nil },
|
||||
class: css_class
|
||||
end
|
||||
|
||||
def icon_for_url_type(url_type)
|
||||
font_awesome_icon(RepositoryGitExtra::URLS_ICONS[url_type][:icon])
|
||||
end
|
||||
|
||||
def label_for_url_type(url_type)
|
||||
RepositoryGitExtra::URLS_ICONS[url_type][:label]
|
||||
end
|
||||
|
||||
def render_options_for_move_repo_select_box(project)
|
||||
projects = Project.active
|
||||
.where(Project.allowed_to_condition(User.current, :manage_repository))
|
||||
.where.not(id: project.id)
|
||||
project_tree_options_for_select(projects, selected: project) if projects.any?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
module GitHostingUsersHelper
|
||||
def user_settings_tabs
|
||||
tabs = super
|
||||
tabs << { name: 'keys', partial: 'gitolite_public_keys/view', label: :label_public_keys }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,102 @@
|
|||
module GitolitePluginSettingsHelper
|
||||
def render_gitolite_params_status(params)
|
||||
content_tag(:ul, class: 'list-unstyled') do
|
||||
content = ''
|
||||
params.each do |param, installed|
|
||||
content << content_tag(:li, style: 'padding: 2px;') do
|
||||
image_tag(image_for_param(installed), style: 'vertical-align: bottom; padding-right: 5px;') +
|
||||
content_tag(:em, label_for_param(param, installed))
|
||||
end
|
||||
end
|
||||
content.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def label_for_param(param, install_status)
|
||||
install_status == 2 ? "#{param} (#{l(:label_gitolite_hook_untouched)})" : param
|
||||
end
|
||||
|
||||
def image_for_param(install_status)
|
||||
case install_status
|
||||
when 0, true
|
||||
'true.png'
|
||||
when 1, false
|
||||
'exclamation.png'
|
||||
else
|
||||
'warning.png'
|
||||
end
|
||||
end
|
||||
|
||||
def render_gitolite_version(version)
|
||||
if version.nil?
|
||||
css_class = 'label label-important'
|
||||
label = l(:label_unknown_gitolite_version)
|
||||
else
|
||||
css_class = 'label label-success'
|
||||
label = version
|
||||
end
|
||||
content_tag(:span, label, class: css_class)
|
||||
end
|
||||
|
||||
def render_temp_dir_writeable(state, label)
|
||||
css_class = state ? 'label label-success' : 'label label-important'
|
||||
content_tag(:span, label, class: css_class)
|
||||
end
|
||||
|
||||
def gitolite_plugin_settings_tabs
|
||||
[
|
||||
{ name: 'gitolite_config_ssh', partial: 'settings/redmine_git_hosting/gitolite_config_ssh', label: :label_tab_ssh },
|
||||
{ name: 'gitolite_config_storage', partial: 'settings/redmine_git_hosting/gitolite_config_storage', label: :label_tab_storage },
|
||||
{ name: 'gitolite_config_file', partial: 'settings/redmine_git_hosting/gitolite_config_file', label: :label_tab_config_file },
|
||||
{ name: 'gitolite_config_global', partial: 'settings/redmine_git_hosting/gitolite_config_global', label: :label_tab_global },
|
||||
{ name: 'gitolite_config_access', partial: 'settings/redmine_git_hosting/gitolite_config_access', label: :label_tab_access },
|
||||
{ name: 'gitolite_config_hooks', partial: 'settings/redmine_git_hosting/gitolite_config_hooks', label: :label_tab_hooks },
|
||||
{ name: 'gitolite_config_cache', partial: 'settings/redmine_git_hosting/gitolite_config_cache', label: :label_tab_cache },
|
||||
{ name: 'gitolite_config_notify', partial: 'settings/redmine_git_hosting/gitolite_config_notify', label: :label_tab_notify },
|
||||
{ name: 'gitolite_redmine_config', partial: 'settings/redmine_git_hosting/redmine_config', label: :label_tab_redmine },
|
||||
{ name: 'gitolite_sidekiq_interface', partial: 'settings/redmine_git_hosting/sidekiq_interface', label: :label_tab_sidekiq_interface },
|
||||
{ name: 'gitolite_config_test', partial: 'settings/redmine_git_hosting/gitolite_config_test', label: :label_tab_config_test },
|
||||
{ name: 'gitolite_recycle_bin', partial: 'settings/redmine_git_hosting/gitolite_recycle_bin', label: :label_tab_gitolite_recycle_bin },
|
||||
{ name: 'gitolite_rescue', partial: 'settings/redmine_git_hosting/gitolite_rescue', label: :label_tab_gitolite_rescue }
|
||||
]
|
||||
end
|
||||
|
||||
def git_cache_options
|
||||
[
|
||||
['Cache Disabled', '0'],
|
||||
['Until next commit', '-1'],
|
||||
['1 Minute or until next commit', '60'],
|
||||
['15 Minutes or until next commit', '900'],
|
||||
['1 Hour or until next commit', '3600'],
|
||||
['1 Day or until next commit', '86400']
|
||||
]
|
||||
end
|
||||
|
||||
def log_level_options
|
||||
RedmineGitHosting::Logger::LOG_LEVELS.map { |level| [l("label_#{level}"), level] }
|
||||
end
|
||||
|
||||
def render_rugged_mandatory_features
|
||||
content = ''
|
||||
RedmineGitHosting::Config.rugged_mandatory_features.each do |feature|
|
||||
opts = if RedmineGitHosting::Config.rugged_features.include?(feature)
|
||||
{ class: 'label label-success' }
|
||||
else
|
||||
{ class: 'label label-important' }
|
||||
end
|
||||
content << content_tag(:span, feature, opts) + "\n"
|
||||
end
|
||||
content.html_safe
|
||||
end
|
||||
|
||||
def render_rugged_optional_features
|
||||
content = ''
|
||||
RedmineGitHosting::Config.rugged_features.each do |feature|
|
||||
unless RedmineGitHosting::Config.rugged_mandatory_features.include?(feature)
|
||||
opts = { class: 'label label-success' }
|
||||
content << content_tag(:span, feature, opts)
|
||||
end
|
||||
end
|
||||
content.html_safe
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
module GitolitePublicKeysHelper
|
||||
|
||||
def keylabel(key)
|
||||
key.user == User.current ? "#{key.title}" : "#{key.user.login}@#{key.title}"
|
||||
end
|
||||
|
||||
|
||||
def can_create_deployment_keys_for_some_project(theuser = User.current)
|
||||
return true if theuser.admin?
|
||||
theuser.projects_by_role.each_key do |role|
|
||||
return true if role.allowed_to?(:create_repository_deployment_credentials)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
module RepositoryDeploymentCredentialsHelper
|
||||
|
||||
def build_list_of_keys(user_keys, other_keys, disabled_keys)
|
||||
option_array = [[l(:label_deployment_credential_select_deploy_key), -1]]
|
||||
option_array += user_keys.map { |key| [keylabel(key), key.id] }
|
||||
|
||||
if !other_keys.empty?
|
||||
option_array2 = other_keys.map { |key| [keylabel(key), key.id] }
|
||||
maxlen = (option_array + option_array2).map { |x| x.first.length }.max
|
||||
|
||||
extra = ([maxlen - l(:select_other_keys).length - 2, 6].max) / 2
|
||||
option_array += [[('-' * extra) + ' ' + l(:select_other_keys) + ' ' + ('-' * extra), -2]]
|
||||
option_array += option_array2
|
||||
end
|
||||
|
||||
options_for_select(option_array, selected: -1, disabled: [-2] + disabled_keys.map(&:id))
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
module RepositoryGitConfigKeysHelper
|
||||
|
||||
def git_config_key_options
|
||||
[
|
||||
[l(:label_git_key_type_config), 'RepositoryGitConfigKey::GitConfig'],
|
||||
[l(:label_git_key_type_option), 'RepositoryGitConfigKey::Option']
|
||||
]
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
module RepositoryMirrorsHelper
|
||||
|
||||
# Mirror Mode
|
||||
def mirror_mode(mirror)
|
||||
[l(:label_mirror_full_mirror), l(:label_mirror_forced_update), l(:label_mirror_fast_forward)][mirror.push_mode]
|
||||
end
|
||||
|
||||
|
||||
# Refspec for mirrors
|
||||
def refspec(mirror, max_refspec = 0)
|
||||
if mirror.mirror_mode?
|
||||
l(:all_references)
|
||||
else
|
||||
result = []
|
||||
result << l(:all_branches) if mirror.include_all_branches
|
||||
result << l(:all_tags) if mirror.include_all_tags
|
||||
result << mirror.explicit_refspec if (max_refspec == 0) || ((1..max_refspec) === mirror.explicit_refspec.length)
|
||||
result << l(:explicit) if (max_refspec > 0) && (mirror.explicit_refspec.length > max_refspec)
|
||||
result.join(',<br />')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def mirrors_options
|
||||
[
|
||||
[l(:label_mirror_full_mirror), 0],
|
||||
[l(:label_mirror_forced_update), 1],
|
||||
[l(:label_mirror_fast_forward), 2]
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def render_push_state(mirror, error)
|
||||
if error
|
||||
status = l(:label_mirror_push_fail)
|
||||
status_css = 'important'
|
||||
else
|
||||
status = l(:label_mirror_push_sucess)
|
||||
status_css = 'success'
|
||||
end
|
||||
|
||||
l(:label_mirror_push_info_html, mirror_url: mirror.url, status: status, status_css: status_css).html_safe
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
module RepositoryPostReceiveUrlsHelper
|
||||
|
||||
# Post-receive Mode
|
||||
def post_receive_mode(prurl)
|
||||
label = []
|
||||
if prurl.github_mode?
|
||||
label << l(:label_github_post)
|
||||
label << "(#{l(:label_split_payloads)})" if prurl.split_payloads?
|
||||
else
|
||||
label << l(:label_empty_get)
|
||||
end
|
||||
label.join(' ')
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
module Gitolitable
|
||||
extend ActiveSupport::Concern
|
||||
include Gitolitable::Authorizations
|
||||
include Gitolitable::Cache
|
||||
include Gitolitable::Config
|
||||
include Gitolitable::Features
|
||||
include Gitolitable::Notifications
|
||||
include Gitolitable::Paths
|
||||
include Gitolitable::Permissions
|
||||
include Gitolitable::Urls
|
||||
include Gitolitable::Users
|
||||
include Gitolitable::Validations
|
||||
end
|
|
@ -0,0 +1,89 @@
|
|||
module Gitolitable
|
||||
module Authorizations
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# These are for repository Gitolite configuration
|
||||
|
||||
def git_daemon_available?
|
||||
User.anonymous.allowed_to?(:view_changesets, project) && git_daemon_enabled?
|
||||
end
|
||||
|
||||
|
||||
def git_web_available?
|
||||
User.anonymous.allowed_to?(:browse_repository, project) && smart_http_enabled?
|
||||
end
|
||||
|
||||
|
||||
def protected_branches_available?
|
||||
protected_branches_enabled? && project.active? && protected_branches.any?
|
||||
end
|
||||
|
||||
|
||||
def clonable_via_http?
|
||||
User.anonymous.allowed_to?(:view_changesets, project) || smart_http_enabled?
|
||||
end
|
||||
|
||||
|
||||
def pushable_via_http?
|
||||
https_access_enabled?
|
||||
end
|
||||
|
||||
|
||||
def git_notification_available?
|
||||
git_notification_enabled? && !mailing_list.empty?
|
||||
end
|
||||
|
||||
|
||||
# These are for repository URLs
|
||||
|
||||
def urls_are_viewable?
|
||||
RedmineGitHosting::Config.show_repositories_url? && User.current.allowed_to?(:view_changesets, project)
|
||||
end
|
||||
|
||||
|
||||
def ssh_access_available?
|
||||
git_ssh_enabled? && !git_annex_enabled? && User.current.allowed_to_commit?(self)
|
||||
end
|
||||
|
||||
|
||||
def https_access_available?
|
||||
https_access_enabled?
|
||||
end
|
||||
|
||||
|
||||
def http_access_available?
|
||||
http_access_enabled?
|
||||
end
|
||||
|
||||
|
||||
def git_access_available?
|
||||
(public_project? || public_repo?) && git_daemon_enabled?
|
||||
end
|
||||
|
||||
|
||||
def go_access_available?
|
||||
(public_project? || public_repo?) && smart_http_enabled? && git_go_enabled?
|
||||
end
|
||||
|
||||
|
||||
def git_annex_access_available?
|
||||
git_annex_enabled?
|
||||
end
|
||||
|
||||
|
||||
def downloadable?
|
||||
git_annex_enabled? ? false : User.current.allowed_to_download?(self)
|
||||
end
|
||||
|
||||
|
||||
def deletable?
|
||||
RedmineGitHosting::Config.delete_git_repositories?
|
||||
end
|
||||
|
||||
|
||||
def movable?
|
||||
!identifier.nil? && !identifier.empty?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,90 @@
|
|||
module Gitolitable
|
||||
module Cache
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
class << self
|
||||
|
||||
# Are repositories identifier unique?
|
||||
#
|
||||
def repo_ident_unique?
|
||||
RedmineGitHosting::Config.unique_repo_identifier?
|
||||
end
|
||||
|
||||
|
||||
# Translate repository path into a unique ID for use in caching of git commands.
|
||||
#
|
||||
def repo_path_to_git_cache_id(repo_path)
|
||||
repo = find_by_path(repo_path, loose: true)
|
||||
repo ? repo.git_cache_id : nil
|
||||
end
|
||||
|
||||
|
||||
# Parse a path of the form <proj1>/<proj2>/<proj3>/<repo> and return the specified
|
||||
# repository. If either 'repo_ident_unique?' is true or the <repo> is a project
|
||||
# identifier, just return the last component. Otherwise,
|
||||
# use the immediate parent (<proj3>) to try to identify the repo.
|
||||
#
|
||||
# Flags:
|
||||
# :loose => true : Try to identify corresponding repo even if path is not quite correct
|
||||
#
|
||||
# Note that the :loose flag is used when interpreting the contents of the
|
||||
# repository. If switching back and forth between the "repo_ident_unique?"
|
||||
# form, it will still identify the repository (as long as there are not more than
|
||||
# one repo with the same identifier.
|
||||
#
|
||||
# Example of data captured by regex :
|
||||
# <MatchData "test/test2/test3/test4/test5.git" 1:"test4/" 2:"test4" 3:"test5" 4:".git">
|
||||
# <MatchData "blabla2.git" 1:nil 2:nil 3:"blabla2" 4:".git">
|
||||
#
|
||||
def find_by_path(path, flags = {})
|
||||
parseit = path.match(/\A.*?(([^\/]+)\/)?([^\/]+?)(\.git)?\z/)
|
||||
return nil if parseit.nil?
|
||||
|
||||
project = Project.find_by_identifier(parseit[3])
|
||||
|
||||
# return default or first repo with blank identifier (or first Git repo--very rare?)
|
||||
if project
|
||||
project.repository || project.repo_blank_ident || project.gitolite_repos.first
|
||||
|
||||
elsif repo_ident_unique? || flags[:loose] && parseit[2].nil?
|
||||
find_by_identifier(parseit[3])
|
||||
|
||||
elsif parseit[2]
|
||||
project = Project.find_by_identifier(parseit[2])
|
||||
|
||||
if project.nil?
|
||||
find_by_identifier(parseit[3])
|
||||
else
|
||||
find_by_identifier_and_project_id(parseit[3], project.id) || (flags[:loose] && find_by_identifier(parseit[3]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# If repositories identifiers are unique, identifier forms a unique label,
|
||||
# else use directory notation: <project identifier>/<repo identifier>
|
||||
#
|
||||
def git_cache_id
|
||||
if identifier.blank?
|
||||
# Should only happen with one repo/project (the default)
|
||||
project.identifier
|
||||
elsif self.class.repo_ident_unique?
|
||||
identifier
|
||||
else
|
||||
"#{project.identifier}/#{identifier}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Note: RedmineGitHosting::Cache doesn't know about repository object, it only knows *git_cache_id*.
|
||||
#
|
||||
def empty_cache!
|
||||
RedmineGitHosting::Cache.clear_cache_for_repository(git_cache_id)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,85 @@
|
|||
module Gitolitable
|
||||
module Config
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def git_config
|
||||
repo_conf = {}
|
||||
|
||||
# This is needed for all Redmine repositories
|
||||
repo_conf['redminegitolite.projectid'] = project.identifier.to_s
|
||||
repo_conf['redminegitolite.repositoryid'] = identifier || ''
|
||||
repo_conf['redminegitolite.repositorykey'] = gitolite_hook_key
|
||||
|
||||
if project.active?
|
||||
repo_conf['http.uploadpack'] = clonable_via_http?.to_s
|
||||
repo_conf['http.receivepack'] = pushable_via_http?.to_s
|
||||
|
||||
if git_notification_available?
|
||||
repo_conf['multimailhook.enabled'] = 'true'
|
||||
repo_conf['multimailhook.mailinglist'] = mailing_list.join(', ')
|
||||
repo_conf['multimailhook.from'] = sender_address
|
||||
repo_conf['multimailhook.emailPrefix'] = email_prefix
|
||||
else
|
||||
repo_conf['multimailhook.enabled'] = 'false'
|
||||
end
|
||||
|
||||
git_config_keys.each do |git|
|
||||
repo_conf[git.key] = git.value
|
||||
end if git_config_keys.any?
|
||||
|
||||
else
|
||||
# Disable repository
|
||||
repo_conf['http.uploadpack'] = 'false'
|
||||
repo_conf['http.receivepack'] = 'false'
|
||||
repo_conf['multimailhook.enabled'] = 'false'
|
||||
end
|
||||
|
||||
repo_conf
|
||||
end
|
||||
|
||||
|
||||
def gitolite_options
|
||||
repo_conf = {}
|
||||
|
||||
git_option_keys.each do |option|
|
||||
repo_conf[option.key] = option.value
|
||||
end if git_option_keys.any?
|
||||
|
||||
repo_conf
|
||||
end
|
||||
|
||||
|
||||
def owner
|
||||
{ name: Setting['app_title'], email: Setting['mail_from'] }
|
||||
end
|
||||
|
||||
|
||||
def github_payload
|
||||
{
|
||||
repository: {
|
||||
owner: owner,
|
||||
description: project.description,
|
||||
fork: false,
|
||||
forks: 0,
|
||||
homepage: project.homepage,
|
||||
name: redmine_name,
|
||||
open_issues: project.issues.open.length,
|
||||
watchers: 0,
|
||||
private: !project.is_public,
|
||||
url: repository_url
|
||||
},
|
||||
pusher: owner,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def repository_url
|
||||
Rails.application.routes.url_helpers.url_for(
|
||||
controller: 'repositories', action: 'show',
|
||||
id: project, repository_id: identifier_param,
|
||||
only_path: false, host: Setting['host_name'], protocol: Setting['protocol']
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,97 @@
|
|||
module Gitolitable
|
||||
module Features
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Always true to force repository fetch_changesets.
|
||||
def report_last_commit
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
# Always true to force repository fetch_changesets.
|
||||
def extra_report_last_commit
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def git_default_branch
|
||||
extra[:default_branch]
|
||||
end
|
||||
|
||||
|
||||
def gitolite_hook_key
|
||||
extra[:key]
|
||||
end
|
||||
|
||||
|
||||
def git_daemon_enabled?
|
||||
extra[:git_daemon]
|
||||
end
|
||||
|
||||
|
||||
def git_annex_enabled?
|
||||
extra[:git_annex]
|
||||
end
|
||||
|
||||
|
||||
def git_notification_enabled?
|
||||
extra[:git_notify]
|
||||
end
|
||||
|
||||
|
||||
def git_ssh_enabled?
|
||||
extra[:git_ssh]
|
||||
end
|
||||
|
||||
|
||||
def git_go_enabled?
|
||||
extra[:git_go]
|
||||
end
|
||||
|
||||
|
||||
def https_access_enabled?
|
||||
extra[:git_https]
|
||||
end
|
||||
|
||||
|
||||
def http_access_enabled?
|
||||
extra[:git_http]
|
||||
end
|
||||
|
||||
|
||||
def smart_http_enabled?
|
||||
https_access_enabled? || http_access_enabled?
|
||||
end
|
||||
|
||||
|
||||
def only_https_access_enabled?
|
||||
https_access_enabled? && !http_access_enabled?
|
||||
end
|
||||
|
||||
|
||||
def only_http_access_enabled?
|
||||
http_access_enabled? && !https_access_enabled?
|
||||
end
|
||||
|
||||
|
||||
def protected_branches_enabled?
|
||||
extra[:protected_branch]
|
||||
end
|
||||
|
||||
|
||||
def public_project?
|
||||
project.is_public?
|
||||
end
|
||||
|
||||
|
||||
def public_repo?
|
||||
extra[:public_repo]
|
||||
end
|
||||
|
||||
|
||||
def urls_order
|
||||
extra[:urls_order]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
module Gitolitable
|
||||
module Notifications
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def mailing_list
|
||||
default_list + global_include_list - global_exclude_list
|
||||
end
|
||||
|
||||
|
||||
def default_list
|
||||
watcher_users.map(&:email_address).map(&:address)
|
||||
end
|
||||
|
||||
|
||||
def global_include_list
|
||||
RedmineGitHosting::Config.gitolite_notify_global_include
|
||||
end
|
||||
|
||||
|
||||
def global_exclude_list
|
||||
RedmineGitHosting::Config.gitolite_notify_global_exclude
|
||||
end
|
||||
|
||||
|
||||
def sender_address
|
||||
if extra.notification_sender.nil? || extra.notification_sender.empty?
|
||||
RedmineGitHosting::Config.gitolite_notify_global_sender_address
|
||||
else
|
||||
extra.notification_sender
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def email_prefix
|
||||
if extra.notification_prefix.nil? || extra.notification_prefix.empty?
|
||||
RedmineGitHosting::Config.gitolite_notify_global_prefix
|
||||
else
|
||||
extra.notification_prefix
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,94 @@
|
|||
module Gitolitable
|
||||
module Paths
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
|
||||
# This is the repository path from Redmine point of view.
|
||||
# It is used to build HTTP(s) urls (including GoLang url).
|
||||
# It doesn't contain references to internal directories like *gitolite_global_storage_dir* or *gitolite_redmine_storage_dir*
|
||||
# to stay abstract from the real repository location.
|
||||
# In this case, the real repository path is deduced from the path given thanks to the *find_by_path* method.
|
||||
#
|
||||
# Example : blabla/test-blabla/uuuuuuuuuuu/oooooo
|
||||
#
|
||||
# Call File.expand_path to add then remove heading /
|
||||
#
|
||||
def redmine_repository_path
|
||||
File.expand_path(File.join('./', get_full_parent_path, git_cache_id), '/')[1..-1]
|
||||
end
|
||||
|
||||
|
||||
# This is the Gitolite repository identifier as it should appear in Gitolite config file.
|
||||
# Example : redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo
|
||||
# (with 'redmine' a subdir of the Gitolite storage directory)
|
||||
#
|
||||
# Call File.expand_path to add then remove heading /
|
||||
#
|
||||
def gitolite_repository_name
|
||||
File.expand_path(File.join('./', RedmineGitHosting::Config.gitolite_redmine_storage_dir, get_full_parent_path, git_cache_id), '/')[1..-1]
|
||||
end
|
||||
|
||||
|
||||
# The Gitolite repository identifier with the .git extension.
|
||||
# Example : redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo.git
|
||||
#
|
||||
def gitolite_repository_name_with_extension
|
||||
"#{gitolite_repository_name}.git"
|
||||
end
|
||||
|
||||
|
||||
# This is the relative path to the Gitolite repository.
|
||||
# Example : repositories/redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo.git
|
||||
# (with 'repositories' the Gitolite storage directory).
|
||||
#
|
||||
def gitolite_repository_path
|
||||
File.join(RedmineGitHosting::Config.gitolite_global_storage_dir, gitolite_repository_name_with_extension)
|
||||
end
|
||||
|
||||
|
||||
# This is the full absolute path to the Gitolite repository.
|
||||
# Example : /home/git/repositories/redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo.git
|
||||
#
|
||||
def gitolite_full_repository_path
|
||||
File.join(RedmineGitHosting::Config.gitolite_home_dir, gitolite_repository_path)
|
||||
end
|
||||
|
||||
|
||||
# A syntaxic sugar used to move repository from a location to an other
|
||||
# Example : repositories/blabla/test-blabla/uuuuuuuuuuu/oooooo
|
||||
#
|
||||
def new_repository_name
|
||||
gitolite_repository_name
|
||||
end
|
||||
|
||||
|
||||
# Used to move repository from a location to an other.
|
||||
# At this point repository url still points to the old location but
|
||||
# it contains the Gitolite storage directory in its path and the '.git' extension.
|
||||
# Strip them to get the old repository name.
|
||||
# Example :
|
||||
# before : repositories/redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo.git
|
||||
# after : redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo
|
||||
#
|
||||
def old_repository_name
|
||||
url.gsub(RedmineGitHosting::Config.gitolite_global_storage_dir, '').gsub('.git', '')
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def get_full_parent_path
|
||||
return '' if !RedmineGitHosting::Config.hierarchical_organisation?
|
||||
parent_parts = []
|
||||
p = project
|
||||
while p.parent
|
||||
parent_id = p.parent.identifier.to_s
|
||||
parent_parts.unshift(parent_id)
|
||||
p = p.parent
|
||||
end
|
||||
parent_parts.join('/')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
module Gitolitable
|
||||
module Permissions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def build_gitolite_permissions(old_perms = {})
|
||||
permissions_builder.build(self, gitolite_users, old_perms)
|
||||
end
|
||||
|
||||
|
||||
# We assume here that ':gitolite_config_file' is different than 'gitolite.conf'
|
||||
# like 'redmine.conf' with 'include "redmine.conf"' in 'gitolite.conf'.
|
||||
# This way, we know that all repos in this file are managed by Redmine so we
|
||||
# don't need to backup users
|
||||
#
|
||||
def backup_gitolite_permissions(current_permissions)
|
||||
if protected_branches_available? || RedmineGitHosting::Config.gitolite_identifier_prefix == ''
|
||||
{}
|
||||
else
|
||||
extract_permissions(current_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def permissions_builder
|
||||
if protected_branches_available?
|
||||
PermissionsBuilder::ProtectedBranches
|
||||
else
|
||||
PermissionsBuilder::Standard
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
SKIP_USERS = ['gitweb', 'daemon', 'DUMMY_REDMINE_KEY', 'REDMINE_ARCHIVED_PROJECT', 'REDMINE_CLOSED_PROJECT']
|
||||
|
||||
|
||||
def extract_permissions(current_permissions)
|
||||
old_permissions = {}
|
||||
|
||||
current_permissions.each do |perm, branch_settings|
|
||||
old_permissions[perm] = {}
|
||||
|
||||
branch_settings.each do |branch, user_list|
|
||||
next if user_list.empty?
|
||||
|
||||
new_user_list = []
|
||||
|
||||
user_list.each do |user|
|
||||
# ignore these users
|
||||
next if SKIP_USERS.include?(user)
|
||||
|
||||
# backup users that are not Redmine users
|
||||
new_user_list.push(user) if !user.include?(RedmineGitHosting::Config.gitolite_identifier_prefix)
|
||||
end
|
||||
|
||||
old_permissions[perm][branch] = new_user_list if new_user_list.any?
|
||||
end
|
||||
end
|
||||
|
||||
old_permissions
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,122 @@
|
|||
module Gitolitable
|
||||
module Urls
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def http_user_login
|
||||
User.current.anonymous? ? '' : "#{User.current.login}@"
|
||||
end
|
||||
|
||||
|
||||
def git_access_path
|
||||
gitolite_repository_name_with_extension
|
||||
end
|
||||
|
||||
|
||||
def http_access_path
|
||||
"#{RedmineGitHosting::Config.http_server_subdir}#{redmine_repository_path}.git"
|
||||
end
|
||||
|
||||
|
||||
def go_access_path
|
||||
"go/#{redmine_repository_path}"
|
||||
end
|
||||
|
||||
|
||||
def ssh_url
|
||||
"ssh://#{RedmineGitHosting::Config.gitolite_user}@#{RedmineGitHosting::Config.ssh_server_domain}/#{git_access_path}"
|
||||
end
|
||||
|
||||
|
||||
def git_url
|
||||
"git://#{RedmineGitHosting::Config.ssh_server_domain}/#{git_access_path}"
|
||||
end
|
||||
|
||||
|
||||
def http_url
|
||||
"http://#{http_user_login}#{RedmineGitHosting::Config.http_root_url}/#{http_access_path}"
|
||||
end
|
||||
|
||||
|
||||
def https_url
|
||||
"https://#{http_user_login}#{RedmineGitHosting::Config.https_root_url}/#{http_access_path}"
|
||||
end
|
||||
|
||||
|
||||
def git_annex_url
|
||||
"#{RedmineGitHosting::Config.gitolite_user}@#{RedmineGitHosting::Config.ssh_server_domain}:#{git_access_path}"
|
||||
end
|
||||
|
||||
|
||||
# This is the url used by Go to clone repository
|
||||
#
|
||||
def go_access_url
|
||||
return '' if !smart_http_enabled?
|
||||
return https_url if https_access_available?
|
||||
return http_url if http_access_available?
|
||||
end
|
||||
|
||||
|
||||
# This is the url to add in Go files
|
||||
#
|
||||
def go_url
|
||||
return '' if !smart_http_enabled?
|
||||
return "#{RedmineGitHosting::Config.https_root_url}/#{go_access_path}" if https_access_available?
|
||||
return "#{RedmineGitHosting::Config.http_root_url}/#{go_access_path}" if http_access_available?
|
||||
end
|
||||
|
||||
|
||||
def ssh_access
|
||||
{ url: ssh_url, committer: User.current.allowed_to_commit?(self).to_s }
|
||||
end
|
||||
|
||||
|
||||
## Unsecure channels (clear password), commit is disabled
|
||||
def http_access
|
||||
{ url: http_url, committer: 'false' }
|
||||
end
|
||||
|
||||
|
||||
def https_access
|
||||
{ url: https_url, committer: User.current.allowed_to_commit?(self).to_s }
|
||||
end
|
||||
|
||||
|
||||
def git_access
|
||||
{ url: git_url, committer: 'false' }
|
||||
end
|
||||
|
||||
|
||||
def git_annex_access
|
||||
{ url: git_annex_url, committer: User.current.allowed_to_commit?(self).to_s }
|
||||
end
|
||||
|
||||
|
||||
def go_access
|
||||
{ url: go_url, committer: 'false' }
|
||||
end
|
||||
|
||||
|
||||
def available_urls
|
||||
hash = {}
|
||||
hash[:ssh] = ssh_access if ssh_access_available?
|
||||
hash[:https] = https_access if https_access_available?
|
||||
hash[:http] = http_access if http_access_available?
|
||||
hash[:git] = git_access if git_access_available?
|
||||
hash[:go] = go_access if go_access_available?
|
||||
hash[:git_annex] = git_annex_access if git_annex_access_available?
|
||||
hash
|
||||
end
|
||||
|
||||
|
||||
def available_urls_sorted
|
||||
return available_urls if urls_order.nil? || urls_order.empty?
|
||||
hash = {}
|
||||
urls_order.each do |url|
|
||||
next if !available_urls[url.to_sym]
|
||||
hash[url.to_sym] = available_urls[url.to_sym]
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,109 @@
|
|||
module Gitolitable
|
||||
module Users
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def gitolite_users
|
||||
if project.active?
|
||||
users_for_active_project
|
||||
elsif project.archived?
|
||||
users_for_archived_project
|
||||
else
|
||||
users_for_closed_project
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def users_for_active_project
|
||||
data = {}
|
||||
data[:rewind_users] = rewind_users + rewind_deploy_users
|
||||
data[:write_users] = write_users
|
||||
data[:read_users] = read_users + read_deploy_users
|
||||
data[:developer_team] = developer_team
|
||||
data[:all_read] = all_users
|
||||
|
||||
# Add other users
|
||||
data[:read_users] << 'DUMMY_REDMINE_KEY' if read_users.empty? && write_users.empty? && rewind_users.empty?
|
||||
data[:read_users] << 'gitweb' if git_web_available?
|
||||
data[:read_users] << 'daemon' if git_daemon_available?
|
||||
|
||||
# Return users
|
||||
data
|
||||
end
|
||||
|
||||
|
||||
def users_for_archived_project
|
||||
data = {}
|
||||
data[:read_users] = ['REDMINE_ARCHIVED_PROJECT']
|
||||
data
|
||||
end
|
||||
|
||||
|
||||
def users_for_closed_project
|
||||
data = {}
|
||||
data[:read_users] = all_users
|
||||
data[:read_users] << 'REDMINE_CLOSED_PROJECT'
|
||||
data
|
||||
end
|
||||
|
||||
|
||||
def users
|
||||
project.users_available
|
||||
end
|
||||
|
||||
|
||||
def rewind_users
|
||||
@rewind_users ||= users.select { |u| u.allowed_to?(:manage_repository, project) }.map { |u| u.gitolite_identifier }.sort
|
||||
end
|
||||
|
||||
|
||||
def write_users
|
||||
@write_users ||= users.select { |u| u.allowed_to?(:commit_access, project) }.map { |u| u.gitolite_identifier }.sort - rewind_users
|
||||
end
|
||||
|
||||
|
||||
def read_users
|
||||
@read_users ||= users.select { |u| u.allowed_to?(:view_changesets, project) }.map { |u| u.gitolite_identifier }.sort - rewind_users - write_users
|
||||
end
|
||||
|
||||
|
||||
def developer_team
|
||||
@developer_team ||= (rewind_users + write_users).sort
|
||||
end
|
||||
|
||||
|
||||
def all_users
|
||||
@all_users ||= (rewind_users + write_users + read_users).sort
|
||||
end
|
||||
|
||||
|
||||
def rewind_deploy_users
|
||||
deploy_users_for_keys(rewind_deploy_keys)
|
||||
end
|
||||
|
||||
|
||||
def read_deploy_users
|
||||
deploy_users_for_keys(read_deploy_keys)
|
||||
end
|
||||
|
||||
|
||||
def rewind_deploy_keys
|
||||
deploy_keys_by_perm('RW+')
|
||||
end
|
||||
|
||||
|
||||
def read_deploy_keys
|
||||
deploy_keys_by_perm('R')
|
||||
end
|
||||
|
||||
|
||||
def deploy_keys_by_perm(perm)
|
||||
deployment_credentials.active.select { |cred| cred.perm == perm }
|
||||
end
|
||||
|
||||
|
||||
def deploy_users_for_keys(keys)
|
||||
keys.map { |cred| cred.gitolite_public_key.owner }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,122 @@
|
|||
module Gitolitable
|
||||
module Validations
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Set URL ourself as relative path.
|
||||
#
|
||||
before_validation :set_git_urls
|
||||
|
||||
# Make sure that identifier does not match Gitolite Admin repository
|
||||
#
|
||||
validates_exclusion_of :identifier, in: %w(gitolite-admin)
|
||||
|
||||
# Place additional constraints on repository identifiers
|
||||
# because of multi repos
|
||||
#
|
||||
validate :additional_constraints_on_identifier
|
||||
validate :identifier_dont_change
|
||||
validate :default_repository_has_identifier
|
||||
|
||||
class << self
|
||||
|
||||
# Build a hash of repository identifier :
|
||||
# <repo_1_identifier> => 1
|
||||
# <repo_2_identifier> => 1
|
||||
# etc...
|
||||
# If the same repository identifier is found many times, increment the corresponding counter.
|
||||
# Repository identifiers are unique if all values of the hash are 1.
|
||||
#
|
||||
def identifiers_to_hash
|
||||
self.all.map(&:identifier).inject(Hash.new(0)) do |h, x|
|
||||
h[x] += 1 unless x.blank?
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def have_duplicated_identifier?
|
||||
(identifiers_to_hash.values.max || 0) > 1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def exists_in_gitolite?
|
||||
RedmineGitHosting::Commands.sudo_dir_exists?(gitolite_repository_path)
|
||||
end
|
||||
|
||||
|
||||
def empty_in_gitolite?
|
||||
RedmineGitHosting::Commands.sudo_repository_empty?(gitolite_repository_path)
|
||||
end
|
||||
|
||||
|
||||
def git_objects_count
|
||||
RedmineGitHosting::Commands.sudo_git_objects_count(File.join(gitolite_repository_path, 'objects'))
|
||||
end
|
||||
|
||||
|
||||
def empty?
|
||||
extra_info.nil? || (!extra_info.has_key?('heads') && !extra_info.has_key?('branches'))
|
||||
end
|
||||
|
||||
|
||||
def data_for_destruction
|
||||
{
|
||||
repo_name: gitolite_repository_name,
|
||||
repo_path: gitolite_full_repository_path,
|
||||
delete_repository: deletable?,
|
||||
git_cache_id: git_cache_id
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Set up git urls for new repositories
|
||||
#
|
||||
def set_git_urls
|
||||
self.url = gitolite_repository_path if self.url.blank?
|
||||
self.root_url = self.url if self.root_url.blank?
|
||||
end
|
||||
|
||||
|
||||
# Check several aspects of repository identifier (only for Redmine 1.4+)
|
||||
# 1) cannot equal identifier of any project
|
||||
# 2) if repo_ident_unique? make sure that repo identifier is globally unique
|
||||
# 3) cannot make this repo the default if there will be some other repo with blank identifier
|
||||
#
|
||||
def additional_constraints_on_identifier
|
||||
if !identifier.blank? && (new_record? || identifier_changed?)
|
||||
errors.add(:identifier, :cannot_equal_project) if Project.find_by_identifier(identifier)
|
||||
|
||||
# See if a repo for another project has the same identifier (existing validations already check for current project)
|
||||
errors.add(:identifier, :taken) if self.class.repo_ident_unique? && Repository.where("identifier = ? and project_id <> ?", identifier, project.id).any?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Make sure identifier hasn't changed. Allow null and blank
|
||||
# Note that simply using identifier_changed doesn't seem to work
|
||||
# if the identifier was "NULL" but the new identifier is ""
|
||||
#
|
||||
def identifier_dont_change
|
||||
return if new_record?
|
||||
errors.add(:identifier, :cannot_change) if (identifier_was.blank? && !identifier.blank?) || (!identifier_was.blank? && identifier_changed?)
|
||||
end
|
||||
|
||||
|
||||
# Need to make sure that we don't take the default slot away from a sibling repo with blank identifier
|
||||
#
|
||||
def default_repository_has_identifier
|
||||
if project && (is_default? || set_as_default?)
|
||||
possibles = Repository.where("project_id = ? and (identifier = '' or identifier is null)", project.id)
|
||||
errors.add(:base, :blank_default_exists) if possibles.any? && (new_record? || possibles.detect { |x| x.id != id })
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
21
plugins/redmine_git_hosting/app/models/git_cache.rb
Normal file
21
plugins/redmine_git_hosting/app/models/git_cache.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class GitCache < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
CACHE_ADAPTERS = [%w[Database database],
|
||||
%w[Memcached memcached],
|
||||
%w[Redis redis]].freeze
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'repo_identifier', 'command', 'command_output'
|
||||
|
||||
## Validations
|
||||
validates :repo_identifier, presence: true
|
||||
validates :command, presence: true
|
||||
validates :command_output, presence: true
|
||||
|
||||
class << self
|
||||
def adapters
|
||||
CACHE_ADAPTERS.map(&:last)
|
||||
end
|
||||
end
|
||||
end
|
9
plugins/redmine_git_hosting/app/models/github_comment.rb
Normal file
9
plugins/redmine_git_hosting/app/models/github_comment.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class GithubComment < ActiveRecord::Base
|
||||
|
||||
## Relations
|
||||
belongs_to :journal
|
||||
|
||||
## Validations
|
||||
validates :github_id, presence: true
|
||||
validates :journal_id, presence: true, uniqueness: { scope: :github_id }
|
||||
end
|
9
plugins/redmine_git_hosting/app/models/github_issue.rb
Normal file
9
plugins/redmine_git_hosting/app/models/github_issue.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class GithubIssue < ActiveRecord::Base
|
||||
|
||||
## Relations
|
||||
belongs_to :issue
|
||||
|
||||
## Validations
|
||||
validates :github_id, presence: true
|
||||
validates :issue_id, presence: true, uniqueness: { scope: :github_id }
|
||||
end
|
202
plugins/redmine_git_hosting/app/models/gitolite_public_key.rb
Normal file
202
plugins/redmine_git_hosting/app/models/gitolite_public_key.rb
Normal file
|
@ -0,0 +1,202 @@
|
|||
class GitolitePublicKey < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
TITLE_LENGTH_LIMIT = 60
|
||||
|
||||
KEY_TYPE_USER = 0
|
||||
KEY_TYPE_DEPLOY = 1
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'title', 'key', 'key_type', 'delete_when_unused'
|
||||
|
||||
## Relations
|
||||
belongs_to :user
|
||||
has_many :repository_deployment_credentials, dependent: :destroy
|
||||
|
||||
## Validations
|
||||
validates :user_id, presence: true
|
||||
|
||||
validates :title, presence: true, uniqueness: { case_sensitive: false, scope: :user_id },
|
||||
length: { maximum: TITLE_LENGTH_LIMIT }, format: /\A[a-z0-9_\-]*\z/i
|
||||
|
||||
validates :identifier, presence: true, uniqueness: { case_sensitive: false, scope: :user_id }
|
||||
validates :key, presence: true
|
||||
validates :key_type, presence: true, numericality: { only_integer: true },
|
||||
inclusion: { in: [KEY_TYPE_USER, KEY_TYPE_DEPLOY] }
|
||||
|
||||
validate :has_not_been_changed
|
||||
validate :key_correctness
|
||||
validate :key_not_admin
|
||||
validate :key_uniqueness
|
||||
|
||||
## Scopes
|
||||
scope :user_key, -> { where(key_type: KEY_TYPE_USER) }
|
||||
scope :deploy_key, -> { where(key_type: KEY_TYPE_DEPLOY) }
|
||||
|
||||
## Callbacks
|
||||
before_validation :strip_whitespace
|
||||
before_validation :remove_control_characters
|
||||
|
||||
before_validation :set_identifier
|
||||
before_validation :set_fingerprint
|
||||
|
||||
def key_type_as_string
|
||||
user_key? ? 'user_key' : 'deploy_key'
|
||||
end
|
||||
|
||||
def to_s
|
||||
title
|
||||
end
|
||||
|
||||
def data_for_destruction
|
||||
{ title: identifier, key: key, location: location, owner: owner }
|
||||
end
|
||||
|
||||
# Returns the path to this key under the gitolite keydir
|
||||
# resolves to <user.gitolite_identifier>/<location>/<owner>.pub
|
||||
#
|
||||
# tile: test-key
|
||||
# identifier: redmine_admin_1@redmine_test_key
|
||||
# identifier: redmine_admin_1@redmine_deploy_key_1
|
||||
#
|
||||
#
|
||||
# keydir/
|
||||
# ├── redmine_git_hosting
|
||||
# │ └── redmine_admin_1
|
||||
# │ ├── redmine_test_key
|
||||
# │ │ └── redmine_admin_1.pub
|
||||
# │ ├── redmine_deploy_key_1
|
||||
# │ │ └── redmine_admin_1.pub
|
||||
# │ └── redmine_deploy_key_2
|
||||
# │ └── redmine_admin_1.pub
|
||||
# └── redmine_gitolite_admin_id_rsa.pub
|
||||
#
|
||||
#
|
||||
# The root folder for this user is the user's identifier
|
||||
# for logical grouping of their keys, which are organized
|
||||
# by their title in subfolders.
|
||||
#
|
||||
# This is due to the new gitolite multi-keys organization
|
||||
# using folders. See https://gitolite.com/gitolite/users.html
|
||||
def gitolite_path
|
||||
File.join('keydir', RedmineGitHosting::Config.gitolite_key_subdir, user.gitolite_identifier, location, owner) + '.pub'
|
||||
end
|
||||
|
||||
# Make sure that current identifier is consistent with current user login.
|
||||
# This method explicitly overrides the static nature of the identifier
|
||||
def reset_identifiers(opts = {})
|
||||
# Fix identifier
|
||||
self.identifier = nil
|
||||
self.fingerprint = nil
|
||||
|
||||
self.identifier = GitolitePublicKeys::GenerateIdentifier.call(self, user, opts)
|
||||
set_fingerprint
|
||||
|
||||
# Need to override the "never change identifier" constraint
|
||||
save(validate: false)
|
||||
end
|
||||
|
||||
# Key type checking functions
|
||||
def user_key?
|
||||
key_type == KEY_TYPE_USER
|
||||
end
|
||||
|
||||
def deploy_key?
|
||||
key_type == KEY_TYPE_DEPLOY
|
||||
end
|
||||
|
||||
def owner
|
||||
identifier.split('@')[0]
|
||||
end
|
||||
|
||||
def location
|
||||
identifier.split('@')[1]
|
||||
end
|
||||
|
||||
def type
|
||||
key.split(' ')[0]
|
||||
end
|
||||
|
||||
def blob
|
||||
key.split(' ')[1]
|
||||
end
|
||||
|
||||
def email
|
||||
key.split(' ')[2]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Strip leading and trailing whitespace
|
||||
# Don't mess with existing keys (since cannot change key text anyway)
|
||||
#
|
||||
def strip_whitespace
|
||||
return unless new_record?
|
||||
|
||||
self.title = title.strip rescue ''
|
||||
self.key = key.strip rescue ''
|
||||
end
|
||||
|
||||
# Remove control characters from key
|
||||
# Don't mess with existing keys (since cannot change key text anyway)
|
||||
#
|
||||
def remove_control_characters
|
||||
return unless new_record?
|
||||
|
||||
self.key = RedmineGitHosting::Utils::Ssh.sanitize_ssh_key(key)
|
||||
end
|
||||
|
||||
# Returns the unique identifier for this key based on the key_type
|
||||
#
|
||||
# For user public keys, this simply is the user's gitolite_identifier.
|
||||
# For deployment keys, we use an incrementing number.
|
||||
#
|
||||
def set_identifier
|
||||
return nil if user_id.nil?
|
||||
|
||||
self.identifier ||= GitolitePublicKeys::GenerateIdentifier.call(self, user)
|
||||
end
|
||||
|
||||
def set_fingerprint
|
||||
self.fingerprint = RedmineGitHosting::Utils::Ssh.ssh_fingerprint(key)
|
||||
rescue RedmineGitHosting::Error::InvalidSshKey => e
|
||||
errors.add(:key, :corrupted)
|
||||
end
|
||||
|
||||
def has_not_been_changed
|
||||
return if new_record?
|
||||
|
||||
%w[identifier key user_id key_type title fingerprint].each do |attribute|
|
||||
method = "#{attribute}_changed?"
|
||||
errors.add(attribute, :cannot_change) if send(method)
|
||||
end
|
||||
end
|
||||
|
||||
# Test correctness of fingerprint from output
|
||||
# and general ssh-(r|d|ecd)sa <key> <id> structure
|
||||
#
|
||||
def key_correctness
|
||||
return false if key.nil?
|
||||
|
||||
key.match(/^(\S+)\s+(\S+)/) && (fingerprint =~ /^(\w{2}:?)+$/i)
|
||||
end
|
||||
|
||||
def key_not_admin
|
||||
errors.add(:key, :taken_by_gitolite_admin) if fingerprint == RedmineGitHosting::Config.gitolite_ssh_public_key_fingerprint
|
||||
end
|
||||
|
||||
def key_uniqueness
|
||||
return unless new_record?
|
||||
|
||||
existing = GitolitePublicKey.find_by_fingerprint(fingerprint)
|
||||
return unless existing
|
||||
|
||||
if existing.user == User.current
|
||||
errors.add(:key, :taken_by_you, name: existing.title)
|
||||
elsif User.current.admin?
|
||||
errors.add(:key, :taken_by_other, login: existing.user.login, name: existing.title)
|
||||
else
|
||||
errors.add(:key, :taken_by_someone)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
class ProtectedBranchesMember < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'principal_id', 'inherited_by'
|
||||
|
||||
## Relations
|
||||
belongs_to :protected_branch, class_name: 'RepositoryProtectedBranche'
|
||||
belongs_to :principal
|
||||
|
||||
## Callbacks
|
||||
after_destroy :remove_dependent_objects
|
||||
|
||||
private
|
||||
|
||||
def remove_dependent_objects
|
||||
return unless principal.class.name == 'Group'
|
||||
|
||||
principal.users.each do |user|
|
||||
member = self.class.find_by_principal_id_and_inherited_by(user.id, principal.id)
|
||||
member.destroy! unless member.nil?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
require_dependency 'redmine/scm/adapters/xitolite_adapter'
|
||||
|
||||
class Repository::Xitolite < Repository::Git
|
||||
# Include Gitolitable concern
|
||||
include Gitolitable
|
||||
|
||||
# Virtual attributes
|
||||
attr_accessor :create_readme
|
||||
attr_accessor :enable_git_annex
|
||||
|
||||
# Redmine uses safe_attributes on Repository, so we need to declare our virtual attributes.
|
||||
safe_attributes 'create_readme', 'enable_git_annex'
|
||||
|
||||
# Relations
|
||||
has_one :extra, dependent: :destroy, foreign_key: 'repository_id', class_name: 'RepositoryGitExtra'
|
||||
has_many :mirrors, dependent: :destroy, foreign_key: 'repository_id', class_name: 'RepositoryMirror'
|
||||
has_many :post_receive_urls, dependent: :destroy, foreign_key: 'repository_id', class_name: 'RepositoryPostReceiveUrl'
|
||||
has_many :deployment_credentials, dependent: :destroy, foreign_key: 'repository_id', class_name: 'RepositoryDeploymentCredential'
|
||||
has_many :git_keys, dependent: :destroy, foreign_key: 'repository_id', class_name: 'RepositoryGitConfigKey'
|
||||
has_many :git_config_keys, dependent: :destroy, foreign_key: 'repository_id', class_name: 'RepositoryGitConfigKey::GitConfig'
|
||||
has_many :git_option_keys, dependent: :destroy, foreign_key: 'repository_id', class_name: 'RepositoryGitConfigKey::Option'
|
||||
has_many :protected_branches, dependent: :destroy, foreign_key: 'repository_id', class_name: 'RepositoryProtectedBranche'
|
||||
|
||||
# Additionnal validations
|
||||
validate :valid_repository_options, on: :create
|
||||
|
||||
acts_as_watchable
|
||||
|
||||
class << self
|
||||
def scm_adapter_class
|
||||
Redmine::Scm::Adapters::XitoliteAdapter
|
||||
end
|
||||
|
||||
def scm_name
|
||||
'Gitolite'
|
||||
end
|
||||
end
|
||||
|
||||
def sti_name
|
||||
'Repository::Xitolite'
|
||||
end
|
||||
|
||||
# Override the original method to accept options hash
|
||||
# which may contain *bypass_cache* flag.
|
||||
#
|
||||
def diff(path, rev, rev_to, opts = {})
|
||||
scm.diff(path, rev, rev_to, opts)
|
||||
end
|
||||
|
||||
def rev_list(revision, args = [])
|
||||
scm.rev_list(revision, args)
|
||||
end
|
||||
|
||||
def rev_parse(revision)
|
||||
scm.rev_parse(revision)
|
||||
end
|
||||
|
||||
def archive(revision, format = 'tar')
|
||||
scm.archive(revision, format)
|
||||
end
|
||||
|
||||
def mirror_push(url, branch, args = [])
|
||||
scm.mirror_push(url, branch, args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_repository_options
|
||||
return unless Additionals.true? create_readme
|
||||
return unless Additionals.true? enable_git_annex
|
||||
|
||||
errors.add(:base, :invalid_options)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
class RepositoryDeploymentCredential < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
VALID_PERMS = ['R', 'RW+'].freeze
|
||||
DEFAULT_PERM = 'RW+'.freeze
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'perm', 'active', 'gitolite_public_key_id'
|
||||
|
||||
## Relations
|
||||
belongs_to :repository
|
||||
belongs_to :gitolite_public_key
|
||||
belongs_to :user
|
||||
|
||||
## Validations
|
||||
validates :repository_id, presence: true,
|
||||
uniqueness: { scope: :gitolite_public_key_id }
|
||||
|
||||
validates :gitolite_public_key_id, presence: true
|
||||
validates :user_id, presence: true
|
||||
validates :perm, presence: true,
|
||||
inclusion: { in: VALID_PERMS }
|
||||
|
||||
validates_associated :repository
|
||||
validates_associated :gitolite_public_key
|
||||
validates_associated :user
|
||||
|
||||
validate :correct_key_type
|
||||
validate :owner_matches_key
|
||||
|
||||
## Scopes
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :inactive, -> { where(active: false) }
|
||||
|
||||
def to_s
|
||||
"#{repository.identifier}-#{gitolite_public_key.identifier} : #{perm}"
|
||||
end
|
||||
|
||||
# Deployment Credentials ignored unless created by someone who still has permission to create them
|
||||
def honored?
|
||||
user.admin? || user.allowed_to?(:create_repository_deployment_credentials, repository.project)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def correct_key_type
|
||||
errors.add(:base, :invalid_key) if gitolite_public_key && gitolite_public_key.key_type_as_string != 'deploy_key'
|
||||
end
|
||||
|
||||
def owner_matches_key
|
||||
return if user.nil? || gitolite_public_key.nil?
|
||||
|
||||
errors.add(:base, :invalid_user) if user != gitolite_public_key.user
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
class RepositoryGitConfigKey < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'type', 'key', 'value'
|
||||
|
||||
## Relations
|
||||
belongs_to :repository
|
||||
|
||||
## Validations
|
||||
validates :repository_id, presence: true
|
||||
validates :type, presence: true, inclusion: { in: ['RepositoryGitConfigKey::GitConfig', 'RepositoryGitConfigKey::Option'] }
|
||||
validates :value, presence: true
|
||||
|
||||
## Callbacks
|
||||
after_save :check_if_key_changed
|
||||
|
||||
## Virtual attribute
|
||||
attr_accessor :key_has_changed
|
||||
attr_accessor :old_key
|
||||
|
||||
# Syntaxic sugar
|
||||
def key_has_changed?
|
||||
key_has_changed
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This is Rails method : <attribute>_changed?
|
||||
# However, the value is cleared before passing the object to the controller.
|
||||
# We need to save it in virtual attribute to trigger Gitolite resync if changed.
|
||||
#
|
||||
def check_if_key_changed
|
||||
if key_changed?
|
||||
self.key_has_changed = true
|
||||
self.old_key = key_change[0]
|
||||
else
|
||||
self.key_has_changed = false
|
||||
self.old_key = ''
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class RepositoryGitConfigKey::GitConfig < RepositoryGitConfigKey
|
||||
|
||||
VALID_CONFIG_KEY_REGEX = /\A[a-zA-Z0-9]+\.[a-zA-Z0-9.]+\z/
|
||||
|
||||
validates :key, presence: true,
|
||||
uniqueness: { case_sensitive: false, scope: [:type, :repository_id] },
|
||||
format: { with: VALID_CONFIG_KEY_REGEX }
|
||||
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
class RepositoryGitConfigKey::Option < RepositoryGitConfigKey
|
||||
|
||||
validates :key, presence: true,
|
||||
uniqueness: { case_sensitive: false, scope: [:type, :repository_id] }
|
||||
|
||||
end
|
115
plugins/redmine_git_hosting/app/models/repository_git_extra.rb
Normal file
115
plugins/redmine_git_hosting/app/models/repository_git_extra.rb
Normal file
|
@ -0,0 +1,115 @@
|
|||
class RepositoryGitExtra < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
SMART_HTTP_OPTIONS = [[l(:label_disabled), '0'],
|
||||
[l(:label_http_only), '3'],
|
||||
[l(:label_https_only), '1'],
|
||||
[l(:label_https_and_http), '2']].freeze
|
||||
|
||||
ALLOWED_URLS = %w[ssh http https go git git_annex].freeze
|
||||
|
||||
URLS_ICONS = { go: { label: 'Go', icon: 'fab_google' },
|
||||
http: { label: 'HTTP', icon: 'fas_external-link-alt' },
|
||||
https: { label: 'HTTPS', icon: 'fas_external-link-alt' },
|
||||
ssh: { label: 'SSH', icon: 'fas_shield-alt' },
|
||||
git: { label: 'Git', icon: 'fab_git' },
|
||||
git_annex: { label: 'GitAnnex', icon: 'fab_git' } }.freeze
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'git_http', 'git_https', 'git_ssh', 'git_go', 'git_daemon', 'git_notify', 'git_annex', 'default_branch',
|
||||
'protected_branch', 'public_repo', 'key', 'urls_order', 'notification_sender', 'notification_prefix'
|
||||
|
||||
## Relations
|
||||
belongs_to :repository
|
||||
|
||||
## Validations
|
||||
validates :repository_id, presence: true, uniqueness: true
|
||||
validates :default_branch, presence: true
|
||||
validates :key, presence: true
|
||||
validates :notification_sender, format: { with: URI::MailTo::EMAIL_REGEXP, allow_blank: true }
|
||||
|
||||
validate :validate_urls_order
|
||||
|
||||
## Serializations
|
||||
serialize :urls_order, Array
|
||||
|
||||
## Callbacks
|
||||
before_save :check_urls_order_consistency
|
||||
after_save :check_if_default_branch_changed
|
||||
|
||||
## Virtual attribute
|
||||
attr_accessor :default_branch_has_changed
|
||||
|
||||
# Syntaxic sugar
|
||||
def default_branch_has_changed?
|
||||
default_branch_has_changed
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_urls_order
|
||||
urls_order.each do |url|
|
||||
errors.add(:urls_order, :invalid) unless ALLOWED_URLS.include?(url)
|
||||
end
|
||||
end
|
||||
|
||||
# This is Rails method : <attribute>_changed?
|
||||
# However, the value is cleared before passing the object to the controller.
|
||||
# We need to save it in virtual attribute to trigger Gitolite resync if changed.
|
||||
#
|
||||
def check_if_default_branch_changed
|
||||
self.default_branch_has_changed = if default_branch_changed?
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def check_urls_order_consistency
|
||||
check_ssh_url
|
||||
check_git_http_urls
|
||||
check_go_url
|
||||
check_git_url
|
||||
check_git_annex_url
|
||||
end
|
||||
|
||||
def check_ssh_url
|
||||
git_ssh? ? add_url('ssh') : remove_url('ssh')
|
||||
end
|
||||
|
||||
def check_git_http_urls
|
||||
if git_http? && git_https?
|
||||
add_url('http')
|
||||
add_url('https')
|
||||
elsif git_http?
|
||||
add_url('http')
|
||||
remove_url('https')
|
||||
elsif git_https?
|
||||
add_url('https')
|
||||
remove_url('http')
|
||||
else
|
||||
remove_url('http')
|
||||
remove_url('https')
|
||||
end
|
||||
end
|
||||
|
||||
def check_go_url
|
||||
git_go? ? add_url('go') : remove_url('go')
|
||||
end
|
||||
|
||||
def check_git_annex_url
|
||||
git_annex? ? add_url('git_annex') : remove_url('git_annex')
|
||||
end
|
||||
|
||||
def check_git_url
|
||||
git_daemon? ? add_url('git') : remove_url('git')
|
||||
end
|
||||
|
||||
def remove_url(url)
|
||||
urls_order.delete(url)
|
||||
end
|
||||
|
||||
def add_url(url)
|
||||
urls_order.push(url).uniq!
|
||||
end
|
||||
end
|
106
plugins/redmine_git_hosting/app/models/repository_mirror.rb
Normal file
106
plugins/redmine_git_hosting/app/models/repository_mirror.rb
Normal file
|
@ -0,0 +1,106 @@
|
|||
class RepositoryMirror < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
PUSHMODE_MIRROR = 0
|
||||
PUSHMODE_FORCE = 1
|
||||
PUSHMODE_FAST_FORWARD = 2
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'url', 'push_mode', 'include_all_branches', 'include_all_tags',
|
||||
'explicit_refspec', 'active'
|
||||
|
||||
## Relations
|
||||
belongs_to :repository
|
||||
|
||||
## Validations
|
||||
validates :repository_id, presence: true
|
||||
|
||||
## Only allow SSH format
|
||||
## ssh://git@redmine.example.org/project1/project2/project3/project4.git
|
||||
## ssh://git@redmine.example.org:2222/project1/project2/project3/project4.git
|
||||
validates :url, presence: true,
|
||||
uniqueness: { case_sensitive: false, scope: :repository_id },
|
||||
format: { with: RedmineGitHosting::Validators::GIT_SSH_URL_REGEX }
|
||||
|
||||
validates :push_mode, presence: true,
|
||||
numericality: { only_integer: true },
|
||||
inclusion: { in: [PUSHMODE_MIRROR, PUSHMODE_FORCE, PUSHMODE_FAST_FORWARD] }
|
||||
|
||||
## Additional validations
|
||||
validate :mirror_configuration
|
||||
|
||||
## Scopes
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :inactive, -> { where(active: false) }
|
||||
scope :has_explicit_refspec, -> { where(push_mode: '> 0') }
|
||||
|
||||
## Callbacks
|
||||
before_validation :strip_whitespace
|
||||
|
||||
def mirror_mode?
|
||||
push_mode == PUSHMODE_MIRROR
|
||||
end
|
||||
|
||||
def force_mode?
|
||||
push_mode == PUSHMODE_FORCE
|
||||
end
|
||||
|
||||
def push_mode_to_s
|
||||
case push_mode
|
||||
when 0
|
||||
'mirror'
|
||||
when 1
|
||||
'force'
|
||||
when 2
|
||||
'fast_forward'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Strip leading and trailing whitespace
|
||||
def strip_whitespace
|
||||
self.url = url.strip rescue ''
|
||||
self.explicit_refspec = explicit_refspec.strip rescue ''
|
||||
end
|
||||
|
||||
def mirror_configuration
|
||||
if mirror_mode?
|
||||
reset_fields
|
||||
elsif include_all_branches? && include_all_tags?
|
||||
mutual_exclusion_error
|
||||
elsif explicit_refspec.present?
|
||||
if include_all_branches?
|
||||
errors.add(:explicit_refspec, "cannot be used with #{l(:label_mirror_include_all_branches)}.")
|
||||
else
|
||||
validate_refspec
|
||||
end
|
||||
elsif !include_all_branches? && !include_all_tags?
|
||||
errors.add(:base, :nothing_to_push)
|
||||
end
|
||||
end
|
||||
|
||||
# Check format of refspec
|
||||
#
|
||||
def validate_refspec
|
||||
RedmineGitHosting::Validators.valid_git_refspec_path?(explicit_refspec)
|
||||
rescue RedmineGitHosting::Error::InvalidRefspec::BadFormat => e
|
||||
errors.add(:explicit_refspec, :bad_format)
|
||||
rescue RedmineGitHosting::Error::InvalidRefspec::NullComponent => e
|
||||
errors.add(:explicit_refspec, :have_null_component)
|
||||
end
|
||||
|
||||
def reset_fields
|
||||
# clear out all extra parameters.. (we use javascript to hide them anyway)
|
||||
self.include_all_branches = false
|
||||
self.include_all_tags = false
|
||||
self.explicit_refspec = ''
|
||||
end
|
||||
|
||||
def mutual_exclusion_error
|
||||
errors.add(:base, "Cannot #{l(:label_mirror_include_all_branches)} and #{l(:label_mirror_include_all_tags)} at the same time.")
|
||||
return if explicit_refspec.blank?
|
||||
|
||||
errors.add(:explicit_refspec, "cannot be used with #{l(:label_mirror_include_all_branches)} or #{l(:label_mirror_include_all_tags)}")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,56 @@
|
|||
require 'uri'
|
||||
|
||||
class RepositoryPostReceiveUrl < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'url', 'mode', 'active', 'use_triggers', 'triggers', 'split_payloads'
|
||||
|
||||
## Relations
|
||||
belongs_to :repository
|
||||
|
||||
## Validations
|
||||
validates :repository_id, presence: true
|
||||
|
||||
# Only allow HTTP(s) format
|
||||
validates :url, presence: true,
|
||||
uniqueness: { case_sensitive: false, scope: :repository_id },
|
||||
format: { with: URI::regexp(%w[http https]) }
|
||||
|
||||
validates :mode, presence: true, inclusion: { in: %i[github get] }
|
||||
|
||||
## Serializations
|
||||
serialize :triggers, Array
|
||||
|
||||
## Scopes
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :inactive, -> { where(active: false) }
|
||||
|
||||
## Callbacks
|
||||
before_validation :strip_whitespace
|
||||
before_validation :remove_blank_triggers
|
||||
|
||||
def mode
|
||||
self[:mode].to_sym
|
||||
end
|
||||
|
||||
def mode=(value)
|
||||
self[:mode] = value.to_s
|
||||
end
|
||||
|
||||
def github_mode?
|
||||
mode == :github
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Strip leading and trailing whitespace
|
||||
def strip_whitespace
|
||||
self.url = url.strip rescue ''
|
||||
end
|
||||
|
||||
# Remove blank entries in triggers
|
||||
def remove_blank_triggers
|
||||
self.triggers = triggers.select(&:present?)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
class RepositoryProtectedBranche < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
VALID_PERMS = ['RW+', 'RW', 'R', '-'].freeze
|
||||
DEFAULT_PERM = 'RW+'.freeze
|
||||
|
||||
acts_as_positioned
|
||||
|
||||
## Attributes
|
||||
safe_attributes 'path', 'permissions', 'position'
|
||||
|
||||
## Relations
|
||||
belongs_to :repository
|
||||
has_many :protected_branches_members, foreign_key: :protected_branch_id, dependent: :destroy
|
||||
has_many :members, through: :protected_branches_members, source: :principal
|
||||
|
||||
## Validations
|
||||
validates :repository_id, presence: true
|
||||
validates :path, presence: true, uniqueness: { scope: %i[permissions repository_id] }
|
||||
validates :permissions, presence: true, inclusion: { in: VALID_PERMS }
|
||||
|
||||
## Scopes
|
||||
default_scope { order(position: :asc) }
|
||||
|
||||
class << self
|
||||
def clone_from(parent)
|
||||
parent = find_by(id: parent) unless parent.is_a? RepositoryProtectedBranche
|
||||
copy = new
|
||||
copy.attributes = parent.attributes
|
||||
copy.repository = parent.repository
|
||||
copy
|
||||
end
|
||||
end
|
||||
|
||||
# Accessors
|
||||
#
|
||||
def users
|
||||
members.select { |m| m.class.name == 'User' }.uniq
|
||||
end
|
||||
|
||||
def groups
|
||||
members.select { |m| m.class.name == 'Group' }.uniq
|
||||
end
|
||||
|
||||
def allowed_users
|
||||
users.map(&:gitolite_identifier).sort
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
Deface::Override.new virtual_path: 'repositories/_navigation',
|
||||
name: 'show-repositories-hook-navigation',
|
||||
insert_before: 'erb[loud]:contains("label_statistics")',
|
||||
original: '88f120e99075ba3246901c6e970ca671d7166855',
|
||||
text: '<%= call_hook(:view_repositories_navigation, repository: @repository) %>'
|
|
@ -0,0 +1,11 @@
|
|||
Deface::Override.new virtual_path: 'repositories/show',
|
||||
name: 'show-repositories-hook-bottom',
|
||||
insert_before: 'erb[silent]:contains("other_formats_links")',
|
||||
original: 'f302d110cd10675a0a952f5f3e1ecfe57ebd38be',
|
||||
text: '<%= call_hook(:view_repositories_show_bottom, repository: @repository) %>'
|
||||
|
||||
Deface::Override.new virtual_path: 'repositories/show',
|
||||
name: 'show-repositories-hook-sidebar',
|
||||
insert_before: 'erb[silent]:contains("html_title")',
|
||||
original: '2a0a09659d76066b896016c72527d479c69463ec',
|
||||
partial: 'hooks/show_repositories_sidebar'
|
|
@ -0,0 +1,77 @@
|
|||
class RepositoryPresenter < SimpleDelegator
|
||||
attr_reader :repository
|
||||
|
||||
def initialize(repository, template)
|
||||
super(template)
|
||||
@repository = repository
|
||||
end
|
||||
|
||||
def link_to_repository
|
||||
link_to repository.name,
|
||||
controller: 'repositories',
|
||||
action: 'show',
|
||||
id: repository.project,
|
||||
repository_id: repository.identifier_param,
|
||||
rev: nil,
|
||||
path: nil
|
||||
end
|
||||
|
||||
def git_urls_box
|
||||
content_tag(:div, class: 'git_url_box', id: urls_container_id) do
|
||||
render_git_urls +
|
||||
render_git_url_text +
|
||||
render_permissions +
|
||||
render_clipboard_button
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_git_urls
|
||||
content_tag(:ul, render_url_list, class: 'git_url_list')
|
||||
end
|
||||
|
||||
def render_url_list
|
||||
s = ''
|
||||
repository.available_urls_sorted.each do |key, value|
|
||||
s << content_tag(:li, link_to(key.upcase, 'javascript:void(0)').html_safe, options_for_git_url(key, value))
|
||||
end
|
||||
s.html_safe
|
||||
end
|
||||
|
||||
def options_for_git_url(key, value)
|
||||
{ class: 'git_url', data: { url: value[:url], target: element_name, committer: committer_label(value) } }
|
||||
end
|
||||
|
||||
def render_git_url_text
|
||||
content_tag(:input, '', class: 'git_url_text', id: url_text_container_id, readonly: 'readonly')
|
||||
end
|
||||
|
||||
def render_permissions
|
||||
content_tag(:div, content_tag(:span, '', id: permissions_container_id), class: 'git_url_permissions')
|
||||
end
|
||||
|
||||
def render_clipboard_button
|
||||
clipboardjs_button_for(url_text_container_id)
|
||||
end
|
||||
|
||||
def committer_label(value)
|
||||
Additionals.true?(value[:committer]) ? l(:label_read_write_permission) : l(:label_read_only_permission)
|
||||
end
|
||||
|
||||
def element_name
|
||||
"repository_#{repository.id}"
|
||||
end
|
||||
|
||||
def urls_container_id
|
||||
"git_url_box_#{element_name}"
|
||||
end
|
||||
|
||||
def permissions_container_id
|
||||
"git_url_permissions_#{element_name}"
|
||||
end
|
||||
|
||||
def url_text_container_id
|
||||
"git_url_text_#{element_name}"
|
||||
end
|
||||
end
|
13
plugins/redmine_git_hosting/app/reports/report_base.rb
Normal file
13
plugins/redmine_git_hosting/app/reports/report_base.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
class ReportBase
|
||||
|
||||
include Redmine::I18n
|
||||
include ReportHelper
|
||||
include ReportQuery
|
||||
|
||||
attr_reader :repository
|
||||
|
||||
def initialize(repository)
|
||||
@repository = repository
|
||||
end
|
||||
|
||||
end
|
44
plugins/redmine_git_hosting/app/reports/report_helper.rb
Normal file
44
plugins/redmine_git_hosting/app/reports/report_helper.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
module ReportHelper
|
||||
def date_to
|
||||
User.current.today
|
||||
end
|
||||
|
||||
def week_day_hash
|
||||
{ day_name(1) => 0,
|
||||
day_name(2) => 0,
|
||||
day_name(3) => 0,
|
||||
day_name(4) => 0,
|
||||
day_name(5) => 0,
|
||||
day_name(6) => 0,
|
||||
day_name(0) => 0 }
|
||||
end
|
||||
|
||||
def hours
|
||||
(0..23).step(1).map { |h| "#{h}h" }
|
||||
end
|
||||
|
||||
def months
|
||||
(1..12).map { |m| l('date.month_names')[m].capitalize }
|
||||
end
|
||||
|
||||
def get_hour_from_date(date)
|
||||
return nil unless date
|
||||
|
||||
time = date.to_time
|
||||
zone = User.current.time_zone
|
||||
local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
|
||||
local.hour
|
||||
end
|
||||
|
||||
def total_by_month_for(method)
|
||||
total = [0] * 12
|
||||
send(method).each { |c| total[(date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
total
|
||||
end
|
||||
|
||||
def total_by_hour_for(method)
|
||||
total = [0] * 24
|
||||
send(method).each { |c| total[get_hour_from_date(c)] += 1 }
|
||||
total
|
||||
end
|
||||
end
|
47
plugins/redmine_git_hosting/app/reports/report_query.rb
Normal file
47
plugins/redmine_git_hosting/app/reports/report_query.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
module ReportQuery
|
||||
private
|
||||
|
||||
def all_changesets
|
||||
@all_changesets ||= Changeset.where(repository_id: repository.id)
|
||||
end
|
||||
|
||||
def all_changes
|
||||
@all_changes ||= Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", repository.id)
|
||||
end
|
||||
|
||||
def all_commits_by_day
|
||||
@all_commits_by_day ||= all_changesets.group(:commit_date)
|
||||
end
|
||||
|
||||
def all_changes_by_day
|
||||
@all_changes_by_day ||= all_changes.group(:commit_date)
|
||||
end
|
||||
|
||||
def redmine_committers
|
||||
@redmine_committers ||= all_changesets.where.not(user_id: nil).distinct.count(:user_id)
|
||||
end
|
||||
|
||||
def external_committers
|
||||
@external_committers ||= all_changesets.where(user_id: nil).distinct.count(:committer)
|
||||
end
|
||||
|
||||
def commits_by_day
|
||||
@commits_by_day ||= all_commits_by_day.count
|
||||
end
|
||||
|
||||
def changes_by_day
|
||||
@changes_by_day ||= all_changes_by_day.count
|
||||
end
|
||||
|
||||
def commits_by_hour
|
||||
@commits_by_hour ||= all_changesets.map(&:committed_on)
|
||||
end
|
||||
|
||||
def commits_by_author
|
||||
@commits_by_author ||= all_changesets.group(:committer).count
|
||||
end
|
||||
|
||||
def changes_by_author
|
||||
@changes_by_author ||= all_changes.group(:committer).count
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
class RepositoryCommitsStats < ReportBase
|
||||
def commits_per_month
|
||||
data = {}
|
||||
data[:categories] = months.reverse
|
||||
data[:series] = []
|
||||
data[:series] << { name: l(:label_commit_plural), data: total_commits_by_month[0..11].reverse }
|
||||
data[:series] << { name: l(:label_change_plural), data: total_changes_by_month[0..11].reverse }
|
||||
data
|
||||
end
|
||||
|
||||
def commits_per_day
|
||||
data = {}
|
||||
data[:categories] = total_commits_by_day.keys
|
||||
data[:series] = []
|
||||
data[:series] << { name: l(:label_commit_plural), data: total_commits_by_day.values }
|
||||
data[:series] << { name: l(:label_change_plural), data: total_changes_by_day.values }
|
||||
data
|
||||
end
|
||||
|
||||
def commits_per_hours
|
||||
data = {}
|
||||
data[:categories] = hours
|
||||
data[:series] = []
|
||||
data[:series] << { name: l(:label_commit_plural), data: total_commits_by_hour }
|
||||
data
|
||||
end
|
||||
|
||||
def commits_per_weekday
|
||||
data = {}
|
||||
data[:name] = l(:label_commit_plural)
|
||||
data[:data] = []
|
||||
total_commits_by_weekday.each do |key, value|
|
||||
data[:data] << [key, value]
|
||||
end
|
||||
[data]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def total_commits_by_month
|
||||
total_by_month_for(:commits_by_day)
|
||||
end
|
||||
|
||||
def total_changes_by_month
|
||||
total_by_month_for(:changes_by_day)
|
||||
end
|
||||
|
||||
def total_commits_by_day
|
||||
@total_commits_by_day ||= all_commits_by_day.order(:commit_date).count
|
||||
end
|
||||
|
||||
def total_changes_by_day
|
||||
return @total_changes_by_day unless @total_changes_by_day.nil?
|
||||
|
||||
@total_changes_by_day = nil
|
||||
changes = {}
|
||||
Changeset.where('repository_id = ?', repository.id).includes(:filechanges).order(:commit_date).each do |changeset|
|
||||
changes[changeset.commit_date] = 0 unless changes.key?(changeset.commit_date)
|
||||
changes[changeset.commit_date] += changeset.filechanges.size
|
||||
end
|
||||
@total_changes_by_day = changes
|
||||
@total_changes_by_day
|
||||
end
|
||||
|
||||
def total_commits_by_hour
|
||||
total_by_hour_for(:commits_by_hour)
|
||||
end
|
||||
|
||||
def total_commits_by_weekday
|
||||
week_day = week_day_hash
|
||||
commits_by_day.each do |commit_date, commit_count|
|
||||
case commit_date.to_date.wday
|
||||
when 0
|
||||
week_day[day_name(0)] += commit_count
|
||||
when 1
|
||||
week_day[day_name(1)] += commit_count
|
||||
when 2
|
||||
week_day[day_name(2)] += commit_count
|
||||
when 3
|
||||
week_day[day_name(3)] += commit_count
|
||||
when 4
|
||||
week_day[day_name(4)] += commit_count
|
||||
when 5
|
||||
week_day[day_name(5)] += commit_count
|
||||
when 6
|
||||
week_day[day_name(6)] += commit_count
|
||||
end
|
||||
end
|
||||
week_day
|
||||
end
|
||||
end
|
|
@ -0,0 +1,107 @@
|
|||
class RepositoryContributorsStats < ReportBase
|
||||
|
||||
def initialize(repository)
|
||||
super
|
||||
@changes_for_committer = {}
|
||||
end
|
||||
|
||||
|
||||
def commits_per_author
|
||||
data = []
|
||||
|
||||
sorted_commits_per_author_with_aliases.each do |committer_hash|
|
||||
commits = {}
|
||||
|
||||
committer_hash[:committers].each do |committer|
|
||||
commits = commits.merge(count_changes_for_committer(committer)) { |key, oldval, newval| newval + oldval }
|
||||
end
|
||||
|
||||
commits = Hash[commits.sort]
|
||||
commits_data = {}
|
||||
commits_data[:author_name] = committer_hash[:name]
|
||||
commits_data[:author_mail] = committer_hash[:mail]
|
||||
commits_data[:total_commits] = committer_hash[:commits]
|
||||
commits_data[:categories] = commits.keys
|
||||
commits_data[:series] = []
|
||||
commits_data[:series] << { name: l(:label_commit_plural), data: commits.values }
|
||||
data.push(commits_data)
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
|
||||
def commits_per_author_global
|
||||
merged = commits_per_author_with_aliases
|
||||
data = {}
|
||||
data[:categories] = merged.map { |x| x[:name] }
|
||||
data[:series] = []
|
||||
data[:series] << { name: l(:label_commit_plural), data: merged.map { |x| x[:commits] } }
|
||||
data[:series] << { name: l(:label_change_plural), data: merged.map { |x| x[:changes] } }
|
||||
data
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Generate mappings from the registered users to the comitters
|
||||
# user_committer_mapping = { name => [comitter, ...] }
|
||||
# registered_committers = [ committer,... ]
|
||||
#
|
||||
def commits_per_author_with_aliases
|
||||
return @commits_per_author_with_aliases if !@commits_per_author_with_aliases.nil?
|
||||
@commits_per_author_with_aliases = nil
|
||||
|
||||
registered_committers = []
|
||||
user_committer_mapping = {}
|
||||
Changeset.select('changesets.committer, changesets.user_id')
|
||||
.where('repository_id = ? and user_id IS NOT NULL', repository.id)
|
||||
.group(:committer, :user_id)
|
||||
.includes(:user).each do |x|
|
||||
name = "#{x.user.firstname} #{x.user.lastname}"
|
||||
registered_committers << x.committer
|
||||
user_committer_mapping[[name, x.user.mail]] ||= []
|
||||
user_committer_mapping[[name, x.user.mail]] << x.committer
|
||||
end
|
||||
|
||||
merged = []
|
||||
commits_by_author.each do |committer, count|
|
||||
# skip all registered users
|
||||
next if registered_committers.include?(committer)
|
||||
|
||||
name = committer.gsub(%r{<.+@.+>}, '').strip
|
||||
mail = committer[/<(.+@.+)>/, 1]
|
||||
merged << { name: name, mail: mail, commits: count, changes: changes_by_author[committer] || 0, committers: [committer] }
|
||||
end
|
||||
user_committer_mapping.each do |identity, committers|
|
||||
count = 0
|
||||
changes = 0
|
||||
committers.each do |c|
|
||||
count += commits_by_author[c] || 0
|
||||
changes += changes_by_author[c] || 0
|
||||
end
|
||||
merged << { name: identity[0], mail: identity[1], commits: count, changes: changes, committers: committers }
|
||||
end
|
||||
|
||||
# sort by name
|
||||
merged.sort! { |x, y| x[:name] <=> y[:name] }
|
||||
|
||||
# merged = merged + [{name:"",commits:0,changes:0}]*(10 - merged.length) if merged.length < 10
|
||||
@commits_per_author_with_aliases = merged
|
||||
@commits_per_author_with_aliases
|
||||
end
|
||||
|
||||
|
||||
def sorted_commits_per_author_with_aliases
|
||||
@committers ||= commits_per_author_with_aliases.sort! { |x, y| y[:commits] <=> x[:commits] }
|
||||
end
|
||||
|
||||
|
||||
def count_changes_for_committer(committer)
|
||||
return @changes_for_committer[committer] if !@changes_for_committer[committer].nil?
|
||||
@changes_for_committer[committer] ||= Changeset.where('repository_id = ? AND committer = ?', repository.id, committer).group(:commit_date).order(:commit_date).count
|
||||
@changes_for_committer[committer]
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
class RepositoryGlobalStats < ReportBase
|
||||
def build
|
||||
data = {}
|
||||
data[l(:label_total_commits)] = total_commits
|
||||
data[l(:label_total_contributors)] = committers
|
||||
data[l(:label_first_commit_date)] = format_date(first_commit.commit_date)
|
||||
data[l(:label_latest_commit_date)] = format_date(last_commit.commit_date)
|
||||
data[l(:label_active_for)] = "#{active_for} #{l(:days, active_for)}"
|
||||
data[l(:label_average_commit_per_day)] = average_commit_per_day
|
||||
data[l(:label_average_contributor_commits)] = average_contributor_commits
|
||||
data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def total_commits
|
||||
@total_commits ||= all_changesets.count
|
||||
end
|
||||
|
||||
def committers
|
||||
@committers ||= redmine_committers + external_committers
|
||||
end
|
||||
|
||||
def first_commit
|
||||
@first_commit ||= all_changesets.order(commit_date: :asc).first
|
||||
end
|
||||
|
||||
def last_commit
|
||||
@last_commit ||= all_changesets.order(commit_date: :asc).last
|
||||
end
|
||||
|
||||
def active_for
|
||||
@active_for ||= (last_commit.commit_date - first_commit.commit_date).to_i
|
||||
end
|
||||
|
||||
def average_commit_per_day
|
||||
@average_commit_per_day ||= total_commits.fdiv(active_for).round(2)
|
||||
end
|
||||
|
||||
def average_contributor_commits
|
||||
@average_contributor_commits ||= total_commits.fdiv(committers).round(2)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
module PermissionsBuilder
|
||||
class Base
|
||||
|
||||
attr_reader :repository
|
||||
attr_reader :gitolite_users
|
||||
attr_reader :old_permissions
|
||||
|
||||
|
||||
def initialize(repository, gitolite_users, old_permissions = {})
|
||||
@repository = repository
|
||||
@gitolite_users = gitolite_users
|
||||
@old_permissions = old_permissions
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def build(repository, gitolite_users, old_permissions = {})
|
||||
new(repository, gitolite_users, old_permissions).build
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def build
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def has_no_users?(type)
|
||||
gitolite_users[type].nil? || gitolite_users[type].empty?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
module PermissionsBuilder
|
||||
class ProtectedBranches < Base
|
||||
|
||||
attr_reader :permissions
|
||||
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@permissions = []
|
||||
end
|
||||
|
||||
|
||||
def build
|
||||
build_protected_branch_permissions
|
||||
permissions
|
||||
end
|
||||
|
||||
|
||||
def build_protected_branch_permissions
|
||||
repository.protected_branches.each do |branch|
|
||||
perms = {}
|
||||
perms[branch.permissions] = {}
|
||||
perms[branch.permissions][branch.path] = branch.allowed_users unless branch.allowed_users.empty?
|
||||
permissions.push(perms)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
module PermissionsBuilder
|
||||
class Standard < Base
|
||||
|
||||
attr_reader :permissions
|
||||
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@permissions = {}
|
||||
@permissions['RW+'] = {}
|
||||
@permissions['RW'] = {}
|
||||
@permissions['R'] = {}
|
||||
end
|
||||
|
||||
|
||||
def build
|
||||
# Build permissions
|
||||
build_permissions
|
||||
|
||||
# Return them
|
||||
[merge_permissions(permissions, old_permissions)]
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def build_permissions
|
||||
@permissions['RW+'][''] = gitolite_users[:rewind_users] unless has_no_users?(:rewind_users)
|
||||
@permissions['RW'][''] = gitolite_users[:write_users] unless has_no_users?(:write_users)
|
||||
@permissions['R'][''] = gitolite_users[:read_users] unless has_no_users?(:read_users)
|
||||
end
|
||||
|
||||
|
||||
def merge_permissions(current_permissions, old_permissions)
|
||||
merge_permissions = {}
|
||||
merge_permissions['RW+'] = {}
|
||||
merge_permissions['RW'] = {}
|
||||
merge_permissions['R'] = {}
|
||||
|
||||
current_permissions.each do |perm, branch_settings|
|
||||
branch_settings.each do |branch, user_list|
|
||||
if user_list.any?
|
||||
if !merge_permissions[perm].has_key?(branch)
|
||||
merge_permissions[perm][branch] = []
|
||||
end
|
||||
merge_permissions[perm][branch] += user_list
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
old_permissions.each do |perm, branch_settings|
|
||||
branch_settings.each do |branch, user_list|
|
||||
if user_list.any?
|
||||
if !merge_permissions[perm].has_key?(branch)
|
||||
merge_permissions[perm][branch] = []
|
||||
end
|
||||
merge_permissions[perm][branch] += user_list
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
merge_permissions.each do |perm, branch_settings|
|
||||
merge_permissions.delete(perm) if merge_permissions[perm].empty?
|
||||
end
|
||||
|
||||
merge_permissions
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,70 @@
|
|||
module RedmineHooks
|
||||
class Base
|
||||
|
||||
attr_reader :object
|
||||
attr_reader :payloads
|
||||
|
||||
|
||||
def initialize(object, payloads = {})
|
||||
@object = object
|
||||
@payloads = payloads
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(object, payloads = {})
|
||||
new(object, payloads).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
def start_message
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def success_message
|
||||
" [success]\n"
|
||||
end
|
||||
|
||||
|
||||
def failure_message
|
||||
" [failure]\n"
|
||||
end
|
||||
|
||||
|
||||
def log_hook_succeeded
|
||||
logger.info('Succeeded!')
|
||||
end
|
||||
|
||||
|
||||
def log_hook_failed
|
||||
logger.error('Failed!')
|
||||
end
|
||||
|
||||
|
||||
def execute_hook(&block)
|
||||
y = ''
|
||||
logger.info(start_message)
|
||||
y << " - #{start_message} ... "
|
||||
yield y
|
||||
y
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,118 @@
|
|||
module RedmineHooks
|
||||
class CallWebservices < Base
|
||||
|
||||
include HttpHelper
|
||||
|
||||
attr_reader :payloads_to_send
|
||||
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
@payloads_to_send = []
|
||||
set_payloads_to_send
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
execute_hook do |out|
|
||||
if needs_push?
|
||||
out << call_webservice
|
||||
else
|
||||
out << "#{skip_message}\n"
|
||||
logger.info(skip_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def post_receive_url
|
||||
object
|
||||
end
|
||||
|
||||
|
||||
def needs_push?
|
||||
return false if payloads.empty?
|
||||
return true unless use_triggers?
|
||||
return false if post_receive_url.triggers.empty?
|
||||
return !payloads_to_send.empty?
|
||||
end
|
||||
|
||||
|
||||
def start_message
|
||||
"Notifying #{post_receive_url.url}"
|
||||
end
|
||||
|
||||
|
||||
def skip_message
|
||||
"This url doesn't need to be notified"
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def set_payloads_to_send
|
||||
if use_triggers?
|
||||
@payloads_to_send = extract_payloads
|
||||
else
|
||||
@payloads_to_send = payloads
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def extract_payloads
|
||||
new_payloads = []
|
||||
payloads.each do |payload|
|
||||
data = RedmineGitHosting::Utils::Git.parse_refspec(payload[:ref])
|
||||
if data[:type] == 'heads' && post_receive_url.triggers.include?(data[:name])
|
||||
new_payloads << payload
|
||||
end
|
||||
end
|
||||
new_payloads
|
||||
end
|
||||
|
||||
|
||||
def use_method
|
||||
post_receive_url.mode == :github ? :http_post : :http_get
|
||||
end
|
||||
|
||||
|
||||
def use_triggers?
|
||||
post_receive_url.use_triggers?
|
||||
end
|
||||
|
||||
|
||||
def split_payloads?
|
||||
post_receive_url.split_payloads?
|
||||
end
|
||||
|
||||
|
||||
def call_webservice
|
||||
if use_method == :http_post && split_payloads?
|
||||
y = ''
|
||||
payloads_to_send.each do |payload|
|
||||
y << do_call_webservice(payload)
|
||||
end
|
||||
y
|
||||
else
|
||||
do_call_webservice(payloads_to_send)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def do_call_webservice(payload)
|
||||
post_failed, post_message = self.send(use_method, post_receive_url.url, { data: { payload: payload } })
|
||||
|
||||
if post_failed
|
||||
logger.error('Failed!')
|
||||
logger.error(post_message)
|
||||
(split_payloads? ? failure_message.gsub("\n", '') : failure_message)
|
||||
else
|
||||
log_hook_succeeded
|
||||
(split_payloads? ? success_message.gsub("\n", '') : success_message)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
module RedmineHooks
|
||||
class FetchChangesets < Base
|
||||
|
||||
def call
|
||||
repository.empty_cache!
|
||||
execute_hook do |out|
|
||||
out << fetch_changesets
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def repository
|
||||
object
|
||||
end
|
||||
|
||||
|
||||
def start_message
|
||||
"Fetching changesets for '#{repository.redmine_name}' repository"
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def fetch_changesets
|
||||
begin
|
||||
repository.fetch_changesets
|
||||
log_hook_succeeded
|
||||
success_message
|
||||
rescue ::Redmine::Scm::Adapters::CommandFailed => e
|
||||
log_hook_failed
|
||||
logger.error("Error during fetching changesets : #{e.message}")
|
||||
failure_message
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,152 @@
|
|||
require 'json'
|
||||
|
||||
module RedmineHooks
|
||||
class GithubIssuesSync < Base
|
||||
|
||||
include HttpHelper
|
||||
|
||||
|
||||
def call
|
||||
sync_with_github
|
||||
end
|
||||
|
||||
|
||||
def project
|
||||
object
|
||||
end
|
||||
|
||||
|
||||
def params
|
||||
payloads
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def github_issue
|
||||
GithubIssue.find_by_github_id(params[:issue][:id])
|
||||
end
|
||||
|
||||
|
||||
def redmine_issue
|
||||
Issue.find_by_subject(params[:issue][:title])
|
||||
end
|
||||
|
||||
|
||||
def sync_with_github
|
||||
create_relation = false
|
||||
|
||||
## We don't have stored relation
|
||||
if github_issue.nil?
|
||||
|
||||
## And we don't have issue in Redmine
|
||||
if redmine_issue.nil?
|
||||
create_relation = true
|
||||
redmine_issue = create_redmine_issue
|
||||
else
|
||||
## Create relation and update issue
|
||||
create_relation = true
|
||||
redmine_issue = update_redmine_issue(redmine_issue)
|
||||
end
|
||||
else
|
||||
## We have one relation, update issue
|
||||
redmine_issue = update_redmine_issue(github_issue.issue)
|
||||
end
|
||||
|
||||
if create_relation
|
||||
github_issue = GithubIssue.new
|
||||
github_issue.github_id = params[:issue][:id]
|
||||
github_issue.issue_id = redmine_issue.id
|
||||
github_issue.save!
|
||||
end
|
||||
|
||||
if params.has_key?(:comment)
|
||||
issue_journal = GithubComment.find_by_github_id(params[:comment][:id])
|
||||
|
||||
if issue_journal.nil?
|
||||
issue_journal = create_issue_journal(github_issue.issue)
|
||||
|
||||
github_comment = GithubComment.new
|
||||
github_comment.github_id = params[:comment][:id]
|
||||
github_comment.journal_id = issue_journal.id
|
||||
github_comment.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def create_redmine_issue
|
||||
logger.info('Github Issues Sync : create new issue')
|
||||
|
||||
issue = project.issues.new
|
||||
issue.tracker_id = project.trackers.first.try(:id)
|
||||
issue.subject = params[:issue][:title].chomp[0, 255]
|
||||
issue.description = params[:issue][:body]
|
||||
issue.updated_on = params[:issue][:updated_at]
|
||||
issue.created_on = params[:issue][:created_at]
|
||||
|
||||
## Get user mail
|
||||
user = find_user(params[:issue][:user][:url])
|
||||
issue.author = user
|
||||
|
||||
issue.save!
|
||||
return issue
|
||||
end
|
||||
|
||||
|
||||
def create_issue_journal(issue)
|
||||
logger.info("Github Issues Sync : create new journal for issue '##{issue.id}'")
|
||||
|
||||
journal = Journal.new
|
||||
journal.journalized_id = issue.id
|
||||
journal.journalized_type = 'Issue'
|
||||
journal.notes = params[:comment][:body]
|
||||
journal.created_on = params[:comment][:created_at]
|
||||
|
||||
## Get user mail
|
||||
user = find_user(params[:comment][:user][:url])
|
||||
journal.user_id = user.id
|
||||
|
||||
journal.save!
|
||||
return journal
|
||||
end
|
||||
|
||||
|
||||
def update_redmine_issue(issue)
|
||||
logger.info("Github Issues Sync : update issue '##{issue.id}'")
|
||||
|
||||
if params[:issue][:state] == 'closed'
|
||||
issue.status_id = 5
|
||||
else
|
||||
issue.status_id = 1
|
||||
end
|
||||
|
||||
issue.subject = params[:issue][:title].chomp[0, 255]
|
||||
issue.description = params[:issue][:body]
|
||||
issue.updated_on = params[:issue][:updated_at]
|
||||
|
||||
issue.save!
|
||||
return issue
|
||||
end
|
||||
|
||||
|
||||
def find_user(url)
|
||||
post_failed, user_data = http_get(url)
|
||||
user_data = JSON.parse(user_data)
|
||||
|
||||
user = User.find_by_mail(user_data['email'])
|
||||
|
||||
if user.nil?
|
||||
logger.info("Github Issues Sync : cannot find user '#{user_data['email']}' in Redmine, use anonymous")
|
||||
user = User.anonymous
|
||||
user.mail = user_data['email']
|
||||
user.firstname = user_data['name']
|
||||
user.lastname = user_data['login']
|
||||
end
|
||||
|
||||
return user
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
module RedmineHooks
|
||||
module HttpHelper
|
||||
|
||||
def http_post(url, opts = {})
|
||||
RedmineGitHosting::Utils::Http.http_post(url, opts)
|
||||
end
|
||||
|
||||
|
||||
def http_get(url, opts = {})
|
||||
RedmineGitHosting::Utils::Http.http_get(url, opts)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
module RedmineHooks
|
||||
class UpdateMirrors < Base
|
||||
|
||||
def call
|
||||
execute_hook do |out|
|
||||
if needs_push?
|
||||
out << call_mirror
|
||||
else
|
||||
out << "#{skip_message}\n"
|
||||
logger.info(skip_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def mirror
|
||||
object
|
||||
end
|
||||
|
||||
|
||||
# If we have an explicit refspec, check it against incoming payloads
|
||||
# Special case: if we do not pass in any payloads, return true
|
||||
def needs_push?
|
||||
return true if payloads.empty?
|
||||
return true if mirror.mirror_mode?
|
||||
return check_ref_spec
|
||||
end
|
||||
|
||||
|
||||
def start_message
|
||||
"Pushing changes to #{mirror.url}"
|
||||
end
|
||||
|
||||
|
||||
def skip_message
|
||||
"This mirror doesn't need to be updated"
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def check_ref_spec
|
||||
refspec_parse = RedmineGitHosting::Validators.valid_git_refspec?(mirror.explicit_refspec)
|
||||
payloads.each do |payload|
|
||||
if splitpath = RedmineGitHosting::Utils::Git.parse_refspec(payload[:ref])
|
||||
return true if payload[:ref] == refspec_parse[1] # Explicit Reference Spec complete path
|
||||
return true if splitpath[:name] == refspec_parse[1] # Explicit Reference Spec no type
|
||||
return true if mirror.include_all_branches? && splitpath[:type] == 'heads'
|
||||
return true if mirror.include_all_tags? && splitpath[:type] == 'tags'
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
def call_mirror
|
||||
push_failed, push_message = RepositoryMirrors::Push.call(mirror)
|
||||
|
||||
if push_failed
|
||||
log_hook_failed
|
||||
logger.error(push_message)
|
||||
failure_message
|
||||
else
|
||||
log_hook_succeeded
|
||||
success_message
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,72 @@
|
|||
module GitolitePublicKeys
|
||||
class GenerateIdentifier
|
||||
|
||||
DEPLOY_PSEUDO_USER = 'deploy_key'
|
||||
|
||||
attr_reader :public_key
|
||||
attr_reader :user
|
||||
attr_reader :skip_auto_increment
|
||||
|
||||
|
||||
def initialize(public_key, user, opts = {})
|
||||
@public_key = public_key
|
||||
@user = user
|
||||
@skip_auto_increment = opts.delete(:skip_auto_increment) { false }
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(public_key, user, opts = {})
|
||||
new(public_key, user, opts).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# Returns the unique identifier for this key based on the key_type
|
||||
#
|
||||
# For user public keys, this simply is the user's gitolite_identifier.
|
||||
# For deployment keys, we use an incrementing number.
|
||||
#
|
||||
def call
|
||||
if public_key.user_key?
|
||||
set_identifier_for_user_key
|
||||
elsif public_key.deploy_key?
|
||||
set_identifier_for_deploy_key
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def set_identifier_for_user_key
|
||||
tag = public_key.title.gsub(/[^0-9a-zA-Z]/, '_')
|
||||
[user.gitolite_identifier, '@', 'redmine_', tag].join
|
||||
end
|
||||
|
||||
|
||||
# Fix https://github.com/jbox-web/redmine_git_hosting/issues/288
|
||||
# Getting user deployment keys count is not sufficient to assure uniqueness of
|
||||
# deployment key identifier. So we need an 'external' counter to increment the global count
|
||||
# while a key with this identifier exists.
|
||||
#
|
||||
def set_identifier_for_deploy_key
|
||||
count = 0
|
||||
begin
|
||||
key_id = generate_deploy_key_identifier(count)
|
||||
count += 1
|
||||
end while user.gitolite_public_keys.deploy_key.map(&:owner).include?(key_id.split('@')[0])
|
||||
key_id
|
||||
end
|
||||
|
||||
|
||||
def generate_deploy_key_identifier(count)
|
||||
key_count = 1 + count
|
||||
key_count += user.gitolite_public_keys.deploy_key.length unless skip_auto_increment
|
||||
[user.gitolite_identifier, '_', DEPLOY_PSEUDO_USER, '_', key_count, '@', 'redmine_', DEPLOY_PSEUDO_USER, '_', key_count].join
|
||||
end
|
||||
|
||||
end
|
||||
end
|
30
plugins/redmine_git_hosting/app/use_cases/projects/base.rb
Normal file
30
plugins/redmine_git_hosting/app/use_cases/projects/base.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
module Projects
|
||||
class Base
|
||||
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
attr_reader :project
|
||||
attr_reader :options
|
||||
|
||||
|
||||
def initialize(project, opts = {})
|
||||
@project = project
|
||||
@options = opts
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(project, opts = {})
|
||||
new(project, opts).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
module Projects
|
||||
class CreateRepository < Base
|
||||
|
||||
def call
|
||||
create_project_repository
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def create_project_repository
|
||||
# Create new repository
|
||||
repository = Repository.factory('Xitolite')
|
||||
repository.is_default = true
|
||||
repository.extra_info = {}
|
||||
repository.extra_info['extra_report_last_commit'] = '1'
|
||||
|
||||
# Save it to database
|
||||
project.repositories << repository
|
||||
|
||||
# Create it in Gitolite
|
||||
Repositories::Create.call(repository, creation_options)
|
||||
end
|
||||
|
||||
|
||||
def creation_options
|
||||
{ create_readme_file: RedmineGitHosting::Config.init_repositories_on_create? }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
module Projects
|
||||
class ExecuteHooks
|
||||
|
||||
attr_reader :project
|
||||
attr_reader :hook_type
|
||||
attr_reader :params
|
||||
|
||||
|
||||
def initialize(project, hook_type, params = {})
|
||||
@project = project
|
||||
@hook_type = hook_type
|
||||
@params = params
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(project, hook_type, params = {})
|
||||
new(project, hook_type, params).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
self.send("execute_#{hook_type}_hook")
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def execute_github_hook
|
||||
RedmineHooks::GithubIssuesSync.call(project, params)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
29
plugins/redmine_git_hosting/app/use_cases/projects/update.rb
Normal file
29
plugins/redmine_git_hosting/app/use_cases/projects/update.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
module Projects
|
||||
class Update < Base
|
||||
|
||||
def call
|
||||
# Adjust daemon status
|
||||
disable_git_daemon_if_not_public
|
||||
resync
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def disable_git_daemon_if_not_public
|
||||
# Go through all gitolite repos and disable Git daemon if necessary
|
||||
project.gitolite_repos.each do |repository|
|
||||
repository.extra[:git_daemon] = false if repository.git_daemon_enabled? && !project.is_public
|
||||
# Save GitExtra in all cases to trigger urls order consistency checks
|
||||
repository.extra.save
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def resync
|
||||
gitolite_accessor.update_projects([project.id], options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
module Repositories
|
||||
class Base
|
||||
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
attr_reader :repository
|
||||
attr_reader :options
|
||||
attr_reader :project
|
||||
|
||||
|
||||
def initialize(repository, opts = {})
|
||||
@repository = repository
|
||||
@options = opts
|
||||
@project = repository.project
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(repository, opts = {})
|
||||
new(repository, opts).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
module Repositories
|
||||
class BuildPayload < Base
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@payloads = []
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
build_payloads
|
||||
end
|
||||
|
||||
|
||||
def refs
|
||||
options
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Returns an array of GitHub post-receive hook style hashes
|
||||
# http://help.github.com/post-receive-hooks/
|
||||
#
|
||||
def build_payloads
|
||||
refs.each do |ref|
|
||||
# Get revisions range
|
||||
range = get_revisions_from_ref(ref)
|
||||
next if range.nil?
|
||||
@payloads << build_payload(ref, range)
|
||||
end
|
||||
@payloads
|
||||
end
|
||||
|
||||
|
||||
def get_revisions_from_ref(ref)
|
||||
oldhead, newhead, refname = ref.split(',')
|
||||
|
||||
# Only pay attention to branch updates
|
||||
return nil if !refname.match(/refs\/heads\//)
|
||||
|
||||
# Get branch name
|
||||
branch_name = refname.gsub('refs/heads/', '')
|
||||
|
||||
if newhead.match(/\A0{40}\z/)
|
||||
# Deleting a branch
|
||||
logger.info("Deleting branch '#{branch_name}'")
|
||||
range = nil
|
||||
elsif oldhead.match(/\A0{40}\z/)
|
||||
# Creating a branch
|
||||
logger.info("Creating branch '#{branch_name}'")
|
||||
range = newhead
|
||||
else
|
||||
range = "#{oldhead}..#{newhead}"
|
||||
end
|
||||
|
||||
range
|
||||
end
|
||||
|
||||
|
||||
def build_payload(ref, range)
|
||||
revisions_in_range = get_revisions_in_range(range)
|
||||
logger.debug("Revisions in range : #{revisions_in_range.join(' ')}")
|
||||
|
||||
# Get refs
|
||||
oldhead, newhead, refname = ref.split(',')
|
||||
|
||||
# Build payload hash
|
||||
repository.github_payload
|
||||
.merge({ before: oldhead, after: newhead, ref: refname, commits: build_commits_list(revisions_in_range) })
|
||||
end
|
||||
|
||||
|
||||
def build_commits_list(revisions_in_range)
|
||||
commits_list = []
|
||||
revisions_in_range.each do |rev|
|
||||
changeset = repository.find_changeset_by_name(rev)
|
||||
next if changeset.nil?
|
||||
commits_list << changeset.github_payload
|
||||
end
|
||||
commits_list
|
||||
end
|
||||
|
||||
|
||||
def get_revisions_in_range(range)
|
||||
repository.rev_list(range, ['--reverse'])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
module Repositories
|
||||
class Create < Base
|
||||
|
||||
def call
|
||||
set_repository_extra
|
||||
create_repository
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def set_repository_extra
|
||||
extra = repository.build_extra(default_extra_options)
|
||||
extra.save!
|
||||
end
|
||||
|
||||
|
||||
def default_extra_options
|
||||
enable_git_annex? ? git_annex_repository_options : standard_repository_options
|
||||
end
|
||||
|
||||
|
||||
def enable_git_annex?
|
||||
options[:enable_git_annex]
|
||||
end
|
||||
|
||||
|
||||
def standard_repository_options
|
||||
{
|
||||
git_daemon: RedmineGitHosting::Config.gitolite_daemon_by_default?,
|
||||
git_notify: RedmineGitHosting::Config.gitolite_notify_by_default?,
|
||||
git_annex: false,
|
||||
default_branch: 'master',
|
||||
key: RedmineGitHosting::Utils::Crypto.generate_secret(64)
|
||||
}.merge(smart_http_options)
|
||||
end
|
||||
|
||||
|
||||
def smart_http_options
|
||||
case RedmineGitHosting::Config.gitolite_http_by_default?
|
||||
when '1' # HTTPS only
|
||||
{ git_https: true }
|
||||
when '2' # HTTPS and HTTP
|
||||
{ git_http: true, git_https: true }
|
||||
when '3' # HTTP only
|
||||
{ git_http: true }
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def git_annex_repository_options
|
||||
{
|
||||
git_http: 0,
|
||||
git_daemon: false,
|
||||
git_notify: false,
|
||||
git_annex: true,
|
||||
default_branch: 'git-annex',
|
||||
key: RedmineGitHosting::Utils::Crypto.generate_secret(64)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def create_repository
|
||||
gitolite_accessor.create_repository(repository, options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,93 @@
|
|||
module Repositories
|
||||
class DownloadRevision
|
||||
|
||||
attr_reader :repository
|
||||
attr_reader :revision
|
||||
attr_reader :format
|
||||
attr_reader :gitolite_repository_path
|
||||
|
||||
attr_reader :commit_id
|
||||
attr_reader :content_type
|
||||
attr_reader :filename
|
||||
|
||||
|
||||
def initialize(repository, revision, format)
|
||||
@repository = repository
|
||||
@revision = revision
|
||||
@format = format
|
||||
@gitolite_repository_path = repository.gitolite_repository_path
|
||||
|
||||
@valid_commit = false
|
||||
@commit_id = nil
|
||||
@content_type = ''
|
||||
@filename = ''
|
||||
|
||||
validate_revision
|
||||
fill_data
|
||||
end
|
||||
|
||||
|
||||
def content
|
||||
repository.archive(commit_id, format)
|
||||
end
|
||||
|
||||
|
||||
def valid_commit?
|
||||
@valid_commit
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def validate_revision
|
||||
commit = nil
|
||||
|
||||
# is the revision a branch?
|
||||
repository.branches.each do |x|
|
||||
if x.to_s == revision
|
||||
commit = x.revision
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# is the revision a tag?
|
||||
if commit.nil?
|
||||
repository.tags.each do |x|
|
||||
if x == revision
|
||||
commit = repository.rev_list(revision).first
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# well, let check if this is a valid commit
|
||||
commit = revision if commit.nil?
|
||||
commit = repository.rev_parse(commit)
|
||||
|
||||
if commit == ''
|
||||
@valid_commit = false
|
||||
else
|
||||
@valid_commit = true
|
||||
@commit_id = commit
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def fill_data
|
||||
case format
|
||||
when 'tar.gz' then
|
||||
extension = 'tar.gz'
|
||||
@content_type = 'application/x-gzip'
|
||||
when 'zip' then
|
||||
extension = 'zip'
|
||||
@content_type = 'application/x-zip'
|
||||
else
|
||||
extension = 'tar'
|
||||
@content_type = 'application/x-tar'
|
||||
end
|
||||
@filename = "#{repository.redmine_name}-#{revision}.#{extension}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,79 @@
|
|||
module Repositories
|
||||
class ExecuteHooks
|
||||
|
||||
attr_reader :repository
|
||||
attr_reader :hook_type
|
||||
attr_reader :payloads
|
||||
|
||||
|
||||
def initialize(repository, hook_type, payloads = {})
|
||||
@repository = repository
|
||||
@hook_type = hook_type
|
||||
@payloads = payloads
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(repository, hook_type, payloads = {})
|
||||
new(repository, hook_type, payloads).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
self.send("execute_#{hook_type}_hook")
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def execute_fetch_changesets_hook
|
||||
RedmineHooks::FetchChangesets.call(repository)
|
||||
end
|
||||
|
||||
|
||||
def execute_update_mirrors_hook
|
||||
message = 'Notifying mirrors about changes to this repository :'
|
||||
y = ''
|
||||
|
||||
## Post to each post-receive URL
|
||||
if repository.mirrors.active.any?
|
||||
logger.info(message)
|
||||
y << "\n#{message}\n"
|
||||
|
||||
repository.mirrors.active.each do |mirror|
|
||||
y << RedmineHooks::UpdateMirrors.call(mirror, payloads)
|
||||
end
|
||||
end
|
||||
|
||||
y
|
||||
end
|
||||
|
||||
|
||||
def execute_call_webservices_hook
|
||||
message = 'Notifying post receive urls about changes to this repository :'
|
||||
y = ''
|
||||
|
||||
## Post to each post-receive URL
|
||||
if repository.post_receive_urls.active.any?
|
||||
logger.info(message)
|
||||
y << "\n#{message}\n"
|
||||
|
||||
repository.post_receive_urls.active.each do |post_receive_url|
|
||||
y << RedmineHooks::CallWebservices.call(post_receive_url, payloads)
|
||||
end
|
||||
end
|
||||
|
||||
y
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
module RepositoryMirrors
|
||||
class Base
|
||||
|
||||
attr_reader :mirror
|
||||
attr_reader :repository
|
||||
|
||||
|
||||
def initialize(mirror)
|
||||
@mirror = mirror
|
||||
@repository = mirror.repository
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(mirror)
|
||||
new(mirror).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
module RepositoryMirrors
|
||||
class Push < Base
|
||||
|
||||
def call
|
||||
push!
|
||||
end
|
||||
|
||||
|
||||
def push!
|
||||
begin
|
||||
push_message = repository.mirror_push(*command)
|
||||
push_failed = false
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
push_message = e.output
|
||||
push_failed = true
|
||||
end
|
||||
|
||||
return push_failed, push_message
|
||||
end
|
||||
|
||||
|
||||
def command
|
||||
[mirror.url, branch, push_args]
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def push_args
|
||||
mirror.mirror_mode? ? ['--mirror'] : mirror_args
|
||||
end
|
||||
|
||||
|
||||
def mirror_args
|
||||
push_args = []
|
||||
push_args << '--force' if mirror.force_mode?
|
||||
push_args << '--all' if mirror.include_all_branches?
|
||||
push_args << '--tags' if mirror.include_all_tags?
|
||||
push_args
|
||||
end
|
||||
|
||||
|
||||
def branch
|
||||
"#{dequote(mirror.explicit_refspec)}" unless mirror.explicit_refspec.blank?
|
||||
end
|
||||
|
||||
|
||||
# Put backquote in front of crucial characters
|
||||
def dequote(in_string)
|
||||
in_string.gsub(/[$,"\\\n]/) { |x| "\\" + x }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
module RepositoryProtectedBranches
|
||||
class MemberManager
|
||||
|
||||
attr_reader :protected_branch
|
||||
|
||||
|
||||
def initialize(protected_branch)
|
||||
@protected_branch = protected_branch
|
||||
end
|
||||
|
||||
|
||||
def current_user_ids
|
||||
protected_branch.users.map(&:id)
|
||||
end
|
||||
|
||||
|
||||
def current_group_ids
|
||||
protected_branch.groups.map(&:id)
|
||||
end
|
||||
|
||||
|
||||
def current_members
|
||||
protected_branch.protected_branches_members
|
||||
end
|
||||
|
||||
|
||||
def users_by_group_id(id)
|
||||
current_members.select { |pbm| pbm.principal.class.name == 'User' && pbm.inherited_by == id }.map(&:principal)
|
||||
end
|
||||
|
||||
|
||||
def add_users(ids)
|
||||
create_user_member(ids, current_user_ids)
|
||||
end
|
||||
|
||||
|
||||
def add_groups(ids)
|
||||
create_group_member(ids, current_group_ids) do |group|
|
||||
ids = group.users.map(&:id)
|
||||
current_ids = users_by_group_id(group.id).map(&:id)
|
||||
create_user_member(ids, current_ids, inherited_by: group.id, destroy: false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def create_user_member(ids, current_ids, opts = {}, &block)
|
||||
create_member(ids, current_ids, 'User', opts, &block)
|
||||
end
|
||||
|
||||
|
||||
def create_group_member(ids, current_ids, opts = {}, &block)
|
||||
create_member(ids, current_ids, 'Group', opts, &block)
|
||||
end
|
||||
|
||||
|
||||
def add_user_from_group(user, group_id)
|
||||
ids = users_by_group_id(group_id).push(user).map(&:id)
|
||||
current_ids = users_by_group_id(group_id).map(&:id)
|
||||
create_user_member(ids, current_ids, inherited_by: group_id, destroy: false)
|
||||
end
|
||||
|
||||
|
||||
def remove_user_from_group(user, group_id)
|
||||
return unless users_by_group_id(group_id).include?(user)
|
||||
member = current_members.find_by_protected_branch_id_and_principal_id_and_inherited_by(protected_branch.id, user.id, group_id)
|
||||
member.destroy! unless member.nil?
|
||||
end
|
||||
|
||||
|
||||
def create_member(ids, current_ids, klass, opts = {}, &block)
|
||||
destroy = opts.fetch(:destroy, true)
|
||||
inherited_by = opts.fetch(:inherited_by, nil)
|
||||
|
||||
ids = (ids || []).collect(&:to_i) - [0]
|
||||
new_ids = ids - current_ids
|
||||
|
||||
new_ids.each do |id|
|
||||
object = klass.constantize.find_by_id(id)
|
||||
next if object.nil?
|
||||
current_members.create(principal_id: object.id, inherited_by: inherited_by)
|
||||
yield object if block_given?
|
||||
end
|
||||
|
||||
if destroy
|
||||
member_to_destroy = current_members.select { |m| m.principal.class.name == klass && !ids.include?(m.principal.id) }
|
||||
member_to_destroy.each(&:destroy) if member_to_destroy.any?
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
154
plugins/redmine_git_hosting/app/use_cases/settings/apply.rb
Normal file
154
plugins/redmine_git_hosting/app/use_cases/settings/apply.rb
Normal file
|
@ -0,0 +1,154 @@
|
|||
module Settings
|
||||
class Apply
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
attr_reader :previous_settings
|
||||
attr_reader :resync_projects
|
||||
attr_reader :resync_ssh_keys
|
||||
attr_reader :regenerate_ssh_keys
|
||||
attr_reader :flush_cache
|
||||
attr_reader :delete_trash_repo
|
||||
|
||||
def initialize(previous_settings, opts = {})
|
||||
@previous_settings = previous_settings
|
||||
@resync_projects = opts.delete(:resync_all_projects) { false }
|
||||
@resync_ssh_keys = opts.delete(:resync_all_ssh_keys) { false }
|
||||
@regenerate_ssh_keys = opts.delete(:regenerate_all_ssh_keys) { false }
|
||||
@flush_cache = opts.delete(:flush_gitolite_cache) { false }
|
||||
@delete_trash_repo = opts.delete(:delete_trash_repo) { [] }
|
||||
end
|
||||
|
||||
class << self
|
||||
def call(previous_settings, opts = {})
|
||||
new(previous_settings, opts).call
|
||||
end
|
||||
end
|
||||
|
||||
def call
|
||||
gitolite_accessor.flush_settings_cache
|
||||
apply_settings
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apply_settings
|
||||
check_gitolite_location
|
||||
check_repo_hierarchy
|
||||
check_gitolite_config
|
||||
check_gitolite_default_values
|
||||
check_hook_config
|
||||
check_cache_config
|
||||
|
||||
do_resync_projects
|
||||
do_resync_ssh_keys
|
||||
do_regenerate_ssh_keys
|
||||
do_flush_cache
|
||||
do_delete_trash_repo
|
||||
do_add_redmine_rw_access
|
||||
end
|
||||
|
||||
def current_setting(setting)
|
||||
Setting.plugin_redmine_git_hosting[setting]
|
||||
end
|
||||
|
||||
def value_has_changed?(setting)
|
||||
previous_settings[setting] != current_setting(setting)
|
||||
end
|
||||
|
||||
def check_gitolite_location
|
||||
## Gitolite location has changed. Remove temp directory, it will be recloned.
|
||||
if value_has_changed?(:gitolite_server_host) ||
|
||||
value_has_changed?(:gitolite_server_port) ||
|
||||
value_has_changed?(:gitolite_user) ||
|
||||
value_has_changed?(:gitolite_temp_dir)
|
||||
|
||||
RedmineGitHosting.logger.info("Temp dir has changed, remove the previous one : '#{previous_settings[:gitolite_temp_dir]}'")
|
||||
FileUtils.rm_rf previous_settings[:gitolite_temp_dir]
|
||||
end
|
||||
end
|
||||
|
||||
def check_repo_hierarchy
|
||||
## Storage infos has changed, move repositories!
|
||||
if value_has_changed?(:gitolite_global_storage_dir) ||
|
||||
value_has_changed?(:gitolite_redmine_storage_dir) ||
|
||||
value_has_changed?(:hierarchical_organisation)
|
||||
|
||||
# Need to update everyone!
|
||||
# We take all root projects (even those who are closed) and move each hierarchy individually
|
||||
count = Project.includes(:repositories).all.select { |x| x if x.parent_id.nil? }.length
|
||||
gitolite_accessor.move_repositories_tree(count) if count.positive?
|
||||
end
|
||||
end
|
||||
|
||||
def check_gitolite_config
|
||||
## Gitolite config file has changed, create a new one!
|
||||
if value_has_changed?(:gitolite_config_file) ||
|
||||
value_has_changed?(:gitolite_identifier_prefix) ||
|
||||
value_has_changed?(:gitolite_identifier_strip_user_id)
|
||||
options = { message: 'Gitolite configuration has been modified, resync all projects (active, closed, archived)...' }
|
||||
gitolite_accessor.update_projects('all', options)
|
||||
end
|
||||
end
|
||||
|
||||
def check_gitolite_default_values
|
||||
## Gitolite default values has changed, update active projects
|
||||
if value_has_changed?(:gitolite_notify_global_prefix) ||
|
||||
value_has_changed?(:gitolite_notify_global_sender_address) ||
|
||||
value_has_changed?(:gitolite_notify_global_include) ||
|
||||
value_has_changed?(:gitolite_notify_global_exclude)
|
||||
|
||||
# Need to update everyone!
|
||||
options = { message: 'Gitolite configuration has been modified, resync all active projects...' }
|
||||
gitolite_accessor.update_projects('active', options)
|
||||
end
|
||||
end
|
||||
|
||||
def check_hook_config
|
||||
## Gitolite hooks config has changed, update our .gitconfig!
|
||||
if value_has_changed?(:gitolite_hooks_debug) ||
|
||||
value_has_changed?(:gitolite_hooks_url) ||
|
||||
value_has_changed?(:gitolite_hooks_are_asynchronous)
|
||||
|
||||
# Need to update our .gitconfig
|
||||
RedmineGitHosting::Config.update_hook_params!
|
||||
end
|
||||
end
|
||||
|
||||
def check_cache_config
|
||||
## Gitolite cache has changed, clear cache entries!
|
||||
RedmineGitHosting::Cache.clear_obsolete_cache_entries if value_has_changed?(:gitolite_cache_max_time)
|
||||
end
|
||||
|
||||
def do_resync_projects
|
||||
## A resync has been asked within the interface, update all projects in force mode
|
||||
options = { message: 'Forced resync of all projects (active, closed, archived)...', force: true }
|
||||
gitolite_accessor.update_projects('all', options) if resync_projects
|
||||
end
|
||||
|
||||
def do_resync_ssh_keys
|
||||
## A resync has been asked within the interface, update all projects in force mode
|
||||
gitolite_accessor.resync_ssh_keys if resync_ssh_keys
|
||||
end
|
||||
|
||||
def do_regenerate_ssh_keys
|
||||
gitolite_accessor.regenerate_ssh_keys if regenerate_ssh_keys
|
||||
end
|
||||
|
||||
def do_flush_cache
|
||||
## A cache flush has been asked within the interface
|
||||
gitolite_accessor.flush_git_cache if flush_cache
|
||||
end
|
||||
|
||||
def do_delete_trash_repo
|
||||
gitolite_accessor.delete_from_recycle_bin(delete_trash_repo) unless delete_trash_repo.empty?
|
||||
end
|
||||
|
||||
def do_add_redmine_rw_access
|
||||
if Additionals.true? current_setting(:redmine_has_rw_access_on_all_repos)
|
||||
gitolite_accessor.enable_rw_access
|
||||
else
|
||||
gitolite_accessor.disable_rw_access
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
<% @entries.each do |entry| %>
|
||||
<% tr_id = Digest::MD5.hexdigest(entry.path)
|
||||
depth = params[:depth].to_i %>
|
||||
<% ent_path = Redmine::CodesetUtil.replace_invalid_utf8(entry.path) %>
|
||||
<% ent_name = Redmine::CodesetUtil.replace_invalid_utf8(entry.name) %>
|
||||
<tr id="<%= tr_id %>" class="<%= h params[:parent_id] %> entry <%= entry.kind %>">
|
||||
<td style="padding-left: <%=18 * depth%>px;" class="<%=
|
||||
@repository.report_last_commit ? "filename" : "filename_no_report" %>">
|
||||
<% if entry.is_dir? %>
|
||||
<span class="expander" onclick="scmEntryClick('<%= tr_id %>', '<%= escape_javascript(url_for(
|
||||
:action => 'show',
|
||||
:id => @project,
|
||||
:repository_id => @repository.identifier_param,
|
||||
:path => to_path_param(ent_path),
|
||||
:rev => @rev,
|
||||
:depth => (depth + 1),
|
||||
:parent_id => tr_id)) %>');"> </span>
|
||||
<% end %>
|
||||
<%= link_to h(ent_name),
|
||||
{:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev},
|
||||
:class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%>
|
||||
</td>
|
||||
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
|
||||
<% if @repository.report_last_commit %>
|
||||
<td class="revision"><%= link_to_revision2(entry.changeset, @repository) if entry.changeset %></td>
|
||||
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
|
||||
<td class="author"><%= entry.author %></td>
|
||||
<td class="comments"><%= entry.changeset.comments.truncate(50) if entry.changeset %></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
|
@ -0,0 +1,51 @@
|
|||
<% show_revision_graph = ( @repository.supports_revision_graph? && path.blank? ) %>
|
||||
<%= if show_revision_graph && revisions && revisions.any?
|
||||
indexed_commits, graph_space = index_commits(revisions, @repository.branches) do |scmid|
|
||||
url_for(
|
||||
:controller => 'archived_repositories',
|
||||
:action => 'revision',
|
||||
:id => project,
|
||||
:repository_id => @repository.identifier_param,
|
||||
:rev => scmid)
|
||||
end
|
||||
render :partial => 'revision_graph',
|
||||
:locals => {
|
||||
:commits => indexed_commits,
|
||||
:space => graph_space
|
||||
}
|
||||
end %>
|
||||
<%= form_tag(
|
||||
{:controller => 'archived_repositories', :action => 'diff', :id => project,
|
||||
:repository_id => @repository.identifier_param, :path => to_path_param(path)},
|
||||
:method => :get
|
||||
) do %>
|
||||
<table class="list changesets">
|
||||
<thead><tr>
|
||||
<th>#</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th><%= l(:label_date) %></th>
|
||||
<th><%= l(:field_author) %></th>
|
||||
<th><%= l(:field_comments) %></th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<% show_diff = revisions.size > 1 %>
|
||||
<% line_num = 1 %>
|
||||
<% revisions.each do |changeset| %>
|
||||
<tr class="changeset <%= cycle 'odd', 'even' %>">
|
||||
<% id_style = (show_revision_graph ? "padding-left:#{(graph_space + 1) * 20}px" : nil) %>
|
||||
<%= content_tag(:td, :class => 'id', :style => id_style) do %>
|
||||
<%= link_to_revision2(changeset, @repository) %>
|
||||
<% end %>
|
||||
<td class="checkbox"><%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('#cbto-#{line_num+1}').attr('checked',true);") if show_diff && (line_num < revisions.size) %></td>
|
||||
<td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('#cb-#{line_num}').attr('checked')) {$('#cb-#{line_num-1}').attr('checked',true);}") if show_diff && (line_num > 1) %></td>
|
||||
<td class="committed_on"><%= format_time(changeset.committed_on) %></td>
|
||||
<td class="author"><%= changeset.author.to_s.truncate(30) %></td>
|
||||
<td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
|
||||
</tr>
|
||||
<% line_num += 1 %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%= submit_tag(l(:label_view_diff), :name => nil) if show_diff %>
|
||||
<% end %>
|
|
@ -0,0 +1,46 @@
|
|||
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %>
|
||||
|
||||
<div class="contextual">
|
||||
<%= render :partial => 'navigation' %>
|
||||
</div>
|
||||
|
||||
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
|
||||
|
||||
<%= render :partial => 'link_to_functions' %>
|
||||
|
||||
<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
|
||||
|
||||
<div class="autoscroll">
|
||||
<table class="filecontent annotate syntaxhl">
|
||||
<tbody>
|
||||
<% line_num = 1; previous_revision = nil %>
|
||||
<% syntax_highlight_lines(@path, Redmine::CodesetUtil.to_utf8_by_setting(@annotate.content)).each do |line| %>
|
||||
<% revision = @annotate.revisions[line_num - 1] %>
|
||||
<tr id="L<%= line_num %>" class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
|
||||
<th class="line-num"><a href="#L<%= line_num %>"><%= line_num %></a></th>
|
||||
<td class="revision">
|
||||
<% if revision && revision != previous_revision %>
|
||||
<%= revision.identifier ?
|
||||
link_to_revision2(revision, @repository) : format_revision(revision) %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="author">
|
||||
<% if revision && revision != previous_revision %>
|
||||
<% author = Redmine::CodesetUtil.to_utf8(revision.author.to_s,
|
||||
@repository.repo_log_encoding) %>
|
||||
<%= author.split('<').first %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="line-code"><pre><%= line.html_safe %></pre></td>
|
||||
</tr>
|
||||
<% line_num += 1; previous_revision = revision %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<% html_title(l(:button_annotate)) -%>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= stylesheet_link_tag 'scm' %>
|
||||
<% end %>
|
|
@ -0,0 +1,89 @@
|
|||
<%# This is used to display basic git setup instructions, like on github... %>
|
||||
<% flash.now[:warning] = l(:notice_empty_repository) %>
|
||||
|
||||
<div class="box">
|
||||
<% if User.current.allowed_to?(:view_changesets, @project) %>
|
||||
<h2><%= l(:label_help_git_setup) %> :</h2>
|
||||
|
||||
<pre>
|
||||
<a href="https://git-scm.com/download" target="_blank"><%= l(:label_download_and_install_git) %></a>
|
||||
git config --global user.name "<%= User.current.name(:firstname_lastname) %>"
|
||||
git config --global user.email <%= User.current.mail ? User.current.mail : "mail@example.com" %>
|
||||
<% if !User.current.anonymous? && User.current.gitolite_public_keys.active.length == 0 %>
|
||||
<%= link_to "Upload SSH Public Key", {:controller => 'my', :action => 'account'} %>
|
||||
<% end %>
|
||||
</pre>
|
||||
|
||||
<% if User.current.allowed_to?(:commit_access, @project) %>
|
||||
<div id="repository_setup">
|
||||
<h2><%= l(:label_help_repository_setup_new) %> :</h2>
|
||||
<pre>
|
||||
mkdir <%= @repository.redmine_name %>
|
||||
cd <%= @repository.redmine_name %>
|
||||
git init
|
||||
touch readme.txt
|
||||
git add readme.txt
|
||||
git commit -m 'Initializing <%= @repository.redmine_name %> repository'
|
||||
git remote add origin <span class="git_url_access"></span>
|
||||
git push -u origin master
|
||||
</pre>
|
||||
|
||||
<h2><%= l(:label_help_repository_setup_existing) %> :</h2>
|
||||
<pre>
|
||||
cd existing_git_repo
|
||||
git remote add origin <span class="git_url_access"></span>
|
||||
git push -u origin master
|
||||
</pre>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<h2><%= l(:label_help_repository_clone) %> :</h2>
|
||||
<pre>
|
||||
git clone <span class="git_url_access"></span>
|
||||
</pre>
|
||||
<% else %>
|
||||
<%= l(:label_help_no_repo_rights) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<h3><%= l(:label_repository_plural) %></h3>
|
||||
<ul class="repository git">
|
||||
<% @repositories.sort.each do |repo| %>
|
||||
<li class="repository git"><%= link_to h(repo.name), {:controller => 'archived_repositories', :action => 'show', :id => @project, :repository_id => repo.identifier_param, :rev => nil, :path => nil},
|
||||
:class => 'repository' + (@repository.is_a?(Repository::Xitolite) ? ' git' : '') + (repo == @repository ? ' selected' : '') %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<% if @repository.is_a?(Repository::Xitolite) && @repository.urls_are_viewable? %>
|
||||
<div class="git_hosting_urls">
|
||||
<h3><%= l(:label_repository_access_url) %></h3>
|
||||
<%= render :partial => 'common/git_urls', :locals => {:repository => @repository} %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_tag do %>
|
||||
$(document).ready(function() {
|
||||
var key = $('#git_url_list li').first().attr('id');
|
||||
var access = access_list[key];
|
||||
|
||||
$('.git_url_access').html(access.url);
|
||||
|
||||
$('#git_url_list li').on('click', function(){
|
||||
var key = $(this).attr('id');
|
||||
var access = access_list[key];
|
||||
|
||||
$('.git_url_access').html(access.url);
|
||||
|
||||
if (access.commiter == true) {
|
||||
$('#repository_setup').show();
|
||||
} else {
|
||||
$('#repository_setup').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
<% end %>
|
||||
<% end %>
|
|
@ -0,0 +1,17 @@
|
|||
h2 = l(:label_archived_repositories)
|
||||
|
||||
- if @archived_projects.any?
|
||||
ul
|
||||
- @archived_projects.each do |project|
|
||||
- if project.repositories.any?
|
||||
li
|
||||
= project.name
|
||||
ul
|
||||
- project.repositories.each do |repository|
|
||||
li
|
||||
= link_to h(repository.url), { controller: 'archived_repositories',
|
||||
action: 'show',
|
||||
id: project,
|
||||
repository_id: repository.identifier_param }
|
||||
- else
|
||||
p.nodata = l(:label_no_data)
|
|
@ -0,0 +1,101 @@
|
|||
<div class="contextual">
|
||||
«
|
||||
<% unless @changeset.previous.nil? -%>
|
||||
<%= link_to_revision2(@changeset.previous, @repository, :text => l(:label_previous)) %>
|
||||
<% else -%>
|
||||
<%= l(:label_previous) %>
|
||||
<% end -%>
|
||||
|
|
||||
<% unless @changeset.next.nil? -%>
|
||||
<%= link_to_revision2(@changeset.next, @repository, :text => l(:label_next)) %>
|
||||
<% else -%>
|
||||
<%= l(:label_next) %>
|
||||
<% end -%>
|
||||
»
|
||||
|
||||
<%= form_tag({:controller => 'archived_repositories',
|
||||
:action => 'revision',
|
||||
:id => @project,
|
||||
:repository_id => @repository.identifier_param,
|
||||
:rev => nil},
|
||||
:method => :get) do %>
|
||||
<%= text_field_tag 'rev', @rev, :size => 8 %>
|
||||
<%= submit_tag 'OK', :name => nil %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<h2><%= avatar(@changeset.user, :size => "24") %><%= l(:label_revision) %> <%= format_revision(@changeset) %></h2>
|
||||
|
||||
<% if @changeset.scmid.present? || @changeset.parents.present? || @changeset.children.present? %>
|
||||
<table class="revision-info">
|
||||
<% if @changeset.scmid.present? %>
|
||||
<tr>
|
||||
<td>ID</td><td><%= h(@changeset.scmid) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @changeset.parents.present? %>
|
||||
<tr>
|
||||
<td><%= l(:label_parent_revision) %></td>
|
||||
<td>
|
||||
<%= @changeset.parents.collect{
|
||||
|p| link_to_revision2(p, @repository, :text => format_revision(p))
|
||||
}.join(", ").html_safe %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @changeset.children.present? %>
|
||||
<tr>
|
||||
<td><%= l(:label_child_revision) %></td>
|
||||
<td>
|
||||
<%= @changeset.children.collect{
|
||||
|p| link_to_revision2(p, @repository, :text => format_revision(p))
|
||||
}.join(", ").html_safe %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
<span class="author">
|
||||
<%= authoring(@changeset.committed_on, @changeset.author) %>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<%= textilizable @changeset.comments %>
|
||||
|
||||
<% if @changeset.issues.visible.any? || User.current.allowed_to?(:manage_related_issues, @repository.project) %>
|
||||
<%= render :partial => 'related_issues' %>
|
||||
<% end %>
|
||||
|
||||
<% if User.current.allowed_to?(:browse_repository, @project) %>
|
||||
<h3><%= l(:label_attachment_plural) %></h3>
|
||||
<ul id="changes-legend">
|
||||
<li class="change change-A"><%= l(:label_added) %></li>
|
||||
<li class="change change-M"><%= l(:label_modified) %></li>
|
||||
<li class="change change-C"><%= l(:label_copied) %></li>
|
||||
<li class="change change-R"><%= l(:label_renamed) %></li>
|
||||
<li class="change change-D"><%= l(:label_deleted) %></li>
|
||||
</ul>
|
||||
|
||||
<p><%= link_to(l(:label_view_diff),
|
||||
:action => 'diff',
|
||||
:id => @project,
|
||||
:repository_id => @repository.identifier_param,
|
||||
:path => "",
|
||||
:rev => @changeset.identifier) if @changeset.filechanges.any? %></p>
|
||||
|
||||
<div class="changeset-changes">
|
||||
<%= render_changeset_changes %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= stylesheet_link_tag "scm" %>
|
||||
<% end %>
|
||||
|
||||
<%
|
||||
title = "#{l(:label_revision)} #{format_revision(@changeset)}"
|
||||
title << " - #{@changeset.comments.truncate(80)}"
|
||||
html_title(title)
|
||||
-%>
|
|
@ -0,0 +1,34 @@
|
|||
<div class="contextual">
|
||||
<%= form_tag(
|
||||
{:controller => 'archived_repositories', :action => 'revision', :id => @project,
|
||||
:repository_id => @repository.identifier_param},
|
||||
:method => :get
|
||||
) do %>
|
||||
<%= l(:label_revision) %>: <%= text_field_tag 'rev', nil, :size => 8 %>
|
||||
<%= submit_tag 'OK' %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<h2><%= l(:label_revision_plural) %></h2>
|
||||
|
||||
<%= render :partial => 'revisions',
|
||||
:locals => {:project => @project,
|
||||
:path => '',
|
||||
:revisions => @changesets,
|
||||
:entry => nil } %>
|
||||
|
||||
<p class="pagination"><%= pagination_links_full @changeset_pages,@changeset_count %></p>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= stylesheet_link_tag "scm" %>
|
||||
<%= auto_discovery_link_tag(
|
||||
:atom,
|
||||
params.merge(
|
||||
{:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
|
||||
<% end %>
|
||||
|
||||
<% other_formats_links do |f| %>
|
||||
<%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
|
||||
<% end %>
|
||||
|
||||
<% html_title(l(:label_revision_plural)) -%>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue