Añade plugin Redmine Git Hosting 4.0.2

This commit is contained in:
Manuel Cillero 2020-12-05 13:57:05 +01:00
parent 472cb1ea76
commit bdd66d941f
494 changed files with 36768 additions and 0 deletions

View file

@ -0,0 +1,16 @@
class ArchivedRepositoriesController < RepositoriesController
skip_before_action :authorize
skip_before_action :find_project_repository, only: :index
before_action :can_view_archived_projects
def index
@archived_projects = Project.where("status = #{Project::STATUS_ARCHIVED}").includes(:repositories)
end
private
def can_view_archived_projects
render_403 unless User.current.admin?
end
end

View file

@ -0,0 +1,31 @@
module XitoliteRepositoryFinder
extend ActiveSupport::Concern
def find_xitolite_repository
begin
@repository = Repository::Xitolite.find(find_repository_param)
rescue ActiveRecord::RecordNotFound => e
render_404
else
@project = @repository.project
render_404 if @project.nil?
end
end
def find_xitolite_repository_by_path
repo_path = params[:repo_path] + '.git'
repository = Repository::Xitolite.find_by_path(repo_path, loose: true)
if repository.nil?
RedmineGitHosting.logger.error("GoRedirector : repository not found at path : '#{repo_path}', exiting !")
render_404
elsif !repository.go_access_available?
RedmineGitHosting.logger.error("GoRedirector : GoAccess is disabled for this repository '#{repository.gitolite_repository_name}', exiting !")
render_403
else
RedmineGitHosting.logger.info("GoRedirector : access granted for repository '#{repository.gitolite_repository_name}'")
@repository = repository
end
end
end

View file

@ -0,0 +1,57 @@
class DownloadGitRevisionController < ApplicationController
include XitoliteRepositoryFinder
before_action :find_xitolite_repository
before_action :can_download_git_revision
before_action :set_download
before_action :validate_download
helper :bootstrap_kit
def index
begin
send_data(@download.content, filename: @download.filename, type: @download.content_type)
rescue => e
flash.now[:error] = l(:git_archive_timeout, timeout: e.output)
render_404
end
end
private
def find_repository_param
params[:id]
end
def can_download_git_revision
render_403 unless User.current.allowed_to_download?(@repository)
end
def set_download
@download = Repositories::DownloadRevision.new(@repository, download_revision, download_format)
end
def download_revision
@download_revision ||= (params[:rev] || 'master')
end
def download_format
@download_format ||= (params[:download_format] || 'tar')
end
def validate_download
if !@download.valid_commit?
flash.now[:error] = l(:error_download_revision_no_such_commit, commit: download_revision)
render_404
end
end
end

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 :bootstrap_kit
def index
@gitolite_user_keys = @user.gitolite_public_keys.user_key.order('title ASC, created_at ASC')
@gitolite_deploy_keys = @user.gitolite_public_keys.deploy_key.order('title ASC, created_at ASC')
end
def create
if params[:create_button]
@gitolite_public_key = @user.gitolite_public_keys.new
@gitolite_public_key.safe_attributes = params[:gitolite_public_key]
if @gitolite_public_key.save
create_ssh_key(@gitolite_public_key)
flash[:notice] = l(:notice_public_key_created, title: view_context.keylabel(@gitolite_public_key))
else
flash[:error] = @gitolite_public_key.errors.full_messages.to_sentence
end
redirect_to @redirect_url
else
redirect_to @cancel_url
end
end
def destroy
return unless request.delete?
if @gitolite_public_key.user == @user || @user.admin?
if @gitolite_public_key.destroy
destroy_ssh_key(@gitolite_public_key)
flash[:notice] = l(:notice_public_key_deleted, title: view_context.keylabel(@gitolite_public_key))
end
redirect_to @redirect_url
else
render_403
end
end
private
def find_user
if params[:user_id]
set_user_from_params
else
set_user_from_current_user
end
end
def set_user_from_params
@user = params[:user_id] == 'current' ? User.current : User.find_by(id: params[:user_id])
if @user
@cancel_url = @redirect_url = url_for(controller: 'users', action: 'edit', id: params[:user_id], tab: 'keys')
else
render_404
end
end
def set_user_from_current_user
if User.current.allowed_to_create_ssh_keys?
@user = User.current
@redirect_url = url_for(controller: 'gitolite_public_keys', action: 'index')
@cancel_url = url_for(controller: 'my', action: 'account')
else
render_403
end
end
def find_gitolite_public_key
@gitolite_public_key = @user.gitolite_public_keys.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def create_ssh_key(ssh_key)
gitolite_accessor.create_ssh_key(ssh_key)
end
def destroy_ssh_key(ssh_key)
gitolite_accessor.destroy_ssh_key(ssh_key)
end
end

View file

@ -0,0 +1,14 @@
class GoRedirectorController < ApplicationController
include XitoliteRepositoryFinder
# prevents login action to be filtered by check_if_login_required application scope filter
skip_before_action :check_if_login_required, :verify_authenticity_token
before_action :find_xitolite_repository_by_path
def index
end
end

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 :bootstrap_kit
def show
respond_to do |format|
format.api
end
end
def edit; end
private
def find_repository_param
params[:repository_id]
end
def check_required_permissions
return render_403 unless @project.module_enabled?(:repository)
return true if User.current.admin?
return render_403 unless User.current.allowed_to_manage_repository?(@repository)
end
def check_xitolite_permissions
case action_name
when 'index', 'show'
perm = "view_#{controller_name}".to_sym
render_403 unless User.current.git_allowed_to?(perm, @repository)
when 'new', 'create'
perm = "create_#{controller_name}".to_sym
render_403 unless User.current.git_allowed_to?(perm, @repository)
when 'edit', 'update', 'destroy'
perm = "edit_#{controller_name}".to_sym
render_403 unless User.current.git_allowed_to?(perm, @repository)
end
end
def render_with_api
respond_to do |format|
format.html { render layout: false }
format.api
end
end
def render_js_redirect
respond_to do |format|
format.js { render js: "window.location = #{success_url.to_json};" }
format.html do
redirect_to success_url
end
end
end
def success_url
url_for(controller: 'repositories', action: 'edit', id: @repository.id, tab: @tab)
end
def call_use_case_and_redirect(opts = {})
# Update Gitolite repository
call_use_case(opts)
render_js_redirect
end
end

View file

@ -0,0 +1,123 @@
class RepositoryDeploymentCredentialsController < RedmineGitHostingController
include RedmineGitHosting::GitoliteAccessor::Methods
before_action :check_xitolite_permissions
before_action :find_deployment_credential, only: %i[edit update destroy]
before_action :find_key, only: %i[edit update destroy]
before_action :find_all_keys, only: %i[index new create]
helper :gitolite_public_keys
def index
@repository_deployment_credentials = @repository.deployment_credentials.all
render layout: false
end
def edit; end
def show
render_404
end
def new
@credential = @repository.deployment_credentials.new
end
def create
@credential = build_new_credential
return unless @credential.save
flash[:notice] = l(:notice_deployment_credential_created)
call_use_case_and_redirect
end
def update
@credential.safe_attributes = params[:repository_deployment_credential]
return unless @credential.save
flash[:notice] = l(:notice_deployment_credential_updated)
call_use_case_and_redirect
end
def destroy
will_delete_key = @key.deploy_key? && @key.delete_when_unused && @key.repository_deployment_credentials.count == 1
@credential.destroy
if will_delete_key && @key.repository_deployment_credentials.empty?
# Key no longer used -- delete it!
@key.destroy
flash[:notice] = l(:notice_deployment_credential_deleted_with_key)
else
flash[:notice] = l(:notice_deployment_credential_deleted)
end
call_use_case_and_redirect
end
private
def set_current_tab
@tab = 'repository_deployment_credentials'
end
def find_deployment_credential
credential = @repository.deployment_credentials.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
else
if credential.user && (User.current.admin? || credential.user == User.current)
@credential = credential
else
render_403
end
end
def find_key
key = @credential.gitolite_public_key
if key&.user && (User.current.admin? || key.user == User.current)
@key = key
elsif key
render_403
else
render_404
end
end
def find_all_keys
# display create_with_key view. Find preexisting keys to offer to user
@user_keys = User.current.gitolite_public_keys.deploy_key.order('title ASC')
@disabled_keys = @repository.deployment_credentials.map(&:gitolite_public_key)
@other_keys = []
# Admin can use other's deploy keys as well
@other_keys = other_deployment_keys if User.current.admin?
end
def other_deployment_keys
users_allowed_to_create_deployment_keys.map { |user| user.gitolite_public_keys.deploy_key.order('title ASC') }.flatten
end
def users_allowed_to_create_deployment_keys
@project.users.select { |user| user != User.current && user.git_allowed_to?(:create_repository_deployment_credentials, @repository) }
end
def call_use_case(opts = {})
options = opts.merge(message: "Update deploy keys for repository : '#{@repository.gitolite_repository_name}'")
gitolite_accessor.update_repository(@repository, options)
end
def build_new_credential
credential = @repository.deployment_credentials.new
credential.safe_attributes = params[:repository_deployment_credential]
key = GitolitePublicKey.find_by(id: params[:repository_deployment_credential][:gitolite_public_key_id])
credential.gitolite_public_key = key unless key.nil?
# If admin, let credential be owned by owner of key...
if User.current.admin?
credential.user = key.user unless key.nil?
else
credential.user = User.current
end
credential
end
end

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 unless @git_config_key.save
flash[:notice] = l(:notice_git_config_key_created)
call_use_case_and_redirect
end
def update
@git_config_key.safe_attributes = params[:repository_git_config_key]
return unless @git_config_key.save
flash[:notice] = l(:notice_git_config_key_updated)
options = @git_config_key.key_has_changed? ? { delete_git_config_key: @git_config_key.old_key } : {}
call_use_case_and_redirect(options)
end
def destroy
return unless @git_config_key.destroy
flash[:notice] = l(:notice_git_config_key_deleted)
options = { delete_git_config_key: @git_config_key.key }
call_use_case_and_redirect(options)
end
private
def key_type
type = params[:type] || params[:repository_git_config_key][:type]
case type
when 'RepositoryGitConfigKey::GitConfig', 'git_config'
:git_config_keys
when 'RepositoryGitConfigKey::Option', 'git_option'
:git_option_keys
else
:git_keys
end
end
def set_current_tab
@tab = 'repository_git_config_keys'
end
def find_repository_git_config_key
@git_config_key = @repository.git_keys.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def call_use_case(opts = {})
options = opts.merge(message: "Rebuild Git config keys respository : '#{@repository.gitolite_repository_name}'")
gitolite_accessor.update_repository(@repository, options)
end
end

View file

@ -0,0 +1,47 @@
class RepositoryGitExtrasController < RedmineGitHostingController
include RedmineGitHosting::GitoliteAccessor::Methods
skip_before_action :set_current_tab
helper :extend_repositories
def update
@git_extra = @repository.extra
@git_extra.safe_attributes = params[:repository_git_extra]
if @git_extra.save
flash.now[:notice] = l(:notice_gitolite_extra_updated)
gitolite_accessor.update_repository(@repository, update_default_branch: @git_extra.default_branch_has_changed?)
else
flash.now[:error] = l(:notice_gitolite_extra_update_failed)
end
end
def sort_urls
@git_extra = @repository.extra
return unless request.post?
if @git_extra.update(urls_order: params[:repository_git_extra])
flash.now[:notice] = l(:notice_gitolite_extra_updated)
else
flash.now[:error] = l(:notice_gitolite_extra_update_failed)
end
end
def move
@move_repository_form = MoveRepositoryForm.new(@repository)
return unless request.post?
@move_repository_form = MoveRepositoryForm.new(@repository)
return unless @move_repository_form.submit(params[:repository_mover])
redirect_to settings_project_path(@repository.project, tab: 'repositories')
end
private
def set_git_extra
@git_extra = @repository.extra
end
end

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.all
render_with_api
end
def new
@mirror = @repository.mirrors.new
end
def create
@mirror = @repository.mirrors.new
@mirror.safe_attributes = params[:repository_mirror]
return unless @mirror.save
flash[:notice] = l(:notice_mirror_created)
render_js_redirect
end
def update
@mirror.safe_attributes = params[:repository_mirror]
return unless @mirror.save
flash[:notice] = l(:notice_mirror_updated)
render_js_redirect
end
def destroy
return unless @mirror.destroy
flash[:notice] = l(:notice_mirror_deleted)
render_js_redirect
end
def push
@push_failed, @shellout = RepositoryMirrors::Push.call(@mirror)
render layout: false
end
private
def set_current_tab
@tab = 'repository_mirrors'
end
def find_repository_mirror
@mirror = @repository.mirrors.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def check_xitolite_permissions
if action_name == 'push'
render_403 unless User.current.git_allowed_to?(:push_repository_mirrors, @repository)
else
super
end
end
end

View file

@ -0,0 +1,51 @@
class RepositoryPostReceiveUrlsController < RedmineGitHostingController
before_action :check_xitolite_permissions
before_action :find_repository_post_receive_url, except: %i[index new create]
accept_api_auth :index, :show
def index
@repository_post_receive_urls = @repository.post_receive_urls.all
render_with_api
end
def new
@post_receive_url = @repository.post_receive_urls.new
end
def create
@post_receive_url = @repository.post_receive_urls.new
@post_receive_url.safe_attributes = params[:repository_post_receive_url]
return unless @post_receive_url.save
flash[:notice] = l(:notice_post_receive_url_created)
render_js_redirect
end
def update
@post_receive_url.safe_attributes = params[:repository_post_receive_url]
return unless @post_receive_url.save
flash[:notice] = l(:notice_post_receive_url_updated)
render_js_redirect
end
def destroy
return unless @post_receive_url.destroy
flash[:notice] = l(:notice_post_receive_url_deleted)
render_js_redirect
end
private
def set_current_tab
@tab = 'repository_post_receive_urls'
end
def find_repository_post_receive_url
@post_receive_url = @repository.post_receive_urls.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end

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.all
render_with_api
end
def new
@protected_branch = @repository.protected_branches.new
end
def create
@protected_branch = @repository.protected_branches.new
@protected_branch.safe_attributes = params[:repository_protected_branche]
return unless @protected_branch.save
check_members
flash[:notice] = l(:notice_protected_branch_created)
call_use_case_and_redirect
end
def update
@protected_branch.safe_attributes = params[:repository_protected_branche]
return unless @protected_branch.save
check_members
flash[:notice] = l(:notice_protected_branch_updated)
call_use_case_and_redirect
end
def destroy
return unless @protected_branch.destroy
flash[:notice] = l(:notice_protected_branch_deleted)
call_use_case_and_redirect
end
def clone
@protected_branch = RepositoryProtectedBranche.clone_from(params[:id])
render 'new'
end
def sort
params[:protected_branch].each_with_index do |id, index|
@repository.protected_branches.where(id: id).update_all(position: index + 1)
end
# Update Gitolite repository
call_use_case
head :ok
end
private
def set_current_tab
@tab = 'repository_protected_branches'
end
def find_repository_protected_branch
@protected_branch = @repository.protected_branches.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def call_use_case(opts = {})
options = opts.merge(message: "Update branch permissions for repository : '#{@repository.gitolite_repository_name}'")
gitolite_accessor.update_repository(@repository, options)
end
def check_members
member_manager = RepositoryProtectedBranches::MemberManager.new(@protected_branch)
member_manager.add_users(params[:user_ids])
member_manager.add_groups(params[:group_ids])
end
end

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,39 @@
module PluginSettingsValidation
module GitoliteAccessConfig
extend ActiveSupport::Concern
included do
# Gitolite Access Config
add_accessor :ssh_server_domain,
:http_server_domain,
:https_server_domain,
:http_server_subdir,
:show_repositories_url,
:gitolite_daemon_by_default,
:gitolite_http_by_default
before_validation do
self.ssh_server_domain = strip_value(ssh_server_domain)
self.http_server_domain = strip_value(http_server_domain)
self.https_server_domain = strip_value(https_server_domain)
self.http_server_subdir = strip_value(http_server_subdir)
end
validates :ssh_server_domain, presence: true, format: { with: RedmineGitHosting::Validators::DOMAIN_REGEX }
validates :http_server_domain, presence: true, format: { with: RedmineGitHosting::Validators::DOMAIN_REGEX }
validates :https_server_domain, format: { with: RedmineGitHosting::Validators::DOMAIN_REGEX }, allow_blank: true
validates :show_repositories_url, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
validates :gitolite_daemon_by_default, presence: true, inclusion: { in: RedmineGitHosting::Validators::BOOLEAN_FIELDS }
validates :gitolite_http_by_default, presence: true, inclusion: { in: %w[0 1 2 3] }, numericality: { only_integer: true }
validate :http_server_subdir_is_relative
end
private
def http_server_subdir_is_relative
errors.add(:http_server_subdir, 'must be relative') if http_server_subdir.starts_with?('/')
end
end
end

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::Logger::LOG_LEVELS }
validates :git_config_username, presence: true
validates :git_config_email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validate :gitolite_config_file_is_relative
validate :tmp_dir_is_absolute
after_validation do
self.gitolite_recycle_bin_expiration_time = convert_time(gitolite_recycle_bin_expiration_time)
if gitolite_config_file == RedmineGitHosting::Config::GITOLITE_DEFAULT_CONFIG_FILE
self.gitolite_identifier_strip_user_id = 'false'
self.gitolite_identifier_prefix = RedmineGitHosting::Config::GITOLITE_IDENTIFIER_DEFAULT_PREFIX
end
end
end
private
def gitolite_config_file_is_relative
errors.add(:gitolite_config_file, 'must be relative') if gitolite_config_file.starts_with?('/')
end
def tmp_dir_is_absolute
errors.add(:gitolite_temp_dir, 'must be absolute') unless gitolite_temp_dir.starts_with?('/')
end
end
end

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::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, presence: true
validates :gitolite_server_host, presence: true
validates :gitolite_server_port, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 65_536 }
validates :gitolite_ssh_private_key, presence: true
validates :gitolite_ssh_public_key, presence: true
validates_each :gitolite_ssh_private_key, :gitolite_ssh_public_key do |record, attr, value|
record.errors.add(attr, 'must exists on filesystem') unless File.exists?(value)
end
end
end
end

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,14 @@
module ArchivedRepositoriesHelper
def link_to_revision2(revision, repository, options = {})
repository = repository.repository if repository.is_a?(Project)
text = options.delete(:text) { format_revision(revision) }
rev = revision.respond_to?(:identifier) ? revision.identifier : revision
link_to(
h(text),
{ controller: 'archived_repositories', action: 'revision', id: repository.project, repository_id: repository.identifier_param, rev: rev },
title: l(:label_revision_id, format_revision(revision))
)
end
end

View file

@ -0,0 +1,33 @@
module BootstrapKit::AjaxHelper
def render_flash_messages_as_js(target = '#flash-messages', opts = {})
js_render(target, render_flash_messages, opts).html_safe
end
def js_render_template(target, template, opts = {})
locals = opts.delete(:locals) { {} }
content = render(template: template, locals: locals)
js_render(target, content, opts)
end
def js_render_partial(target, partial, opts = {})
locals = opts.delete(:locals) { {} }
content = render(partial: partial, locals: locals)
js_render(target, content, opts)
end
def js_render(target, content, opts = {})
method = opts.delete(:method) { :inject }
"$('#{target}').#{js_rendering_method(method)}(\"#{escape_javascript(content)}\");\n".html_safe
end
def js_rendering_method(method)
case method
when :append
'append'
when :inject
'html'
when :replace
'replaceWith'
end
end
end

View file

@ -0,0 +1,8 @@
module BootstrapKit::PresenterHelper
def present(object, klass = nil, *args)
klass ||= "#{object.class.base_class}Presenter".constantize
presenter = klass.new(object, self, *args)
yield presenter if block_given?
presenter
end
end

View file

@ -0,0 +1,59 @@
module BootstrapKitHelper
include BootstrapKit::AjaxHelper
include BootstrapKit::PresenterHelper
def bootstrap_load_base
stylesheet_link_tag('bootstrap_custom', plugin: 'redmine_git_hosting') +
bs_include_css('bootstrap_custom')
end
def bootstrap_load_module(bs_module)
method = "load_bs_module_#{bs_module}"
send(method)
end
def checked_image_with_exclamation(checked = true)
checked ? image_tag('toggle_check.png') : image_tag('exclamation.png')
end
private
def bs_include_js(js)
javascript_include_tag "bootstrap/#{js}", plugin: 'redmine_git_hosting'
end
def bs_include_css(css)
stylesheet_link_tag "bootstrap/#{css}", plugin: 'redmine_git_hosting'
end
def load_bs_module_alerts
bs_include_js('bootstrap_alert') +
bs_include_js('bootstrap_alert_helper') +
bs_include_js('bootstrap_transitions') +
bs_include_css('bootstrap_alert') +
bs_include_css('bootstrap_animations') +
bs_include_css('bootstrap_close')
end
def load_bs_module_label
bs_include_css('bootstrap_label')
end
def load_bs_module_modals
bs_include_js('bootstrap_modal')
end
def load_bs_module_sortable
bs_include_js('bootstrap_sortable_helper')
end
def load_bs_module_tables
bs_include_css('bootstrap_tables')
end
def load_bs_module_tooltip
bs_include_js('bootstrap_tooltip') +
bs_include_js('bootstrap_tooltip_helper') +
bs_include_css('bootstrap_tooltip')
end
end

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
content_tag(:i, '', title: base_label.join(' '), class: base_class)
end
def deployment_credentials_feature(repository)
label = l(:label_deployment_credentials)
css_class = 'fas fa-lock'
enabled = repository.deployment_credentials.active.any?
[label, css_class, enabled]
end
def post_receive_urls_feature(repository)
label = l(:label_post_receive_urls)
css_class = 'fas fa-external-link-alt'
enabled = repository.post_receive_urls.active.any?
[label, css_class, enabled]
end
def mirrors_feature(repository)
label = l(:label_repository_mirrors)
css_class = 'fas fa-cloud-upload-alt'
enabled = repository.mirrors.active.any?
[label, css_class, enabled]
end
def git_daemon_feature(repository)
label = l(:label_git_daemon)
css_class = 'fab fa-git'
enabled = repository.git_access_available?
[label, css_class, enabled]
end
def git_http_feature(repository)
label = l(:label_smart_http)
css_class = 'fas fa-cloud-download-alt'
enabled = repository.smart_http_enabled?
[label, css_class, enabled]
end
def git_notify_feature(repository)
label = l(:label_git_notify)
css_class = 'fas fa-bullhorn'
enabled = repository.git_notification_enabled?
[label, css_class, enabled]
end
def protected_branch_feature(repository)
label = l(:label_protected_branch)
css_class = 'fas fa-shield-alt'
enabled = repository.protected_branches_available?
[label, css_class, enabled]
end
def git_annex_feature(repository)
label = l(:label_git_annex)
css_class = 'fas fa-cloud-upload-alt'
enabled = repository.git_annex_enabled?
[label, css_class, enabled]
end
def public_repo_feature(repository)
label = l(:label_public_repo)
css_class = 'fas fa-users'
enabled = repository.public_repo?
[label, css_class, enabled]
end
end

View file

@ -0,0 +1,67 @@
module ExtendRepositoriesHelper
def encoding_field(form, repository)
content_tag(:p) do
form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
label: l(:field_scm_path_encoding)
) + '<br />'.html_safe + l(:text_scm_path_encoding_note)
end
end
def available_download_format(repository, rev = nil)
%w[zip tar tar.gz].map { |f| [f, download_git_revision_repository_path(repository, rev: rev, download_format: f)] }
end
def create_readme_field(form, repository)
return unless repository.new_record?
content_tag(:p) do
hidden_field_tag('repository[create_readme]', 'false', id: '') +
content_tag(:label, l(:label_init_repo_with_readme), for: 'repository_create_readme') +
check_box_tag('repository[create_readme]', 'true', RedmineGitHosting::Config.init_repositories_on_create?)
end
end
def enable_git_annex_field(form, repository)
return unless repository.new_record?
content_tag(:p) do
hidden_field_tag('repository[enable_git_annex]', 'false', id: '') +
content_tag(:label, l(:label_init_repo_with_git_annex), for: 'repository_enable_git_annex') +
check_box_tag('repository[enable_git_annex]', 'true')
end
end
def repository_branches_list(branches)
options_for_select branches.collect { |b| [b.to_s, b.to_s] }, selected: branches.find(&:is_default).to_s
end
def render_repository_quick_jump(repository)
options = repository.project.repositories.map { |r| [r.redmine_name, edit_repository_path(r)] }
select_tag('repository_quick_jump_box',
options_for_select(options, selected: edit_repository_path(repository)),
onchange: 'if (this.value != \'\') { window.location = this.value; }')
end
def link_to_repository(repo, current_repo)
css_class = ['repository', (repo == current_repo ? 'selected' : ''), current_repo.type.split('::')[1].downcase].join(' ')
link_to h(repo.name),
{ controller: 'repositories', action: 'show', id: @project, repository_id: repo.identifier_param, rev: nil, path: nil },
class: css_class
end
def icon_for_url_type(url_type)
font_awesome_icon(RepositoryGitExtra::URLS_ICONS[url_type][:icon])
end
def label_for_url_type(url_type)
RepositoryGitExtra::URLS_ICONS[url_type][:label]
end
def render_options_for_move_repo_select_box(project)
projects = Project.active
.where(Project.allowed_to_condition(User.current, :manage_repository))
.where.not(id: project.id)
project_tree_options_for_select(projects, selected: project) if projects.any?
end
end

View file

@ -0,0 +1,6 @@
module GitHostingUsersHelper
def user_settings_tabs
tabs = super
tabs << { name: 'keys', partial: 'gitolite_public_keys/view', label: :label_public_keys }
end
end

View file

@ -0,0 +1,102 @@
module GitolitePluginSettingsHelper
def render_gitolite_params_status(params)
content_tag(:ul, class: 'list-unstyled') do
content = ''
params.each do |param, installed|
content << content_tag(:li, style: 'padding: 2px;') do
image_tag(image_for_param(installed), style: 'vertical-align: bottom; padding-right: 5px;') +
content_tag(:em, label_for_param(param, installed))
end
end
content.html_safe
end
end
def label_for_param(param, install_status)
install_status == 2 ? "#{param} (#{l(:label_gitolite_hook_untouched)})" : param
end
def image_for_param(install_status)
case install_status
when 0, true
'true.png'
when 1, false
'exclamation.png'
else
'warning.png'
end
end
def render_gitolite_version(version)
if version.nil?
css_class = 'label label-important'
label = l(:label_unknown_gitolite_version)
else
css_class = 'label label-success'
label = version
end
content_tag(:span, label, class: css_class)
end
def render_temp_dir_writeable(state, label)
css_class = state ? 'label label-success' : 'label label-important'
content_tag(:span, label, class: css_class)
end
def gitolite_plugin_settings_tabs
[
{ name: 'gitolite_config_ssh', partial: 'settings/redmine_git_hosting/gitolite_config_ssh', label: :label_tab_ssh },
{ name: 'gitolite_config_storage', partial: 'settings/redmine_git_hosting/gitolite_config_storage', label: :label_tab_storage },
{ name: 'gitolite_config_file', partial: 'settings/redmine_git_hosting/gitolite_config_file', label: :label_tab_config_file },
{ name: 'gitolite_config_global', partial: 'settings/redmine_git_hosting/gitolite_config_global', label: :label_tab_global },
{ name: 'gitolite_config_access', partial: 'settings/redmine_git_hosting/gitolite_config_access', label: :label_tab_access },
{ name: 'gitolite_config_hooks', partial: 'settings/redmine_git_hosting/gitolite_config_hooks', label: :label_tab_hooks },
{ name: 'gitolite_config_cache', partial: 'settings/redmine_git_hosting/gitolite_config_cache', label: :label_tab_cache },
{ name: 'gitolite_config_notify', partial: 'settings/redmine_git_hosting/gitolite_config_notify', label: :label_tab_notify },
{ name: 'gitolite_redmine_config', partial: 'settings/redmine_git_hosting/redmine_config', label: :label_tab_redmine },
{ name: 'gitolite_sidekiq_interface', partial: 'settings/redmine_git_hosting/sidekiq_interface', label: :label_tab_sidekiq_interface },
{ name: 'gitolite_config_test', partial: 'settings/redmine_git_hosting/gitolite_config_test', label: :label_tab_config_test },
{ name: 'gitolite_recycle_bin', partial: 'settings/redmine_git_hosting/gitolite_recycle_bin', label: :label_tab_gitolite_recycle_bin },
{ name: 'gitolite_rescue', partial: 'settings/redmine_git_hosting/gitolite_rescue', label: :label_tab_gitolite_rescue }
]
end
def git_cache_options
[
['Cache Disabled', '0'],
['Until next commit', '-1'],
['1 Minute or until next commit', '60'],
['15 Minutes or until next commit', '900'],
['1 Hour or until next commit', '3600'],
['1 Day or until next commit', '86400']
]
end
def log_level_options
RedmineGitHosting::Logger::LOG_LEVELS.map { |level| [l("label_#{level}"), level] }
end
def render_rugged_mandatory_features
content = ''
RedmineGitHosting::Config.rugged_mandatory_features.each do |feature|
opts = if RedmineGitHosting::Config.rugged_features.include?(feature)
{ class: 'label label-success' }
else
{ class: 'label label-important' }
end
content << content_tag(:span, feature, opts) + "\n"
end
content.html_safe
end
def render_rugged_optional_features
content = ''
RedmineGitHosting::Config.rugged_features.each do |feature|
unless RedmineGitHosting::Config.rugged_mandatory_features.include?(feature)
opts = { class: 'label label-success' }
content << content_tag(:span, feature, opts)
end
end
content.html_safe
end
end

View file

@ -0,0 +1,16 @@
module GitolitePublicKeysHelper
def keylabel(key)
key.user == User.current ? "#{key.title}" : "#{key.user.login}@#{key.title}"
end
def can_create_deployment_keys_for_some_project(theuser = User.current)
return true if theuser.admin?
theuser.projects_by_role.each_key do |role|
return true if role.allowed_to?(:create_repository_deployment_credentials)
end
return false
end
end

View file

@ -0,0 +1,19 @@
module RepositoryDeploymentCredentialsHelper
def build_list_of_keys(user_keys, other_keys, disabled_keys)
option_array = [[l(:label_deployment_credential_select_deploy_key), -1]]
option_array += user_keys.map { |key| [keylabel(key), key.id] }
if !other_keys.empty?
option_array2 = other_keys.map { |key| [keylabel(key), key.id] }
maxlen = (option_array + option_array2).map { |x| x.first.length }.max
extra = ([maxlen - l(:select_other_keys).length - 2, 6].max) / 2
option_array += [[('-' * extra) + ' ' + l(:select_other_keys) + ' ' + ('-' * extra), -2]]
option_array += option_array2
end
options_for_select(option_array, selected: -1, disabled: [-2] + disabled_keys.map(&:id))
end
end

View file

@ -0,0 +1,10 @@
module RepositoryGitConfigKeysHelper
def git_config_key_options
[
[l(:label_git_key_type_config), 'RepositoryGitConfigKey::GitConfig'],
[l(:label_git_key_type_option), 'RepositoryGitConfigKey::Option']
]
end
end

View file

@ -0,0 +1,45 @@
module RepositoryMirrorsHelper
# Mirror Mode
def mirror_mode(mirror)
[l(:label_mirror_full_mirror), l(:label_mirror_forced_update), l(:label_mirror_fast_forward)][mirror.push_mode]
end
# Refspec for mirrors
def refspec(mirror, max_refspec = 0)
if mirror.mirror_mode?
l(:all_references)
else
result = []
result << l(:all_branches) if mirror.include_all_branches
result << l(:all_tags) if mirror.include_all_tags
result << mirror.explicit_refspec if (max_refspec == 0) || ((1..max_refspec) === mirror.explicit_refspec.length)
result << l(:explicit) if (max_refspec > 0) && (mirror.explicit_refspec.length > max_refspec)
result.join(',<br />')
end
end
def mirrors_options
[
[l(:label_mirror_full_mirror), 0],
[l(:label_mirror_forced_update), 1],
[l(:label_mirror_fast_forward), 2]
]
end
def render_push_state(mirror, error)
if error
status = l(:label_mirror_push_fail)
status_css = 'important'
else
status = l(:label_mirror_push_sucess)
status_css = 'success'
end
l(:label_mirror_push_info_html, mirror_url: mirror.url, status: status, status_css: status_css).html_safe
end
end

View file

@ -0,0 +1,15 @@
module RepositoryPostReceiveUrlsHelper
# Post-receive Mode
def post_receive_mode(prurl)
label = []
if prurl.github_mode?
label << l(:label_github_post)
label << "(#{l(:label_split_payloads)})" if prurl.split_payloads?
else
label << l(:label_empty_get)
end
label.join(' ')
end
end

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,89 @@
module Gitolitable
module Authorizations
extend ActiveSupport::Concern
# These are for repository Gitolite configuration
def git_daemon_available?
User.anonymous.allowed_to?(:view_changesets, project) && git_daemon_enabled?
end
def git_web_available?
User.anonymous.allowed_to?(:browse_repository, project) && smart_http_enabled?
end
def protected_branches_available?
protected_branches_enabled? && project.active? && protected_branches.any?
end
def clonable_via_http?
User.anonymous.allowed_to?(:view_changesets, project) || smart_http_enabled?
end
def pushable_via_http?
https_access_enabled?
end
def git_notification_available?
git_notification_enabled? && !mailing_list.empty?
end
# These are for repository URLs
def urls_are_viewable?
RedmineGitHosting::Config.show_repositories_url? && User.current.allowed_to?(:view_changesets, project)
end
def ssh_access_available?
git_ssh_enabled? && !git_annex_enabled? && User.current.allowed_to_commit?(self)
end
def https_access_available?
https_access_enabled?
end
def http_access_available?
http_access_enabled?
end
def git_access_available?
(public_project? || public_repo?) && git_daemon_enabled?
end
def go_access_available?
(public_project? || public_repo?) && smart_http_enabled? && git_go_enabled?
end
def git_annex_access_available?
git_annex_enabled?
end
def downloadable?
git_annex_enabled? ? false : User.current.allowed_to_download?(self)
end
def deletable?
RedmineGitHosting::Config.delete_git_repositories?
end
def movable?
!identifier.nil? && !identifier.empty?
end
end
end

View file

@ -0,0 +1,90 @@
module Gitolitable
module Cache
extend ActiveSupport::Concern
included do
class << self
# Are repositories identifier unique?
#
def repo_ident_unique?
RedmineGitHosting::Config.unique_repo_identifier?
end
# Translate repository path into a unique ID for use in caching of git commands.
#
def repo_path_to_git_cache_id(repo_path)
repo = find_by_path(repo_path, loose: true)
repo ? repo.git_cache_id : nil
end
# Parse a path of the form <proj1>/<proj2>/<proj3>/<repo> and return the specified
# repository. If either 'repo_ident_unique?' is true or the <repo> is a project
# identifier, just return the last component. Otherwise,
# use the immediate parent (<proj3>) to try to identify the repo.
#
# Flags:
# :loose => true : Try to identify corresponding repo even if path is not quite correct
#
# Note that the :loose flag is used when interpreting the contents of the
# repository. If switching back and forth between the "repo_ident_unique?"
# form, it will still identify the repository (as long as there are not more than
# one repo with the same identifier.
#
# Example of data captured by regex :
# <MatchData "test/test2/test3/test4/test5.git" 1:"test4/" 2:"test4" 3:"test5" 4:".git">
# <MatchData "blabla2.git" 1:nil 2:nil 3:"blabla2" 4:".git">
#
def find_by_path(path, flags = {})
parseit = path.match(/\A.*?(([^\/]+)\/)?([^\/]+?)(\.git)?\z/)
return nil if parseit.nil?
project = Project.find_by_identifier(parseit[3])
# return default or first repo with blank identifier (or first Git repo--very rare?)
if project
project.repository || project.repo_blank_ident || project.gitolite_repos.first
elsif repo_ident_unique? || flags[:loose] && parseit[2].nil?
find_by_identifier(parseit[3])
elsif parseit[2]
project = Project.find_by_identifier(parseit[2])
if project.nil?
find_by_identifier(parseit[3])
else
find_by_identifier_and_project_id(parseit[3], project.id) || (flags[:loose] && find_by_identifier(parseit[3]))
end
end
end
end
end
# If repositories identifiers are unique, identifier forms a unique label,
# else use directory notation: <project identifier>/<repo identifier>
#
def git_cache_id
if identifier.blank?
# Should only happen with one repo/project (the default)
project.identifier
elsif self.class.repo_ident_unique?
identifier
else
"#{project.identifier}/#{identifier}"
end
end
# Note: RedmineGitHosting::Cache doesn't know about repository object, it only knows *git_cache_id*.
#
def empty_cache!
RedmineGitHosting::Cache.clear_cache_for_repository(git_cache_id)
end
end
end

View file

@ -0,0 +1,85 @@
module Gitolitable
module Config
extend ActiveSupport::Concern
def git_config
repo_conf = {}
# This is needed for all Redmine repositories
repo_conf['redminegitolite.projectid'] = project.identifier.to_s
repo_conf['redminegitolite.repositoryid'] = identifier || ''
repo_conf['redminegitolite.repositorykey'] = gitolite_hook_key
if project.active?
repo_conf['http.uploadpack'] = clonable_via_http?.to_s
repo_conf['http.receivepack'] = pushable_via_http?.to_s
if git_notification_available?
repo_conf['multimailhook.enabled'] = 'true'
repo_conf['multimailhook.mailinglist'] = mailing_list.join(', ')
repo_conf['multimailhook.from'] = sender_address
repo_conf['multimailhook.emailPrefix'] = email_prefix
else
repo_conf['multimailhook.enabled'] = 'false'
end
git_config_keys.each do |git|
repo_conf[git.key] = git.value
end if git_config_keys.any?
else
# Disable repository
repo_conf['http.uploadpack'] = 'false'
repo_conf['http.receivepack'] = 'false'
repo_conf['multimailhook.enabled'] = 'false'
end
repo_conf
end
def gitolite_options
repo_conf = {}
git_option_keys.each do |option|
repo_conf[option.key] = option.value
end if git_option_keys.any?
repo_conf
end
def owner
{ name: Setting['app_title'], email: Setting['mail_from'] }
end
def github_payload
{
repository: {
owner: owner,
description: project.description,
fork: false,
forks: 0,
homepage: project.homepage,
name: redmine_name,
open_issues: project.issues.open.length,
watchers: 0,
private: !project.is_public,
url: repository_url
},
pusher: owner,
}
end
def repository_url
Rails.application.routes.url_helpers.url_for(
controller: 'repositories', action: 'show',
id: project, repository_id: identifier_param,
only_path: false, host: Setting['host_name'], protocol: Setting['protocol']
)
end
end
end

View file

@ -0,0 +1,97 @@
module Gitolitable
module Features
extend ActiveSupport::Concern
# Always true to force repository fetch_changesets.
def report_last_commit
true
end
# Always true to force repository fetch_changesets.
def extra_report_last_commit
true
end
def git_default_branch
extra[:default_branch]
end
def gitolite_hook_key
extra[:key]
end
def git_daemon_enabled?
extra[:git_daemon]
end
def git_annex_enabled?
extra[:git_annex]
end
def git_notification_enabled?
extra[:git_notify]
end
def git_ssh_enabled?
extra[:git_ssh]
end
def git_go_enabled?
extra[:git_go]
end
def https_access_enabled?
extra[:git_https]
end
def http_access_enabled?
extra[:git_http]
end
def smart_http_enabled?
https_access_enabled? || http_access_enabled?
end
def only_https_access_enabled?
https_access_enabled? && !http_access_enabled?
end
def only_http_access_enabled?
http_access_enabled? && !https_access_enabled?
end
def protected_branches_enabled?
extra[:protected_branch]
end
def public_project?
project.is_public?
end
def public_repo?
extra[:public_repo]
end
def urls_order
extra[:urls_order]
end
end
end

View file

@ -0,0 +1,43 @@
module Gitolitable
module Notifications
extend ActiveSupport::Concern
def mailing_list
default_list + global_include_list - global_exclude_list
end
def default_list
watcher_users.map(&:email_address).map(&:address)
end
def global_include_list
RedmineGitHosting::Config.gitolite_notify_global_include
end
def global_exclude_list
RedmineGitHosting::Config.gitolite_notify_global_exclude
end
def sender_address
if extra.notification_sender.nil? || extra.notification_sender.empty?
RedmineGitHosting::Config.gitolite_notify_global_sender_address
else
extra.notification_sender
end
end
def email_prefix
if extra.notification_prefix.nil? || extra.notification_prefix.empty?
RedmineGitHosting::Config.gitolite_notify_global_prefix
else
extra.notification_prefix
end
end
end
end

View file

@ -0,0 +1,94 @@
module Gitolitable
module Paths
extend ActiveSupport::Concern
# This is the repository path from Redmine point of view.
# It is used to build HTTP(s) urls (including GoLang url).
# It doesn't contain references to internal directories like *gitolite_global_storage_dir* or *gitolite_redmine_storage_dir*
# to stay abstract from the real repository location.
# In this case, the real repository path is deduced from the path given thanks to the *find_by_path* method.
#
# Example : blabla/test-blabla/uuuuuuuuuuu/oooooo
#
# Call File.expand_path to add then remove heading /
#
def redmine_repository_path
File.expand_path(File.join('./', get_full_parent_path, git_cache_id), '/')[1..-1]
end
# This is the Gitolite repository identifier as it should appear in Gitolite config file.
# Example : redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo
# (with 'redmine' a subdir of the Gitolite storage directory)
#
# Call File.expand_path to add then remove heading /
#
def gitolite_repository_name
File.expand_path(File.join('./', RedmineGitHosting::Config.gitolite_redmine_storage_dir, get_full_parent_path, git_cache_id), '/')[1..-1]
end
# The Gitolite repository identifier with the .git extension.
# Example : redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo.git
#
def gitolite_repository_name_with_extension
"#{gitolite_repository_name}.git"
end
# This is the relative path to the Gitolite repository.
# Example : repositories/redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo.git
# (with 'repositories' the Gitolite storage directory).
#
def gitolite_repository_path
File.join(RedmineGitHosting::Config.gitolite_global_storage_dir, gitolite_repository_name_with_extension)
end
# This is the full absolute path to the Gitolite repository.
# Example : /home/git/repositories/redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo.git
#
def gitolite_full_repository_path
File.join(RedmineGitHosting::Config.gitolite_home_dir, gitolite_repository_path)
end
# A syntaxic sugar used to move repository from a location to an other
# Example : repositories/blabla/test-blabla/uuuuuuuuuuu/oooooo
#
def new_repository_name
gitolite_repository_name
end
# Used to move repository from a location to an other.
# At this point repository url still points to the old location but
# it contains the Gitolite storage directory in its path and the '.git' extension.
# Strip them to get the old repository name.
# Example :
# before : repositories/redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo.git
# after : redmine/blabla/test-blabla/uuuuuuuuuuu/oooooo
#
def old_repository_name
url.gsub(RedmineGitHosting::Config.gitolite_global_storage_dir, '').gsub('.git', '')
end
private
def get_full_parent_path
return '' if !RedmineGitHosting::Config.hierarchical_organisation?
parent_parts = []
p = project
while p.parent
parent_id = p.parent.identifier.to_s
parent_parts.unshift(parent_id)
p = p.parent
end
parent_parts.join('/')
end
end
end

View file

@ -0,0 +1,66 @@
module Gitolitable
module Permissions
extend ActiveSupport::Concern
def build_gitolite_permissions(old_perms = {})
permissions_builder.build(self, gitolite_users, old_perms)
end
# We assume here that ':gitolite_config_file' is different than 'gitolite.conf'
# like 'redmine.conf' with 'include "redmine.conf"' in 'gitolite.conf'.
# This way, we know that all repos in this file are managed by Redmine so we
# don't need to backup users
#
def backup_gitolite_permissions(current_permissions)
if protected_branches_available? || RedmineGitHosting::Config.gitolite_identifier_prefix == ''
{}
else
extract_permissions(current_permissions)
end
end
private
def permissions_builder
if protected_branches_available?
PermissionsBuilder::ProtectedBranches
else
PermissionsBuilder::Standard
end
end
SKIP_USERS = ['gitweb', 'daemon', 'DUMMY_REDMINE_KEY', 'REDMINE_ARCHIVED_PROJECT', 'REDMINE_CLOSED_PROJECT']
def extract_permissions(current_permissions)
old_permissions = {}
current_permissions.each do |perm, branch_settings|
old_permissions[perm] = {}
branch_settings.each do |branch, user_list|
next if user_list.empty?
new_user_list = []
user_list.each do |user|
# ignore these users
next if SKIP_USERS.include?(user)
# backup users that are not Redmine users
new_user_list.push(user) if !user.include?(RedmineGitHosting::Config.gitolite_identifier_prefix)
end
old_permissions[perm][branch] = new_user_list if new_user_list.any?
end
end
old_permissions
end
end
end

View file

@ -0,0 +1,122 @@
module Gitolitable
module Urls
extend ActiveSupport::Concern
def http_user_login
User.current.anonymous? ? '' : "#{User.current.login}@"
end
def git_access_path
gitolite_repository_name_with_extension
end
def http_access_path
"#{RedmineGitHosting::Config.http_server_subdir}#{redmine_repository_path}.git"
end
def go_access_path
"go/#{redmine_repository_path}"
end
def ssh_url
"ssh://#{RedmineGitHosting::Config.gitolite_user}@#{RedmineGitHosting::Config.ssh_server_domain}/#{git_access_path}"
end
def git_url
"git://#{RedmineGitHosting::Config.ssh_server_domain}/#{git_access_path}"
end
def http_url
"http://#{http_user_login}#{RedmineGitHosting::Config.http_root_url}/#{http_access_path}"
end
def https_url
"https://#{http_user_login}#{RedmineGitHosting::Config.https_root_url}/#{http_access_path}"
end
def git_annex_url
"#{RedmineGitHosting::Config.gitolite_user}@#{RedmineGitHosting::Config.ssh_server_domain}:#{git_access_path}"
end
# This is the url used by Go to clone repository
#
def go_access_url
return '' if !smart_http_enabled?
return https_url if https_access_available?
return http_url if http_access_available?
end
# This is the url to add in Go files
#
def go_url
return '' if !smart_http_enabled?
return "#{RedmineGitHosting::Config.https_root_url}/#{go_access_path}" if https_access_available?
return "#{RedmineGitHosting::Config.http_root_url}/#{go_access_path}" if http_access_available?
end
def ssh_access
{ url: ssh_url, committer: User.current.allowed_to_commit?(self).to_s }
end
## Unsecure channels (clear password), commit is disabled
def http_access
{ url: http_url, committer: 'false' }
end
def https_access
{ url: https_url, committer: User.current.allowed_to_commit?(self).to_s }
end
def git_access
{ url: git_url, committer: 'false' }
end
def git_annex_access
{ url: git_annex_url, committer: User.current.allowed_to_commit?(self).to_s }
end
def go_access
{ url: go_url, committer: 'false' }
end
def available_urls
hash = {}
hash[:ssh] = ssh_access if ssh_access_available?
hash[:https] = https_access if https_access_available?
hash[:http] = http_access if http_access_available?
hash[:git] = git_access if git_access_available?
hash[:go] = go_access if go_access_available?
hash[:git_annex] = git_annex_access if git_annex_access_available?
hash
end
def available_urls_sorted
return available_urls if urls_order.nil? || urls_order.empty?
hash = {}
urls_order.each do |url|
next if !available_urls[url.to_sym]
hash[url.to_sym] = available_urls[url.to_sym]
end
hash
end
end
end

View file

@ -0,0 +1,109 @@
module Gitolitable
module Users
extend ActiveSupport::Concern
def gitolite_users
if project.active?
users_for_active_project
elsif project.archived?
users_for_archived_project
else
users_for_closed_project
end
end
def users_for_active_project
data = {}
data[:rewind_users] = rewind_users + rewind_deploy_users
data[:write_users] = write_users
data[:read_users] = read_users + read_deploy_users
data[:developer_team] = developer_team
data[:all_read] = all_users
# Add other users
data[:read_users] << 'DUMMY_REDMINE_KEY' if read_users.empty? && write_users.empty? && rewind_users.empty?
data[:read_users] << 'gitweb' if git_web_available?
data[:read_users] << 'daemon' if git_daemon_available?
# Return users
data
end
def users_for_archived_project
data = {}
data[:read_users] = ['REDMINE_ARCHIVED_PROJECT']
data
end
def users_for_closed_project
data = {}
data[:read_users] = all_users
data[:read_users] << 'REDMINE_CLOSED_PROJECT'
data
end
def users
project.users_available
end
def rewind_users
@rewind_users ||= users.select { |u| u.allowed_to?(:manage_repository, project) }.map { |u| u.gitolite_identifier }.sort
end
def write_users
@write_users ||= users.select { |u| u.allowed_to?(:commit_access, project) }.map { |u| u.gitolite_identifier }.sort - rewind_users
end
def read_users
@read_users ||= users.select { |u| u.allowed_to?(:view_changesets, project) }.map { |u| u.gitolite_identifier }.sort - rewind_users - write_users
end
def developer_team
@developer_team ||= (rewind_users + write_users).sort
end
def all_users
@all_users ||= (rewind_users + write_users + read_users).sort
end
def rewind_deploy_users
deploy_users_for_keys(rewind_deploy_keys)
end
def read_deploy_users
deploy_users_for_keys(read_deploy_keys)
end
def rewind_deploy_keys
deploy_keys_by_perm('RW+')
end
def read_deploy_keys
deploy_keys_by_perm('R')
end
def deploy_keys_by_perm(perm)
deployment_credentials.active.select { |cred| cred.perm == perm }
end
def deploy_users_for_keys(keys)
keys.map { |cred| cred.gitolite_public_key.owner }
end
end
end

View file

@ -0,0 +1,122 @@
module Gitolitable
module Validations
extend ActiveSupport::Concern
included do
# Set URL ourself as relative path.
#
before_validation :set_git_urls
# Make sure that identifier does not match Gitolite Admin repository
#
validates_exclusion_of :identifier, in: %w(gitolite-admin)
# Place additional constraints on repository identifiers
# because of multi repos
#
validate :additional_constraints_on_identifier
validate :identifier_dont_change
validate :default_repository_has_identifier
class << self
# Build a hash of repository identifier :
# <repo_1_identifier> => 1
# <repo_2_identifier> => 1
# etc...
# If the same repository identifier is found many times, increment the corresponding counter.
# Repository identifiers are unique if all values of the hash are 1.
#
def identifiers_to_hash
self.all.map(&:identifier).inject(Hash.new(0)) do |h, x|
h[x] += 1 unless x.blank?
h
end
end
def have_duplicated_identifier?
(identifiers_to_hash.values.max || 0) > 1
end
end
end
def exists_in_gitolite?
RedmineGitHosting::Commands.sudo_dir_exists?(gitolite_repository_path)
end
def empty_in_gitolite?
RedmineGitHosting::Commands.sudo_repository_empty?(gitolite_repository_path)
end
def git_objects_count
RedmineGitHosting::Commands.sudo_git_objects_count(File.join(gitolite_repository_path, 'objects'))
end
def empty?
extra_info.nil? || (!extra_info.has_key?('heads') && !extra_info.has_key?('branches'))
end
def data_for_destruction
{
repo_name: gitolite_repository_name,
repo_path: gitolite_full_repository_path,
delete_repository: deletable?,
git_cache_id: git_cache_id
}
end
private
# Set up git urls for new repositories
#
def set_git_urls
self.url = gitolite_repository_path if self.url.blank?
self.root_url = self.url if self.root_url.blank?
end
# Check several aspects of repository identifier (only for Redmine 1.4+)
# 1) cannot equal identifier of any project
# 2) if repo_ident_unique? make sure that repo identifier is globally unique
# 3) cannot make this repo the default if there will be some other repo with blank identifier
#
def additional_constraints_on_identifier
if !identifier.blank? && (new_record? || identifier_changed?)
errors.add(:identifier, :cannot_equal_project) if Project.find_by_identifier(identifier)
# See if a repo for another project has the same identifier (existing validations already check for current project)
errors.add(:identifier, :taken) if self.class.repo_ident_unique? && Repository.where("identifier = ? and project_id <> ?", identifier, project.id).any?
end
end
# Make sure identifier hasn't changed. Allow null and blank
# Note that simply using identifier_changed doesn't seem to work
# if the identifier was "NULL" but the new identifier is ""
#
def identifier_dont_change
return if new_record?
errors.add(:identifier, :cannot_change) if (identifier_was.blank? && !identifier.blank?) || (!identifier_was.blank? && identifier_changed?)
end
# Need to make sure that we don't take the default slot away from a sibling repo with blank identifier
#
def default_repository_has_identifier
if project && (is_default? || set_as_default?)
possibles = Repository.where("project_id = ? and (identifier = '' or identifier is null)", project.id)
errors.add(:base, :blank_default_exists) if possibles.any? && (new_record? || possibles.detect { |x| x.id != id })
end
end
end
end

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,9 @@
class GithubComment < ActiveRecord::Base
## Relations
belongs_to :journal
## Validations
validates :github_id, presence: true
validates :journal_id, presence: true, uniqueness: { scope: :github_id }
end

View file

@ -0,0 +1,9 @@
class GithubIssue < ActiveRecord::Base
## Relations
belongs_to :issue
## Validations
validates :github_id, presence: true
validates :issue_id, presence: true, uniqueness: { scope: :github_id }
end

View file

@ -0,0 +1,202 @@
class GitolitePublicKey < ActiveRecord::Base
include Redmine::SafeAttributes
TITLE_LENGTH_LIMIT = 60
KEY_TYPE_USER = 0
KEY_TYPE_DEPLOY = 1
## Attributes
safe_attributes 'title', 'key', 'key_type', 'delete_when_unused'
## Relations
belongs_to :user
has_many :repository_deployment_credentials, dependent: :destroy
## Validations
validates :user_id, presence: true
validates :title, presence: true, uniqueness: { case_sensitive: false, scope: :user_id },
length: { maximum: TITLE_LENGTH_LIMIT }, format: /\A[a-z0-9_\-]*\z/i
validates :identifier, presence: true, uniqueness: { case_sensitive: false, scope: :user_id }
validates :key, presence: true
validates :key_type, presence: true, numericality: { only_integer: true },
inclusion: { in: [KEY_TYPE_USER, KEY_TYPE_DEPLOY] }
validate :has_not_been_changed
validate :key_correctness
validate :key_not_admin
validate :key_uniqueness
## Scopes
scope :user_key, -> { where(key_type: KEY_TYPE_USER) }
scope :deploy_key, -> { where(key_type: KEY_TYPE_DEPLOY) }
## Callbacks
before_validation :strip_whitespace
before_validation :remove_control_characters
before_validation :set_identifier
before_validation :set_fingerprint
def key_type_as_string
user_key? ? 'user_key' : 'deploy_key'
end
def to_s
title
end
def data_for_destruction
{ title: identifier, key: key, location: location, owner: owner }
end
# Returns the path to this key under the gitolite keydir
# resolves to <user.gitolite_identifier>/<location>/<owner>.pub
#
# tile: test-key
# identifier: redmine_admin_1@redmine_test_key
# identifier: redmine_admin_1@redmine_deploy_key_1
#
#
# keydir/
# ├── redmine_git_hosting
# │   └── redmine_admin_1
# │   ├── redmine_test_key
# │   │   └── redmine_admin_1.pub
# │   ├── redmine_deploy_key_1
# │   │   └── redmine_admin_1.pub
# │   └── redmine_deploy_key_2
# │   └── redmine_admin_1.pub
# └── redmine_gitolite_admin_id_rsa.pub
#
#
# The root folder for this user is the user's identifier
# for logical grouping of their keys, which are organized
# by their title in subfolders.
#
# This is due to the new gitolite multi-keys organization
# using folders. See https://gitolite.com/gitolite/users.html
def gitolite_path
File.join('keydir', RedmineGitHosting::Config.gitolite_key_subdir, user.gitolite_identifier, location, owner) + '.pub'
end
# Make sure that current identifier is consistent with current user login.
# This method explicitly overrides the static nature of the identifier
def reset_identifiers(opts = {})
# Fix identifier
self.identifier = nil
self.fingerprint = nil
self.identifier = GitolitePublicKeys::GenerateIdentifier.call(self, user, opts)
set_fingerprint
# Need to override the "never change identifier" constraint
save(validate: false)
end
# Key type checking functions
def user_key?
key_type == KEY_TYPE_USER
end
def deploy_key?
key_type == KEY_TYPE_DEPLOY
end
def owner
identifier.split('@')[0]
end
def location
identifier.split('@')[1]
end
def type
key.split(' ')[0]
end
def blob
key.split(' ')[1]
end
def email
key.split(' ')[2]
end
private
# Strip leading and trailing whitespace
# Don't mess with existing keys (since cannot change key text anyway)
#
def strip_whitespace
return unless new_record?
self.title = title.strip rescue ''
self.key = key.strip rescue ''
end
# Remove control characters from key
# Don't mess with existing keys (since cannot change key text anyway)
#
def remove_control_characters
return unless new_record?
self.key = RedmineGitHosting::Utils::Ssh.sanitize_ssh_key(key)
end
# Returns the unique identifier for this key based on the key_type
#
# For user public keys, this simply is the user's gitolite_identifier.
# For deployment keys, we use an incrementing number.
#
def set_identifier
return nil if user_id.nil?
self.identifier ||= GitolitePublicKeys::GenerateIdentifier.call(self, user)
end
def set_fingerprint
self.fingerprint = RedmineGitHosting::Utils::Ssh.ssh_fingerprint(key)
rescue RedmineGitHosting::Error::InvalidSshKey => e
errors.add(:key, :corrupted)
end
def has_not_been_changed
return if new_record?
%w[identifier key user_id key_type title fingerprint].each do |attribute|
method = "#{attribute}_changed?"
errors.add(attribute, :cannot_change) if send(method)
end
end
# Test correctness of fingerprint from output
# and general ssh-(r|d|ecd)sa <key> <id> structure
#
def key_correctness
return false if key.nil?
key.match(/^(\S+)\s+(\S+)/) && (fingerprint =~ /^(\w{2}:?)+$/i)
end
def key_not_admin
errors.add(:key, :taken_by_gitolite_admin) if fingerprint == RedmineGitHosting::Config.gitolite_ssh_public_key_fingerprint
end
def key_uniqueness
return unless new_record?
existing = GitolitePublicKey.find_by_fingerprint(fingerprint)
return unless existing
if existing.user == User.current
errors.add(:key, :taken_by_you, name: existing.title)
elsif User.current.admin?
errors.add(:key, :taken_by_other, login: existing.user.login, name: existing.title)
else
errors.add(:key, :taken_by_someone)
end
end
end

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! unless member.nil?
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,55 @@
class RepositoryDeploymentCredential < ActiveRecord::Base
include Redmine::SafeAttributes
VALID_PERMS = ['R', 'RW+'].freeze
DEFAULT_PERM = 'RW+'.freeze
## Attributes
safe_attributes 'perm', 'active', 'gitolite_public_key_id'
## Relations
belongs_to :repository
belongs_to :gitolite_public_key
belongs_to :user
## Validations
validates :repository_id, presence: true,
uniqueness: { scope: :gitolite_public_key_id }
validates :gitolite_public_key_id, presence: true
validates :user_id, presence: true
validates :perm, presence: true,
inclusion: { in: VALID_PERMS }
validates_associated :repository
validates_associated :gitolite_public_key
validates_associated :user
validate :correct_key_type
validate :owner_matches_key
## Scopes
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
def to_s
"#{repository.identifier}-#{gitolite_public_key.identifier} : #{perm}"
end
# Deployment Credentials ignored unless created by someone who still has permission to create them
def honored?
user.admin? || user.allowed_to?(:create_repository_deployment_credentials, repository.project)
end
private
def correct_key_type
errors.add(:base, :invalid_key) if gitolite_public_key && gitolite_public_key.key_type_as_string != 'deploy_key'
end
def owner_matches_key
return if user.nil? || gitolite_public_key.nil?
errors.add(:base, :invalid_user) if user != gitolite_public_key.user
end
end

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,9 @@
class RepositoryGitConfigKey::GitConfig < RepositoryGitConfigKey
VALID_CONFIG_KEY_REGEX = /\A[a-zA-Z0-9]+\.[a-zA-Z0-9.]+\z/
validates :key, presence: true,
uniqueness: { case_sensitive: false, scope: [:type, :repository_id] },
format: { with: VALID_CONFIG_KEY_REGEX }
end

View file

@ -0,0 +1,6 @@
class RepositoryGitConfigKey::Option < RepositoryGitConfigKey
validates :key, presence: true,
uniqueness: { case_sensitive: false, scope: [: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,106 @@
class RepositoryMirror < ActiveRecord::Base
include Redmine::SafeAttributes
PUSHMODE_MIRROR = 0
PUSHMODE_FORCE = 1
PUSHMODE_FAST_FORWARD = 2
## Attributes
safe_attributes 'url', 'push_mode', 'include_all_branches', 'include_all_tags',
'explicit_refspec', 'active'
## Relations
belongs_to :repository
## Validations
validates :repository_id, presence: true
## Only allow SSH format
## ssh://git@redmine.example.org/project1/project2/project3/project4.git
## ssh://git@redmine.example.org:2222/project1/project2/project3/project4.git
validates :url, presence: true,
uniqueness: { case_sensitive: false, scope: :repository_id },
format: { with: RedmineGitHosting::Validators::GIT_SSH_URL_REGEX }
validates :push_mode, presence: true,
numericality: { only_integer: true },
inclusion: { in: [PUSHMODE_MIRROR, PUSHMODE_FORCE, PUSHMODE_FAST_FORWARD] }
## Additional validations
validate :mirror_configuration
## Scopes
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
scope :has_explicit_refspec, -> { where(push_mode: '> 0') }
## Callbacks
before_validation :strip_whitespace
def mirror_mode?
push_mode == PUSHMODE_MIRROR
end
def force_mode?
push_mode == PUSHMODE_FORCE
end
def push_mode_to_s
case push_mode
when 0
'mirror'
when 1
'force'
when 2
'fast_forward'
end
end
private
# Strip leading and trailing whitespace
def strip_whitespace
self.url = url.strip rescue ''
self.explicit_refspec = explicit_refspec.strip rescue ''
end
def mirror_configuration
if mirror_mode?
reset_fields
elsif include_all_branches? && include_all_tags?
mutual_exclusion_error
elsif explicit_refspec.present?
if include_all_branches?
errors.add(:explicit_refspec, "cannot be used with #{l(:label_mirror_include_all_branches)}.")
else
validate_refspec
end
elsif !include_all_branches? && !include_all_tags?
errors.add(:base, :nothing_to_push)
end
end
# Check format of refspec
#
def validate_refspec
RedmineGitHosting::Validators.valid_git_refspec_path?(explicit_refspec)
rescue RedmineGitHosting::Error::InvalidRefspec::BadFormat => e
errors.add(:explicit_refspec, :bad_format)
rescue RedmineGitHosting::Error::InvalidRefspec::NullComponent => e
errors.add(:explicit_refspec, :have_null_component)
end
def reset_fields
# clear out all extra parameters.. (we use javascript to hide them anyway)
self.include_all_branches = false
self.include_all_tags = false
self.explicit_refspec = ''
end
def mutual_exclusion_error
errors.add(:base, "Cannot #{l(:label_mirror_include_all_branches)} and #{l(:label_mirror_include_all_tags)} at the same time.")
return if explicit_refspec.blank?
errors.add(:explicit_refspec, "cannot be used with #{l(:label_mirror_include_all_branches)} or #{l(:label_mirror_include_all_tags)}")
end
end

View file

@ -0,0 +1,56 @@
require 'uri'
class RepositoryPostReceiveUrl < ActiveRecord::Base
include Redmine::SafeAttributes
## Attributes
safe_attributes 'url', 'mode', 'active', 'use_triggers', 'triggers', 'split_payloads'
## Relations
belongs_to :repository
## Validations
validates :repository_id, presence: true
# Only allow HTTP(s) format
validates :url, presence: true,
uniqueness: { case_sensitive: false, scope: :repository_id },
format: { with: URI::regexp(%w[http https]) }
validates :mode, presence: true, inclusion: { in: %i[github get] }
## Serializations
serialize :triggers, Array
## Scopes
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
## Callbacks
before_validation :strip_whitespace
before_validation :remove_blank_triggers
def mode
self[:mode].to_sym
end
def mode=(value)
self[:mode] = value.to_s
end
def github_mode?
mode == :github
end
private
# Strip leading and trailing whitespace
def strip_whitespace
self.url = url.strip rescue ''
end
# Remove blank entries in triggers
def remove_blank_triggers
self.triggers = triggers.select(&:present?)
end
end

View file

@ -0,0 +1,47 @@
class RepositoryProtectedBranche < ActiveRecord::Base
include Redmine::SafeAttributes
VALID_PERMS = ['RW+', 'RW', 'R', '-'].freeze
DEFAULT_PERM = 'RW+'.freeze
acts_as_positioned
## Attributes
safe_attributes 'path', 'permissions', 'position'
## Relations
belongs_to :repository
has_many :protected_branches_members, foreign_key: :protected_branch_id, dependent: :destroy
has_many :members, through: :protected_branches_members, source: :principal
## Validations
validates :repository_id, presence: true
validates :path, presence: true, uniqueness: { scope: %i[permissions repository_id] }
validates :permissions, presence: true, inclusion: { in: VALID_PERMS }
## Scopes
default_scope { order(position: :asc) }
class << self
def clone_from(parent)
parent = find_by(id: parent) unless parent.is_a? RepositoryProtectedBranche
copy = new
copy.attributes = parent.attributes
copy.repository = parent.repository
copy
end
end
# Accessors
#
def users
members.select { |m| m.class.name == 'User' }.uniq
end
def groups
members.select { |m| m.class.name == 'Group' }.uniq
end
def allowed_users
users.map(&:gitolite_identifier).sort
end
end

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
content_tag(:div, class: 'git_url_box', id: urls_container_id) do
render_git_urls +
render_git_url_text +
render_permissions +
render_clipboard_button
end
end
private
def render_git_urls
content_tag(:ul, render_url_list, class: 'git_url_list')
end
def render_url_list
s = ''
repository.available_urls_sorted.each do |key, value|
s << content_tag(:li, link_to(key.upcase, 'javascript:void(0)').html_safe, options_for_git_url(key, value))
end
s.html_safe
end
def options_for_git_url(key, value)
{ class: 'git_url', data: { url: value[:url], target: element_name, committer: committer_label(value) } }
end
def render_git_url_text
content_tag(:input, '', class: 'git_url_text', id: url_text_container_id, readonly: 'readonly')
end
def render_permissions
content_tag(:div, content_tag(:span, '', id: permissions_container_id), class: 'git_url_permissions')
end
def render_clipboard_button
clipboardjs_button_for(url_text_container_id)
end
def committer_label(value)
Additionals.true?(value[:committer]) ? l(:label_read_write_permission) : l(:label_read_only_permission)
end
def element_name
"repository_#{repository.id}"
end
def urls_container_id
"git_url_box_#{element_name}"
end
def permissions_container_id
"git_url_permissions_#{element_name}"
end
def url_text_container_id
"git_url_text_#{element_name}"
end
end

View file

@ -0,0 +1,13 @@
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,107 @@
class RepositoryContributorsStats < ReportBase
def initialize(repository)
super
@changes_for_committer = {}
end
def commits_per_author
data = []
sorted_commits_per_author_with_aliases.each do |committer_hash|
commits = {}
committer_hash[:committers].each do |committer|
commits = commits.merge(count_changes_for_committer(committer)) { |key, oldval, newval| newval + oldval }
end
commits = Hash[commits.sort]
commits_data = {}
commits_data[:author_name] = committer_hash[:name]
commits_data[:author_mail] = committer_hash[:mail]
commits_data[:total_commits] = committer_hash[:commits]
commits_data[:categories] = commits.keys
commits_data[:series] = []
commits_data[:series] << { name: l(:label_commit_plural), data: commits.values }
data.push(commits_data)
end
data
end
def commits_per_author_global
merged = commits_per_author_with_aliases
data = {}
data[:categories] = merged.map { |x| x[:name] }
data[:series] = []
data[:series] << { name: l(:label_commit_plural), data: merged.map { |x| x[:commits] } }
data[:series] << { name: l(:label_change_plural), data: merged.map { |x| x[:changes] } }
data
end
private
# Generate mappings from the registered users to the comitters
# user_committer_mapping = { name => [comitter, ...] }
# registered_committers = [ committer,... ]
#
def commits_per_author_with_aliases
return @commits_per_author_with_aliases if !@commits_per_author_with_aliases.nil?
@commits_per_author_with_aliases = nil
registered_committers = []
user_committer_mapping = {}
Changeset.select('changesets.committer, changesets.user_id')
.where('repository_id = ? and user_id IS NOT NULL', repository.id)
.group(:committer, :user_id)
.includes(:user).each do |x|
name = "#{x.user.firstname} #{x.user.lastname}"
registered_committers << x.committer
user_committer_mapping[[name, x.user.mail]] ||= []
user_committer_mapping[[name, x.user.mail]] << x.committer
end
merged = []
commits_by_author.each do |committer, count|
# skip all registered users
next if registered_committers.include?(committer)
name = committer.gsub(%r{<.+@.+>}, '').strip
mail = committer[/<(.+@.+)>/, 1]
merged << { name: name, mail: mail, commits: count, changes: changes_by_author[committer] || 0, committers: [committer] }
end
user_committer_mapping.each do |identity, committers|
count = 0
changes = 0
committers.each do |c|
count += commits_by_author[c] || 0
changes += changes_by_author[c] || 0
end
merged << { name: identity[0], mail: identity[1], commits: count, changes: changes, committers: committers }
end
# sort by name
merged.sort! { |x, y| x[:name] <=> y[:name] }
# merged = merged + [{name:"",commits:0,changes:0}]*(10 - merged.length) if merged.length < 10
@commits_per_author_with_aliases = merged
@commits_per_author_with_aliases
end
def sorted_commits_per_author_with_aliases
@committers ||= commits_per_author_with_aliases.sort! { |x, y| y[:commits] <=> x[:commits] }
end
def count_changes_for_committer(committer)
return @changes_for_committer[committer] if !@changes_for_committer[committer].nil?
@changes_for_committer[committer] ||= Changeset.where('repository_id = ? AND committer = ?', repository.id, committer).group(:commit_date).order(:commit_date).count
@changes_for_committer[committer]
end
end

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,38 @@
module PermissionsBuilder
class Base
attr_reader :repository
attr_reader :gitolite_users
attr_reader :old_permissions
def initialize(repository, gitolite_users, old_permissions = {})
@repository = repository
@gitolite_users = gitolite_users
@old_permissions = old_permissions
end
class << self
def build(repository, gitolite_users, old_permissions = {})
new(repository, gitolite_users, old_permissions).build
end
end
def build
raise NotImplementedError
end
private
def has_no_users?(type)
gitolite_users[type].nil? || gitolite_users[type].empty?
end
end
end

View file

@ -0,0 +1,29 @@
module PermissionsBuilder
class ProtectedBranches < Base
attr_reader :permissions
def initialize(*args)
super
@permissions = []
end
def build
build_protected_branch_permissions
permissions
end
def build_protected_branch_permissions
repository.protected_branches.each do |branch|
perms = {}
perms[branch.permissions] = {}
perms[branch.permissions][branch.path] = branch.allowed_users unless branch.allowed_users.empty?
permissions.push(perms)
end
end
end
end

View file

@ -0,0 +1,71 @@
module PermissionsBuilder
class Standard < Base
attr_reader :permissions
def initialize(*args)
super
@permissions = {}
@permissions['RW+'] = {}
@permissions['RW'] = {}
@permissions['R'] = {}
end
def build
# Build permissions
build_permissions
# Return them
[merge_permissions(permissions, old_permissions)]
end
private
def build_permissions
@permissions['RW+'][''] = gitolite_users[:rewind_users] unless has_no_users?(:rewind_users)
@permissions['RW'][''] = gitolite_users[:write_users] unless has_no_users?(:write_users)
@permissions['R'][''] = gitolite_users[:read_users] unless has_no_users?(:read_users)
end
def merge_permissions(current_permissions, old_permissions)
merge_permissions = {}
merge_permissions['RW+'] = {}
merge_permissions['RW'] = {}
merge_permissions['R'] = {}
current_permissions.each do |perm, branch_settings|
branch_settings.each do |branch, user_list|
if user_list.any?
if !merge_permissions[perm].has_key?(branch)
merge_permissions[perm][branch] = []
end
merge_permissions[perm][branch] += user_list
end
end
end
old_permissions.each do |perm, branch_settings|
branch_settings.each do |branch, user_list|
if user_list.any?
if !merge_permissions[perm].has_key?(branch)
merge_permissions[perm][branch] = []
end
merge_permissions[perm][branch] += user_list
end
end
end
merge_permissions.each do |perm, branch_settings|
merge_permissions.delete(perm) if merge_permissions[perm].empty?
end
merge_permissions
end
end
end

View file

@ -0,0 +1,70 @@
module RedmineHooks
class Base
attr_reader :object
attr_reader :payloads
def initialize(object, payloads = {})
@object = object
@payloads = payloads
end
class << self
def call(object, payloads = {})
new(object, payloads).call
end
end
def call
raise NotImplementedError
end
def start_message
raise NotImplementedError
end
private
def logger
RedmineGitHosting.logger
end
def success_message
" [success]\n"
end
def failure_message
" [failure]\n"
end
def log_hook_succeeded
logger.info('Succeeded!')
end
def log_hook_failed
logger.error('Failed!')
end
def execute_hook(&block)
y = ''
logger.info(start_message)
y << " - #{start_message} ... "
yield y
y
end
end
end

View file

@ -0,0 +1,118 @@
module RedmineHooks
class CallWebservices < Base
include HttpHelper
attr_reader :payloads_to_send
def initialize(*args)
super
@payloads_to_send = []
set_payloads_to_send
end
def call
execute_hook do |out|
if needs_push?
out << call_webservice
else
out << "#{skip_message}\n"
logger.info(skip_message)
end
end
end
def post_receive_url
object
end
def needs_push?
return false if payloads.empty?
return true unless use_triggers?
return false if post_receive_url.triggers.empty?
return !payloads_to_send.empty?
end
def start_message
"Notifying #{post_receive_url.url}"
end
def skip_message
"This url doesn't need to be notified"
end
private
def set_payloads_to_send
if use_triggers?
@payloads_to_send = extract_payloads
else
@payloads_to_send = payloads
end
end
def extract_payloads
new_payloads = []
payloads.each do |payload|
data = RedmineGitHosting::Utils::Git.parse_refspec(payload[:ref])
if data[:type] == 'heads' && post_receive_url.triggers.include?(data[:name])
new_payloads << payload
end
end
new_payloads
end
def use_method
post_receive_url.mode == :github ? :http_post : :http_get
end
def use_triggers?
post_receive_url.use_triggers?
end
def split_payloads?
post_receive_url.split_payloads?
end
def call_webservice
if use_method == :http_post && split_payloads?
y = ''
payloads_to_send.each do |payload|
y << do_call_webservice(payload)
end
y
else
do_call_webservice(payloads_to_send)
end
end
def do_call_webservice(payload)
post_failed, post_message = self.send(use_method, post_receive_url.url, { data: { payload: payload } })
if post_failed
logger.error('Failed!')
logger.error(post_message)
(split_payloads? ? failure_message.gsub("\n", '') : failure_message)
else
log_hook_succeeded
(split_payloads? ? success_message.gsub("\n", '') : success_message)
end
end
end
end

View file

@ -0,0 +1,38 @@
module RedmineHooks
class FetchChangesets < Base
def call
repository.empty_cache!
execute_hook do |out|
out << fetch_changesets
end
end
def repository
object
end
def start_message
"Fetching changesets for '#{repository.redmine_name}' repository"
end
private
def fetch_changesets
begin
repository.fetch_changesets
log_hook_succeeded
success_message
rescue ::Redmine::Scm::Adapters::CommandFailed => e
log_hook_failed
logger.error("Error during fetching changesets : #{e.message}")
failure_message
end
end
end
end

View file

@ -0,0 +1,152 @@
require 'json'
module RedmineHooks
class GithubIssuesSync < Base
include HttpHelper
def call
sync_with_github
end
def project
object
end
def params
payloads
end
private
def github_issue
GithubIssue.find_by_github_id(params[:issue][:id])
end
def redmine_issue
Issue.find_by_subject(params[:issue][:title])
end
def sync_with_github
create_relation = false
## We don't have stored relation
if github_issue.nil?
## And we don't have issue in Redmine
if redmine_issue.nil?
create_relation = true
redmine_issue = create_redmine_issue
else
## Create relation and update issue
create_relation = true
redmine_issue = update_redmine_issue(redmine_issue)
end
else
## We have one relation, update issue
redmine_issue = update_redmine_issue(github_issue.issue)
end
if create_relation
github_issue = GithubIssue.new
github_issue.github_id = params[:issue][:id]
github_issue.issue_id = redmine_issue.id
github_issue.save!
end
if params.has_key?(:comment)
issue_journal = GithubComment.find_by_github_id(params[:comment][:id])
if issue_journal.nil?
issue_journal = create_issue_journal(github_issue.issue)
github_comment = GithubComment.new
github_comment.github_id = params[:comment][:id]
github_comment.journal_id = issue_journal.id
github_comment.save!
end
end
end
def create_redmine_issue
logger.info('Github Issues Sync : create new issue')
issue = project.issues.new
issue.tracker_id = project.trackers.first.try(:id)
issue.subject = params[:issue][:title].chomp[0, 255]
issue.description = params[:issue][:body]
issue.updated_on = params[:issue][:updated_at]
issue.created_on = params[:issue][:created_at]
## Get user mail
user = find_user(params[:issue][:user][:url])
issue.author = user
issue.save!
return issue
end
def create_issue_journal(issue)
logger.info("Github Issues Sync : create new journal for issue '##{issue.id}'")
journal = Journal.new
journal.journalized_id = issue.id
journal.journalized_type = 'Issue'
journal.notes = params[:comment][:body]
journal.created_on = params[:comment][:created_at]
## Get user mail
user = find_user(params[:comment][:user][:url])
journal.user_id = user.id
journal.save!
return journal
end
def update_redmine_issue(issue)
logger.info("Github Issues Sync : update issue '##{issue.id}'")
if params[:issue][:state] == 'closed'
issue.status_id = 5
else
issue.status_id = 1
end
issue.subject = params[:issue][:title].chomp[0, 255]
issue.description = params[:issue][:body]
issue.updated_on = params[:issue][:updated_at]
issue.save!
return issue
end
def find_user(url)
post_failed, user_data = http_get(url)
user_data = JSON.parse(user_data)
user = User.find_by_mail(user_data['email'])
if user.nil?
logger.info("Github Issues Sync : cannot find user '#{user_data['email']}' in Redmine, use anonymous")
user = User.anonymous
user.mail = user_data['email']
user.firstname = user_data['name']
user.lastname = user_data['login']
end
return user
end
end
end

View file

@ -0,0 +1,14 @@
module RedmineHooks
module HttpHelper
def http_post(url, opts = {})
RedmineGitHosting::Utils::Http.http_post(url, opts)
end
def http_get(url, opts = {})
RedmineGitHosting::Utils::Http.http_get(url, opts)
end
end
end

View file

@ -0,0 +1,71 @@
module RedmineHooks
class UpdateMirrors < Base
def call
execute_hook do |out|
if needs_push?
out << call_mirror
else
out << "#{skip_message}\n"
logger.info(skip_message)
end
end
end
def mirror
object
end
# If we have an explicit refspec, check it against incoming payloads
# Special case: if we do not pass in any payloads, return true
def needs_push?
return true if payloads.empty?
return true if mirror.mirror_mode?
return check_ref_spec
end
def start_message
"Pushing changes to #{mirror.url}"
end
def skip_message
"This mirror doesn't need to be updated"
end
private
def check_ref_spec
refspec_parse = RedmineGitHosting::Validators.valid_git_refspec?(mirror.explicit_refspec)
payloads.each do |payload|
if splitpath = RedmineGitHosting::Utils::Git.parse_refspec(payload[:ref])
return true if payload[:ref] == refspec_parse[1] # Explicit Reference Spec complete path
return true if splitpath[:name] == refspec_parse[1] # Explicit Reference Spec no type
return true if mirror.include_all_branches? && splitpath[:type] == 'heads'
return true if mirror.include_all_tags? && splitpath[:type] == 'tags'
end
end
false
end
def call_mirror
push_failed, push_message = RepositoryMirrors::Push.call(mirror)
if push_failed
log_hook_failed
logger.error(push_message)
failure_message
else
log_hook_succeeded
success_message
end
end
end
end

View file

@ -0,0 +1,72 @@
module GitolitePublicKeys
class GenerateIdentifier
DEPLOY_PSEUDO_USER = 'deploy_key'
attr_reader :public_key
attr_reader :user
attr_reader :skip_auto_increment
def initialize(public_key, user, opts = {})
@public_key = public_key
@user = user
@skip_auto_increment = opts.delete(:skip_auto_increment) { false }
end
class << self
def call(public_key, user, opts = {})
new(public_key, user, opts).call
end
end
# Returns the unique identifier for this key based on the key_type
#
# For user public keys, this simply is the user's gitolite_identifier.
# For deployment keys, we use an incrementing number.
#
def call
if public_key.user_key?
set_identifier_for_user_key
elsif public_key.deploy_key?
set_identifier_for_deploy_key
end
end
private
def set_identifier_for_user_key
tag = public_key.title.gsub(/[^0-9a-zA-Z]/, '_')
[user.gitolite_identifier, '@', 'redmine_', tag].join
end
# Fix https://github.com/jbox-web/redmine_git_hosting/issues/288
# Getting user deployment keys count is not sufficient to assure uniqueness of
# deployment key identifier. So we need an 'external' counter to increment the global count
# while a key with this identifier exists.
#
def set_identifier_for_deploy_key
count = 0
begin
key_id = generate_deploy_key_identifier(count)
count += 1
end while user.gitolite_public_keys.deploy_key.map(&:owner).include?(key_id.split('@')[0])
key_id
end
def generate_deploy_key_identifier(count)
key_count = 1 + count
key_count += user.gitolite_public_keys.deploy_key.length unless skip_auto_increment
[user.gitolite_identifier, '_', DEPLOY_PSEUDO_USER, '_', key_count, '@', 'redmine_', DEPLOY_PSEUDO_USER, '_', key_count].join
end
end
end

View file

@ -0,0 +1,30 @@
module Projects
class Base
include RedmineGitHosting::GitoliteAccessor::Methods
attr_reader :project
attr_reader :options
def initialize(project, opts = {})
@project = project
@options = opts
end
class << self
def call(project, opts = {})
new(project, opts).call
end
end
def call
raise NotImplementedError
end
end
end

View file

@ -0,0 +1,32 @@
module Projects
class CreateRepository < Base
def call
create_project_repository
end
private
def create_project_repository
# Create new repository
repository = Repository.factory('Xitolite')
repository.is_default = true
repository.extra_info = {}
repository.extra_info['extra_report_last_commit'] = '1'
# Save it to database
project.repositories << repository
# Create it in Gitolite
Repositories::Create.call(repository, creation_options)
end
def creation_options
{ create_readme_file: RedmineGitHosting::Config.init_repositories_on_create? }
end
end
end

View file

@ -0,0 +1,38 @@
module Projects
class ExecuteHooks
attr_reader :project
attr_reader :hook_type
attr_reader :params
def initialize(project, hook_type, params = {})
@project = project
@hook_type = hook_type
@params = params
end
class << self
def call(project, hook_type, params = {})
new(project, hook_type, params).call
end
end
def call
self.send("execute_#{hook_type}_hook")
end
private
def execute_github_hook
RedmineHooks::GithubIssuesSync.call(project, params)
end
end
end

View file

@ -0,0 +1,29 @@
module Projects
class Update < Base
def call
# Adjust daemon status
disable_git_daemon_if_not_public
resync
end
private
def disable_git_daemon_if_not_public
# Go through all gitolite repos and disable Git daemon if necessary
project.gitolite_repos.each do |repository|
repository.extra[:git_daemon] = false if repository.git_daemon_enabled? && !project.is_public
# Save GitExtra in all cases to trigger urls order consistency checks
repository.extra.save
end
end
def resync
gitolite_accessor.update_projects([project.id], options)
end
end
end

View file

@ -0,0 +1,40 @@
module Repositories
class Base
include RedmineGitHosting::GitoliteAccessor::Methods
attr_reader :repository
attr_reader :options
attr_reader :project
def initialize(repository, opts = {})
@repository = repository
@options = opts
@project = repository.project
end
class << self
def call(repository, opts = {})
new(repository, opts).call
end
end
def call
raise NotImplementedError
end
private
def logger
RedmineGitHosting.logger
end
end
end

View file

@ -0,0 +1,91 @@
module Repositories
class BuildPayload < Base
def initialize(*args)
super
@payloads = []
end
def call
build_payloads
end
def refs
options
end
private
# Returns an array of GitHub post-receive hook style hashes
# http://help.github.com/post-receive-hooks/
#
def build_payloads
refs.each do |ref|
# Get revisions range
range = get_revisions_from_ref(ref)
next if range.nil?
@payloads << build_payload(ref, range)
end
@payloads
end
def get_revisions_from_ref(ref)
oldhead, newhead, refname = ref.split(',')
# Only pay attention to branch updates
return nil if !refname.match(/refs\/heads\//)
# Get branch name
branch_name = refname.gsub('refs/heads/', '')
if newhead.match(/\A0{40}\z/)
# Deleting a branch
logger.info("Deleting branch '#{branch_name}'")
range = nil
elsif oldhead.match(/\A0{40}\z/)
# Creating a branch
logger.info("Creating branch '#{branch_name}'")
range = newhead
else
range = "#{oldhead}..#{newhead}"
end
range
end
def build_payload(ref, range)
revisions_in_range = get_revisions_in_range(range)
logger.debug("Revisions in range : #{revisions_in_range.join(' ')}")
# Get refs
oldhead, newhead, refname = ref.split(',')
# Build payload hash
repository.github_payload
.merge({ before: oldhead, after: newhead, ref: refname, commits: build_commits_list(revisions_in_range) })
end
def build_commits_list(revisions_in_range)
commits_list = []
revisions_in_range.each do |rev|
changeset = repository.find_changeset_by_name(rev)
next if changeset.nil?
commits_list << changeset.github_payload
end
commits_list
end
def get_revisions_in_range(range)
repository.rev_list(range, ['--reverse'])
end
end
end

View file

@ -0,0 +1,71 @@
module Repositories
class Create < Base
def call
set_repository_extra
create_repository
end
private
def set_repository_extra
extra = repository.build_extra(default_extra_options)
extra.save!
end
def default_extra_options
enable_git_annex? ? git_annex_repository_options : standard_repository_options
end
def enable_git_annex?
options[:enable_git_annex]
end
def standard_repository_options
{
git_daemon: RedmineGitHosting::Config.gitolite_daemon_by_default?,
git_notify: RedmineGitHosting::Config.gitolite_notify_by_default?,
git_annex: false,
default_branch: 'master',
key: RedmineGitHosting::Utils::Crypto.generate_secret(64)
}.merge(smart_http_options)
end
def smart_http_options
case RedmineGitHosting::Config.gitolite_http_by_default?
when '1' # HTTPS only
{ git_https: true }
when '2' # HTTPS and HTTP
{ git_http: true, git_https: true }
when '3' # HTTP only
{ git_http: true }
else
{}
end
end
def git_annex_repository_options
{
git_http: 0,
git_daemon: false,
git_notify: false,
git_annex: true,
default_branch: 'git-annex',
key: RedmineGitHosting::Utils::Crypto.generate_secret(64)
}
end
def create_repository
gitolite_accessor.create_repository(repository, options)
end
end
end

View file

@ -0,0 +1,93 @@
module Repositories
class DownloadRevision
attr_reader :repository
attr_reader :revision
attr_reader :format
attr_reader :gitolite_repository_path
attr_reader :commit_id
attr_reader :content_type
attr_reader :filename
def initialize(repository, revision, format)
@repository = repository
@revision = revision
@format = format
@gitolite_repository_path = repository.gitolite_repository_path
@valid_commit = false
@commit_id = nil
@content_type = ''
@filename = ''
validate_revision
fill_data
end
def content
repository.archive(commit_id, format)
end
def valid_commit?
@valid_commit
end
private
def validate_revision
commit = nil
# is the revision a branch?
repository.branches.each do |x|
if x.to_s == revision
commit = x.revision
break
end
end
# is the revision a tag?
if commit.nil?
repository.tags.each do |x|
if x == revision
commit = repository.rev_list(revision).first
break
end
end
end
# well, let check if this is a valid commit
commit = revision if commit.nil?
commit = repository.rev_parse(commit)
if commit == ''
@valid_commit = false
else
@valid_commit = true
@commit_id = commit
end
end
def fill_data
case format
when 'tar.gz' then
extension = 'tar.gz'
@content_type = 'application/x-gzip'
when 'zip' then
extension = 'zip'
@content_type = 'application/x-zip'
else
extension = 'tar'
@content_type = 'application/x-tar'
end
@filename = "#{repository.redmine_name}-#{revision}.#{extension}"
end
end
end

View file

@ -0,0 +1,79 @@
module Repositories
class ExecuteHooks
attr_reader :repository
attr_reader :hook_type
attr_reader :payloads
def initialize(repository, hook_type, payloads = {})
@repository = repository
@hook_type = hook_type
@payloads = payloads
end
class << self
def call(repository, hook_type, payloads = {})
new(repository, hook_type, payloads).call
end
end
def call
self.send("execute_#{hook_type}_hook")
end
private
def logger
RedmineGitHosting.logger
end
def execute_fetch_changesets_hook
RedmineHooks::FetchChangesets.call(repository)
end
def execute_update_mirrors_hook
message = 'Notifying mirrors about changes to this repository :'
y = ''
## Post to each post-receive URL
if repository.mirrors.active.any?
logger.info(message)
y << "\n#{message}\n"
repository.mirrors.active.each do |mirror|
y << RedmineHooks::UpdateMirrors.call(mirror, payloads)
end
end
y
end
def execute_call_webservices_hook
message = 'Notifying post receive urls about changes to this repository :'
y = ''
## Post to each post-receive URL
if repository.post_receive_urls.active.any?
logger.info(message)
y << "\n#{message}\n"
repository.post_receive_urls.active.each do |post_receive_url|
y << RedmineHooks::CallWebservices.call(post_receive_url, payloads)
end
end
y
end
end
end

View file

@ -0,0 +1,28 @@
module RepositoryMirrors
class Base
attr_reader :mirror
attr_reader :repository
def initialize(mirror)
@mirror = mirror
@repository = mirror.repository
end
class << self
def call(mirror)
new(mirror).call
end
end
def call
raise NotImplementedError
end
end
end

View file

@ -0,0 +1,55 @@
module RepositoryMirrors
class Push < Base
def call
push!
end
def push!
begin
push_message = repository.mirror_push(*command)
push_failed = false
rescue RedmineGitHosting::Error::GitoliteCommandException => e
push_message = e.output
push_failed = true
end
return push_failed, push_message
end
def command
[mirror.url, branch, push_args]
end
private
def push_args
mirror.mirror_mode? ? ['--mirror'] : mirror_args
end
def mirror_args
push_args = []
push_args << '--force' if mirror.force_mode?
push_args << '--all' if mirror.include_all_branches?
push_args << '--tags' if mirror.include_all_tags?
push_args
end
def branch
"#{dequote(mirror.explicit_refspec)}" unless mirror.explicit_refspec.blank?
end
# Put backquote in front of crucial characters
def dequote(in_string)
in_string.gsub(/[$,"\\\n]/) { |x| "\\" + x }
end
end
end

View file

@ -0,0 +1,91 @@
module RepositoryProtectedBranches
class MemberManager
attr_reader :protected_branch
def initialize(protected_branch)
@protected_branch = protected_branch
end
def current_user_ids
protected_branch.users.map(&:id)
end
def current_group_ids
protected_branch.groups.map(&:id)
end
def current_members
protected_branch.protected_branches_members
end
def users_by_group_id(id)
current_members.select { |pbm| pbm.principal.class.name == 'User' && pbm.inherited_by == id }.map(&:principal)
end
def add_users(ids)
create_user_member(ids, current_user_ids)
end
def add_groups(ids)
create_group_member(ids, current_group_ids) do |group|
ids = group.users.map(&:id)
current_ids = users_by_group_id(group.id).map(&:id)
create_user_member(ids, current_ids, inherited_by: group.id, destroy: false)
end
end
def create_user_member(ids, current_ids, opts = {}, &block)
create_member(ids, current_ids, 'User', opts, &block)
end
def create_group_member(ids, current_ids, opts = {}, &block)
create_member(ids, current_ids, 'Group', opts, &block)
end
def add_user_from_group(user, group_id)
ids = users_by_group_id(group_id).push(user).map(&:id)
current_ids = users_by_group_id(group_id).map(&:id)
create_user_member(ids, current_ids, inherited_by: group_id, destroy: false)
end
def remove_user_from_group(user, group_id)
return unless users_by_group_id(group_id).include?(user)
member = current_members.find_by_protected_branch_id_and_principal_id_and_inherited_by(protected_branch.id, user.id, group_id)
member.destroy! unless member.nil?
end
def create_member(ids, current_ids, klass, opts = {}, &block)
destroy = opts.fetch(:destroy, true)
inherited_by = opts.fetch(:inherited_by, nil)
ids = (ids || []).collect(&:to_i) - [0]
new_ids = ids - current_ids
new_ids.each do |id|
object = klass.constantize.find_by_id(id)
next if object.nil?
current_members.create(principal_id: object.id, inherited_by: inherited_by)
yield object if block_given?
end
if destroy
member_to_destroy = current_members.select { |m| m.principal.class.name == klass && !ids.include?(m.principal.id) }
member_to_destroy.each(&:destroy) if member_to_destroy.any?
end
end
end
end

View file

@ -0,0 +1,154 @@
module Settings
class Apply
include RedmineGitHosting::GitoliteAccessor::Methods
attr_reader :previous_settings
attr_reader :resync_projects
attr_reader :resync_ssh_keys
attr_reader :regenerate_ssh_keys
attr_reader :flush_cache
attr_reader :delete_trash_repo
def initialize(previous_settings, opts = {})
@previous_settings = previous_settings
@resync_projects = opts.delete(:resync_all_projects) { false }
@resync_ssh_keys = opts.delete(:resync_all_ssh_keys) { false }
@regenerate_ssh_keys = opts.delete(:regenerate_all_ssh_keys) { false }
@flush_cache = opts.delete(:flush_gitolite_cache) { false }
@delete_trash_repo = opts.delete(:delete_trash_repo) { [] }
end
class << self
def call(previous_settings, opts = {})
new(previous_settings, opts).call
end
end
def call
gitolite_accessor.flush_settings_cache
apply_settings
end
private
def apply_settings
check_gitolite_location
check_repo_hierarchy
check_gitolite_config
check_gitolite_default_values
check_hook_config
check_cache_config
do_resync_projects
do_resync_ssh_keys
do_regenerate_ssh_keys
do_flush_cache
do_delete_trash_repo
do_add_redmine_rw_access
end
def current_setting(setting)
Setting.plugin_redmine_git_hosting[setting]
end
def value_has_changed?(setting)
previous_settings[setting] != current_setting(setting)
end
def check_gitolite_location
## Gitolite location has changed. Remove temp directory, it will be recloned.
if value_has_changed?(:gitolite_server_host) ||
value_has_changed?(:gitolite_server_port) ||
value_has_changed?(:gitolite_user) ||
value_has_changed?(:gitolite_temp_dir)
RedmineGitHosting.logger.info("Temp dir has changed, remove the previous one : '#{previous_settings[:gitolite_temp_dir]}'")
FileUtils.rm_rf previous_settings[:gitolite_temp_dir]
end
end
def check_repo_hierarchy
## Storage infos has changed, move repositories!
if value_has_changed?(:gitolite_global_storage_dir) ||
value_has_changed?(:gitolite_redmine_storage_dir) ||
value_has_changed?(:hierarchical_organisation)
# Need to update everyone!
# We take all root projects (even those who are closed) and move each hierarchy individually
count = Project.includes(:repositories).all.select { |x| x if x.parent_id.nil? }.length
gitolite_accessor.move_repositories_tree(count) if count.positive?
end
end
def check_gitolite_config
## Gitolite config file has changed, create a new one!
if value_has_changed?(:gitolite_config_file) ||
value_has_changed?(:gitolite_identifier_prefix) ||
value_has_changed?(:gitolite_identifier_strip_user_id)
options = { message: 'Gitolite configuration has been modified, resync all projects (active, closed, archived)...' }
gitolite_accessor.update_projects('all', options)
end
end
def check_gitolite_default_values
## Gitolite default values has changed, update active projects
if value_has_changed?(:gitolite_notify_global_prefix) ||
value_has_changed?(:gitolite_notify_global_sender_address) ||
value_has_changed?(:gitolite_notify_global_include) ||
value_has_changed?(:gitolite_notify_global_exclude)
# Need to update everyone!
options = { message: 'Gitolite configuration has been modified, resync all active projects...' }
gitolite_accessor.update_projects('active', options)
end
end
def check_hook_config
## Gitolite hooks config has changed, update our .gitconfig!
if value_has_changed?(:gitolite_hooks_debug) ||
value_has_changed?(:gitolite_hooks_url) ||
value_has_changed?(:gitolite_hooks_are_asynchronous)
# Need to update our .gitconfig
RedmineGitHosting::Config.update_hook_params!
end
end
def check_cache_config
## Gitolite cache has changed, clear cache entries!
RedmineGitHosting::Cache.clear_obsolete_cache_entries if value_has_changed?(:gitolite_cache_max_time)
end
def do_resync_projects
## A resync has been asked within the interface, update all projects in force mode
options = { message: 'Forced resync of all projects (active, closed, archived)...', force: true }
gitolite_accessor.update_projects('all', options) if resync_projects
end
def do_resync_ssh_keys
## A resync has been asked within the interface, update all projects in force mode
gitolite_accessor.resync_ssh_keys if resync_ssh_keys
end
def do_regenerate_ssh_keys
gitolite_accessor.regenerate_ssh_keys if regenerate_ssh_keys
end
def do_flush_cache
## A cache flush has been asked within the interface
gitolite_accessor.flush_git_cache if flush_cache
end
def do_delete_trash_repo
gitolite_accessor.delete_from_recycle_bin(delete_trash_repo) unless delete_trash_repo.empty?
end
def do_add_redmine_rw_access
if Additionals.true? current_setting(:redmine_has_rw_access_on_all_repos)
gitolite_accessor.enable_rw_access
else
gitolite_accessor.disable_rw_access
end
end
end
end

View file

@ -0,0 +1,31 @@
<% @entries.each do |entry| %>
<% tr_id = Digest::MD5.hexdigest(entry.path)
depth = params[:depth].to_i %>
<% ent_path = Redmine::CodesetUtil.replace_invalid_utf8(entry.path) %>
<% ent_name = Redmine::CodesetUtil.replace_invalid_utf8(entry.name) %>
<tr id="<%= tr_id %>" class="<%= h params[:parent_id] %> entry <%= entry.kind %>">
<td style="padding-left: <%=18 * depth%>px;" class="<%=
@repository.report_last_commit ? "filename" : "filename_no_report" %>">
<% if entry.is_dir? %>
<span class="expander" onclick="scmEntryClick('<%= tr_id %>', '<%= escape_javascript(url_for(
:action => 'show',
:id => @project,
:repository_id => @repository.identifier_param,
:path => to_path_param(ent_path),
:rev => @rev,
:depth => (depth + 1),
:parent_id => tr_id)) %>');">&nbsp;</span>
<% end %>
<%= link_to h(ent_name),
{:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev},
:class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%>
</td>
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
<% if @repository.report_last_commit %>
<td class="revision"><%= link_to_revision2(entry.changeset, @repository) if entry.changeset %></td>
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
<td class="author"><%= entry.author %></td>
<td class="comments"><%= entry.changeset.comments.truncate(50) if entry.changeset %></td>
<% end %>
</tr>
<% end %>

View file

@ -0,0 +1,51 @@
<% show_revision_graph = ( @repository.supports_revision_graph? && path.blank? ) %>
<%= if show_revision_graph && revisions && revisions.any?
indexed_commits, graph_space = index_commits(revisions, @repository.branches) do |scmid|
url_for(
:controller => 'archived_repositories',
:action => 'revision',
:id => project,
:repository_id => @repository.identifier_param,
:rev => scmid)
end
render :partial => 'revision_graph',
:locals => {
:commits => indexed_commits,
:space => graph_space
}
end %>
<%= form_tag(
{:controller => 'archived_repositories', :action => 'diff', :id => project,
:repository_id => @repository.identifier_param, :path => to_path_param(path)},
:method => :get
) do %>
<table class="list changesets">
<thead><tr>
<th>#</th>
<th></th>
<th></th>
<th><%= l(:label_date) %></th>
<th><%= l(:field_author) %></th>
<th><%= l(:field_comments) %></th>
</tr></thead>
<tbody>
<% show_diff = revisions.size > 1 %>
<% line_num = 1 %>
<% revisions.each do |changeset| %>
<tr class="changeset <%= cycle 'odd', 'even' %>">
<% id_style = (show_revision_graph ? "padding-left:#{(graph_space + 1) * 20}px" : nil) %>
<%= content_tag(:td, :class => 'id', :style => id_style) do %>
<%= link_to_revision2(changeset, @repository) %>
<% end %>
<td class="checkbox"><%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('#cbto-#{line_num+1}').attr('checked',true);") if show_diff && (line_num < revisions.size) %></td>
<td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('#cb-#{line_num}').attr('checked')) {$('#cb-#{line_num-1}').attr('checked',true);}") if show_diff && (line_num > 1) %></td>
<td class="committed_on"><%= format_time(changeset.committed_on) %></td>
<td class="author"><%= changeset.author.to_s.truncate(30) %></td>
<td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
</tr>
<% line_num += 1 %>
<% end %>
</tbody>
</table>
<%= submit_tag(l(:label_view_diff), :name => nil) if show_diff %>
<% end %>

View file

@ -0,0 +1,46 @@
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %>
<div class="contextual">
<%= render :partial => 'navigation' %>
</div>
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
<%= render :partial => 'link_to_functions' %>
<% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
<div class="autoscroll">
<table class="filecontent annotate syntaxhl">
<tbody>
<% line_num = 1; previous_revision = nil %>
<% syntax_highlight_lines(@path, Redmine::CodesetUtil.to_utf8_by_setting(@annotate.content)).each do |line| %>
<% revision = @annotate.revisions[line_num - 1] %>
<tr id="L<%= line_num %>" class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
<th class="line-num"><a href="#L<%= line_num %>"><%= line_num %></a></th>
<td class="revision">
<% if revision && revision != previous_revision %>
<%= revision.identifier ?
link_to_revision2(revision, @repository) : format_revision(revision) %>
<% end %>
</td>
<td class="author">
<% if revision && revision != previous_revision %>
<% author = Redmine::CodesetUtil.to_utf8(revision.author.to_s,
@repository.repo_log_encoding) %>
<%= author.split('<').first %>
<% end %>
</td>
<td class="line-code"><pre><%= line.html_safe %></pre></td>
</tr>
<% line_num += 1; previous_revision = revision %>
<% end %>
</tbody>
</table>
</div>
<% html_title(l(:button_annotate)) -%>
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'scm' %>
<% end %>

View file

@ -0,0 +1,89 @@
<%# This is used to display basic git setup instructions, like on github... %>
<% flash.now[:warning] = l(:notice_empty_repository) %>
<div class="box">
<% if User.current.allowed_to?(:view_changesets, @project) %>
<h2><%= l(:label_help_git_setup) %> :</h2>
<pre>
<a href="https://git-scm.com/download" target="_blank"><%= l(:label_download_and_install_git) %></a>
git config --global user.name "<%= User.current.name(:firstname_lastname) %>"
git config --global user.email <%= User.current.mail ? User.current.mail : "mail@example.com" %>
<% if !User.current.anonymous? && User.current.gitolite_public_keys.active.length == 0 %>
<%= link_to "Upload SSH Public Key", {:controller => 'my', :action => 'account'} %>
<% end %>
</pre>
<% if User.current.allowed_to?(:commit_access, @project) %>
<div id="repository_setup">
<h2><%= l(:label_help_repository_setup_new) %> :</h2>
<pre>
mkdir <%= @repository.redmine_name %>
cd <%= @repository.redmine_name %>
git init
touch readme.txt
git add readme.txt
git commit -m 'Initializing <%= @repository.redmine_name %> repository'
git remote add origin <span class="git_url_access"></span>
git push -u origin master
</pre>
<h2><%= l(:label_help_repository_setup_existing) %> :</h2>
<pre>
cd existing_git_repo
git remote add origin <span class="git_url_access"></span>
git push -u origin master
</pre>
</div>
<% end %>
<h2><%= l(:label_help_repository_clone) %> :</h2>
<pre>
git clone <span class="git_url_access"></span>
</pre>
<% else %>
<%= l(:label_help_no_repo_rights) %>
<% end %>
</div>
<% content_for :sidebar do %>
<h3><%= l(:label_repository_plural) %></h3>
<ul class="repository git">
<% @repositories.sort.each do |repo| %>
<li class="repository git"><%= link_to h(repo.name), {:controller => 'archived_repositories', :action => 'show', :id => @project, :repository_id => repo.identifier_param, :rev => nil, :path => nil},
:class => 'repository' + (@repository.is_a?(Repository::Xitolite) ? ' git' : '') + (repo == @repository ? ' selected' : '') %>
</li>
<% end %>
</ul>
<% if @repository.is_a?(Repository::Xitolite) && @repository.urls_are_viewable? %>
<div class="git_hosting_urls">
<h3><%= l(:label_repository_access_url) %></h3>
<%= render :partial => 'common/git_urls', :locals => {:repository => @repository} %>
</div>
<% end %>
<% end %>
<% content_for :header_tags do %>
<%= javascript_tag do %>
$(document).ready(function() {
var key = $('#git_url_list li').first().attr('id');
var access = access_list[key];
$('.git_url_access').html(access.url);
$('#git_url_list li').on('click', function(){
var key = $(this).attr('id');
var access = access_list[key];
$('.git_url_access').html(access.url);
if (access.commiter == true) {
$('#repository_setup').show();
} else {
$('#repository_setup').hide();
}
});
});
<% end %>
<% end %>

View file

@ -0,0 +1,17 @@
h2 = l(:label_archived_repositories)
- if @archived_projects.any?
ul
- @archived_projects.each do |project|
- if project.repositories.any?
li
= project.name
ul
- project.repositories.each do |repository|
li
= link_to h(repository.url), { controller: 'archived_repositories',
action: 'show',
id: project,
repository_id: repository.identifier_param }
- else
p.nodata = l(:label_no_data)

View file

@ -0,0 +1,101 @@
<div class="contextual">
&#171;
<% unless @changeset.previous.nil? -%>
<%= link_to_revision2(@changeset.previous, @repository, :text => l(:label_previous)) %>
<% else -%>
<%= l(:label_previous) %>
<% end -%>
|
<% unless @changeset.next.nil? -%>
<%= link_to_revision2(@changeset.next, @repository, :text => l(:label_next)) %>
<% else -%>
<%= l(:label_next) %>
<% end -%>
&#187;&nbsp;
<%= form_tag({:controller => 'archived_repositories',
:action => 'revision',
:id => @project,
:repository_id => @repository.identifier_param,
:rev => nil},
:method => :get) do %>
<%= text_field_tag 'rev', @rev, :size => 8 %>
<%= submit_tag 'OK', :name => nil %>
<% end %>
</div>
<h2><%= avatar(@changeset.user, :size => "24") %><%= l(:label_revision) %> <%= format_revision(@changeset) %></h2>
<% if @changeset.scmid.present? || @changeset.parents.present? || @changeset.children.present? %>
<table class="revision-info">
<% if @changeset.scmid.present? %>
<tr>
<td>ID</td><td><%= h(@changeset.scmid) %></td>
</tr>
<% end %>
<% if @changeset.parents.present? %>
<tr>
<td><%= l(:label_parent_revision) %></td>
<td>
<%= @changeset.parents.collect{
|p| link_to_revision2(p, @repository, :text => format_revision(p))
}.join(", ").html_safe %>
</td>
</tr>
<% end %>
<% if @changeset.children.present? %>
<tr>
<td><%= l(:label_child_revision) %></td>
<td>
<%= @changeset.children.collect{
|p| link_to_revision2(p, @repository, :text => format_revision(p))
}.join(", ").html_safe %>
</td>
</tr>
<% end %>
</table>
<% end %>
<p>
<span class="author">
<%= authoring(@changeset.committed_on, @changeset.author) %>
</span>
</p>
<%= textilizable @changeset.comments %>
<% if @changeset.issues.visible.any? || User.current.allowed_to?(:manage_related_issues, @repository.project) %>
<%= render :partial => 'related_issues' %>
<% end %>
<% if User.current.allowed_to?(:browse_repository, @project) %>
<h3><%= l(:label_attachment_plural) %></h3>
<ul id="changes-legend">
<li class="change change-A"><%= l(:label_added) %></li>
<li class="change change-M"><%= l(:label_modified) %></li>
<li class="change change-C"><%= l(:label_copied) %></li>
<li class="change change-R"><%= l(:label_renamed) %></li>
<li class="change change-D"><%= l(:label_deleted) %></li>
</ul>
<p><%= link_to(l(:label_view_diff),
:action => 'diff',
:id => @project,
:repository_id => @repository.identifier_param,
:path => "",
:rev => @changeset.identifier) if @changeset.filechanges.any? %></p>
<div class="changeset-changes">
<%= render_changeset_changes %>
</div>
<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
<% end %>
<%
title = "#{l(:label_revision)} #{format_revision(@changeset)}"
title << " - #{@changeset.comments.truncate(80)}"
html_title(title)
-%>

View file

@ -0,0 +1,34 @@
<div class="contextual">
<%= form_tag(
{:controller => 'archived_repositories', :action => 'revision', :id => @project,
:repository_id => @repository.identifier_param},
:method => :get
) do %>
<%= l(:label_revision) %>: <%= text_field_tag 'rev', nil, :size => 8 %>
<%= submit_tag 'OK' %>
<% end %>
</div>
<h2><%= l(:label_revision_plural) %></h2>
<%= render :partial => 'revisions',
:locals => {:project => @project,
:path => '',
:revisions => @changesets,
:entry => nil } %>
<p class="pagination"><%= pagination_links_full @changeset_pages,@changeset_count %></p>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
<%= auto_discovery_link_tag(
:atom,
params.merge(
{:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
<% end %>
<% other_formats_links do |f| %>
<%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
<% end %>
<% html_title(l(:label_revision_plural)) -%>

Some files were not shown because too many files have changed in this diff Show more