Añade el plugin Redmine Git Hosting 5.0.0

This commit is contained in:
Manuel Cillero 2021-03-20 13:29:16 +01:00
parent cfa0d58b18
commit a3bddad233
458 changed files with 30396 additions and 1 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,4 @@
class RepositoryGitConfigKey::Option < RepositoryGitConfigKey
validates :key, presence: true,
uniqueness: { case_sensitive: false, scope: %i[type repository_id] }
end

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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) %>'

View file

@ -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'

View file

@ -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

View file

@ -0,0 +1,11 @@
class ReportBase
include Redmine::I18n
include ReportHelper
include ReportQuery
attr_reader :repository
def initialize(repository)
@repository = repository
end
end

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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'

View file

@ -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)

View file

@ -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 }

View file

@ -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'

View file

@ -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)) }

View file

@ -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)
});
});

View file

@ -0,0 +1 @@
= render partial: 'gitolite_public_keys/view'

View file

@ -0,0 +1,2 @@
- content_for :header_tags do
meta name="go-import" content="#{@repository.go_url} git #{@repository.go_access_url}"

View file

@ -0,0 +1,3 @@
- if @repositories.size > 1
- content_for :sidebar do
= call_hook(:view_repositories_show_sidebar, repository: @repository)

View file

@ -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