Añade el plugin Redmine Git Hosting 5.0.0
This commit is contained in:
parent
cfa0d58b18
commit
a3bddad233
458 changed files with 30396 additions and 1 deletions
|
@ -0,0 +1,17 @@
|
|||
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,29 @@
|
|||
module XitoliteRepositoryFinder
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def find_xitolite_repository
|
||||
@repository = Repository::Xitolite.find(find_repository_param)
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
render_404
|
||||
else
|
||||
@project = @repository.project
|
||||
render_404 if @project.nil?
|
||||
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,46 @@
|
|||
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 :git_hosting
|
||||
|
||||
def index
|
||||
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
|
||||
|
||||
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
|
||||
return if @download.valid_commit?
|
||||
|
||||
flash.now[:error] = l(:error_download_revision_no_such_commit, commit: download_revision)
|
||||
render_404
|
||||
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 :git_hosting
|
||||
|
||||
def index
|
||||
@gitolite_user_keys = @user.gitolite_public_keys.user_key.sorted
|
||||
@gitolite_deploy_keys = @user.gitolite_public_keys.deploy_key.sorted
|
||||
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,10 @@
|
|||
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 :git_hosting
|
||||
|
||||
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,124 @@
|
|||
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.sorted
|
||||
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 render action: 'new' 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 render action: 'edit' 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)
|
||||
@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
|
||||
keys = users_allowed_to_create_deployment_keys.map { |user| user.gitolite_public_keys.deploy_key.order(:title) }
|
||||
keys.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 render action: 'new' 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 render action: 'edit' 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,46 @@
|
|||
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.sorted
|
||||
render_with_api
|
||||
end
|
||||
|
||||
def new
|
||||
@mirror = @repository.mirrors.new
|
||||
end
|
||||
|
||||
def create
|
||||
@mirror = @repository.mirrors.new
|
||||
@mirror.safe_attributes = params[:repository_mirror]
|
||||
return render action: 'new' unless @mirror.save
|
||||
|
||||
flash[:notice] = l(:notice_mirror_created)
|
||||
render_js_redirect
|
||||
end
|
||||
|
||||
def update
|
||||
@mirror.safe_attributes = params[:repository_mirror]
|
||||
return render action: 'edit' 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,52 @@
|
|||
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
|
||||
# skip_before_action :verify_authenticity_token, only: %i[create update]
|
||||
|
||||
def index
|
||||
@repository_post_receive_urls = @repository.post_receive_urls.sorted
|
||||
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 render action: 'new' 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 render action: 'edit' 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.sorted
|
||||
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 render action: 'new' 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 render action: 'edit' 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,37 @@
|
|||
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,
|
||||
: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 :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::FileLogger::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::DEFAULT_PARSER.make_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, :gitolite_server_host, :gitolite_ssh_private_key, :gitolite_ssh_public_key,
|
||||
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_each :gitolite_ssh_private_key, :gitolite_ssh_public_key do |record, attr, value|
|
||||
record.errors.add(attr, 'must exists on filesystem') unless File.exist? 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,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
|
||||
|
||||
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,68 @@
|
|||
module ExtendRepositoriesHelper
|
||||
def encoding_field(form, _repository)
|
||||
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?
|
||||
|
||||
tag.p do
|
||||
hidden_field_tag('repository[create_readme]', 'false', id: '') +
|
||||
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?
|
||||
|
||||
tag.p do
|
||||
hidden_field_tag('repository[enable_git_annex]', 'false', id: '') +
|
||||
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.sort
|
||||
options.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,31 @@
|
|||
module GitHostingHelper
|
||||
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
|
||||
|
||||
def checked_image_with_exclamation(checked = true)
|
||||
checked ? image_tag('toggle_check.png') : image_tag('exclamation.png')
|
||||
end
|
||||
|
||||
def render_shell_text(text)
|
||||
Redmine::SyntaxHighlighting.highlight_by_language text, 'shell'
|
||||
end
|
||||
|
||||
def gitolite_project_settings_tabs
|
||||
tabs = []
|
||||
|
||||
tabs << { name: 'db',
|
||||
action: :show,
|
||||
partial: 'projects/settings/db',
|
||||
label: :label_db }
|
||||
|
||||
tabs << { name: 'db2',
|
||||
action: :show,
|
||||
partial: 'projects/settings/db',
|
||||
label: :label_db }
|
||||
tabs
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
module GitHostingUsersHelper
|
||||
def user_settings_tabs
|
||||
tabs = super
|
||||
tabs << { name: 'keys', partial: 'gitolite_public_keys/view', label: :label_public_keys }
|
||||
end
|
||||
|
||||
# Hacked render_api_custom_values to add plugin values to user api.
|
||||
# @NOTE: there is no solution for index.api, because @user is missing
|
||||
# @TODO
|
||||
def render_api_custom_values(custom_values, api)
|
||||
rc = super
|
||||
|
||||
if @user.present?
|
||||
api.array :ssh_keys do
|
||||
@user.gitolite_public_keys.each do |key|
|
||||
api.ssh_key do
|
||||
api.id key.id
|
||||
api.key_type key.key_type_as_string
|
||||
api.title key.title
|
||||
api.key key.key
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rc
|
||||
end
|
||||
end
|
|
@ -0,0 +1,128 @@
|
|||
module GitolitePluginSettingsHelper
|
||||
def render_gitolite_params_status(params)
|
||||
tag.ul class: 'list-unstyled' do
|
||||
content = ''
|
||||
params.each do |param, installed|
|
||||
content << tag.li do
|
||||
image_tag(image_for_param(installed), style: 'vertical-align: bottom; padding-right: 5px;') +
|
||||
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-error'
|
||||
label = l(:label_unknown_gitolite_version)
|
||||
else
|
||||
css_class = 'label label-success'
|
||||
label = version
|
||||
end
|
||||
tag.span label, class: css_class
|
||||
end
|
||||
|
||||
def render_temp_dir_writeable(state, label)
|
||||
css_class = state ? 'label label-success' : 'label label-error'
|
||||
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::FileLogger::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-error' }
|
||||
end
|
||||
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 << tag.span(feature, opts)
|
||||
end
|
||||
end
|
||||
content.html_safe
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
module GitolitePublicKeysHelper
|
||||
def keylabel(key)
|
||||
key.user == User.current ? key.title&.to_s : "#{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
|
||||
false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
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.present?
|
||||
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,8 @@
|
|||
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,43 @@
|
|||
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 << tag.li(l(:all_branches)) if mirror.include_all_branches
|
||||
result << tag.li(l(:all_tags)) if mirror.include_all_tags
|
||||
result << tag.li(mirror.explicit_refspec) if max_refspec.zero? || ((1..max_refspec) === mirror.explicit_refspec.length)
|
||||
result << tag.li(l(:explicit)) if max_refspec.positive? && (mirror.explicit_refspec.length > max_refspec)
|
||||
|
||||
tag.ul(safe_join(result), class: 'list-unstyled') if result.any?
|
||||
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 = 'error'
|
||||
else
|
||||
status = l :label_mirror_push_sucess
|
||||
status_css = 'success'
|
||||
end
|
||||
|
||||
t :label_mirror_push_info_html, mirror_url: mirror.url, status: status, status_css: status_css
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
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,73 @@
|
|||
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?
|
||||
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.present?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,83 @@
|
|||
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,78 @@
|
|||
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,79 @@
|
|||
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,29 @@
|
|||
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
|
||||
extra.notification_sender.presence || RedmineGitHosting::Config.gitolite_notify_global_sender_address
|
||||
end
|
||||
|
||||
def email_prefix
|
||||
extra.notification_prefix.presence || RedmineGitHosting::Config.gitolite_notify_global_prefix
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,90 @@
|
|||
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 '' unless 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,60 @@
|
|||
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 = %w[gitweb daemon DUMMY_REDMINE_KEY REDMINE_ARCHIVED_PROJECT REDMINE_CLOSED_PROJECT].freeze
|
||||
|
||||
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) unless 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,114 @@
|
|||
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
|
||||
url = "ssh://#{RedmineGitHosting::Config.gitolite_user}@#{RedmineGitHosting::Config.ssh_server_domain}"
|
||||
|
||||
url << if RedmineGitHosting::Config.gitolite_server_port == '22'
|
||||
"/#{git_access_path}"
|
||||
else
|
||||
":#{RedmineGitHosting::Config.gitolite_server_port}/#{git_access_path}"
|
||||
end
|
||||
|
||||
url
|
||||
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 '' unless 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 '' unless 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.blank?
|
||||
|
||||
hash = {}
|
||||
urls_order.each do |url|
|
||||
available_url = available_urls[url.to_sym]
|
||||
next if available_url.blank?
|
||||
|
||||
hash[url.to_sym] = available_url
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,95 @@
|
|||
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,113 @@
|
|||
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
|
||||
all.map(&:identifier).inject(Hash.new(0)) do |h, x|
|
||||
h[x] += 1 if x.present?
|
||||
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.key?('heads') && !extra_info.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 url.blank?
|
||||
self.root_url = url if 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.present? && (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)
|
||||
if self.class.repo_ident_unique? && Repository.where("identifier = ? and project_id <> ?", identifier, project.id).any?
|
||||
errors.add :identifier, :taken
|
||||
end
|
||||
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?
|
||||
|
||||
if (identifier_was.blank? && identifier.present?) || (identifier_was.present? && identifier_changed?)
|
||||
errors.add :identifier, :cannot_change
|
||||
end
|
||||
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
|
8
plugins/redmine_git_hosting/app/models/github_comment.rb
Normal file
8
plugins/redmine_git_hosting/app/models/github_comment.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class GithubComment < ActiveRecord::Base
|
||||
## Relations
|
||||
belongs_to :journal
|
||||
|
||||
## Validations
|
||||
validates :github_id, presence: true
|
||||
validates :journal_id, presence: true, uniqueness: { scope: :github_id }
|
||||
end
|
8
plugins/redmine_git_hosting/app/models/github_issue.rb
Normal file
8
plugins/redmine_git_hosting/app/models/github_issue.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class GithubIssue < ActiveRecord::Base
|
||||
## Relations
|
||||
belongs_to :issue
|
||||
|
||||
## Validations
|
||||
validates :github_id, presence: true
|
||||
validates :issue_id, presence: true, uniqueness: { scope: :github_id }
|
||||
end
|
203
plugins/redmine_git_hosting/app/models/gitolite_public_key.rb
Normal file
203
plugins/redmine_git_hosting/app/models/gitolite_public_key.rb
Normal file
|
@ -0,0 +1,203 @@
|
|||
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) }
|
||||
scope :sorted, -> { order(:title, :created_at) }
|
||||
|
||||
## 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!
|
||||
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,56 @@
|
|||
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, exclusion: { in: [-1] }
|
||||
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) }
|
||||
scope :sorted, -> { order(:id) }
|
||||
|
||||
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,7 @@
|
|||
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: %i[type repository_id] },
|
||||
format: { with: VALID_CONFIG_KEY_REGEX }
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
class RepositoryGitConfigKey::Option < RepositoryGitConfigKey
|
||||
validates :key, presence: true,
|
||||
uniqueness: { case_sensitive: false, scope: %i[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
|
107
plugins/redmine_git_hosting/app/models/repository_mirror.rb
Normal file
107
plugins/redmine_git_hosting/app/models/repository_mirror.rb
Normal file
|
@ -0,0 +1,107 @@
|
|||
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') }
|
||||
scope :sorted, -> { order(:url) }
|
||||
|
||||
## 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,61 @@
|
|||
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::DEFAULT_PARSER.make_regexp(%w[http https]) }
|
||||
|
||||
validates :mode, presence: true, inclusion: { in: %i[github get post] }
|
||||
|
||||
## Serializations
|
||||
serialize :triggers, Array
|
||||
|
||||
## Scopes
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :inactive, -> { where(active: false) }
|
||||
scope :sorted, -> { order(:url) }
|
||||
|
||||
## 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 = begin
|
||||
url.strip
|
||||
rescue StandardError
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Remove blank entries in triggers
|
||||
def remove_blank_triggers
|
||||
self.triggers = triggers.select(&:present?)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
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) }
|
||||
scope :sorted, -> { order(:path) }
|
||||
|
||||
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
|
||||
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
|
||||
tag.ul(render_url_list, class: 'git_url_list')
|
||||
end
|
||||
|
||||
def render_url_list
|
||||
s = ''
|
||||
repository.available_urls_sorted.each do |key, value|
|
||||
s << 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
|
||||
tag.input '', class: 'git_url_text', id: url_text_container_id, readonly: 'readonly'
|
||||
end
|
||||
|
||||
def render_permissions
|
||||
tag.div 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
|
11
plugins/redmine_git_hosting/app/reports/report_base.rb
Normal file
11
plugins/redmine_git_hosting/app/reports/report_base.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
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,105 @@
|
|||
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: repository.id)
|
||||
.where.not(user_id: nil)
|
||||
.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] unless @changes_for_committer[committer].nil?
|
||||
|
||||
@changes_for_committer[committer] ||= Changeset.where(repository_id: repository.id, committer: 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,27 @@
|
|||
module PermissionsBuilder
|
||||
class Base
|
||||
attr_reader :repository, :gitolite_users, :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 no_users?(type)
|
||||
gitolite_users[type].blank?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
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,60 @@
|
|||
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 no_users?(:rewind_users)
|
||||
@permissions['RW'][''] = gitolite_users[:write_users] unless no_users?(:write_users)
|
||||
@permissions['R'][''] = gitolite_users[:read_users] unless 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|
|
||||
next unless user_list.any?
|
||||
|
||||
merge_permissions[perm][branch] = [] unless merge_permissions[perm].key?(branch)
|
||||
merge_permissions[perm][branch] += user_list
|
||||
end
|
||||
end
|
||||
|
||||
old_permissions.each do |perm, branch_settings|
|
||||
branch_settings.each do |branch, user_list|
|
||||
next unless user_list.any?
|
||||
|
||||
merge_permissions[perm][branch] = [] unless merge_permissions[perm].key?(branch)
|
||||
merge_permissions[perm][branch] += user_list
|
||||
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,54 @@
|
|||
module RedmineHooks
|
||||
class Base
|
||||
attr_reader :object, :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,111 @@
|
|||
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 true if post_receive_url.mode == :post
|
||||
return false if payloads.empty?
|
||||
return true unless use_triggers?
|
||||
return false if post_receive_url.triggers.empty?
|
||||
|
||||
!payloads_to_send.empty?
|
||||
end
|
||||
|
||||
def start_message
|
||||
uri = URI post_receive_url.url
|
||||
if uri.password.present?
|
||||
uri.user = nil
|
||||
uri.password = nil
|
||||
"Notifying #{uri} (with base auth)"
|
||||
else
|
||||
"Notifying #{post_receive_url.url}"
|
||||
end
|
||||
end
|
||||
|
||||
def skip_message
|
||||
"This url doesn't need to be notified"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_payloads_to_send
|
||||
@payloads_to_send = if post_receive_url.mode == :post
|
||||
{}
|
||||
elsif use_triggers?
|
||||
extract_payloads
|
||||
else
|
||||
payloads
|
||||
end
|
||||
end
|
||||
|
||||
def extract_payloads
|
||||
new_payloads = []
|
||||
payloads.each do |payload|
|
||||
data = RedmineGitHosting::Utils::Git.parse_refspec(payload[:ref])
|
||||
new_payloads << payload if data[:type] == 'heads' && post_receive_url.triggers.include?(data[:name])
|
||||
end
|
||||
new_payloads
|
||||
end
|
||||
|
||||
def use_method
|
||||
post_receive_url.mode == :get ? :http_get : :http_post
|
||||
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 = send(use_method, post_receive_url.url, { data: { payload: payload } })
|
||||
|
||||
if post_failed
|
||||
logger.error 'Failed!'
|
||||
logger.error post_message
|
||||
(split_payloads? ? failure_message.delete("\n") : failure_message)
|
||||
else
|
||||
log_hook_succeeded
|
||||
(split_payloads? ? success_message.delete("\n") : success_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
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
|
||||
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
|
|
@ -0,0 +1,138 @@
|
|||
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?
|
||||
create_relation = true
|
||||
|
||||
## And we don't have issue in Redmine
|
||||
redmine_issue = if redmine_issue.nil?
|
||||
create_redmine_issue
|
||||
else
|
||||
## Create relation and update 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.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!
|
||||
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!
|
||||
journal
|
||||
end
|
||||
|
||||
def update_redmine_issue(issue)
|
||||
logger.info("Github Issues Sync : update issue '##{issue.id}'")
|
||||
|
||||
issue.status_id = if params[:issue][:state] == 'closed'
|
||||
5
|
||||
else
|
||||
1
|
||||
end
|
||||
|
||||
issue.subject = params[:issue][:title].chomp[0, 255]
|
||||
issue.description = params[:issue][:body]
|
||||
issue.updated_on = params[:issue][:updated_at]
|
||||
|
||||
issue.save!
|
||||
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
|
||||
|
||||
user
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
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,63 @@
|
|||
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?
|
||||
|
||||
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|
|
||||
next unless 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
|
||||
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,59 @@
|
|||
module GitolitePublicKeys
|
||||
class GenerateIdentifier
|
||||
DEPLOY_PSEUDO_USER = 'deploy_key'.freeze
|
||||
|
||||
attr_reader :public_key, :user, :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
|
22
plugins/redmine_git_hosting/app/use_cases/projects/base.rb
Normal file
22
plugins/redmine_git_hosting/app/use_cases/projects/base.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
module Projects
|
||||
class Base
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
attr_reader :project, :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,27 @@
|
|||
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,27 @@
|
|||
module Projects
|
||||
class ExecuteHooks
|
||||
attr_reader :project, :hook_type, :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
|
||||
send("execute_#{hook_type}_hook")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_github_hook
|
||||
RedmineHooks::GithubIssuesSync.call(project, params)
|
||||
end
|
||||
end
|
||||
end
|
24
plugins/redmine_git_hosting/app/use_cases/projects/update.rb
Normal file
24
plugins/redmine_git_hosting/app/use_cases/projects/update.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
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,29 @@
|
|||
module Repositories
|
||||
class Base
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
attr_reader :repository, :options, :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,83 @@
|
|||
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,61 @@
|
|||
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,78 @@
|
|||
module Repositories
|
||||
class DownloadRevision
|
||||
attr_reader :repository, :revision, :format, :gitolite_repository_path, :commit_id, :content_type, :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'
|
||||
extension = 'tar.gz'
|
||||
@content_type = 'application/x-gzip'
|
||||
when 'zip'
|
||||
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,65 @@
|
|||
module Repositories
|
||||
class ExecuteHooks
|
||||
attr_reader :repository, :hook_type, :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
|
||||
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,20 @@
|
|||
module RepositoryMirrors
|
||||
class Base
|
||||
attr_reader :mirror, :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,46 @@
|
|||
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,79 @@
|
|||
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!
|
||||
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
|
149
plugins/redmine_git_hosting/app/use_cases/settings/apply.rb
Normal file
149
plugins/redmine_git_hosting/app/use_cases/settings/apply.rb
Normal file
|
@ -0,0 +1,149 @@
|
|||
module Settings
|
||||
class Apply
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
attr_reader :previous_settings, :resync_projects, :resync_ssh_keys, :regenerate_ssh_keys, :flush_cache, :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.count { |x| x if x.parent_id.nil? }
|
||||
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,3 @@
|
|||
- content_for :header_tags do
|
||||
= stylesheet_link_tag 'application', plugin: 'redmine_git_hosting'
|
||||
= javascript_include_tag 'application', plugin: 'redmine_git_hosting'
|
|
@ -0,0 +1,23 @@
|
|||
- content_for :header_tags do
|
||||
= additionals_library_load :clipboardjs
|
||||
|
||||
= stylesheet_link_tag 'git_urls', plugin: 'redmine_git_hosting'
|
||||
= javascript_include_tag 'git_urls', plugin: 'redmine_git_hosting'
|
||||
|
||||
javascript:
|
||||
$(function() {
|
||||
setFirstGitUrl('.git_url_list'); setGitUrls('.git_url');
|
||||
$('.clipboard-button').tooltip();
|
||||
})
|
||||
|
||||
- repositories ||= Array.wrap(repository)
|
||||
- if repositories.map(&:available_urls_sorted).any?
|
||||
- repositories.sort_by { |r| r.is_default ? 0 : 1 }.each do |repository|
|
||||
- next if repository.available_urls_sorted.empty?
|
||||
- present repository do |p|
|
||||
.repository-urls
|
||||
= p.link_to_repository if repositories.count > 1
|
||||
= p.git_urls_box
|
||||
|
||||
- else
|
||||
#git_url_box = l(:label_repository_access_not_configured)
|
|
@ -0,0 +1,16 @@
|
|||
- if @project.module_enabled?(:repository) && \
|
||||
@project.repository.is_a?(Repository::Xitolite) && \
|
||||
User.current.allowed_to?(:view_changesets, @project)
|
||||
.git_hosting
|
||||
h3
|
||||
= @project.repositories.count > 1 ? l(:label_repository_plural) : l(:label_repository)
|
||||
= render partial: 'common/git_urls', locals: { repositories: @project.gitolite_repos }
|
||||
- if @project.repositories.count > 1
|
||||
.clear-both
|
||||
p
|
||||
= link_to l(:label_see_other_repositories), { controller: '/repositories',
|
||||
action: 'show',
|
||||
id: @project,
|
||||
repository_id: @project.repository.identifier_param,
|
||||
rev: nil,
|
||||
path: nil }
|
|
@ -0,0 +1,29 @@
|
|||
#validation_messages
|
||||
= error_messages_for 'gitolite_public_key'
|
||||
|
||||
= labelled_form_for :gitolite_public_key, GitolitePublicKey.new,
|
||||
url: { controller: 'gitolite_public_keys', action: 'create', user_id: params[:id], tab: params[:id] && 'keys' },
|
||||
html: { method: :post } do |f|
|
||||
p
|
||||
= f.text_field :title, label: :label_identifier_can_be_arbitrary, required: true, style: 'width: 97%;'
|
||||
|
||||
- if can_create_deployment_keys_for_some_project(@user)
|
||||
p
|
||||
= f.select :key_type,
|
||||
options_for_select([[l(:label_user_key), 0], [l(:label_deploy_key), 1]]),
|
||||
{ required: true, label: :label_key_type },
|
||||
{ class: 'select_key_type' }
|
||||
#key_type_options style="display: none;"
|
||||
p
|
||||
= f.check_box :delete_when_unused, required: true, label: :label_deployment_credential_delete_when_unused
|
||||
p
|
||||
= f.text_area :key, label: :label_public_key, required: true,
|
||||
style: 'width: 97%; height: 200px; overflow: auto;',
|
||||
cols: nil,
|
||||
rows: nil
|
||||
em
|
||||
= l(:label_cut_and_paste)
|
||||
br
|
||||
br
|
||||
= submit_tag l(:button_create), name: 'create_button'
|
||||
= submit_tag l(:button_cancel), name: 'cancel_button'
|
|
@ -0,0 +1,21 @@
|
|||
table.list
|
||||
- if ssh_keys.empty?
|
||||
tr
|
||||
td
|
||||
label = l(:label_no_public_keys)
|
||||
- else
|
||||
- ssh_keys.each do |key|
|
||||
tr class="#{'highlight' if @gitolite_public_key == key}"
|
||||
td style="text-align: left;"
|
||||
= h(key)
|
||||
td style="text-align: left;"
|
||||
i.fas.fa-check style="color: green; margin-left: 5px; margin-right: 5px;"
|
||||
= key.fingerprint
|
||||
- if params[:id]
|
||||
td style="text-align: left;"
|
||||
= key.gitolite_path
|
||||
td.buttons style="width: 10%;"
|
||||
= link_to l(:button_delete), public_key_path(key, user_id: params[:id]),
|
||||
method: 'delete',
|
||||
class: 'icon icon-del',
|
||||
data: { confirm: l(:text_gitolite_key_destroy_confirmation, title: keylabel(key)) }
|
|
@ -0,0 +1,37 @@
|
|||
h3 = l(:label_my_public_keys)
|
||||
|
||||
fieldset.public_key_view
|
||||
legend = l(:label_current_user_keys)
|
||||
= render partial: 'gitolite_public_keys/ssh_keys', locals: { ssh_keys: @gitolite_user_keys }
|
||||
|
||||
br
|
||||
|
||||
fieldset.public_key_view
|
||||
legend = l(:label_current_deploy_keys)
|
||||
= render partial: 'gitolite_public_keys/ssh_keys', locals: { ssh_keys: @gitolite_deploy_keys }
|
||||
|
||||
br
|
||||
|
||||
fieldset.public_key_view
|
||||
legend = l(:label_public_key_new)
|
||||
= render partial: 'gitolite_public_keys/form', locals: { user: @user }
|
||||
|
||||
- content_for :header_tags do
|
||||
= stylesheet_link_tag 'application', plugin: 'redmine_git_hosting'
|
||||
|
||||
javascript:
|
||||
function key_type_change(element) {
|
||||
var idx = element.selectedIndex;
|
||||
if (idx == 0) {
|
||||
$('#key_type_options').hide();
|
||||
$('#gitolite_public_key_delete_when_unused').prop("checked", true);
|
||||
} else {
|
||||
$('#key_type_options').show();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#gitolite_public_key_key_type').on('change', function() {
|
||||
key_type_change(this)
|
||||
});
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
= render partial: 'gitolite_public_keys/view'
|
|
@ -0,0 +1,2 @@
|
|||
- content_for :header_tags do
|
||||
meta name="go-import" content="#{@repository.go_url} git #{@repository.go_access_url}"
|
|
@ -0,0 +1,3 @@
|
|||
- if @repositories.size > 1
|
||||
- content_for :sidebar do
|
||||
= call_hook(:view_repositories_show_sidebar, repository: @repository)
|
|
@ -0,0 +1,64 @@
|
|||
- if User.current.admin?
|
||||
.contextual
|
||||
= link_to l(:label_administration), plugin_settings_path(id: 'redmine_git_hosting'), class: 'icon icon-settings'
|
||||
|
||||
- if User.current.allowed_to? :manage_repository, @project
|
||||
p = link_to l(:label_repository_new),
|
||||
new_project_repository_path(@project),
|
||||
class: 'icon icon-add'
|
||||
|
||||
- if @project.repositories.any?
|
||||
.autoscroll
|
||||
table.list
|
||||
thead
|
||||
tr
|
||||
th = l :field_identifier
|
||||
th = l :field_repository_is_default
|
||||
th = l :label_scm
|
||||
th = l :label_repository
|
||||
th = l :label_repository_default_branch
|
||||
th = l :label_repository_enabled_capabilities
|
||||
th
|
||||
tbody
|
||||
- @project.repositories.sort.each do |repository|
|
||||
tr
|
||||
td = repository.identifier.presence || ''
|
||||
td = checked_image repository.is_default?
|
||||
td = repository.scm_name
|
||||
td = link_to h(repository.url),
|
||||
{ controller: 'repositories',
|
||||
action: 'show',
|
||||
id: @project,
|
||||
repository_id: repository.identifier_param }
|
||||
td
|
||||
- if repository.is_a? Repository::Xitolite
|
||||
span.label.label-info = repository.git_default_branch
|
||||
td align='center'
|
||||
- if repository.is_a? Repository::Xitolite
|
||||
- if repository.git_annex_enabled?
|
||||
= render_feature repository, :git_annex
|
||||
- else
|
||||
= render_feature repository, :deployment_credentials
|
||||
= render_feature repository, :post_receive_urls
|
||||
= render_feature repository, :mirrors
|
||||
= render_feature repository, :git_daemon
|
||||
= render_feature repository, :git_http
|
||||
= render_feature repository, :git_notify
|
||||
= render_feature repository, :protected_branch
|
||||
= render_feature repository, :public_repo
|
||||
|
||||
td.buttons
|
||||
- if User.current.allowed_to? :manage_repository, @project
|
||||
= link_to l(:label_user_plural), committers_repository_path(repository), class: 'icon icon-user'
|
||||
= link_to l(:button_edit), edit_repository_path(repository), class: 'icon icon-edit'
|
||||
- if repository.is_a?(Repository::Xitolite) && repository.movable?
|
||||
= link_to l(:button_move), move_repository_git_extras_path(repository), class: 'icon icon-move'
|
||||
= delete_link repository_path(repository)
|
||||
|
||||
javascript:
|
||||
$(function() {
|
||||
$('.icon-git').tooltip();
|
||||
})
|
||||
|
||||
- else
|
||||
p.nodata = l :label_no_data
|
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