Añade plugin Redmine Git Hosting 4.0.2
This commit is contained in:
parent
472cb1ea76
commit
bdd66d941f
494 changed files with 36768 additions and 0 deletions
70
plugins/redmine_git_hosting/lib/default_settings.yml
Normal file
70
plugins/redmine_git_hosting/lib/default_settings.yml
Normal file
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
# Gitolite SSH Config
|
||||
gitolite_user: 'git'
|
||||
gitolite_server_host: '127.0.0.1'
|
||||
gitolite_server_port: '22'
|
||||
gitolite_ssh_private_key: <%= Rails.root.join('plugins', 'redmine_git_hosting', 'ssh_keys', 'redmine_gitolite_admin_id_rsa') %>
|
||||
gitolite_ssh_public_key: <%= Rails.root.join('plugins', 'redmine_git_hosting', 'ssh_keys', 'redmine_gitolite_admin_id_rsa.pub') %>
|
||||
|
||||
# Gitolite Storage Config
|
||||
gitolite_global_storage_dir: 'repositories/'
|
||||
gitolite_redmine_storage_dir: ''
|
||||
gitolite_recycle_bin_dir: 'recycle_bin/'
|
||||
|
||||
# Gitolite Config File
|
||||
gitolite_config_file: 'gitolite.conf'
|
||||
gitolite_identifier_prefix: 'redmine_'
|
||||
gitolite_identifier_strip_user_id: 'false'
|
||||
|
||||
# Gitolite Global Config
|
||||
gitolite_temp_dir: <%= Rails.root.join('tmp', 'redmine_git_hosting') %>
|
||||
gitolite_recycle_bin_expiration_time: '24.0'
|
||||
gitolite_log_level: 'info'
|
||||
git_config_username: 'Redmine Git Hosting'
|
||||
git_config_email: 'redmine@example.net'
|
||||
|
||||
# Gitolite Hooks Config
|
||||
gitolite_overwrite_existing_hooks: 'true'
|
||||
gitolite_hooks_are_asynchronous: 'false'
|
||||
gitolite_hooks_debug: 'false'
|
||||
gitolite_hooks_url: 'http://localhost:3000'
|
||||
|
||||
# Gitolite Cache Config
|
||||
gitolite_cache_max_time: '86400'
|
||||
gitolite_cache_max_size: '16'
|
||||
gitolite_cache_max_elements: '2000'
|
||||
gitolite_cache_adapter: 'database'
|
||||
|
||||
# Gitolite Access Config
|
||||
ssh_server_domain: 'localhost'
|
||||
http_server_domain: 'localhost'
|
||||
https_server_domain: 'localhost'
|
||||
http_server_subdir: ''
|
||||
show_repositories_url: 'true'
|
||||
gitolite_daemon_by_default: 'false'
|
||||
gitolite_http_by_default: '1'
|
||||
|
||||
# Redmine Config
|
||||
redmine_has_rw_access_on_all_repos: 'true'
|
||||
all_projects_use_git: 'false'
|
||||
init_repositories_on_create: 'false'
|
||||
delete_git_repositories: 'true'
|
||||
|
||||
# This params work together!
|
||||
# When hierarchical_organisation = true unique_repo_identifier MUST be false
|
||||
# When hierarchical_organisation = false unique_repo_identifier MUST be true
|
||||
hierarchical_organisation: 'true'
|
||||
unique_repo_identifier: 'false'
|
||||
|
||||
# Download Revision Config
|
||||
download_revision_enabled: 'true'
|
||||
|
||||
# Git Mailing List Config
|
||||
gitolite_notify_by_default: 'false'
|
||||
gitolite_notify_global_prefix: '[REDMINE]'
|
||||
gitolite_notify_global_sender_address: 'redmine@example.net'
|
||||
gitolite_notify_global_include: []
|
||||
gitolite_notify_global_exclude: []
|
||||
|
||||
# Sidekiq Config
|
||||
gitolite_use_sidekiq: 'false'
|
2
plugins/redmine_git_hosting/lib/hrack/init.rb
Normal file
2
plugins/redmine_git_hosting/lib/hrack/init.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
||||
require 'hrack'
|
3
plugins/redmine_git_hosting/lib/hrack/lib/hrack.rb
Normal file
3
plugins/redmine_git_hosting/lib/hrack/lib/hrack.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
module Hrack
|
||||
require 'hrack/bundle'
|
||||
end
|
16
plugins/redmine_git_hosting/lib/hrack/lib/hrack/bundle.rb
Normal file
16
plugins/redmine_git_hosting/lib/hrack/lib/hrack/bundle.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
require 'rack/builder'
|
||||
require 'rack/parser'
|
||||
require 'hrack/server'
|
||||
|
||||
module Hrack
|
||||
module Bundle
|
||||
module_function
|
||||
|
||||
def new(config)
|
||||
Rack::Builder.new do
|
||||
use Rack::Parser
|
||||
run Hrack::Server.new(config)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
142
plugins/redmine_git_hosting/lib/hrack/lib/hrack/server.rb
Normal file
142
plugins/redmine_git_hosting/lib/hrack/lib/hrack/server.rb
Normal file
|
@ -0,0 +1,142 @@
|
|||
require 'digest/sha1'
|
||||
|
||||
module Hrack
|
||||
class Server
|
||||
|
||||
attr_reader :params
|
||||
|
||||
PLAIN_TYPE = { 'Content-Type' => 'text/plain' }
|
||||
|
||||
|
||||
def initialize(config = {})
|
||||
end
|
||||
|
||||
|
||||
def call(env)
|
||||
dup._call(env)
|
||||
end
|
||||
|
||||
|
||||
def _call(env)
|
||||
@env = env
|
||||
@req = Rack::Request.new(env)
|
||||
@params = @req.params.deep_symbolize_keys
|
||||
|
||||
command, @project = match_routing
|
||||
|
||||
return render_404('Command Not Found') if !command
|
||||
return render_404('Project Not Found') if !@project
|
||||
|
||||
self.method(command).call()
|
||||
end
|
||||
|
||||
|
||||
def post_receive_redmine
|
||||
@repository = find_repository
|
||||
return render_404('Repository Not Found') if @repository.nil?
|
||||
return render_403('The hook key provided is not valid. Please let your server admin know about it') if !valid_encoded_time?(params[:clear_time], params[:encoded_time], @repository.gitolite_hook_key)
|
||||
|
||||
@res = Rack::Response.new
|
||||
@res.status = 200
|
||||
@res['Content-Type'] = 'text/plain;'
|
||||
@res.finish do
|
||||
@res.write Repositories::ExecuteHooks.call(@repository, :fetch_changesets)
|
||||
@res.write Repositories::ExecuteHooks.call(@repository, :update_mirrors, payloads)
|
||||
@res.write Repositories::ExecuteHooks.call(@repository, :call_webservices, payloads)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def post_receive_github
|
||||
Projects::ExecuteHooks.call(@project, :github, params)
|
||||
render_200('OK!')
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def payloads
|
||||
@payloads ||= Repositories::BuildPayload.call(@repository, params[:refs])
|
||||
end
|
||||
|
||||
|
||||
def match_routing
|
||||
command = find_command
|
||||
project = find_project
|
||||
return command, project
|
||||
end
|
||||
|
||||
|
||||
def find_command
|
||||
return nil if !path_parameters.has_key?(:type)
|
||||
case path_parameters[:type]
|
||||
when 'redmine'
|
||||
:post_receive_redmine
|
||||
when 'github'
|
||||
:post_receive_github
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def find_project
|
||||
if path_parameters.has_key?(:projectid)
|
||||
Project.find_by_identifier(path_parameters[:projectid])
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Locate that actual repository that is in use here.
|
||||
# Notice that an empty "repositoryid" is assumed to refer to the default repo for a project
|
||||
def find_repository
|
||||
if params[:repositoryid] && !params[:repositoryid].blank?
|
||||
@project.repositories.find_by_identifier(params[:repositoryid])
|
||||
else
|
||||
# return default or first repo with blank identifier
|
||||
@project.repository || @project.repo_blank_ident
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def render_200(message)
|
||||
[200, PLAIN_TYPE, [message]]
|
||||
end
|
||||
|
||||
|
||||
def render_404(message)
|
||||
[404, PLAIN_TYPE, [message]]
|
||||
end
|
||||
|
||||
|
||||
def render_403(message)
|
||||
[403, PLAIN_TYPE, [message]]
|
||||
end
|
||||
|
||||
|
||||
def path_parameters
|
||||
@env['action_dispatch.request.path_parameters']
|
||||
end
|
||||
|
||||
|
||||
def valid_encoded_time?(clear_time, encoded_time, key)
|
||||
cur_time = Time.new.utc.to_i
|
||||
test_time = clear_time.to_i
|
||||
not_to_late?(cur_time, test_time) && encode_key(clear_time, key) == encoded_time.to_s
|
||||
end
|
||||
|
||||
|
||||
def not_to_late?(cur_time, test_time)
|
||||
cur_time - test_time < 5 * 60
|
||||
end
|
||||
|
||||
|
||||
def encode_key(time, key)
|
||||
Digest::SHA1.hexdigest(time.to_s + key.to_s).to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
module Hrack
|
||||
VERSION = '1.0.0'
|
||||
end
|
73
plugins/redmine_git_hosting/lib/load_gitolite_hooks.rb
Normal file
73
plugins/redmine_git_hosting/lib/load_gitolite_hooks.rb
Normal file
|
@ -0,0 +1,73 @@
|
|||
RedmineGitHosting::GitoliteHooks.register_hooks do
|
||||
# Set source dir
|
||||
source_dir Rails.root.join('plugins', 'redmine_git_hosting', 'contrib', 'hooks').to_s
|
||||
|
||||
# Declare GitoliteHooks to install
|
||||
|
||||
# Install executable
|
||||
gitolite_hook do
|
||||
name 'redmine_gitolite.rb'
|
||||
source 'post-receive/redmine_gitolite.rb'
|
||||
destination 'post-receive'
|
||||
executable true
|
||||
end
|
||||
|
||||
gitolite_hook do
|
||||
name 'mail_notifications.py'
|
||||
source 'post-receive/mail_notifications.py'
|
||||
destination 'post-receive.d/mail_notifications'
|
||||
executable true
|
||||
end
|
||||
|
||||
|
||||
# Install libs
|
||||
gitolite_hook do
|
||||
name 'git_hosting_config.rb'
|
||||
source 'post-receive/lib/git_hosting_config.rb'
|
||||
destination 'lib/git_hosting/config.rb'
|
||||
executable false
|
||||
end
|
||||
|
||||
gitolite_hook do
|
||||
name 'git_hosting_custom_hook.rb'
|
||||
source 'post-receive/lib/git_hosting_custom_hook.rb'
|
||||
destination 'lib/git_hosting/custom_hook.rb'
|
||||
executable false
|
||||
end
|
||||
|
||||
gitolite_hook do
|
||||
name 'git_hosting_http_helper.rb'
|
||||
source 'post-receive/lib/git_hosting_http_helper.rb'
|
||||
destination 'lib/git_hosting/http_helper.rb'
|
||||
executable false
|
||||
end
|
||||
|
||||
gitolite_hook do
|
||||
name 'git_hosting_hook_logger.rb'
|
||||
source 'post-receive/lib/git_hosting_hook_logger.rb'
|
||||
destination 'lib/git_hosting/hook_logger.rb'
|
||||
executable false
|
||||
end
|
||||
|
||||
gitolite_hook do
|
||||
name 'git_hosting_post_receive.rb'
|
||||
source 'post-receive/lib/git_hosting_post_receive.rb'
|
||||
destination 'lib/git_hosting/post_receive.rb'
|
||||
executable false
|
||||
end
|
||||
|
||||
gitolite_hook do
|
||||
name 'git_multimail.py'
|
||||
source 'post-receive/lib/git_multimail.py'
|
||||
destination 'post-receive.d/git_multimail.py'
|
||||
executable false
|
||||
end
|
||||
end
|
||||
|
||||
# Gitolite hooks can be found in Redmine root dir or in plugin root dir
|
||||
[
|
||||
Rails.root.join('redmine_git_hosting_hooks.rb').to_s,
|
||||
Rails.root.join('plugins', 'redmine_git_hosting', 'custom_hooks.rb').to_s
|
||||
].each do |file|
|
||||
require_dependency file if File.exist?(file)
|
||||
end
|
|
@ -0,0 +1,580 @@
|
|||
require 'redmine/scm/adapters/abstract_adapter'
|
||||
|
||||
# XitoliteAdapter inherits from GitAdapter but some classes which are define directly in GitAdapter are not inherited
|
||||
# (GitBranch, ScmCommandAborted and maybe others) so it raises NameError exception.
|
||||
# To fix this I had to reimplement (copy/past) the whole GitAdapter class in XitoliteAdapter...
|
||||
# I wanted to avoid it to avoid code duplication but it seems to be the only way...
|
||||
|
||||
module Redmine
|
||||
module Scm
|
||||
module Adapters
|
||||
class XitoliteAdapter < AbstractAdapter
|
||||
|
||||
# Git executable name
|
||||
XITOLITE_BIN = Redmine::Configuration['scm_git_command'] || "git"
|
||||
|
||||
class GitBranch < Branch
|
||||
attr_accessor :is_default
|
||||
end
|
||||
|
||||
class << self
|
||||
|
||||
def client_command
|
||||
@@bin ||= XITOLITE_BIN
|
||||
end
|
||||
|
||||
|
||||
def sq_bin
|
||||
@@sq_bin ||= shell_quote_command
|
||||
end
|
||||
|
||||
|
||||
def client_version
|
||||
@@client_version ||= (scm_command_version || [])
|
||||
end
|
||||
|
||||
|
||||
def client_available
|
||||
!client_version.empty?
|
||||
end
|
||||
|
||||
|
||||
def scm_command_version
|
||||
scm_version = scm_version_from_command_line.dup
|
||||
if scm_version.respond_to?(:force_encoding)
|
||||
scm_version.force_encoding('ASCII-8BIT')
|
||||
end
|
||||
if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
|
||||
m[2].scan(%r{\d+}).collect(&:to_i)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Change from the original method
|
||||
def scm_version_from_command_line
|
||||
RedmineGitHosting::Commands.git_version
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def initialize(url, root_url = nil, login = nil, password = nil, path_encoding = nil)
|
||||
super
|
||||
@path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
|
||||
end
|
||||
|
||||
|
||||
def path_encoding
|
||||
@path_encoding
|
||||
end
|
||||
|
||||
|
||||
def info
|
||||
begin
|
||||
Info.new(root_url: url, lastrev: lastrev('', nil))
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def branches
|
||||
return @branches if !@branches.nil?
|
||||
@branches = []
|
||||
cmd_args = %w|branch --no-color --verbose --no-abbrev|
|
||||
git_cmd(cmd_args) do |io|
|
||||
io.each_line do |line|
|
||||
branch_rev = line.match('\s*(\*?)\s*(.*?)\s*([0-9a-f]{40}).*$')
|
||||
bran = GitBranch.new(branch_rev[2].to_s.force_encoding(Encoding::UTF_8))
|
||||
bran.revision = branch_rev[3]
|
||||
bran.scmid = branch_rev[3]
|
||||
bran.is_default = (branch_rev[1] == '*')
|
||||
@branches << bran
|
||||
end
|
||||
end
|
||||
@branches.sort!
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
[]
|
||||
end
|
||||
|
||||
|
||||
def tags
|
||||
return @tags if !@tags.nil?
|
||||
@tags = []
|
||||
cmd_args = %w|tag|
|
||||
git_cmd(cmd_args) do |io|
|
||||
@tags = io.readlines.sort!.map { |t| t.strip.force_encoding(Encoding::UTF_8) }
|
||||
end
|
||||
@tags
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
[]
|
||||
end
|
||||
|
||||
|
||||
def default_branch
|
||||
bras = self.branches
|
||||
return nil if bras.nil?
|
||||
default_bras = bras.select { |x| x.is_default == true }
|
||||
return default_bras.first.to_s if !default_bras.empty?
|
||||
master_bras = bras.select { |x| x.to_s == 'master' }
|
||||
master_bras.empty? ? bras.first.to_s : 'master'
|
||||
end
|
||||
|
||||
|
||||
def entry(path = nil, identifier = nil)
|
||||
parts = path.to_s.split(%r{[\/\\]}).select { |n| !n.blank? }
|
||||
search_path = parts[0..-2].join('/')
|
||||
search_name = parts[-1]
|
||||
if search_path.blank? && search_name.blank?
|
||||
# Root entry
|
||||
Entry.new(path: '', kind: 'dir')
|
||||
else
|
||||
# Search for the entry in the parent directory
|
||||
es = entries(search_path, identifier, report_last_commit: false)
|
||||
es ? es.detect { |e| e.name == search_name } : nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def entries(path = nil, identifier = nil, options = {})
|
||||
path ||= ''
|
||||
p = scm_iconv(@path_encoding, 'UTF-8', path)
|
||||
entries = Entries.new
|
||||
cmd_args = %w|ls-tree -l|
|
||||
cmd_args << "HEAD:#{p}" if identifier.nil?
|
||||
cmd_args << "#{identifier}:#{p}" if identifier
|
||||
git_cmd(cmd_args) do |io|
|
||||
io.each_line do |line|
|
||||
e = line.chomp.to_s
|
||||
if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
|
||||
type = $1
|
||||
sha = $2
|
||||
size = $3
|
||||
name = $4
|
||||
if name.respond_to?(:force_encoding)
|
||||
name.force_encoding(@path_encoding)
|
||||
end
|
||||
full_path = p.empty? ? name : "#{p}/#{name}"
|
||||
n = scm_iconv('UTF-8', @path_encoding, name)
|
||||
full_p = scm_iconv('UTF-8', @path_encoding, full_path)
|
||||
entries << Entry.new({:name => n,
|
||||
:path => full_p,
|
||||
:kind => (type == "tree") ? 'dir' : 'file',
|
||||
:size => (type == "tree") ? nil : size,
|
||||
:lastrev => options[:report_last_commit] ?
|
||||
lastrev(full_path, identifier) : Revision.new
|
||||
}) unless entries.detect { |entry| entry.name == name }
|
||||
end
|
||||
end
|
||||
end
|
||||
entries.sort_by_name
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
[]
|
||||
end
|
||||
|
||||
|
||||
def lastrev(path, rev)
|
||||
return nil if path.nil?
|
||||
cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
|
||||
cmd_args << rev if rev
|
||||
cmd_args << "--" << path unless path.empty?
|
||||
lines = []
|
||||
git_cmd(cmd_args) { |io| lines = io.readlines }
|
||||
begin
|
||||
id = lines[0].split[1]
|
||||
author = lines[1].match('Author:\s+(.*)$')[1]
|
||||
time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
|
||||
|
||||
Revision.new({
|
||||
:identifier => id,
|
||||
:scmid => id,
|
||||
:author => author,
|
||||
:time => time,
|
||||
:message => nil,
|
||||
:paths => nil
|
||||
})
|
||||
rescue NoMethodError => e
|
||||
logger.error("The revision '#{path}' has a wrong format")
|
||||
return nil
|
||||
end
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
def revisions(path, identifier_from, identifier_to, options = {})
|
||||
revs = Revisions.new
|
||||
cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller --parents --stdin|
|
||||
cmd_args << "--reverse" if options[:reverse]
|
||||
cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
|
||||
cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
|
||||
revisions = []
|
||||
if identifier_from || identifier_to
|
||||
revisions << ''
|
||||
revisions[0] << "#{identifier_from}.." if identifier_from
|
||||
revisions[0] << "#{identifier_to}" if identifier_to
|
||||
else
|
||||
revisions += options[:includes] unless options[:includes].blank?
|
||||
revisions += options[:excludes].map { |r| "^#{r}" } unless options[:excludes].blank?
|
||||
end
|
||||
|
||||
git_cmd(cmd_args, { write_stdin: true }) do |io|
|
||||
io.binmode
|
||||
io.puts(revisions.join("\n"))
|
||||
io.close_write
|
||||
|
||||
files = []
|
||||
changeset = {}
|
||||
|
||||
# 0: not parsing desc or files
|
||||
# 1: parsing desc
|
||||
# 2: parsing files
|
||||
parsing_descr = 0
|
||||
|
||||
io.each_line do |line|
|
||||
if line =~ /^commit ([0-9a-f]{40})(( [0-9a-f]{40})*)$/
|
||||
key = 'commit'
|
||||
value = $1
|
||||
parents_str = $2
|
||||
if parsing_descr == 1 || parsing_descr == 2
|
||||
parsing_descr = 0
|
||||
revision = Revision.new({
|
||||
:identifier => changeset[:commit],
|
||||
:scmid => changeset[:commit],
|
||||
:author => changeset[:author],
|
||||
:time => Time.parse(changeset[:date]),
|
||||
:message => changeset[:description],
|
||||
:paths => files,
|
||||
:parents => changeset[:parents]
|
||||
})
|
||||
if block_given?
|
||||
yield revision
|
||||
else
|
||||
revs << revision
|
||||
end
|
||||
changeset = {}
|
||||
files = []
|
||||
end
|
||||
changeset[:commit] = $1
|
||||
unless parents_str.nil? or parents_str == ''
|
||||
changeset[:parents] = parents_str.strip.split(' ')
|
||||
end
|
||||
elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
|
||||
key = $1
|
||||
value = $2
|
||||
if key == 'Author'
|
||||
changeset[:author] = value
|
||||
elsif key == 'CommitDate'
|
||||
changeset[:date] = value
|
||||
end
|
||||
elsif (parsing_descr == 0) && line.chomp.to_s == ''
|
||||
parsing_descr = 1
|
||||
changeset[:description] = ''
|
||||
elsif (parsing_descr == 1 || parsing_descr == 2) \
|
||||
&& line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
|
||||
parsing_descr = 2
|
||||
fileaction = $1
|
||||
filepath = $2
|
||||
p = scm_iconv('UTF-8', @path_encoding, filepath)
|
||||
files << { action: fileaction, path: p }
|
||||
elsif (parsing_descr == 1 || parsing_descr == 2) \
|
||||
&& line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
|
||||
parsing_descr = 2
|
||||
fileaction = $1
|
||||
filepath = $3
|
||||
p = scm_iconv('UTF-8', @path_encoding, filepath)
|
||||
files << { action: fileaction, path: p }
|
||||
elsif (parsing_descr == 1) && line.chomp.to_s == ''
|
||||
parsing_descr = 2
|
||||
elsif (parsing_descr == 1)
|
||||
changeset[:description] << line[4..-1]
|
||||
end
|
||||
end
|
||||
|
||||
if changeset[:commit]
|
||||
revision = Revision.new({
|
||||
:identifier => changeset[:commit],
|
||||
:scmid => changeset[:commit],
|
||||
:author => changeset[:author],
|
||||
:time => Time.parse(changeset[:date]),
|
||||
:message => changeset[:description],
|
||||
:paths => files,
|
||||
:parents => changeset[:parents]
|
||||
})
|
||||
if block_given?
|
||||
yield revision
|
||||
else
|
||||
revs << revision
|
||||
end
|
||||
end
|
||||
end
|
||||
revs
|
||||
rescue ScmCommandAborted => e
|
||||
err_msg = "git log error: #{e.message}"
|
||||
logger.error(err_msg)
|
||||
if block_given?
|
||||
raise CommandFailed, err_msg
|
||||
else
|
||||
revs
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Override the original method to accept options hash
|
||||
# which may contain *bypass_cache* flag and pass the options hash to *git_cmd*.
|
||||
#
|
||||
def diff(path, identifier_from, identifier_to = nil, opts = {})
|
||||
path ||= ''
|
||||
cmd_args = []
|
||||
if identifier_to
|
||||
cmd_args << 'diff' << '--no-color' << identifier_to << identifier_from
|
||||
else
|
||||
cmd_args << 'show' << '--no-color' << identifier_from
|
||||
end
|
||||
cmd_args << '--' << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
|
||||
diff = []
|
||||
git_cmd(cmd_args, opts) do |io|
|
||||
io.each_line do |line|
|
||||
diff << line
|
||||
end
|
||||
end
|
||||
diff
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
def annotate(path, identifier = nil)
|
||||
identifier = 'HEAD' if identifier.blank?
|
||||
cmd_args = %w|blame --encoding=UTF-8|
|
||||
cmd_args << '-p' << identifier << '--' << scm_iconv(@path_encoding, 'UTF-8', path)
|
||||
blame = Annotate.new
|
||||
content = nil
|
||||
git_cmd(cmd_args) { |io| io.binmode; content = io.read }
|
||||
# git annotates binary files
|
||||
return nil if binary_data?(content)
|
||||
identifier = ''
|
||||
# git shows commit author on the first occurrence only
|
||||
authors_by_commit = {}
|
||||
content.split("\n").each do |line|
|
||||
if line =~ /^([0-9a-f]{39,40})\s.*/
|
||||
identifier = $1
|
||||
elsif line =~ /^author (.+)/
|
||||
authors_by_commit[identifier] = $1.strip
|
||||
elsif line =~ /^\t(.*)/
|
||||
blame.add_line($1, Revision.new(
|
||||
:identifier => identifier,
|
||||
:revision => identifier,
|
||||
:scmid => identifier,
|
||||
:author => authors_by_commit[identifier]
|
||||
))
|
||||
identifier = ''
|
||||
author = ''
|
||||
end
|
||||
end
|
||||
blame
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
def cat(path, identifier = nil)
|
||||
identifier = 'HEAD' if identifier.nil?
|
||||
cmd_args = %w|show --no-color|
|
||||
cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
|
||||
cat = nil
|
||||
git_cmd(cmd_args) do |io|
|
||||
io.binmode
|
||||
cat = io.read
|
||||
end
|
||||
cat
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
# Added to be compatible with EmailDiff plugin
|
||||
#
|
||||
def changed_files(path = nil, rev = 'HEAD')
|
||||
path ||= ''
|
||||
cmd_args = []
|
||||
cmd_args << 'log' << '--no-color' << '--pretty=format:%cd' << '--name-status' << '-1' << rev
|
||||
cmd_args << '--' << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
|
||||
changed_files = []
|
||||
git_cmd(cmd_args) do |io|
|
||||
io.each_line do |line|
|
||||
changed_files << line
|
||||
end
|
||||
end
|
||||
changed_files
|
||||
end
|
||||
|
||||
|
||||
# Added for GitDownloadRevision
|
||||
#
|
||||
def rev_list(revision, args)
|
||||
cmd_args = ['rev-list', *args, revision]
|
||||
git_cmd(cmd_args) do |io|
|
||||
@revisions_list = io.readlines.map { |t| t.strip }
|
||||
end
|
||||
@revisions_list
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
[]
|
||||
end
|
||||
|
||||
|
||||
# Added for GitDownloadRevision / GithubPayload
|
||||
#
|
||||
def rev_parse(revision)
|
||||
cmd_args = ['rev-parse', '--quiet', '--verify', revision]
|
||||
git_cmd(cmd_args) do |io|
|
||||
@parsed_revision = io.readlines.map { |t| t.strip }.first
|
||||
end
|
||||
@parsed_revision
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
# Added for GitDownloadRevision
|
||||
#
|
||||
def archive(revision, format)
|
||||
cmd_args = ['archive']
|
||||
case format
|
||||
when 'tar' then
|
||||
cmd_args << '--format=tar'
|
||||
when 'tar.gz' then
|
||||
cmd_args << '--format=tar.gz'
|
||||
cmd_args << '-7'
|
||||
when 'zip' then
|
||||
cmd_args << '--format=zip'
|
||||
cmd_args << '-7'
|
||||
else
|
||||
cmd_args << '--format=tar'
|
||||
end
|
||||
cmd_args << revision
|
||||
git_cmd(cmd_args, bypass_cache: true) do |io|
|
||||
io.binmode
|
||||
@content = io.read
|
||||
end
|
||||
@content
|
||||
rescue ScmCommandAborted => e
|
||||
logger.error(e.message)
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
# Added for MirrorPush
|
||||
#
|
||||
def mirror_push(mirror_url, branch = nil, args = [])
|
||||
cmd_args = git_mirror_cmd.concat(['push', *args, mirror_url, branch]).compact
|
||||
cmd = cmd_args.shift
|
||||
RedmineGitHosting::Utils::Exec.capture(cmd, cmd_args, { merge_output: true })
|
||||
end
|
||||
|
||||
|
||||
class Revision < Redmine::Scm::Adapters::Revision
|
||||
# Returns the readable identifier
|
||||
def format_identifier
|
||||
identifier[0, 8]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def git_cmd(args, options = {}, &block)
|
||||
# Get options
|
||||
bypass_cache = options.delete(:bypass_cache) { false }
|
||||
|
||||
# Build git command line
|
||||
cmd_str = prepare_command(args)
|
||||
|
||||
# Insert cache between shell execution and caller
|
||||
if !git_cache_id.nil? && git_cache_enabled? && !bypass_cache
|
||||
RedmineGitHosting::ShellRedirector.execute(cmd_str, git_cache_id, options, &block)
|
||||
else
|
||||
Redmine::Scm::Adapters::AbstractAdapter.shellout(cmd_str, options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def prepare_command(args)
|
||||
# Get our basics args
|
||||
full_args = base_args
|
||||
# Concat with Redmine args
|
||||
full_args += args
|
||||
# Quote args
|
||||
full_args.map { |e| shell_quote(e.to_s) }.join(' ')
|
||||
end
|
||||
|
||||
|
||||
# Compute string from repo_path that should be same as: repo.git_cache_id
|
||||
# If only we had access to the repo (we don't).
|
||||
# We perform caching here to speed this up, since this function gets called
|
||||
# many times during the course of a repository lookup.
|
||||
def git_cache_id
|
||||
logger.debug("Lookup for git_cache_id with repository path '#{repo_path}' ... ")
|
||||
@git_cache_id ||= Repository::Xitolite.repo_path_to_git_cache_id(repo_path)
|
||||
logger.warn("Unable to find git_cache_id for '#{repo_path}', bypass cache... ") if @git_cache_id.nil?
|
||||
logger.debug("git_cache_id found : #{@git_cache_id}") if !@git_cache_id.nil?
|
||||
@git_cache_id
|
||||
end
|
||||
|
||||
|
||||
def base_args
|
||||
RedmineGitHosting::Commands.sudo_git_args_for_repo(repo_path).concat(git_args)
|
||||
end
|
||||
|
||||
|
||||
def git_mirror_cmd
|
||||
RedmineGitHosting::Commands.sudo_git_args_for_repo(repo_path, git_push_args)
|
||||
end
|
||||
|
||||
|
||||
def git_push_args
|
||||
['env', "GIT_SSH=#{RedmineGitHosting::Config.gitolite_mirroring_script}"]
|
||||
end
|
||||
|
||||
|
||||
def repo_path
|
||||
root_url || url
|
||||
end
|
||||
|
||||
|
||||
def git_args
|
||||
self.class.client_version_above?([1, 7, 2]) ? ['-c', 'core.quotepath=false', '-c', 'log.decorate=no'] : []
|
||||
end
|
||||
|
||||
|
||||
def git_cache_enabled?
|
||||
RedmineGitHosting::Config.gitolite_cache_max_time != 0
|
||||
end
|
||||
|
||||
def binary_data?(content)
|
||||
if Gem::Version.new(Redmine::VERSION.to_s) >= Gem::Version.new('3.4')
|
||||
ScmData.binary?(content)
|
||||
else
|
||||
content.is_binary_data?
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
63
plugins/redmine_git_hosting/lib/redmine_git_hosting.rb
Normal file
63
plugins/redmine_git_hosting/lib/redmine_git_hosting.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Redmine Permissions
|
||||
require 'redmine_permissions'
|
||||
|
||||
# Redmine Menus
|
||||
require 'redmine_menus'
|
||||
|
||||
# Redmine Hooks
|
||||
require 'redmine_git_hosting/hooks'
|
||||
|
||||
# Redmine SCM
|
||||
Redmine::Scm::Base.add 'Xitolite'
|
||||
|
||||
module RedmineGitHosting
|
||||
extend self
|
||||
|
||||
# Load RedminePluginLoader
|
||||
require 'redmine_git_hosting/redmine_plugin_loader'
|
||||
extend RedminePluginLoader
|
||||
|
||||
set_plugin_name 'redmine_git_hosting'
|
||||
|
||||
set_autoloaded_paths 'forms',
|
||||
'presenters',
|
||||
'reports',
|
||||
'services',
|
||||
'use_cases',
|
||||
['controllers', 'concerns'],
|
||||
['models', 'concerns']
|
||||
|
||||
def logger
|
||||
@logger ||= RedmineGitHosting::Logger.init_logs!('RedmineGitHosting', logfile, loglevel)
|
||||
end
|
||||
|
||||
def logfile
|
||||
Rails.root.join('log', 'git_hosting.log')
|
||||
end
|
||||
|
||||
def loglevel
|
||||
case RedmineGitHosting::Config.gitolite_log_level
|
||||
when 'debug' then
|
||||
Logger::DEBUG
|
||||
when 'info' then
|
||||
Logger::INFO
|
||||
when 'warn' then
|
||||
Logger::WARN
|
||||
when 'error' then
|
||||
Logger::ERROR
|
||||
else
|
||||
Logger::INFO
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Set up autoload of patches
|
||||
Rails.configuration.to_prepare do
|
||||
# Redmine Git Hosting Libs and Patches
|
||||
RedmineGitHosting.load_plugin!
|
||||
|
||||
# Redmine SCM adapter
|
||||
require_dependency 'redmine/scm/adapters/xitolite_adapter'
|
||||
|
||||
require 'hrack/init'
|
||||
end
|
12
plugins/redmine_git_hosting/lib/redmine_git_hosting/auth.rb
Normal file
12
plugins/redmine_git_hosting/lib/redmine_git_hosting/auth.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
module RedmineGitHosting
|
||||
class Auth
|
||||
def find(login, password)
|
||||
user = User.find_by_login(login)
|
||||
# Return if user not found
|
||||
return nil if user.nil?
|
||||
|
||||
# Return user if password matches
|
||||
user if user.check_password?(password)
|
||||
end
|
||||
end
|
||||
end
|
62
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache.rb
Normal file
62
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
module RedmineGitHosting
|
||||
module Cache
|
||||
extend self
|
||||
|
||||
# Used in ShellRedirector but define here to keep a clean interface.
|
||||
#
|
||||
def max_cache_size
|
||||
RedmineGitHosting::Config.gitolite_cache_max_size
|
||||
end
|
||||
|
||||
def set_cache(repo_id, out_value, primary_key, secondary_key = nil)
|
||||
return if out_value.strip.empty?
|
||||
|
||||
command = compose_key(primary_key, secondary_key)
|
||||
adapter.apply_cache_limit if adapter.set_cache(repo_id, command, out_value)
|
||||
end
|
||||
|
||||
def get_cache(repo_id, primary_key, secondary_key = nil)
|
||||
command = compose_key(primary_key, secondary_key)
|
||||
cached = adapter.get_cache(repo_id, command)
|
||||
# Return result as a string stream
|
||||
cached.nil? ? nil : StringIO.new(cached)
|
||||
end
|
||||
|
||||
def flush_cache!
|
||||
adapter.flush_cache!
|
||||
end
|
||||
|
||||
# After resetting cache timing parameters -- delete entries that no-longer match
|
||||
def clear_obsolete_cache_entries
|
||||
adapter.clear_obsolete_cache_entries
|
||||
end
|
||||
|
||||
# Clear the cache entries for given repository / git_cache_id
|
||||
def clear_cache_for_repository(repo_id)
|
||||
adapter.clear_cache_for_repository(repo_id)
|
||||
end
|
||||
|
||||
def adapter
|
||||
case RedmineGitHosting::Config.gitolite_cache_adapter
|
||||
when 'database'
|
||||
Database
|
||||
when 'memcached'
|
||||
Memcached
|
||||
when 'redis'
|
||||
Redis
|
||||
else
|
||||
Database
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compose_key(key1, key2)
|
||||
if key2&.present?
|
||||
key1 + "\n" + key2
|
||||
else
|
||||
key1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
77
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache/abstract_cache.rb
vendored
Normal file
77
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache/abstract_cache.rb
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
module RedmineGitHosting
|
||||
module Cache
|
||||
class AbstractCache
|
||||
class << self
|
||||
|
||||
def max_cache_size
|
||||
@max_cache_size ||= RedmineGitHosting::Config.gitolite_cache_max_size
|
||||
end
|
||||
|
||||
|
||||
def max_cache_time
|
||||
@max_cache_time ||= RedmineGitHosting::Config.gitolite_cache_max_time
|
||||
end
|
||||
|
||||
|
||||
def max_cache_elements
|
||||
@max_cache_elements ||= RedmineGitHosting::Config.gitolite_cache_max_elements
|
||||
end
|
||||
|
||||
|
||||
def set_cache(repo_id, command, output)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
def get_cache(repo_id, command)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
def flush_cache!
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
def clear_obsolete_cache_entries
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
def clear_cache_for_repository(repo_id)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
def apply_cache_limit
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def time_limit
|
||||
return if max_cache_time < 0 # No expiration needed
|
||||
current_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
|
||||
limit = current_time - max_cache_time
|
||||
limit
|
||||
end
|
||||
|
||||
|
||||
def valid_cache_entry?(cached_entry_date)
|
||||
return true if max_cache_time < 0 # No expiration needed
|
||||
current_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
|
||||
expired = current_time.to_i - cached_entry_date.to_i > max_cache_time
|
||||
!expired
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
55
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache/database.rb
vendored
Normal file
55
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache/database.rb
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
module RedmineGitHosting
|
||||
module Cache
|
||||
class Database < AbstractCache
|
||||
class << self
|
||||
def set_cache(repo_id, command, output)
|
||||
logger.debug("DB Adapter : inserting cache entry for repository '#{repo_id}'")
|
||||
begin
|
||||
GitCache.create(command: command, command_output: output, repo_identifier: repo_id)
|
||||
true
|
||||
rescue => e
|
||||
logger.error("DB Adapter : could not insert in cache, this is the error : '#{e.message}'")
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def get_cache(repo_id, command)
|
||||
cached = GitCache.find_by_repo_identifier_and_command(repo_id, command)
|
||||
if cached
|
||||
if valid_cache_entry?(cached.created_at)
|
||||
# Update updated_at flag
|
||||
cached.touch unless cached.command_output.nil?
|
||||
out = cached.command_output
|
||||
else
|
||||
cached.destroy
|
||||
out = nil
|
||||
end
|
||||
else
|
||||
out = nil
|
||||
end
|
||||
out
|
||||
end
|
||||
|
||||
def flush_cache!
|
||||
ActiveRecord::Base.connection.execute('TRUNCATE git_caches')
|
||||
end
|
||||
|
||||
def clear_obsolete_cache_entries
|
||||
return if time_limit.nil?
|
||||
|
||||
deleted = GitCache.where('created_at < ?', time_limit).delete_all
|
||||
logger.info("DB Adapter : removed '#{deleted}' expired cache entries among all repositories")
|
||||
end
|
||||
|
||||
def clear_cache_for_repository(repo_id)
|
||||
deleted = GitCache.where(repo_identifier: repo_id).delete_all
|
||||
logger.info("DB Adapter : removed '#{deleted}' expired cache entries for repository '#{repo_id}'")
|
||||
end
|
||||
|
||||
def apply_cache_limit
|
||||
GitCache.order(:created_at).first.destroy if max_cache_elements >= 0 && GitCache.count > max_cache_elements
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
99
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache/memcached.rb
vendored
Normal file
99
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache/memcached.rb
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
require 'dalli'
|
||||
require 'digest/sha1'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Cache
|
||||
class Memcached < AbstractCache
|
||||
class << self
|
||||
|
||||
def set_cache(repo_id, command, output)
|
||||
logger.debug("Memcached Adapter : inserting cache entry for repository '#{repo_id}'")
|
||||
|
||||
# Create a SHA256 of the Git command as key id
|
||||
hashed_command = hash_key(command)
|
||||
|
||||
begin
|
||||
create_or_update_repo_references(repo_id, hashed_command)
|
||||
client.set(hashed_command, output)
|
||||
true
|
||||
rescue => e
|
||||
logger.error("Memcached Adapter : could not insert in cache, this is the error : '#{e.message}'")
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def get_cache(repo_id, command)
|
||||
client.get(hash_key(command))
|
||||
end
|
||||
|
||||
|
||||
def flush_cache!
|
||||
client.flush
|
||||
end
|
||||
|
||||
|
||||
# Return true, this is done automatically by Memcached with the
|
||||
# *max_cache_time* params (see below)
|
||||
#
|
||||
def clear_obsolete_cache_entries
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def clear_cache_for_repository(repo_id)
|
||||
# Create a SHA256 of the repo_id as key id
|
||||
hashed_repo_id = hash_key(repo_id)
|
||||
# Find repository references in Memcached
|
||||
repo_references = client.get(hashed_repo_id)
|
||||
return true if repo_references.nil?
|
||||
# Delete reference keys
|
||||
repo_references = repo_references.split(',').select { |r| !r.empty? }
|
||||
repo_references.map { |key| client.delete(key) }
|
||||
logger.info("Memcached Adapter : removed '#{repo_references.size}' expired cache entries for repository '#{repo_id}'")
|
||||
# Reset references count
|
||||
client.set(hashed_repo_id, '', max_cache_time, raw: true)
|
||||
end
|
||||
|
||||
|
||||
# Return true. If cache is full, Memcached drop the oldest objects to add new ones.
|
||||
#
|
||||
def apply_cache_limit
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def create_or_update_repo_references(repo_id, reference)
|
||||
# Create a SHA256 of the repo_id as key id
|
||||
hashed_repo_id = hash_key(repo_id)
|
||||
# Find it in Memcached
|
||||
repo_references = client.get(hashed_repo_id)
|
||||
if repo_references.nil?
|
||||
client.set(hashed_repo_id, reference, max_cache_time, raw: true)
|
||||
else
|
||||
client.append(hashed_repo_id, ',' + reference)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def hash_key(key)
|
||||
Digest::SHA256.hexdigest(key)
|
||||
end
|
||||
|
||||
|
||||
def client
|
||||
@client ||= Dalli::Client.new('localhost:11211', memcached_options)
|
||||
end
|
||||
|
||||
|
||||
def memcached_options
|
||||
{ namespace: 'redmine_git_hosting', compress: true, expires_in: max_cache_time, value_max_bytes: max_cache_size }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
117
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache/redis.rb
vendored
Normal file
117
plugins/redmine_git_hosting/lib/redmine_git_hosting/cache/redis.rb
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
require 'redis'
|
||||
require 'digest/sha1'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Cache
|
||||
class Redis < AbstractCache
|
||||
class << self
|
||||
|
||||
def set_cache(repo_id, command, output)
|
||||
logger.debug("Redis Adapter : inserting cache entry for repository '#{repo_id}'")
|
||||
|
||||
# Create a SHA256 of the Git command as key id
|
||||
hashed_command = hash_key(repo_id, command)
|
||||
|
||||
# If *max_cache_time* is set to -1 (until next commit) then
|
||||
# set the cache time to 1 day (we don't know when will be the next commit)
|
||||
cache_time = (max_cache_time < 0) ? 86_400 : max_cache_time
|
||||
|
||||
begin
|
||||
client.set(hashed_command, output, ex: cache_time)
|
||||
true
|
||||
rescue => e
|
||||
logger.error("Redis Adapter : could not insert in cache, this is the error : '#{e.message}'")
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def get_cache(repo_id, command)
|
||||
logger.debug("Redis Adapter : getting cache entry for repository '#{repo_id}'")
|
||||
client.get(hash_key(repo_id, command))
|
||||
end
|
||||
|
||||
|
||||
def flush_cache!
|
||||
deleted = 0
|
||||
client.scan_each(match: all_entries) do |key|
|
||||
client.del(key)
|
||||
deleted += 1
|
||||
end
|
||||
logger.info("Redis Adapter : removed '#{deleted}' expired cache entries among all repositories")
|
||||
end
|
||||
|
||||
|
||||
# Return true, this is done automatically by Redis with the
|
||||
# *max_cache_time* params (see above)
|
||||
#
|
||||
def clear_obsolete_cache_entries
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def clear_cache_for_repository(repo_id)
|
||||
deleted = 0
|
||||
client.scan_each(match: all_entries_for_repo(repo_id)) do |key|
|
||||
client.del(key)
|
||||
deleted += 1
|
||||
end
|
||||
logger.info("Redis Adapter : removed '#{deleted}' expired cache entries for repository '#{repo_id}'")
|
||||
end
|
||||
|
||||
|
||||
# Return true.
|
||||
#
|
||||
def apply_cache_limit
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def redis_namespace
|
||||
'git_hosting_cache'
|
||||
end
|
||||
|
||||
|
||||
def all_entries
|
||||
"#{redis_namespace}:*"
|
||||
end
|
||||
|
||||
|
||||
def all_entries_for_repo(repo_id)
|
||||
"#{redis_namespace}:#{digest(repo_id)}:*"
|
||||
end
|
||||
|
||||
|
||||
# Prefix each key with *git_hosting_cache:* to store them in a subdirectory.
|
||||
# When flushing cache, get all keys with this prefix and delete them.
|
||||
# Make SHA256 of the Git command as identifier
|
||||
#
|
||||
def hash_key(repo_id, command)
|
||||
"#{redis_namespace}:#{digest(repo_id)}:#{digest(command)}"
|
||||
end
|
||||
|
||||
|
||||
def digest(string)
|
||||
Digest::SHA256.hexdigest(string)[0..16]
|
||||
end
|
||||
|
||||
|
||||
def client
|
||||
@client ||= ::Redis.new(redis_options)
|
||||
end
|
||||
|
||||
|
||||
# Specify the Redis DB.
|
||||
# However, I don't know exactly how it's used by Redis...
|
||||
#
|
||||
def redis_options
|
||||
{ db: redis_namespace, driver: :hiredis }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
module RedmineGitHosting
|
||||
module Commands
|
||||
extend Commands::Base
|
||||
extend Commands::Git
|
||||
extend Commands::Gitolite
|
||||
extend Commands::Ssh
|
||||
extend Commands::Sudo
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
module RedmineGitHosting
|
||||
module Commands
|
||||
module Base
|
||||
extend self
|
||||
|
||||
# Wrapper to Open3.capture.
|
||||
#
|
||||
def capture(args = [], opts = {})
|
||||
cmd = args.shift
|
||||
RedmineGitHosting::Utils::Exec.capture(cmd, args, opts)
|
||||
end
|
||||
|
||||
|
||||
# Wrapper to Open3.capture.
|
||||
#
|
||||
def execute(args = [], opts = {})
|
||||
cmd = args.shift
|
||||
RedmineGitHosting::Utils::Exec.execute(cmd, args, opts)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,135 @@
|
|||
module RedmineGitHosting
|
||||
module Commands
|
||||
module Git
|
||||
extend self
|
||||
|
||||
############################
|
||||
# #
|
||||
# Sudo+Git Shell Wrapper #
|
||||
# #
|
||||
############################
|
||||
|
||||
|
||||
# Send Git command with Sudo
|
||||
#
|
||||
def sudo_git(*params)
|
||||
if RedmineGitHosting::Config.gitolite_use_sudo?
|
||||
cmd = sudo_git_cmd.concat(params)
|
||||
else
|
||||
cmd = ['git'].concat(params)
|
||||
end
|
||||
capture(cmd)
|
||||
end
|
||||
|
||||
|
||||
def sudo_git_cmd(args = [])
|
||||
sudo.concat(git(args))
|
||||
end
|
||||
|
||||
|
||||
def sudo_git_args_for_repo(repo_path, args = [])
|
||||
sudo.concat(git(args)).concat(git_args_for_repo(repo_path))
|
||||
end
|
||||
|
||||
|
||||
def sudo_unset_git_global_param(key)
|
||||
logger.info("Unset Git global parameter : #{key}")
|
||||
|
||||
begin
|
||||
_, _, code = sudo_shell('git', 'config', '--global', '--unset', key)
|
||||
return true
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
if code == 5
|
||||
return true
|
||||
else
|
||||
logger.error("Error while removing Git global parameter : #{key}")
|
||||
logger.error(e.output)
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def sudo_set_git_global_param(namespace, key, value)
|
||||
key = prefix_key(namespace, key)
|
||||
|
||||
return sudo_unset_git_global_param(key) if value == ''
|
||||
|
||||
logger.info("Set Git global parameter : #{key} (#{value})")
|
||||
|
||||
begin
|
||||
sudo_git('config', '--global', key, value)
|
||||
return true
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("Error while setting Git global parameter : #{key} (#{value})")
|
||||
logger.error(e.output)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Return a hash with global config parameters.
|
||||
def sudo_get_git_global_params(namespace)
|
||||
begin
|
||||
params = sudo_git('config', '--get-regexp', namespace).split("\n")
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("Problems to retrieve Gitolite hook parameters in Gitolite config 'namespace : #{namespace}'")
|
||||
params = []
|
||||
end
|
||||
|
||||
git_config_as_hash(namespace, params)
|
||||
end
|
||||
|
||||
|
||||
def git_version
|
||||
begin
|
||||
sudo_git('--version')
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("Can't retrieve Git version: #{e.output}")
|
||||
'unknown'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Return the Git command with prepend args (mainly env vars like FOO=BAR git push).
|
||||
#
|
||||
def git(args = [])
|
||||
[*args, Repository::Xitolite.scm_command]
|
||||
end
|
||||
|
||||
|
||||
def git_args_for_repo(repo_path)
|
||||
['--git-dir', repo_path]
|
||||
end
|
||||
|
||||
|
||||
# Returns the global gitconfig prefix for
|
||||
# a config with that given key under the
|
||||
# hooks namespace.
|
||||
#
|
||||
def prefix_key(namespace, key)
|
||||
[namespace, '.', key].join
|
||||
end
|
||||
|
||||
|
||||
def git_config_as_hash(namespace, params)
|
||||
value_hash = {}
|
||||
|
||||
params.each do |value_pair|
|
||||
next if value_pair.empty?
|
||||
next if !value_pair.start_with?(namespace)
|
||||
global_key = value_pair.split(' ')[0]
|
||||
value = value_pair.split(' ')[1]
|
||||
key = global_key.split('.')[1]
|
||||
value_hash[key] = value
|
||||
end
|
||||
|
||||
value_hash
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
module RedmineGitHosting
|
||||
module Commands
|
||||
module Gitolite
|
||||
extend self
|
||||
|
||||
#################################
|
||||
# #
|
||||
# Sudo+Gitolite Shell Wrapper #
|
||||
# #
|
||||
#################################
|
||||
|
||||
def gitolite_infos
|
||||
ssh_capture('info')
|
||||
end
|
||||
|
||||
def sudo_gitolite_query_rc(param)
|
||||
sudo_capture('gitolite', 'query-rc', param).try(:chomp)
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("Can't retrieve Gitolite param : #{e.output}")
|
||||
nil
|
||||
end
|
||||
|
||||
def sudo_update_gitolite!
|
||||
if gitolite_command.nil?
|
||||
logger.error("gitolite_command is nil, can't update Gitolite !")
|
||||
return
|
||||
end
|
||||
logger.info("Running '#{gitolite_command.join(' ')}' on the Gitolite install ...")
|
||||
begin
|
||||
sudo_shell(*gitolite_command)
|
||||
return true
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error(e.output)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def gitolite_repository_count
|
||||
sudo_capture('gitolite', 'list-phy-repos').split("\n").length
|
||||
end
|
||||
|
||||
# Test if repository is empty on Gitolite side
|
||||
#
|
||||
def sudo_repository_empty?(repo_path)
|
||||
if gitolite_home_dir.nil?
|
||||
logger.info('gitolite_home_dir is not set, because of incomplete/incorrect gitolite setup')
|
||||
return true
|
||||
end
|
||||
|
||||
repo_path = File.join(gitolite_home_dir, repo_path, 'objects')
|
||||
count = sudo_git_objects_count(repo_path)
|
||||
count.to_i.zero?
|
||||
end
|
||||
|
||||
def sudo_git_objects_count(repo_path)
|
||||
cmd = if RedmineGitHosting::Config.gitolite_use_sudo?
|
||||
['eval', 'find', repo_path, '-type', 'f', '|', 'wc', '-l']
|
||||
else
|
||||
['bash', '-c', "find #{repo_path} -type f | wc -l"]
|
||||
end
|
||||
|
||||
begin
|
||||
sudo_capture(*cmd)
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("Can't retrieve Git objects count : #{e.output}")
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gitolite_command
|
||||
RedmineGitHosting::Config.gitolite_command
|
||||
end
|
||||
|
||||
def gitolite_home_dir
|
||||
RedmineGitHosting::Config.gitolite_home_dir
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,58 @@
|
|||
module RedmineGitHosting
|
||||
module Commands
|
||||
module Ssh
|
||||
extend self
|
||||
|
||||
# Return only the output from the ssh command.
|
||||
#
|
||||
def ssh_capture(*params)
|
||||
cmd = ssh.concat(params)
|
||||
capture(cmd)
|
||||
end
|
||||
|
||||
|
||||
# Execute a command in the gitolite forced environment through this user
|
||||
# i.e., executes 'ssh git@localhost <command>'
|
||||
#
|
||||
# Returns stdout, stderr and the exit code
|
||||
#
|
||||
def ssh_shell(*params)
|
||||
cmd = ssh.concat(params)
|
||||
execute(cmd)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Return the SSH command with basic args
|
||||
#
|
||||
def ssh
|
||||
['ssh', *ssh_shell_params]
|
||||
end
|
||||
|
||||
|
||||
# Returns the ssh prefix arguments for all ssh_* commands.
|
||||
#
|
||||
# These are as follows:
|
||||
# * (-T) Never request tty
|
||||
# * (-i <gitolite_ssh_private_key>) Use the SSH keys given in Settings
|
||||
# * (-p <gitolite_server_port>) Use port from settings
|
||||
# * (-o BatchMode=yes) Never ask for a password
|
||||
# * <gitolite_user>@<gitolite_server_host> (see +gitolite_url+)
|
||||
#
|
||||
def ssh_shell_params
|
||||
[
|
||||
'-T',
|
||||
'-o', 'BatchMode=yes',
|
||||
'-o', 'UserKnownHostsFile=/dev/null',
|
||||
'-o', 'StrictHostKeyChecking=no',
|
||||
'-p', RedmineGitHosting::Config.gitolite_server_port,
|
||||
'-i', RedmineGitHosting::Config.gitolite_ssh_private_key,
|
||||
RedmineGitHosting::Config.gitolite_url
|
||||
]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,227 @@
|
|||
require 'digest/md5'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Commands
|
||||
module Sudo
|
||||
extend self
|
||||
|
||||
##########################
|
||||
# #
|
||||
# SUDO Shell Wrapper #
|
||||
# #
|
||||
##########################
|
||||
|
||||
|
||||
# Pipe file content via sudo to dest_file.
|
||||
# Expect file content to end with EOL (\n)
|
||||
#
|
||||
def sudo_install_file(content, dest_file, filemode)
|
||||
stdin = [ 'cat', '<<\EOF', '>' + dest_file, "\n" + content.to_s + "EOF" ].join(' ')
|
||||
|
||||
begin
|
||||
sudo_pipe_data(stdin)
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error(e.output)
|
||||
return false
|
||||
else
|
||||
begin
|
||||
sudo_chmod(filemode, dest_file)
|
||||
return true
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error(e.output)
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Test if a file exists with size > 0
|
||||
#
|
||||
def sudo_file_exists?(filename)
|
||||
sudo_test(filename, '-s')
|
||||
end
|
||||
|
||||
|
||||
# Test if a directory exists
|
||||
#
|
||||
def sudo_dir_exists?(dirname)
|
||||
sudo_test(dirname, '-r')
|
||||
end
|
||||
|
||||
|
||||
# Test properties of a path from the git user.
|
||||
#
|
||||
# e.g., Test if a directory exists: sudo_test('~/somedir', '-d')
|
||||
#
|
||||
def sudo_test(path, testarg)
|
||||
_, _ , code = sudo_shell('test', testarg, path)
|
||||
return code == 0
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.debug("File check for #{path} failed : #{e.message}")
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
# Calls mkdir with the given arguments on the git user's side.
|
||||
#
|
||||
# e.g., sudo_mkdir('-p', '/some/path')
|
||||
#
|
||||
def sudo_mkdir(*args)
|
||||
sudo_shell('mkdir', *args)
|
||||
end
|
||||
|
||||
|
||||
# Syntaxic sugar for 'mkdir -p'
|
||||
#
|
||||
def sudo_mkdir_p(path)
|
||||
sudo_mkdir('-p', path)
|
||||
end
|
||||
|
||||
|
||||
# Calls chmod with the given arguments on the git user's side.
|
||||
#
|
||||
# e.g., sudo_chmod('755', '/some/path')
|
||||
#
|
||||
def sudo_chmod(mode, file)
|
||||
sudo_shell('chmod', mode, file)
|
||||
end
|
||||
|
||||
|
||||
# Removes a directory and all subdirectories below gitolite_user's $HOME.
|
||||
#
|
||||
# Assumes a relative path.
|
||||
#
|
||||
# If force=true, it will delete using 'rm -rf <path>', otherwise
|
||||
# it uses rmdir
|
||||
#
|
||||
def sudo_rmdir(path, force = false)
|
||||
if force
|
||||
sudo_shell('rm', '-rf', path)
|
||||
else
|
||||
sudo_shell('rmdir', path)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Syntaxic sugar for 'rm -rf' command
|
||||
#
|
||||
def sudo_rm_rf(path)
|
||||
sudo_rmdir(path, true)
|
||||
end
|
||||
|
||||
|
||||
# Moves a file/directory to a new target.
|
||||
#
|
||||
def sudo_move(old_path, new_path)
|
||||
sudo_shell('mv', old_path, new_path)
|
||||
end
|
||||
|
||||
|
||||
def sudo_get_dir_size(directory)
|
||||
sudo_capture('du', '-sh', directory).split(' ')[0] rescue ''
|
||||
end
|
||||
|
||||
|
||||
def sudo_cat(file)
|
||||
sudo_capture('cat', file) rescue ''
|
||||
end
|
||||
|
||||
|
||||
# Test if file content has changed
|
||||
#
|
||||
def sudo_file_changed?(source_file, dest_file)
|
||||
hash_content(content_from_redmine_side(source_file)) != hash_content(content_from_gitolite_side(dest_file))
|
||||
end
|
||||
|
||||
|
||||
# Test if file permissions has changed
|
||||
#
|
||||
def sudo_file_perms_changed?(filemode, dest_file)
|
||||
current_mode = sudo_capture('stat', '-c', "%a", dest_file)
|
||||
current_mode.chomp != filemode
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error(e.output)
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
# Return only the output of the shell command.
|
||||
# Throws an exception if the shell command does not exit with code 0.
|
||||
#
|
||||
def sudo_capture(*params)
|
||||
cmd = sudo.concat(params)
|
||||
capture(cmd)
|
||||
end
|
||||
|
||||
|
||||
# Execute a command as the gitolite user defined in +GitoliteWrapper.gitolite_user+.
|
||||
#
|
||||
# Will shell out to +sudo -n -u <gitolite_user> params+
|
||||
#
|
||||
def sudo_shell(*params)
|
||||
cmd = sudo.concat(params)
|
||||
execute(cmd)
|
||||
end
|
||||
|
||||
|
||||
# Write data on stdin and return the output of the shell command.
|
||||
# Throws an exception if the shell command does not exit with code 0.
|
||||
#
|
||||
def sudo_pipe_data(stdin)
|
||||
cmd = sudo.push('sh')
|
||||
capture(cmd, { stdin_data: stdin, binmode: true })
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Return the Sudo command with basic args.
|
||||
#
|
||||
def sudo
|
||||
if RedmineGitHosting::Config.gitolite_use_sudo?
|
||||
['sudo', *sudo_shell_params]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns the sudo prefix to all sudo_* commands.
|
||||
#
|
||||
# These are as follows:
|
||||
# * (-i) login as `gitolite_user` (setting ENV['HOME')
|
||||
# * (-n) non-interactive
|
||||
# * (-u `gitolite_user`) target user
|
||||
#
|
||||
def sudo_shell_params
|
||||
['-n', '-u', RedmineGitHosting::Config.gitolite_user, '-i']
|
||||
end
|
||||
|
||||
|
||||
# Return a md5 hash of the string passed.
|
||||
#
|
||||
def hash_content(content)
|
||||
Digest::MD5.hexdigest(content)
|
||||
end
|
||||
|
||||
|
||||
# Return the content of a local (Redmine side) file.
|
||||
#
|
||||
def content_from_redmine_side(file)
|
||||
File.read(file)
|
||||
rescue Errno::ENOENT => e
|
||||
logger.error(e.message)
|
||||
''
|
||||
end
|
||||
|
||||
|
||||
# Return the content of a file on Gitolite side.
|
||||
#
|
||||
def content_from_gitolite_side(destination_path)
|
||||
sudo_cat(destination_path)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
GITHUB_ISSUE = 'https://github.com/jbox-web/redmine_git_hosting/issues'
|
||||
GITHUB_WIKI = 'http://redmine-git-hosting.io/configuration/variables/'
|
||||
|
||||
GITOLITE_DEFAULT_CONFIG_FILE = 'gitolite.conf'
|
||||
GITOLITE_IDENTIFIER_DEFAULT_PREFIX = 'redmine_'
|
||||
|
||||
extend Config::Base
|
||||
extend Config::GitoliteAccess
|
||||
extend Config::GitoliteBase
|
||||
extend Config::GitoliteCache
|
||||
extend Config::GitoliteConfigTests
|
||||
extend Config::GitoliteHooks
|
||||
extend Config::GitoliteInfos
|
||||
extend Config::GitoliteNotifications
|
||||
extend Config::GitoliteStorage
|
||||
extend Config::Mirroring
|
||||
extend Config::RedmineConfig
|
||||
end
|
||||
end
|
|
@ -0,0 +1,97 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module Base
|
||||
extend self
|
||||
|
||||
###############################
|
||||
## ##
|
||||
## CONFIGURATION ACCESSORS ##
|
||||
## ##
|
||||
###############################
|
||||
|
||||
def get_setting(setting, bool = false)
|
||||
if bool
|
||||
Additionals.true? do_get_setting(setting)
|
||||
else
|
||||
do_get_setting(setting)
|
||||
end
|
||||
end
|
||||
|
||||
def reload_from_file!
|
||||
## Get default config from init.rb
|
||||
default_hash = Redmine::Plugin.find('redmine_git_hosting').settings[:default]
|
||||
do_reload_config(default_hash)
|
||||
end
|
||||
|
||||
def dump_settings
|
||||
puts YAML.dump Redmine::Plugin.find('redmine_git_hosting').settings[:default]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def do_get_setting(setting)
|
||||
setting = setting.to_sym
|
||||
|
||||
## Wrap this in a begin/rescue statement because Setting table
|
||||
## may not exist on first migration
|
||||
begin
|
||||
value = Setting.plugin_redmine_git_hosting[setting]
|
||||
rescue
|
||||
value = Redmine::Plugin.find('redmine_git_hosting').settings[:default][setting]
|
||||
else
|
||||
## The Setting table exist but does not contain the value yet, fallback to default
|
||||
value = Redmine::Plugin.find('redmine_git_hosting').settings[:default][setting] if value.nil?
|
||||
end
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
def do_reload_config(default_hash)
|
||||
## Refresh Settings cache
|
||||
Setting.check_cache
|
||||
|
||||
## Get actual values
|
||||
valuehash = (Setting.plugin_redmine_git_hosting).clone rescue {}
|
||||
|
||||
## Update!
|
||||
changes = 0
|
||||
|
||||
default_hash.each do |key, value|
|
||||
if valuehash[key] != value
|
||||
console_logger.info("Changing '#{key}' : #{valuehash[key]} => #{value}")
|
||||
valuehash[key] = value
|
||||
changes += 1
|
||||
end
|
||||
end
|
||||
|
||||
if changes.zero?
|
||||
console_logger.info('No changes necessary.')
|
||||
else
|
||||
commit_changes(valuehash)
|
||||
end
|
||||
end
|
||||
|
||||
def commit_changes(valuehash)
|
||||
console_logger.info('Committing changes ... ')
|
||||
begin
|
||||
## Update Settings
|
||||
Setting.plugin_redmine_git_hosting = valuehash
|
||||
## Refresh Settings cache
|
||||
Setting.check_cache
|
||||
console_logger.info('Success!')
|
||||
rescue => e
|
||||
console_logger.error('Failure.')
|
||||
console_logger.error(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
def console_logger
|
||||
RedmineGitHosting::ConsoleLogger
|
||||
end
|
||||
|
||||
def file_logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,70 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module GitoliteAccess
|
||||
extend self
|
||||
|
||||
def gitolite_http_by_default?
|
||||
get_setting(:gitolite_http_by_default)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_daemon_by_default?
|
||||
get_setting(:gitolite_daemon_by_default, true)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_notify_by_default?
|
||||
get_setting(:gitolite_notify_by_default, true)
|
||||
end
|
||||
|
||||
|
||||
def ssh_server_domain
|
||||
get_setting(:ssh_server_domain)
|
||||
end
|
||||
|
||||
|
||||
def http_server_domain
|
||||
get_setting(:http_server_domain)
|
||||
end
|
||||
|
||||
|
||||
def https_server_domain
|
||||
get_setting(:https_server_domain)
|
||||
end
|
||||
|
||||
|
||||
def http_server_subdir
|
||||
get_setting(:http_server_subdir)
|
||||
end
|
||||
|
||||
|
||||
def http_root_url
|
||||
my_root_url(false)
|
||||
end
|
||||
|
||||
|
||||
def https_root_url
|
||||
my_root_url(true)
|
||||
end
|
||||
|
||||
|
||||
def redmine_root_url
|
||||
Redmine::Utils::relative_url_root
|
||||
end
|
||||
|
||||
|
||||
def my_root_url(ssl = false)
|
||||
if ssl && https_server_domain != ''
|
||||
server_domain = https_server_domain
|
||||
else
|
||||
server_domain = http_server_domain
|
||||
end
|
||||
|
||||
# Remove any path from httpServer.
|
||||
# No trailing /.
|
||||
File.join(server_domain[/^[^\/]*/], redmine_root_url, '/')[0..-2]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,132 @@
|
|||
require 'etc'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Config
|
||||
module GitoliteBase
|
||||
extend self
|
||||
|
||||
def check_cache
|
||||
@gitolite_home_dir = nil
|
||||
@mirroring_keys_installed = nil
|
||||
@mirroring_public_key = nil
|
||||
@gitolite_ssh_fingerprint = nil
|
||||
end
|
||||
|
||||
|
||||
def redmine_user
|
||||
@redmine_user ||= (%x[whoami]).chomp.strip
|
||||
end
|
||||
|
||||
|
||||
def gitolite_use_sudo?
|
||||
redmine_user != gitolite_user
|
||||
end
|
||||
|
||||
|
||||
def gitolite_home_dir
|
||||
@gitolite_home_dir ||= Etc.getpwnam(gitolite_user).dir rescue nil
|
||||
end
|
||||
|
||||
|
||||
def gitolite_bin_dir
|
||||
@gitolite_bin_dir ||= RedmineGitHosting::Commands.sudo_gitolite_query_rc('GL_BINDIR')
|
||||
end
|
||||
|
||||
|
||||
def gitolite_lib_dir
|
||||
@gitolite_lib_dir ||= RedmineGitHosting::Commands.sudo_gitolite_query_rc('GL_LIBDIR')
|
||||
end
|
||||
|
||||
|
||||
def gitolite_user
|
||||
get_setting(:gitolite_user)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_server_host
|
||||
get_setting(:gitolite_server_host)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_server_port
|
||||
get_setting(:gitolite_server_port)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_ssh_private_key
|
||||
get_setting(:gitolite_ssh_private_key)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_ssh_public_key
|
||||
get_setting(:gitolite_ssh_public_key)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_ssh_public_key_fingerprint
|
||||
@gitolite_ssh_fingerprint ||= RedmineGitHosting::Utils::Ssh.ssh_fingerprint(File.read(gitolite_ssh_public_key))
|
||||
end
|
||||
|
||||
|
||||
def gitolite_config_file
|
||||
File.basename(get_setting(:gitolite_config_file))
|
||||
end
|
||||
|
||||
|
||||
def gitolite_config_dir
|
||||
dirs = File.dirname(gitolite_config_file).split('/')
|
||||
if dirs[0] != '.'
|
||||
File.join('conf', *dirs)
|
||||
else
|
||||
'conf'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_identifier_prefix
|
||||
get_setting(:gitolite_identifier_prefix)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_identifier_strip_user_id?
|
||||
get_setting(:gitolite_identifier_strip_user_id, true)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_key_subdir
|
||||
'redmine_git_hosting'
|
||||
end
|
||||
|
||||
|
||||
def git_config_username
|
||||
get_setting(:git_config_username)
|
||||
end
|
||||
|
||||
|
||||
def git_config_email
|
||||
get_setting(:git_config_email)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_temp_dir
|
||||
get_setting(:gitolite_temp_dir)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_url
|
||||
[gitolite_user, '@', gitolite_server_host].join
|
||||
end
|
||||
|
||||
|
||||
def gitolite_admin_dir
|
||||
File.join(gitolite_temp_dir, gitolite_user, 'gitolite-admin.git')
|
||||
end
|
||||
|
||||
|
||||
def gitolite_log_level
|
||||
get_setting(:gitolite_log_level)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module GitoliteCache
|
||||
extend self
|
||||
|
||||
def gitolite_cache_max_time
|
||||
get_setting(:gitolite_cache_max_time).to_i
|
||||
end
|
||||
|
||||
|
||||
def gitolite_cache_max_elements
|
||||
get_setting(:gitolite_cache_max_elements).to_i
|
||||
end
|
||||
|
||||
|
||||
def gitolite_cache_max_size
|
||||
get_setting(:gitolite_cache_max_size).to_i * 1024 * 1024
|
||||
end
|
||||
|
||||
|
||||
def gitolite_cache_adapter
|
||||
get_setting(:gitolite_cache_adapter)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,95 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module GitoliteConfigTests
|
||||
extend self
|
||||
|
||||
###############################
|
||||
## ##
|
||||
## TEMP DIR ##
|
||||
## ##
|
||||
###############################
|
||||
|
||||
@temp_dir_path = nil
|
||||
@previous_temp_dir_path = nil
|
||||
|
||||
def create_temp_dir
|
||||
if @previous_temp_dir_path != gitolite_temp_dir
|
||||
@previous_temp_dir_path = gitolite_temp_dir
|
||||
@temp_dir_path = gitolite_admin_dir
|
||||
end
|
||||
|
||||
if !File.directory?(@temp_dir_path)
|
||||
file_logger.info("Create Gitolite Admin directory : '#{@temp_dir_path}'")
|
||||
begin
|
||||
FileUtils.mkdir_p @temp_dir_path
|
||||
FileUtils.chmod 0700, @temp_dir_path
|
||||
rescue => e
|
||||
file_logger.error("Cannot create Gitolite Admin directory : '#{@temp_dir_path}'")
|
||||
end
|
||||
end
|
||||
|
||||
@temp_dir_path
|
||||
end
|
||||
|
||||
|
||||
@temp_dir_writeable = false
|
||||
|
||||
def temp_dir_writeable?(opts = {})
|
||||
@temp_dir_writeable = false if opts.has_key?(:reset) && opts[:reset] == true
|
||||
|
||||
if !@temp_dir_writeable
|
||||
file_logger.debug("Testing if Gitolite Admin directory '#{create_temp_dir}' is writeable ...")
|
||||
mytestfile = File.join(create_temp_dir, 'writecheck')
|
||||
if !File.directory?(create_temp_dir)
|
||||
@temp_dir_writeable = false
|
||||
else
|
||||
begin
|
||||
FileUtils.touch mytestfile
|
||||
FileUtils.rm mytestfile
|
||||
rescue => e
|
||||
@temp_dir_writeable = false
|
||||
else
|
||||
@temp_dir_writeable = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@temp_dir_writeable
|
||||
end
|
||||
|
||||
|
||||
###############################
|
||||
## ##
|
||||
## SUDO TESTS ##
|
||||
## ##
|
||||
###############################
|
||||
|
||||
## SUDO TEST1
|
||||
def can_redmine_sudo_to_gitolite_user?
|
||||
return true unless gitolite_use_sudo?
|
||||
file_logger.info("Testing if Redmine user '#{redmine_user}' can sudo to Gitolite user '#{gitolite_user}'...")
|
||||
result = execute_sudo_test(gitolite_user) do
|
||||
RedmineGitHosting::Commands.sudo_capture('whoami')
|
||||
end
|
||||
result ? file_logger.info('OK!') : file_logger.error('Error while testing can_redmine_sudo_to_gitolite_user')
|
||||
result
|
||||
end
|
||||
|
||||
|
||||
def execute_sudo_test(user, &block)
|
||||
begin
|
||||
test = yield if block_given?
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
return false
|
||||
else
|
||||
if test.match(/#{user}/)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module GitoliteHooks
|
||||
extend self
|
||||
|
||||
def gitolite_hooks_namespace
|
||||
'redminegitolite'
|
||||
end
|
||||
|
||||
|
||||
def gitolite_hooks_url
|
||||
[get_setting(:gitolite_hooks_url), '/githooks/post-receive/redmine'].join
|
||||
end
|
||||
|
||||
|
||||
def gitolite_hooks_debug
|
||||
get_setting(:gitolite_hooks_debug, true)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_hooks_are_asynchronous
|
||||
get_setting(:gitolite_hooks_are_asynchronous, true)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_overwrite_existing_hooks?
|
||||
get_setting(:gitolite_overwrite_existing_hooks, true)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_local_code_dir
|
||||
@gitolite_local_code_dir ||= RedmineGitHosting::Commands.sudo_gitolite_query_rc('LOCAL_CODE')
|
||||
end
|
||||
|
||||
|
||||
def gitolite_hooks_dir
|
||||
if gitolite_version == 3
|
||||
File.join(gitolite_local_code_dir, 'hooks', 'common')
|
||||
else
|
||||
File.join(gitolite_home_dir, '.gitolite', 'hooks', 'common')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def check_hooks_install!
|
||||
{
|
||||
hook_files: RedmineGitHosting::GitoliteHooks.hooks_installed?,
|
||||
global_params: RedmineGitHosting::GitoliteParams::GlobalParams.new.installed?,
|
||||
mailer_params: RedmineGitHosting::GitoliteParams::MailerParams.new.installed?
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def install_hooks!
|
||||
{
|
||||
hook_files: RedmineGitHosting::GitoliteHooks.install_hooks!,
|
||||
global_params: RedmineGitHosting::GitoliteParams::GlobalParams.new.install!,
|
||||
mailer_params: RedmineGitHosting::GitoliteParams::MailerParams.new.install!
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def update_hook_params!
|
||||
RedmineGitHosting::GitoliteParams::GlobalParams.new.install!
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,87 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module GitoliteInfos
|
||||
extend self
|
||||
|
||||
##########################
|
||||
# #
|
||||
# GITOLITE INFOS #
|
||||
# #
|
||||
##########################
|
||||
|
||||
|
||||
def rugged_features
|
||||
Rugged.features
|
||||
end
|
||||
|
||||
|
||||
def rugged_mandatory_features
|
||||
[:threads, :ssh]
|
||||
end
|
||||
|
||||
|
||||
def libgit2_version
|
||||
Rugged.libgit2_version.join('.')
|
||||
end
|
||||
|
||||
|
||||
def gitolite_infos
|
||||
begin
|
||||
RedmineGitHosting::Commands.gitolite_infos
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
file_logger.error('Error while getting Gitolite infos, check your SSH keys (path, permissions) or your Git user.')
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_version
|
||||
file_logger.debug('Getting Gitolite version...')
|
||||
@gitolite_version ||= find_version(gitolite_infos)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_banner
|
||||
file_logger.debug('Getting Gitolite banner...')
|
||||
gitolite_infos
|
||||
end
|
||||
|
||||
|
||||
def find_version(output)
|
||||
return nil if output.blank?
|
||||
line = output.split("\n")[0]
|
||||
if line =~ /gitolite[ -]v?2./
|
||||
2
|
||||
elsif line.include?('running gitolite3')
|
||||
3
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_command
|
||||
if gitolite_version == 2
|
||||
['gl-setup']
|
||||
elsif gitolite_version == 3
|
||||
['gitolite', 'setup']
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repository_count
|
||||
return 'This is Gitolite v2, not implemented...' if gitolite_version != 3
|
||||
file_logger.debug('Getting Gitolite physical repositories list...')
|
||||
begin
|
||||
RedmineGitHosting::Commands.gitolite_repository_count
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
file_logger.error('Error while getting Gitolite physical repositories list')
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module GitoliteNotifications
|
||||
extend self
|
||||
|
||||
def gitolite_notify_global_prefix
|
||||
get_setting(:gitolite_notify_global_prefix)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_notify_global_sender_address
|
||||
get_setting(:gitolite_notify_global_sender_address)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_notify_global_include
|
||||
get_setting(:gitolite_notify_global_include)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_notify_global_exclude
|
||||
get_setting(:gitolite_notify_global_exclude)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module GitoliteStorage
|
||||
extend self
|
||||
|
||||
def gitolite_global_storage_dir
|
||||
get_setting(:gitolite_global_storage_dir)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_redmine_storage_dir
|
||||
get_setting(:gitolite_redmine_storage_dir)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_recycle_bin_dir
|
||||
get_setting(:gitolite_recycle_bin_dir)
|
||||
end
|
||||
|
||||
|
||||
def recycle_bin_dir
|
||||
File.join(gitolite_home_dir, gitolite_recycle_bin_dir) rescue nil
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module Mirroring
|
||||
extend self
|
||||
|
||||
def mirroring_public_key
|
||||
@mirroring_public_key ||= MirrorKeysInstaller.mirroring_public_key(gitolite_ssh_public_key)
|
||||
end
|
||||
|
||||
|
||||
def mirroring_keys_installed?
|
||||
@mirroring_keys_installed ||= MirrorKeysInstaller.new(gitolite_home_dir, gitolite_ssh_public_key, gitolite_ssh_private_key).installed?
|
||||
end
|
||||
|
||||
|
||||
def gitolite_mirroring_script
|
||||
File.join(gitolite_home_dir, '.ssh', 'run_gitolite_admin_ssh')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
module RedmineGitHosting
|
||||
module Config
|
||||
module RedmineConfig
|
||||
extend self
|
||||
|
||||
def gitolite_use_sidekiq?
|
||||
get_setting(:gitolite_use_sidekiq, true)
|
||||
end
|
||||
|
||||
def sidekiq_available?
|
||||
@sidekiq_available ||=
|
||||
begin
|
||||
require 'sidekiq'
|
||||
require 'sidekiq/api'
|
||||
rescue LoadError
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def hierarchical_organisation?
|
||||
get_setting(:hierarchical_organisation, true)
|
||||
end
|
||||
|
||||
def unique_repo_identifier?
|
||||
get_setting(:unique_repo_identifier, true)
|
||||
end
|
||||
|
||||
def all_projects_use_git?
|
||||
get_setting(:all_projects_use_git, true)
|
||||
end
|
||||
|
||||
def init_repositories_on_create?
|
||||
get_setting(:init_repositories_on_create, true)
|
||||
end
|
||||
|
||||
def show_repositories_url?
|
||||
get_setting(:show_repositories_url, true)
|
||||
end
|
||||
|
||||
def download_revision_enabled?
|
||||
get_setting(:download_revision_enabled, true)
|
||||
end
|
||||
|
||||
def delete_git_repositories?
|
||||
get_setting(:delete_git_repositories, true)
|
||||
end
|
||||
|
||||
def gitolite_recycle_bin_expiration_time
|
||||
(get_setting(:gitolite_recycle_bin_expiration_time).to_f * 60).to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
module RedmineGitHosting
|
||||
module ConsoleLogger
|
||||
extend self
|
||||
|
||||
def title(message, &block)
|
||||
info("\n * #{message} :")
|
||||
yield if block_given?
|
||||
info(" Done !\n\n")
|
||||
end
|
||||
|
||||
def debug(message)
|
||||
puts message
|
||||
logger.debug(message.strip)
|
||||
end
|
||||
|
||||
def info(message)
|
||||
puts message
|
||||
logger.info(message.strip)
|
||||
end
|
||||
|
||||
def warn
|
||||
puts message
|
||||
logger.warn(message.strip)
|
||||
end
|
||||
|
||||
def error(message)
|
||||
puts message
|
||||
logger.error(message.strip)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
end
|
||||
end
|
24
plugins/redmine_git_hosting/lib/redmine_git_hosting/error.rb
Normal file
24
plugins/redmine_git_hosting/lib/redmine_git_hosting/error.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
module RedmineGitHosting
|
||||
module Error
|
||||
# Used to register errors when pulling and pushing the conf file
|
||||
class GitoliteException < StandardError; end
|
||||
class GitoliteWrapperException < GitoliteException; end
|
||||
class InvalidSshKey < GitoliteException; end
|
||||
|
||||
class InvalidRefspec < GitoliteException
|
||||
class BadFormat < InvalidRefspec; end
|
||||
class NullComponent < InvalidRefspec; end
|
||||
end
|
||||
|
||||
# Used to register errors when pulling and pushing the conf file
|
||||
class GitoliteCommandException < GitoliteException
|
||||
attr_reader :command
|
||||
attr_reader :output
|
||||
|
||||
def initialize(command, output)
|
||||
@command = command
|
||||
@output = output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,83 @@
|
|||
module RedmineGitHosting
|
||||
class GitAccess
|
||||
DOWNLOAD_COMMANDS = %w[git-upload-pack git-upload-archive].freeze
|
||||
PUSH_COMMANDS = %w[git-receive-pack].freeze
|
||||
|
||||
def download_access_check(actor, repository, is_ssl = false)
|
||||
# First check that SmartHTTP is enabled for repository
|
||||
return smart_http_is_disabled!(repository) unless smart_http_enabled_for_download?(repository, is_ssl)
|
||||
|
||||
# Then check user permissions
|
||||
if actor.is_a?(User)
|
||||
user_download_access_check(actor, repository)
|
||||
else
|
||||
raise 'Wrong actor'
|
||||
end
|
||||
end
|
||||
|
||||
def upload_access_check(actor, repository)
|
||||
# First check that SmartHTTP is enabled for repository
|
||||
return smart_http_is_disabled!(repository) unless smart_http_enabled_for_upload?(repository)
|
||||
|
||||
# Then check user permissions
|
||||
if actor.is_a?(User)
|
||||
user_upload_access_check(actor, repository)
|
||||
else
|
||||
raise 'Wrong actor'
|
||||
end
|
||||
end
|
||||
|
||||
def user_download_access_check(user, repository)
|
||||
if user&.allowed_to_clone?(repository)
|
||||
build_status_object(true)
|
||||
else
|
||||
build_status_object(false, "You don't have access")
|
||||
end
|
||||
end
|
||||
|
||||
def user_upload_access_check(user, repository)
|
||||
if user&.allowed_to_commit?(repository)
|
||||
build_status_object(true)
|
||||
else
|
||||
build_status_object(false, "You don't have access")
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def build_status_object(status, message = '')
|
||||
logger.warn(message) unless status
|
||||
GitAccessStatus.new(status, message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def smart_http_is_disabled!(repository)
|
||||
build_status_object(false, "SmartHTTP is disabled for repository '#{repository.gitolite_repository_name}' !")
|
||||
end
|
||||
|
||||
def smart_http_enabled_for_download?(repository, is_ssl = false)
|
||||
# SmartHTTP is disabled
|
||||
return false unless repository.smart_http_enabled?
|
||||
# HTTPS only but no SSL
|
||||
return false if repository.only_https_access_enabled? && !is_ssl
|
||||
# HTTP only but have SSL (weird..)
|
||||
return false if repository.only_http_access_enabled? && is_ssl
|
||||
|
||||
# Else return true
|
||||
true
|
||||
end
|
||||
|
||||
def smart_http_enabled_for_upload?(repository)
|
||||
# HTTPS only
|
||||
return true if repository.pushable_via_http?
|
||||
|
||||
# Else
|
||||
false
|
||||
end
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
module RedmineGitHosting
|
||||
class GitAccessStatus
|
||||
attr_accessor :status, :message
|
||||
alias_method :allowed?, :status
|
||||
|
||||
def initialize(status, message = '')
|
||||
@status = status
|
||||
@message = message
|
||||
end
|
||||
|
||||
def to_json
|
||||
{ status: @status, message: @message }.to_json
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,127 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteAccessor
|
||||
extend self
|
||||
|
||||
module Methods
|
||||
private
|
||||
|
||||
def gitolite_accessor
|
||||
RedmineGitHosting::GitoliteAccessor
|
||||
end
|
||||
end
|
||||
|
||||
def create_ssh_key(ssh_key, opts = {})
|
||||
logger.info("User '#{User.current.login}' has added a SSH key")
|
||||
resync_gitolite(:add_ssh_key, ssh_key.id, opts)
|
||||
end
|
||||
|
||||
def destroy_ssh_key(ssh_key, opts = {})
|
||||
ssh_key = ssh_key.data_for_destruction if ssh_key.is_a?(GitolitePublicKey)
|
||||
logger.info("User '#{User.current.login}' has deleted a SSH key")
|
||||
resync_gitolite(:delete_ssh_key, ssh_key, opts)
|
||||
end
|
||||
|
||||
def resync_ssh_keys(opts = {})
|
||||
logger.info('Forced resync of all ssh keys...')
|
||||
resync_gitolite(:resync_ssh_keys, 'all', opts)
|
||||
end
|
||||
|
||||
def regenerate_ssh_keys(opts = {})
|
||||
logger.info('Forced regenerate of all ssh keys...')
|
||||
resync_gitolite(:regenerate_ssh_keys, 'all', opts)
|
||||
end
|
||||
|
||||
def create_repository(repository, opts = {})
|
||||
logger.info("User '#{User.current.login}' has created a new repository '#{repository.gitolite_repository_name}'")
|
||||
resync_gitolite(:add_repository, repository.id, opts)
|
||||
end
|
||||
|
||||
def update_repository(repository, opts = {})
|
||||
logger.info("User '#{User.current.login}' has modified repository '#{repository.gitolite_repository_name}'")
|
||||
resync_gitolite(:update_repository, repository.id, opts)
|
||||
end
|
||||
|
||||
def move_repository(repository, opts = {})
|
||||
logger.info("User '#{User.current.login}' has moved repository '#{repository.gitolite_repository_name}'")
|
||||
resync_gitolite(:move_repository, repository.id, opts)
|
||||
end
|
||||
|
||||
def destroy_repository(repository, opts = {})
|
||||
logger.info("User '#{User.current.login}' has removed repository '#{repository.gitolite_repository_name}'")
|
||||
resync_gitolite(:delete_repository, repository.data_for_destruction, opts)
|
||||
end
|
||||
|
||||
def destroy_repositories(repositories, opts = {})
|
||||
message = opts.delete(:message) { ' ' }
|
||||
logger.info(message)
|
||||
repositories.each do |repository|
|
||||
resync_gitolite(:delete_repository, repository)
|
||||
end
|
||||
end
|
||||
|
||||
def update_projects(projects, opts = {})
|
||||
message = opts.delete(:message) { ' ' }
|
||||
logger.info(message)
|
||||
resync_gitolite(:update_projects, projects, opts)
|
||||
end
|
||||
|
||||
def move_project_hierarchy(project)
|
||||
logger.info("Move repositories of project : '#{project}'")
|
||||
resync_gitolite(:move_repositories, project.id)
|
||||
end
|
||||
|
||||
def move_repositories_tree(count)
|
||||
logger.info('Gitolite configuration has been modified : repositories hierarchy')
|
||||
logger.info("Resync all projects (root projects : '#{count}')...")
|
||||
resync_gitolite(:move_repositories_tree, count)
|
||||
end
|
||||
|
||||
def purge_recycle_bin
|
||||
resync_gitolite(:purge_recycle_bin, 'all')
|
||||
end
|
||||
|
||||
def delete_from_recycle_bin(repositories)
|
||||
resync_gitolite(:delete_from_recycle_bin, repositories)
|
||||
end
|
||||
|
||||
def flush_git_cache
|
||||
logger.info('Flush Git Cache !')
|
||||
RedmineGitHosting::Cache.flush_cache!
|
||||
end
|
||||
|
||||
def flush_settings_cache
|
||||
resync_gitolite(:flush_settings_cache, 'flush!', { flush_cache: true })
|
||||
end
|
||||
|
||||
def enable_rw_access
|
||||
logger.info('Enable RW access on all Gitolite repositories')
|
||||
resync_gitolite(:enable_rw_access, 'enable_rw_access')
|
||||
end
|
||||
|
||||
def disable_rw_access
|
||||
logger.info('Disable RW access on all Gitolite repositories')
|
||||
resync_gitolite(:disable_rw_access, 'disable_rw_access')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
def resync_gitolite(command, object, options = {})
|
||||
if options.has_key?(:bypass_sidekiq) && options[:bypass_sidekiq] == true
|
||||
bypass = true
|
||||
else
|
||||
bypass = false
|
||||
end
|
||||
|
||||
if RedmineGitHosting::Config.gitolite_use_sidekiq? &&
|
||||
RedmineGitHosting::Config.sidekiq_available? && !bypass
|
||||
GithostingShellWorker.maybe_do(command, object, options)
|
||||
else
|
||||
GitoliteWrapper.resync_gitolite(command, object, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHandlers
|
||||
module Repositories
|
||||
class AddRepository < Base
|
||||
|
||||
def call
|
||||
if !configuration_exists?
|
||||
# Create repository in Gitolite
|
||||
log_repo_not_exist('create it ...')
|
||||
create_repository_config
|
||||
|
||||
elsif configuration_exists? && force
|
||||
# Recreate repository in Gitolite
|
||||
log_repo_already_exist('force mode !')
|
||||
recreate_repository_config
|
||||
|
||||
else
|
||||
log_repo_already_exist('exit !')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repo_name
|
||||
repository.gitolite_repository_name
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repo_path
|
||||
repository.gitolite_repository_path
|
||||
end
|
||||
|
||||
|
||||
attr_reader :force
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@force = options.delete(:force) { false }
|
||||
@old_perms = options.delete(:old_perms) { {} }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,129 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHandlers
|
||||
module Repositories
|
||||
class Base
|
||||
|
||||
attr_reader :gitolite_config
|
||||
attr_reader :repository
|
||||
attr_reader :context
|
||||
attr_reader :options
|
||||
|
||||
|
||||
def initialize(gitolite_config, repository, context, options = {})
|
||||
@gitolite_config = gitolite_config
|
||||
@repository = repository
|
||||
@context = context
|
||||
@options = options.dup
|
||||
@old_perms = {}
|
||||
end
|
||||
|
||||
class << self
|
||||
|
||||
def call(gitolite_config, repository, context, options = {})
|
||||
new(gitolite_config, repository, context, options).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def backup_old_perms
|
||||
@old_perms = repository.backup_gitolite_permissions(gitolite_repo_conf.permissions[0])
|
||||
end
|
||||
|
||||
|
||||
def configuration_exists?
|
||||
!gitolite_repo_conf.nil?
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repo_conf
|
||||
gitolite_config.repos[gitolite_repo_name]
|
||||
end
|
||||
|
||||
|
||||
def create_repository_config
|
||||
# Create Gitolite config
|
||||
repo_conf = build_repository_config
|
||||
|
||||
# Update permissions
|
||||
repo_conf.permissions = repository.build_gitolite_permissions(@old_perms)
|
||||
|
||||
# Add it to Gitolite
|
||||
gitolite_config.add_repo(repo_conf)
|
||||
|
||||
# Return repository conf
|
||||
repo_conf
|
||||
end
|
||||
|
||||
|
||||
def update_repository_config
|
||||
recreate_repository_config
|
||||
end
|
||||
|
||||
|
||||
def delete_repository_config
|
||||
gitolite_config.rm_repo(gitolite_repo_name)
|
||||
end
|
||||
|
||||
|
||||
def recreate_repository_config
|
||||
# Backup old perms
|
||||
backup_old_perms
|
||||
|
||||
# Remove repo from Gitolite conf, we're gonna recreate it
|
||||
delete_repository_config
|
||||
|
||||
# Recreate repository in Gitolite
|
||||
create_repository_config
|
||||
end
|
||||
|
||||
|
||||
def build_repository_config
|
||||
repo_conf = ::Gitolite::Config::Repo.new(repository.gitolite_repository_name)
|
||||
|
||||
repository.git_config.each do |key, value|
|
||||
repo_conf.set_git_config(key, value)
|
||||
end
|
||||
|
||||
repository.gitolite_options.each do |key, value|
|
||||
repo_conf.set_gitolite_option(key, value)
|
||||
end
|
||||
|
||||
repo_conf
|
||||
end
|
||||
|
||||
|
||||
def log_ok_and_continue(message)
|
||||
logger.info("#{context} : repository '#{gitolite_repo_name}' exists in Gitolite, #{message}")
|
||||
logger.debug("#{context} : repository path '#{gitolite_repo_path}'")
|
||||
end
|
||||
|
||||
|
||||
def log_repo_not_exist(message)
|
||||
logger.warn("#{context} : repository '#{gitolite_repo_name}' does not exist in Gitolite, #{message}")
|
||||
logger.debug("#{context} : repository path '#{gitolite_repo_path}'")
|
||||
end
|
||||
|
||||
|
||||
def log_repo_already_exist(message)
|
||||
logger.warn("#{context} : repository '#{gitolite_repo_name}' already exists in Gitolite, #{message}")
|
||||
logger.debug("#{context} : repository path '#{gitolite_repo_path}'")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHandlers
|
||||
module Repositories
|
||||
class DeleteRepository < Base
|
||||
|
||||
def call
|
||||
if configuration_exists?
|
||||
log_ok_and_continue('delete it ...')
|
||||
|
||||
# Delete Gitolite repository
|
||||
delete_repository_config
|
||||
else
|
||||
log_repo_not_exist('exit !')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repo_name
|
||||
repository[:repo_name]
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repo_path
|
||||
repository[:repo_path]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,207 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHandlers
|
||||
module Repositories
|
||||
class MoveRepository < Base
|
||||
|
||||
def call
|
||||
if configuration_exists?
|
||||
perform_repository_move
|
||||
else
|
||||
logger.error("#{context} : repository '#{old_repo_name}' does not exist in Gitolite, exit !")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def perform_repository_move
|
||||
logger.info("#{context} : Moving '#{old_repo_name}' to '#{new_repo_name}' ...")
|
||||
|
||||
debug_output
|
||||
|
||||
if move_physical_repo(old_relative_path, new_relative_path, new_relative_parent_path)
|
||||
# Update repository paths in database
|
||||
update_repository_fields
|
||||
|
||||
# Update Gitolite configuration
|
||||
update_gitolite
|
||||
|
||||
# Return old path to delete it
|
||||
old_relative_parent_path
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def update_repository_fields
|
||||
repository.update_column(:url, new_relative_path)
|
||||
repository.update_column(:root_url, new_relative_path)
|
||||
end
|
||||
|
||||
|
||||
def update_gitolite
|
||||
# Get old repository permissions
|
||||
old_perms = repository.backup_gitolite_permissions(gitolite_repo_conf.permissions[0])
|
||||
|
||||
# Remove repository from Gitolite configuration
|
||||
gitolite_config.rm_repo(old_repo_name)
|
||||
|
||||
# Recreate it
|
||||
AddRepository.call(gitolite_config, repository, context, old_perms: old_perms)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repo_conf
|
||||
@repo_conf ||= gitolite_config.repos[old_repo_name]
|
||||
end
|
||||
|
||||
|
||||
def repo_id
|
||||
@repo_id ||= repository.redmine_name
|
||||
end
|
||||
|
||||
|
||||
def old_repo_name
|
||||
@old_repo_name ||= repository.old_repository_name
|
||||
end
|
||||
|
||||
|
||||
def new_repo_name
|
||||
@new_repo_name ||= repository.new_repository_name
|
||||
end
|
||||
|
||||
|
||||
def old_relative_path
|
||||
@old_relative_path ||= repository.url
|
||||
end
|
||||
|
||||
|
||||
def new_relative_path
|
||||
@new_relative_path ||= repository.gitolite_repository_path
|
||||
end
|
||||
|
||||
|
||||
def old_relative_parent_path
|
||||
@old_relative_parent_path ||= old_relative_path.gsub(repo_id + '.git', '')
|
||||
end
|
||||
|
||||
|
||||
def new_relative_parent_path
|
||||
@new_relative_parent_path ||= new_relative_path.gsub(repo_id + '.git', '')
|
||||
end
|
||||
|
||||
|
||||
def debug_output
|
||||
logger.debug("#{context} : Old repository name (for Gitolite) : #{old_repo_name}")
|
||||
logger.debug("#{context} : New repository name (for Gitolite) : #{new_repo_name}")
|
||||
logger.debug("#{context} : Old relative path (for Redmine code browser) : #{old_relative_path}")
|
||||
logger.debug("#{context} : New relative path (for Redmine code browser) : #{new_relative_path}")
|
||||
logger.debug("#{context} : Old relative parent path (for Gitolite) : #{old_relative_parent_path}")
|
||||
logger.debug("#{context} : New relative parent path (for Gitolite) : #{new_relative_parent_path}")
|
||||
end
|
||||
|
||||
|
||||
def move_physical_repo(old_path, new_path, new_parent_path)
|
||||
## CASE 0
|
||||
if old_path == new_path
|
||||
logger.info("#{context} : old repository and new repository are identical '#{old_path}', nothing to do, exit !")
|
||||
return true
|
||||
end
|
||||
|
||||
# Now we have multiple options, due to the way gitolite sets up repositories
|
||||
new_path_exists = directory_exists?(new_path)
|
||||
old_path_exists = directory_exists?(old_path)
|
||||
|
||||
## CASE 1
|
||||
if new_path_exists && old_path_exists
|
||||
return move_physical_repo_case_1(old_path, new_path)
|
||||
|
||||
## CASE 2
|
||||
elsif !new_path_exists && old_path_exists
|
||||
return move_physical_repo_case_2(old_path, new_path, new_parent_path)
|
||||
|
||||
## CASE 3
|
||||
elsif !new_path_exists && !old_path_exists
|
||||
logger.error("#{context} : both old repository '#{old_path}' and new repository '#{new_path}' does not exist, cannot move it, exit but let Gitolite create the new repo !")
|
||||
return true
|
||||
|
||||
## CASE 4
|
||||
elsif new_path_exists && !old_path_exists
|
||||
logger.error("#{context} : old repository '#{old_path}' does not exist, but the new one does, use it !")
|
||||
return true
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def move_physical_repo_case_1(old_path, new_path)
|
||||
if empty_repository?(new_path)
|
||||
logger.warn("#{context} : target repository '#{new_path}' already exists and is empty, remove it ...")
|
||||
delete_directory!(new_path, :target)
|
||||
else
|
||||
logger.warn("#{context} : target repository '#{new_path}' exists and is not empty, considered as already moved, try to remove the old_path if empty")
|
||||
if empty_repository?(old_path)
|
||||
delete_directory!(old_path, :source)
|
||||
else
|
||||
logger.error("#{context} : the source repository directory is not empty, cannot remove it, exit ! (This repo will be orphan)")
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def move_physical_repo_case_2(old_path, new_path, new_parent_path)
|
||||
logger.debug("#{context} : really moving Gitolite repository from '#{old_path}' to '#{new_path}'")
|
||||
|
||||
create_parent_directory(new_parent_path) if !directory_exists?(new_parent_path)
|
||||
|
||||
begin
|
||||
RedmineGitHosting::Commands.sudo_move(old_path, new_path)
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("move_physical_repo(#{old_path}, #{new_path}) failed")
|
||||
return false
|
||||
else
|
||||
logger.info("#{context} : done !")
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def delete_directory!(dir, type)
|
||||
begin
|
||||
RedmineGitHosting::Commands.sudo_rm_rf(dir)
|
||||
return true
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("#{context} : removing existing #{type} repository failed, exit !")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def empty_repository?(dir)
|
||||
RedmineGitHosting::Commands.sudo_repository_empty?(dir)
|
||||
end
|
||||
|
||||
|
||||
def directory_exists?(dir)
|
||||
RedmineGitHosting::Commands.sudo_dir_exists?(dir)
|
||||
end
|
||||
|
||||
|
||||
def create_parent_directory(new_parent_path)
|
||||
begin
|
||||
RedmineGitHosting::Commands.sudo_mkdir_p(new_parent_path)
|
||||
return true
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("#{context} : creation of parent path '#{new_parent_path}' failed, exit !")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHandlers
|
||||
module Repositories
|
||||
class UpdateRepository < Base
|
||||
|
||||
def call
|
||||
if configuration_exists?
|
||||
log_ok_and_continue('update it ...')
|
||||
|
||||
# Update Gitolite repository
|
||||
update_repository_config
|
||||
else
|
||||
log_repo_not_exist('exit !')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repo_name
|
||||
repository.gitolite_repository_name
|
||||
end
|
||||
|
||||
|
||||
def gitolite_repo_path
|
||||
repository.gitolite_repository_path
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHandlers
|
||||
module SshKeys
|
||||
class AddSshKey < Base
|
||||
|
||||
def call
|
||||
repo_key = find_gitolite_key(key.owner, key.location)
|
||||
|
||||
# Add it if not found
|
||||
if repo_key.nil?
|
||||
admin.add_key(build_gitolite_key(key))
|
||||
else
|
||||
logger.info("#{context} : SSH key '#{key.owner}@#{key.location}' already exists in Gitolite, update it ...")
|
||||
repo_key.type = key.type
|
||||
repo_key.blob = key.blob
|
||||
repo_key.email = key.email
|
||||
repo_key.owner = key.owner
|
||||
repo_key.location = key.location
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHandlers
|
||||
module SshKeys
|
||||
class Base
|
||||
|
||||
attr_reader :admin
|
||||
attr_reader :key
|
||||
attr_reader :context
|
||||
|
||||
|
||||
def initialize(admin, key, context)
|
||||
@admin = admin
|
||||
@key = key
|
||||
@context = context
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(admin, key, context)
|
||||
new(admin, key, context).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def find_gitolite_key(owner, location)
|
||||
admin.ssh_keys[owner].find_all { |k| k.location == location && k.owner == owner }.first
|
||||
end
|
||||
|
||||
|
||||
def build_gitolite_key(key)
|
||||
::Gitolite::SSHKey.new(key.type, key.blob, key.email, key.owner, key.location)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHandlers
|
||||
module SshKeys
|
||||
class DeleteSshKey < Base
|
||||
|
||||
def call
|
||||
repo_key = find_gitolite_key(key[:owner], key[:location])
|
||||
|
||||
# Remove it if found
|
||||
if repo_key
|
||||
admin.rm_key(repo_key)
|
||||
else
|
||||
logger.info("#{context} : SSH key '#{key[:owner]}@#{key[:location]}' does not exits in Gitolite, exit !")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,156 @@
|
|||
module RedmineGitHosting
|
||||
class GitoliteHook
|
||||
|
||||
class << self
|
||||
|
||||
def def_field(*names)
|
||||
class_eval do
|
||||
names.each do |name|
|
||||
define_method(name) do |*args|
|
||||
args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def_field :name, :source, :destination, :executable
|
||||
|
||||
attr_reader :source_dir
|
||||
|
||||
|
||||
def initialize(source_dir, &block)
|
||||
@source_dir = source_dir
|
||||
instance_eval(&block)
|
||||
end
|
||||
|
||||
|
||||
def source_path
|
||||
File.join(source_dir, source)
|
||||
end
|
||||
|
||||
|
||||
def destination_path
|
||||
File.join(gitolite_hooks_dir, destination)
|
||||
end
|
||||
|
||||
|
||||
def parent_path
|
||||
dirname = File.dirname(destination)
|
||||
dirname = '' if dirname == '.'
|
||||
File.join(gitolite_hooks_dir, dirname)
|
||||
end
|
||||
|
||||
|
||||
def filemode
|
||||
executable ? '755' : '644'
|
||||
end
|
||||
|
||||
|
||||
def installed?
|
||||
if !file_exists?
|
||||
1
|
||||
elsif hook_file_has_changed?
|
||||
2
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def install!
|
||||
if !file_exists?
|
||||
logger.info("Hook '#{name}' does not exist, installing it ...")
|
||||
install_hook
|
||||
elsif hook_file_has_changed?
|
||||
logger.warn("Hook '#{name}' is already present but it's not ours!")
|
||||
if force_update?
|
||||
logger.info("Restoring '#{name}' hook since forceInstallHook == true")
|
||||
install_hook
|
||||
else
|
||||
logger.info("Leaving '#{name}' hook untouched since forceInstallHook == false")
|
||||
end
|
||||
else
|
||||
logger.info("Hook '#{name}' is correcly installed")
|
||||
end
|
||||
installed?
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def install_hook
|
||||
create_parent_dir if !directory_exists?
|
||||
if install_hook_file
|
||||
logger.info("Hook '#{name}' installed")
|
||||
update_gitolite
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def force_update?
|
||||
RedmineGitHosting::Config.gitolite_overwrite_existing_hooks?
|
||||
end
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def hook_file_has_changed?
|
||||
RedmineGitHosting::Commands.sudo_file_changed?(source_path, destination_path) ||
|
||||
RedmineGitHosting::Commands.sudo_file_perms_changed?(filemode, destination_path)
|
||||
end
|
||||
|
||||
|
||||
def file_exists?
|
||||
RedmineGitHosting::Commands.sudo_file_exists?(destination_path)
|
||||
end
|
||||
|
||||
|
||||
def install_hook_file
|
||||
logger.info("Installing hook '#{source_path}' in '#{destination_path}'")
|
||||
begin
|
||||
content = File.read(source_path)
|
||||
rescue Errno::ENOENT => e
|
||||
logger.error("Errors while installing hook '#{e.message}'")
|
||||
return false
|
||||
else
|
||||
RedmineGitHosting::Commands.sudo_install_file(content, destination_path, filemode)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def update_gitolite
|
||||
RedmineGitHosting::Commands.sudo_update_gitolite!
|
||||
end
|
||||
|
||||
|
||||
def gitolite_hooks_dir
|
||||
RedmineGitHosting::Config.gitolite_hooks_dir
|
||||
end
|
||||
|
||||
|
||||
def directory_exists?
|
||||
RedmineGitHosting::Commands.sudo_dir_exists?(parent_path)
|
||||
end
|
||||
|
||||
|
||||
def create_parent_dir
|
||||
logger.info("Installing hook directory '#{parent_path}'")
|
||||
|
||||
begin
|
||||
RedmineGitHosting::Commands.sudo_mkdir_p(parent_path)
|
||||
return true
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("Problems installing hook directory '#{parent_path}'")
|
||||
logger.error(e.output)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteHooks
|
||||
|
||||
class << self
|
||||
|
||||
def register_hooks(&block)
|
||||
@gitolite_hooks ||= []
|
||||
class_eval(&block)
|
||||
end
|
||||
|
||||
|
||||
def registered_hooks
|
||||
@gitolite_hooks
|
||||
end
|
||||
|
||||
|
||||
def source_dir(source_dir)
|
||||
@source_dir = source_dir
|
||||
end
|
||||
|
||||
|
||||
def hooks_installed?
|
||||
installed = {}
|
||||
registered_hooks.each do |hook|
|
||||
begin
|
||||
installed[hook.name] = hook.installed?
|
||||
rescue Exception => msg
|
||||
installed[hook.name] = false
|
||||
end
|
||||
end
|
||||
installed
|
||||
end
|
||||
|
||||
|
||||
def install_hooks!
|
||||
installed = {}
|
||||
registered_hooks.each do |hook|
|
||||
installed[hook.name] = hook.install!
|
||||
end
|
||||
installed
|
||||
end
|
||||
|
||||
|
||||
def gitolite_hook(&block)
|
||||
@gitolite_hooks << RedmineGitHosting::GitoliteHook.new(@source_dir, &block)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteParams
|
||||
module BaseParam
|
||||
|
||||
private
|
||||
|
||||
# Return a hash with global config parameters.
|
||||
#
|
||||
def get_git_config_params(namespace)
|
||||
RedmineGitHosting::Commands.sudo_get_git_global_params(namespace)
|
||||
end
|
||||
|
||||
|
||||
def set_git_config_param(namespace, key, value)
|
||||
RedmineGitHosting::Commands.sudo_set_git_global_param(namespace, key, value)
|
||||
end
|
||||
|
||||
|
||||
def unset_git_config_param(key)
|
||||
RedmineGitHosting::Commands.sudo_unset_git_global_param(key)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,49 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteParams
|
||||
class GlobalParams
|
||||
|
||||
include BaseParam
|
||||
|
||||
attr_reader :gitolite_hooks_url
|
||||
attr_reader :debug_mode
|
||||
attr_reader :async_mode
|
||||
|
||||
attr_reader :namespace
|
||||
attr_reader :current_params
|
||||
|
||||
|
||||
def initialize
|
||||
# Params to set
|
||||
@gitolite_hooks_url = RedmineGitHosting::Config.gitolite_hooks_url
|
||||
@debug_mode = RedmineGitHosting::Config.gitolite_hooks_debug.to_s
|
||||
@async_mode = RedmineGitHosting::Config.gitolite_hooks_are_asynchronous.to_s
|
||||
|
||||
# Namespace where to set params
|
||||
@namespace = RedmineGitHosting::Config.gitolite_hooks_namespace
|
||||
|
||||
# Get current params
|
||||
@current_params = get_git_config_params(@namespace)
|
||||
|
||||
# Build hash of installed params
|
||||
@installed = {}
|
||||
end
|
||||
|
||||
|
||||
def installed?
|
||||
@installed['redmineurl'] = (current_params['redmineurl'] == gitolite_hooks_url)
|
||||
@installed['debugmode'] = (current_params['debugmode'] == debug_mode)
|
||||
@installed['asyncmode'] = (current_params['asyncmode'] == async_mode)
|
||||
@installed
|
||||
end
|
||||
|
||||
|
||||
def install!
|
||||
@installed['redmineurl'] = set_git_config_param(namespace, 'redmineurl', gitolite_hooks_url)
|
||||
@installed['debugmode'] = set_git_config_param(namespace, 'debugmode', debug_mode)
|
||||
@installed['asyncmode'] = set_git_config_param(namespace, 'asyncmode', async_mode)
|
||||
@installed
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,76 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteParams
|
||||
class MailerParams
|
||||
|
||||
include BaseParam
|
||||
|
||||
attr_reader :namespace
|
||||
attr_reader :current_params
|
||||
attr_reader :current_mailer_params
|
||||
|
||||
|
||||
def initialize
|
||||
## Namespace where to set params
|
||||
@namespace = 'multimailhook'
|
||||
|
||||
## Get current params
|
||||
@current_params = get_git_config_params(@namespace)
|
||||
@current_mailer_params = get_mailer_params
|
||||
|
||||
# Build hash of installed params
|
||||
@installed = {}
|
||||
end
|
||||
|
||||
|
||||
def installed?
|
||||
mailer_params.each do |param|
|
||||
next if current_mailer_params[param].empty?
|
||||
@installed[param] = (current_params[param] == current_mailer_params[param])
|
||||
end
|
||||
@installed
|
||||
end
|
||||
|
||||
|
||||
def install!
|
||||
mailer_params.each do |param|
|
||||
next if current_mailer_params[param].empty?
|
||||
@installed[param] = set_git_config_param(namespace, param, current_mailer_params[param])
|
||||
end
|
||||
@installed
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def mailer_params
|
||||
%w(mailer environment smtpauth smtpserver smtpport smtpuser smtppass)
|
||||
end
|
||||
|
||||
|
||||
def get_mailer_params
|
||||
params = {}
|
||||
params['environment'] = 'gitolite'
|
||||
params['mailer'] = mailer
|
||||
params['smtpauth'] = smtpauth_enabled?.to_s
|
||||
params['smtpserver'] = ActionMailer::Base.smtp_settings[:address].to_s
|
||||
params['smtpport'] = ActionMailer::Base.smtp_settings[:port].to_s
|
||||
params['smtpuser'] = ActionMailer::Base.smtp_settings[:user_name] || ''
|
||||
params['smtppass'] = ActionMailer::Base.smtp_settings[:password] || ''
|
||||
params
|
||||
end
|
||||
|
||||
|
||||
def mailer
|
||||
ActionMailer::Base.delivery_method == :smtp ? 'smtp' : 'sendmail'
|
||||
end
|
||||
|
||||
|
||||
def smtpauth_enabled?
|
||||
auth = ActionMailer::Base.smtp_settings[:authentication]
|
||||
auth != nil && auth != '' && auth != :none
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,109 @@
|
|||
require 'gitolite'
|
||||
|
||||
module RedmineGitHosting
|
||||
module GitoliteWrapper
|
||||
extend self
|
||||
|
||||
# Update the Gitolite Repository
|
||||
#
|
||||
# action: An API action defined in one of the gitolite/* classes.
|
||||
#
|
||||
def resync_gitolite(action, object, options = {})
|
||||
# Symbolize keys before using them
|
||||
action = action.to_sym
|
||||
options = options.symbolize_keys
|
||||
|
||||
# Flush cache if needed
|
||||
flush_cache(options)
|
||||
|
||||
# Return if the action is only to flush cache on Sidekiq side
|
||||
if action == :flush_settings_cache
|
||||
logger.info('Settings cache flushed!')
|
||||
return
|
||||
else
|
||||
execute_action(action, object, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def flush_cache(options = {})
|
||||
if options.has_key?(:flush_cache) && options[:flush_cache] == true
|
||||
logger.info('Flush Settings Cache !')
|
||||
Setting.check_cache if Setting.respond_to?(:check_cache)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Be sure to have a Gitolite::GitoliteAdmin object.
|
||||
# Return if issues.
|
||||
#
|
||||
def execute_action(action, object, options = {})
|
||||
begin
|
||||
admin = gitolite_admin
|
||||
rescue Rugged::SshError => e
|
||||
logger.error 'Invalid Gitolite Admin SSH Keys'
|
||||
logger.error(e.message)
|
||||
rescue Rugged::NetworkError => e
|
||||
logger.error 'Access denied for Gitolite Admin SSH Keys'
|
||||
logger.error(e.message)
|
||||
rescue Rugged::OSError => e
|
||||
logger.error 'Invalid connection params'
|
||||
logger.error(e.message)
|
||||
rescue Rugged::RepositoryError => e
|
||||
logger.error "Gitolite couldn't write to its admin repo copy"
|
||||
logger.error "Try recreating '#{gitolite_admin_dir}'"
|
||||
logger.error(e.message)
|
||||
else
|
||||
call_gitolite_wrapper(action, admin, object, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_admin
|
||||
RedmineGitHosting::Config.create_temp_dir
|
||||
logger.debug("Accessing gitolite-admin.git at '#{gitolite_admin_dir}'")
|
||||
::Gitolite::GitoliteAdmin.new(gitolite_admin_dir, gitolite_admin_settings)
|
||||
end
|
||||
|
||||
|
||||
def gitolite_admin_dir
|
||||
RedmineGitHosting::Config.gitolite_admin_dir
|
||||
end
|
||||
|
||||
|
||||
def call_gitolite_wrapper(action, admin, object, options = {})
|
||||
begin
|
||||
klass = GitoliteWrappers::Base.find_by_action_name(action)
|
||||
rescue RedmineGitHosting::Error::GitoliteWrapperException => e
|
||||
logger.error(e.message)
|
||||
else
|
||||
klass.call(admin, object, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def gitolite_admin_settings
|
||||
{
|
||||
git_user: RedmineGitHosting::Config.gitolite_user,
|
||||
hostname: "#{RedmineGitHosting::Config.gitolite_server_host}:#{RedmineGitHosting::Config.gitolite_server_port}",
|
||||
host: "#{RedmineGitHosting::Config.gitolite_server_host}:#{RedmineGitHosting::Config.gitolite_server_port}",
|
||||
author_name: RedmineGitHosting::Config.git_config_username,
|
||||
author_email: RedmineGitHosting::Config.git_config_email,
|
||||
public_key: RedmineGitHosting::Config.gitolite_ssh_public_key,
|
||||
private_key: RedmineGitHosting::Config.gitolite_ssh_private_key,
|
||||
key_subdir: RedmineGitHosting::Config.gitolite_key_subdir,
|
||||
config_file: RedmineGitHosting::Config.gitolite_config_file,
|
||||
config_dir: RedmineGitHosting::Config.gitolite_config_dir
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,112 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
class Base
|
||||
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
attr_reader :admin
|
||||
attr_reader :object_id
|
||||
attr_reader :options
|
||||
attr_reader :gitolite_config
|
||||
|
||||
|
||||
def initialize(admin, object_id, options = {})
|
||||
@admin = admin
|
||||
@object_id = object_id
|
||||
@options = options
|
||||
@gitolite_config = admin.config
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def call(admin, object_id, options = {})
|
||||
new(admin, object_id, options).call
|
||||
end
|
||||
|
||||
|
||||
def inherited(klass)
|
||||
@wrappers ||= {}
|
||||
@wrappers[klass.name.demodulize.underscore.to_sym] = klass
|
||||
end
|
||||
|
||||
|
||||
def wrappers
|
||||
@wrappers ||= {}
|
||||
end
|
||||
|
||||
|
||||
def find_by_action_name(action)
|
||||
if wrappers.has_key?(action)
|
||||
wrappers[action]
|
||||
else
|
||||
raise RedmineGitHosting::Error::GitoliteWrapperException.new("No available Wrapper for action '#{action}' found.")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def call
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
def gitolite_admin_repo_commit(message = '')
|
||||
logger.info("#{context} : commiting to Gitolite...")
|
||||
admin.save("#{context} : #{message}")
|
||||
rescue => e
|
||||
logger.error(e.message)
|
||||
end
|
||||
|
||||
|
||||
def create_gitolite_repository(repository)
|
||||
GitoliteHandlers::Repositories::AddRepository.call(gitolite_config, repository, context, options)
|
||||
end
|
||||
|
||||
|
||||
def update_gitolite_repository(repository)
|
||||
GitoliteHandlers::Repositories::UpdateRepository.call(gitolite_config, repository, context, options)
|
||||
end
|
||||
|
||||
|
||||
def delete_gitolite_repository(repository)
|
||||
GitoliteHandlers::Repositories::DeleteRepository.call(gitolite_config, repository, context, options)
|
||||
end
|
||||
|
||||
|
||||
def move_gitolite_repository(repository)
|
||||
GitoliteHandlers::Repositories::MoveRepository.call(gitolite_config, repository, context, options)
|
||||
end
|
||||
|
||||
|
||||
def create_gitolite_key(key)
|
||||
GitoliteHandlers::SshKeys::AddSshKey.call(admin, key, context)
|
||||
end
|
||||
|
||||
|
||||
def delete_gitolite_key(key)
|
||||
GitoliteHandlers::SshKeys::DeleteSshKey.call(admin, key, context)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def context
|
||||
self.class.name.demodulize.underscore
|
||||
end
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def log_object_dont_exist
|
||||
logger.error("#{context} : repository does not exist anymore, object is nil, exit !")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Global
|
||||
module Common
|
||||
|
||||
def redmine_gitolite_key
|
||||
'redmine_gitolite_admin_id_rsa'
|
||||
end
|
||||
|
||||
|
||||
def all_repository
|
||||
'@all'
|
||||
end
|
||||
|
||||
|
||||
def all_repository_config
|
||||
gitolite_config.repos[all_repository]
|
||||
end
|
||||
|
||||
|
||||
def rw_access_config
|
||||
repo_conf = ::Gitolite::Config::Repo.new(all_repository)
|
||||
repo_conf.permissions = rw_access_perms
|
||||
repo_conf
|
||||
end
|
||||
|
||||
|
||||
def rw_access_perms
|
||||
permissions = {}
|
||||
permissions['RW+'] = {}
|
||||
permissions['RW+'][''] = [redmine_gitolite_key]
|
||||
[permissions]
|
||||
end
|
||||
|
||||
|
||||
def repo_conf
|
||||
all_repository_config
|
||||
end
|
||||
|
||||
|
||||
def perms
|
||||
repo_conf.permissions.select { |p| p.has_key? 'RW+' }
|
||||
end
|
||||
|
||||
|
||||
# RedmineGitHosting key can act on any refspec ('') so it should be in that 'subgroup'
|
||||
#
|
||||
def users
|
||||
perms[0]['RW+']['']
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Global
|
||||
class DeleteFromRecycleBin < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
RedmineGitHosting::RecycleBin.delete_content(object_id)
|
||||
RedmineGitHosting.logger.info('delete_from_recycle_bin : done !')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Global
|
||||
class DisableRwAccess < GitoliteWrappers::Base
|
||||
|
||||
include Common
|
||||
|
||||
def call
|
||||
if all_repository_config.nil?
|
||||
logger.info("#{context} : RW access on all Gitolite repositories already disabled.")
|
||||
return
|
||||
else
|
||||
admin.transaction do
|
||||
remove_redmine_key
|
||||
gitolite_admin_repo_commit('Disable RW access on all Gitolite repositories')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def remove_redmine_key
|
||||
# RedmineGitHosting key must be in [RW+][''] group
|
||||
# Return if those groups are absent : it means that our key is not here
|
||||
return if perms.empty? || !perms[0]['RW+'].include?('')
|
||||
|
||||
# Check for key presence
|
||||
return if !users.include?(redmine_gitolite_key)
|
||||
|
||||
# Delete the key
|
||||
repo_conf.permissions[0]['RW+'][''].delete(redmine_gitolite_key)
|
||||
|
||||
# We cannot remove this repository as it may contains other configuration that we didn't check.
|
||||
# Instead add a dummy key so the repo_conf is still valid for Gitolite
|
||||
# RW+ = <empty string> is not valid
|
||||
repo_conf.permissions[0]['RW+'][''].push('DUMMY_REDMINE_KEY') if repo_conf.permissions[0]['RW+'][''].empty?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Global
|
||||
class EnableRwAccess < GitoliteWrappers::Base
|
||||
|
||||
include Common
|
||||
|
||||
def call
|
||||
if all_repository_config.nil?
|
||||
admin.transaction do
|
||||
gitolite_config.add_repo(rw_access_config)
|
||||
gitolite_admin_repo_commit('Enable RW access on all Gitolite repositories')
|
||||
end
|
||||
else
|
||||
logger.info("#{context} : '@all' repository already configured, check for RedmineGitHosting key presence")
|
||||
admin.transaction do
|
||||
add_redmine_key
|
||||
gitolite_admin_repo_commit('Enable RW access on all Gitolite repositories')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add_redmine_key
|
||||
# RedmineGitHosting key must be in RW+ group
|
||||
# If not create the RW+ group and add the key
|
||||
if perms.empty?
|
||||
logger.info("#{context} : No permissions set for '@all' repository, add RedmineGitHosting key")
|
||||
repo_conf.permissions = rw_access_perms
|
||||
elsif users.nil?
|
||||
logger.info("#{context} : RedmineGitHosting key is not present, add it !")
|
||||
repo_conf.permissions[0]['RW+'][''] = [redmine_gitolite_key]
|
||||
elsif !users.include?(redmine_gitolite_key)
|
||||
logger.info("#{context} : RedmineGitHosting key is not present, add it !")
|
||||
repo_conf.permissions[0]['RW+'][''].push(redmine_gitolite_key)
|
||||
else
|
||||
logger.info("#{context} : RedmineGitHosting key is present, nothing to do.")
|
||||
end
|
||||
|
||||
# Delete DUMMY_REDMINE_KEY if present
|
||||
repo_conf.permissions[0]['RW+'][''].delete('DUMMY_REDMINE_KEY') if repo_conf.permissions[0]['RW+'][''].include?('DUMMY_REDMINE_KEY')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Global
|
||||
class PurgeRecycleBin < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
RedmineGitHosting::RecycleBin.delete_expired_content
|
||||
RedmineGitHosting.logger.info('purge_recycle_bin : done !')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Projects
|
||||
module Common
|
||||
|
||||
def handle_repositories_move(projects)
|
||||
repo_list = []
|
||||
delete_parent_path = []
|
||||
projects.reverse.each do |project|
|
||||
project.gitolite_repos.reverse.each do |repository|
|
||||
repo_list << repository.gitolite_repository_name
|
||||
delete_parent_path << move_gitolite_repository(repository)
|
||||
end
|
||||
gitolite_admin_repo_commit("#{context} : #{project.identifier} | #{repo_list}")
|
||||
end
|
||||
delete_parent_path
|
||||
end
|
||||
|
||||
|
||||
def clean_path(path_list)
|
||||
path_list.compact.uniq.sort.reverse.each do |path|
|
||||
rmdir(path)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def rmdir(path)
|
||||
logger.info("#{context} : cleaning repository path : '#{path}'")
|
||||
begin
|
||||
RedmineGitHosting::Commands.sudo_rmdir(path)
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("#{context} : error while cleaning repository path '#{path}'")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Projects
|
||||
class MoveRepositories < GitoliteWrappers::Base
|
||||
|
||||
include Common
|
||||
|
||||
def call
|
||||
return if git_projects.empty?
|
||||
admin.transaction do
|
||||
@delete_parent_path = []
|
||||
@delete_parent_path += handle_repositories_move(git_projects)
|
||||
# Remove empty directories
|
||||
clean_path(@delete_parent_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def git_projects
|
||||
@git_projects ||= projects.uniq.select { |p| p.gitolite_repos.any? }
|
||||
end
|
||||
|
||||
|
||||
def projects
|
||||
@projects ||= Project.find_by_id(object_id).self_and_descendants
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Projects
|
||||
class MoveRepositoriesTree < GitoliteWrappers::Base
|
||||
|
||||
include Common
|
||||
|
||||
# Move repositories tree in a single transaction
|
||||
#
|
||||
def call
|
||||
admin.transaction do
|
||||
@delete_parent_path = []
|
||||
projects.each do |project|
|
||||
# Only take projects that have Git repos.
|
||||
git_projects = project.self_and_descendants.uniq.select { |p| p.gitolite_repos.any? }
|
||||
next if git_projects.empty?
|
||||
@delete_parent_path += handle_repositories_move(git_projects)
|
||||
end
|
||||
# Remove empty directories
|
||||
clean_path(@delete_parent_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def projects
|
||||
@projects ||= Project.includes(:repositories).all.select { |x| x.parent_id.nil? }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Projects
|
||||
class UpdateProjects < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
return if git_projects.empty?
|
||||
admin.transaction do
|
||||
git_projects.each do |project|
|
||||
if project.gitolite_repos.any?
|
||||
handle_project_update(project)
|
||||
gitolite_admin_repo_commit(project.identifier)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def git_projects
|
||||
@git_projects ||= projects.uniq.select { |p| p.gitolite_repos.any? }
|
||||
end
|
||||
|
||||
|
||||
def projects
|
||||
@projects ||=
|
||||
case object_id
|
||||
when 'all'
|
||||
Project.includes(:repositories).all
|
||||
when 'active'
|
||||
Project.active.includes(:repositories).all
|
||||
when 'active_or_closed'
|
||||
Project.active_or_closed.includes(:repositories).all
|
||||
else
|
||||
object_id.map { |project_id| Project.find_by_id(project_id) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def handle_project_update(project)
|
||||
project.gitolite_repos.each do |repository|
|
||||
options[:force] == true ? create_gitolite_repository(repository) : update_gitolite_repository(repository)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Repositories
|
||||
class AddRepository < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
if !repository.nil?
|
||||
create_repository
|
||||
else
|
||||
log_object_dont_exist
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def repository
|
||||
@repository ||= Repository.find_by_id(object_id)
|
||||
end
|
||||
|
||||
|
||||
def create_repository
|
||||
admin.transaction do
|
||||
create_gitolite_repository(repository)
|
||||
gitolite_admin_repo_commit(repository.gitolite_repository_name)
|
||||
|
||||
@recovered = RedmineGitHosting::RecycleBin.restore_object_from_recycle(repository.gitolite_repository_name, repository.gitolite_full_repository_path)
|
||||
|
||||
if !@recovered
|
||||
logger.info("#{context} : let Gitolite create empty repository '#{repository.gitolite_repository_path}'")
|
||||
else
|
||||
logger.info("#{context} : restored existing Gitolite repository '#{repository.gitolite_repository_path}' for update")
|
||||
end
|
||||
end
|
||||
|
||||
# Call Gitolite plugins
|
||||
logger.info('Execute Gitolite Plugins')
|
||||
|
||||
# Create README file or initialize GitAnnex
|
||||
RedmineGitHosting::Plugins.execute(:post_create, repository, options.merge(recovered: @recovered))
|
||||
|
||||
# Fetch changeset
|
||||
repository.fetch_changesets
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Repositories
|
||||
class DeleteRepository < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
if !repository.nil? && !repository.empty?
|
||||
delete_repository
|
||||
else
|
||||
log_object_dont_exist
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def repository
|
||||
@repository ||= object_id.symbolize_keys
|
||||
end
|
||||
|
||||
|
||||
def delete_repository
|
||||
admin.transaction do
|
||||
delete_gitolite_repository(repository)
|
||||
gitolite_admin_repo_commit(repository[:repo_name])
|
||||
end
|
||||
|
||||
# Call Gitolite plugins
|
||||
logger.info('Execute Gitolite Plugins')
|
||||
|
||||
# Move repository to RecycleBin
|
||||
RedmineGitHosting::Plugins.execute(:post_delete, repository)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Repositories
|
||||
class MoveRepository < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
if !repository.nil?
|
||||
move_repository
|
||||
else
|
||||
log_object_dont_exist
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def repository
|
||||
@repository ||= Repository.find_by_id(object_id)
|
||||
end
|
||||
|
||||
|
||||
def move_repository
|
||||
admin.transaction do
|
||||
move_gitolite_repository(repository)
|
||||
gitolite_admin_repo_commit(repository.gitolite_repository_name)
|
||||
end
|
||||
|
||||
# Fetch changeset
|
||||
repository.fetch_changesets
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Repositories
|
||||
class UpdateRepository < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
if !repository.nil?
|
||||
update_repository
|
||||
else
|
||||
log_object_dont_exist
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def repository
|
||||
@repository ||= Repository.find_by_id(object_id)
|
||||
end
|
||||
|
||||
|
||||
def update_repository
|
||||
admin.transaction do
|
||||
update_gitolite_repository(repository)
|
||||
gitolite_admin_repo_commit(repository.gitolite_repository_name)
|
||||
end
|
||||
|
||||
# Call Gitolite plugins
|
||||
logger.info('Execute Gitolite Plugins')
|
||||
|
||||
# Delete Git Config Keys
|
||||
RedmineGitHosting::Plugins.execute(:post_update, repository, options)
|
||||
|
||||
# Fetch changeset
|
||||
repository.fetch_changesets
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Users
|
||||
class AddSshKey < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
logger.info("Adding SSH key '#{ssh_key.identifier}'")
|
||||
admin.transaction do
|
||||
create_gitolite_key(ssh_key)
|
||||
gitolite_admin_repo_commit("Add SSH key : #{ssh_key.identifier}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def ssh_key
|
||||
@ssh_key ||= GitolitePublicKey.find_by_id(object_id)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Users
|
||||
class DeleteSshKey < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
logger.info("Deleting SSH key '#{ssh_key[:title]}'")
|
||||
admin.transaction do
|
||||
delete_gitolite_key(ssh_key)
|
||||
gitolite_admin_repo_commit("Delete SSH key : #{ssh_key[:title]}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def ssh_key
|
||||
@ssh_key ||= object_id.symbolize_keys
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Users
|
||||
class RegenerateSshKeys < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
GitolitePublicKey.all.each do |ssh_key|
|
||||
gitolite_accessor.destroy_ssh_key(ssh_key, bypass_sidekiq: true)
|
||||
ssh_key.reset_identifiers(skip_auto_increment: true)
|
||||
gitolite_accessor.create_ssh_key(ssh_key, bypass_sidekiq: true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
module RedmineGitHosting
|
||||
module GitoliteWrappers
|
||||
module Users
|
||||
class ResyncSshKeys < GitoliteWrappers::Base
|
||||
|
||||
def call
|
||||
admin.transaction do
|
||||
GitolitePublicKey.all.each do |ssh_key|
|
||||
create_gitolite_key(ssh_key)
|
||||
gitolite_admin_repo_commit("Add SSH key : #{ssh_key.identifier}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
81
plugins/redmine_git_hosting/lib/redmine_git_hosting/hooks.rb
Normal file
81
plugins/redmine_git_hosting/lib/redmine_git_hosting/hooks.rb
Normal file
|
@ -0,0 +1,81 @@
|
|||
require 'github/markup'
|
||||
|
||||
module RedmineGitHosting
|
||||
class GitHostingHookListener < Redmine::Hook::ViewListener
|
||||
render_on :view_projects_show_left, partial: 'projects/git_urls'
|
||||
render_on :view_repository_edit_top, partial: 'repositories/edit_top'
|
||||
render_on :view_repositories_show_contextual, partial: 'repositories/show_top'
|
||||
render_on :view_repository_edit_bottom, partial: 'repositories/edit_bottom'
|
||||
render_on :view_repositories_show_sidebar, partial: 'repositories/git_hosting_sidebar'
|
||||
render_on :view_repositories_navigation, partial: 'repositories/git_hosting_navigation'
|
||||
|
||||
def view_layouts_base_html_head(_context = {})
|
||||
header = ''
|
||||
header << stylesheet_link_tag(:plugin, plugin: 'redmine_git_hosting') + "\n"
|
||||
header << javascript_include_tag(:plugin, plugin: 'redmine_git_hosting') + "\n"
|
||||
header
|
||||
end
|
||||
|
||||
def view_my_account_contextual(context)
|
||||
user = context[:user]
|
||||
link_to(l(:label_my_public_keys), public_keys_path, class: 'icon icon-passwd') if user.allowed_to_create_ssh_keys?
|
||||
end
|
||||
|
||||
def self.default_url_options
|
||||
{ script_name: Redmine::Utils.relative_url_root }
|
||||
end
|
||||
|
||||
REDMINE_MARKDOWN_EXT = %w[.txt].freeze
|
||||
GITHUB_MARKDOWN_EXT = %w[.markdown .mdown .mkdn .md].freeze
|
||||
|
||||
def view_repositories_show_bottom(context)
|
||||
path = get_path(context)
|
||||
rev = get_rev(context)
|
||||
repository = context[:repository]
|
||||
readme_file = find_readme_file(repository, path, rev)
|
||||
|
||||
return '' if readme_file.nil?
|
||||
|
||||
content = get_formated_text(repository, readme_file, rev)
|
||||
|
||||
context[:controller].send(:render_to_string, partial: 'repositories/readme', locals: { html: content })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_path(context)
|
||||
context[:request].params['path'] || ''
|
||||
end
|
||||
|
||||
def get_rev(context)
|
||||
rev = context[:request].params['rev']
|
||||
rev.presence
|
||||
end
|
||||
|
||||
def find_readme_file(repository, path, rev)
|
||||
(repository.entries(path, rev) || []).find { |f| f.name =~ /README((\.).*)?/i }
|
||||
end
|
||||
|
||||
def get_formated_text(repository, file, rev)
|
||||
raw_readme_text = Redmine::CodesetUtil.to_utf8_by_setting(repository.cat(file.path, rev))
|
||||
content =
|
||||
if redmine_file?(file)
|
||||
formatter_name = Redmine::WikiFormatting.format_names.find { |name| name =~ /markdown/i }
|
||||
Redmine::WikiFormatting.formatter_for(formatter_name).new(raw_readme_text).to_html
|
||||
elsif github_file?(file)
|
||||
RedmineGitHosting::MarkdownRenderer.to_html(raw_readme_text)
|
||||
else
|
||||
GitHub::Markup.render(file.path, raw_readme_text)
|
||||
end
|
||||
content
|
||||
end
|
||||
|
||||
def redmine_file?(file)
|
||||
REDMINE_MARKDOWN_EXT.include?(File.extname(file.path))
|
||||
end
|
||||
|
||||
def github_file?(file)
|
||||
GITHUB_MARKDOWN_EXT.include?(File.extname(file.path))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
require 'logger'
|
||||
|
||||
module RedmineGitHosting
|
||||
class Logger < ::Logger
|
||||
|
||||
LOG_LEVELS = [
|
||||
'debug',
|
||||
'info',
|
||||
'warn',
|
||||
'error'
|
||||
]
|
||||
|
||||
def self.init_logs!(appname, logfile, loglevel)
|
||||
logger = new(logfile)
|
||||
logger.progname = appname
|
||||
logger.level = loglevel
|
||||
logger.formatter = proc do |severity, time, progname, msg|
|
||||
"#{time} [#{severity}] #{msg}\n"
|
||||
end
|
||||
logger
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
require 'html/pipeline'
|
||||
|
||||
module RedmineGitHosting
|
||||
module MarkdownRenderer
|
||||
extend self
|
||||
|
||||
def to_html(markdown)
|
||||
pipeline.call(markdown)[:output].to_s
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def pipeline
|
||||
HTML::Pipeline.new(filters)
|
||||
end
|
||||
|
||||
|
||||
def filters
|
||||
[
|
||||
RedmineGitHosting::RedcarpetFilter,
|
||||
HTML::Pipeline::AutolinkFilter,
|
||||
HTML::Pipeline::TableOfContentsFilter
|
||||
]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,129 @@
|
|||
module RedmineGitHosting
|
||||
class MirrorKeysInstaller
|
||||
|
||||
attr_reader :gitolite_home_dir
|
||||
attr_reader :gitolite_ssh_public_key
|
||||
attr_reader :gitolite_ssh_private_key
|
||||
|
||||
GITOLITE_MIRRORING_KEYS_NAME = 'redmine_gitolite_admin_id_rsa_mirroring'
|
||||
|
||||
|
||||
def initialize(gitolite_home_dir, gitolite_ssh_public_key, gitolite_ssh_private_key)
|
||||
@gitolite_home_dir = gitolite_home_dir
|
||||
@gitolite_ssh_public_key = gitolite_ssh_public_key
|
||||
@gitolite_ssh_private_key = gitolite_ssh_private_key
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def mirroring_public_key(gitolite_ssh_public_key)
|
||||
begin
|
||||
format_mirror_key(File.read(gitolite_ssh_public_key))
|
||||
rescue => e
|
||||
RedmineGitHosting.logger.error("Error while loading mirroring public key : #{e.output}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def format_mirror_key(key)
|
||||
key = key.chomp.strip
|
||||
key.split(/[\t ]+/)[0].to_s + ' ' + key.split(/[\t ]+/)[1].to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def installed?
|
||||
installable? && install!
|
||||
end
|
||||
|
||||
|
||||
def installable?
|
||||
return false if gitolite_home_dir.nil?
|
||||
return false if gitolite_ssh_public_key_content.nil?
|
||||
return false if gitolite_ssh_private_key_content.nil?
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
def install!
|
||||
logger.info('Installing Redmine Gitolite mirroring SSH keys ...')
|
||||
installed = install_public_key && install_private_key && install_mirroring_script
|
||||
logger.info('Done!')
|
||||
installed
|
||||
end
|
||||
|
||||
|
||||
def install_public_key
|
||||
install_file(gitolite_ssh_public_key_content, gitolite_ssh_public_key_dest_path, '644') do
|
||||
logger.error("Failed to install Redmine Git Hosting mirroring SSH public key : #{e.output}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def install_private_key
|
||||
install_file(gitolite_ssh_private_key_content, gitolite_ssh_private_key_dest_path, '600') do
|
||||
logger.error("Failed to install Redmine Git Hosting mirroring SSH private key : #{e.output}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def install_mirroring_script
|
||||
install_file(mirroring_script_content, RedmineGitHosting::Config.gitolite_mirroring_script, '700') do
|
||||
logger.error("Failed to install Redmine Git Hosting mirroring script : #{e.output}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def mirroring_script_content
|
||||
[
|
||||
'#!/bin/sh', "\n",
|
||||
'exec', 'ssh', '-T', '-o', 'BatchMode=yes', '-o', 'StrictHostKeyChecking=no', '-i', gitolite_ssh_private_key_dest_path, '"$@"',
|
||||
"\n"
|
||||
].join(' ')
|
||||
end
|
||||
|
||||
|
||||
def gitolite_ssh_public_key_content
|
||||
File.read(gitolite_ssh_public_key)
|
||||
rescue => e
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
def gitolite_ssh_private_key_content
|
||||
File.read(gitolite_ssh_private_key)
|
||||
rescue => e
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
def gitolite_ssh_public_key_dest_path
|
||||
File.join(gitolite_home_dir, '.ssh', "#{GITOLITE_MIRRORING_KEYS_NAME}.pub")
|
||||
end
|
||||
|
||||
|
||||
def gitolite_ssh_private_key_dest_path
|
||||
File.join(gitolite_home_dir, '.ssh', GITOLITE_MIRRORING_KEYS_NAME)
|
||||
end
|
||||
|
||||
|
||||
def install_file(source, destination, perms, &block)
|
||||
RedmineGitHosting::Commands.sudo_install_file(source, destination, perms)
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
yield
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,70 @@
|
|||
require_dependency 'changeset'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module ChangesetPatch
|
||||
|
||||
def github_payload
|
||||
data = {}
|
||||
data[:id] = revision
|
||||
data[:message] = comments
|
||||
data[:timestamp] = committed_on
|
||||
data[:author] = author_data
|
||||
data[:added] = added_files
|
||||
data[:modified] = modified_files
|
||||
data[:removed] = removed_files
|
||||
data[:url] = url_for_revision(revision)
|
||||
data
|
||||
end
|
||||
|
||||
|
||||
def author_data
|
||||
{ name: author_name, email: author_email }
|
||||
end
|
||||
|
||||
|
||||
def author_name
|
||||
RedmineGitHosting::Utils::Git.author_name(committer)
|
||||
end
|
||||
|
||||
|
||||
def author_email
|
||||
RedmineGitHosting::Utils::Git.author_email(committer)
|
||||
end
|
||||
|
||||
|
||||
def added_files
|
||||
filechanges_by_action('A')
|
||||
end
|
||||
|
||||
|
||||
def modified_files
|
||||
filechanges_by_action('M')
|
||||
end
|
||||
|
||||
|
||||
def removed_files
|
||||
filechanges_by_action('D')
|
||||
end
|
||||
|
||||
|
||||
def filechanges_by_action(action)
|
||||
filechanges.select { |c| c.action == action }.map(&:path)
|
||||
end
|
||||
|
||||
|
||||
def url_for_revision(revision)
|
||||
Rails.application.routes.url_helpers.url_for(
|
||||
controller: 'repositories', action: 'revision',
|
||||
id: project, repository_id: repository.identifier_param, rev: revision,
|
||||
only_path: false, host: Setting['host_name'], protocol: Setting['protocol']
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Changeset.included_modules.include?(RedmineGitHosting::Patches::ChangesetPatch)
|
||||
Changeset.send(:prepend, RedmineGitHosting::Patches::ChangesetPatch)
|
||||
end
|
|
@ -0,0 +1,144 @@
|
|||
require 'grack/auth'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module GrackAuthPatch
|
||||
|
||||
def call(env)
|
||||
@env = env
|
||||
@request = Rack::Request.new(env)
|
||||
@auth = Rack::Auth::Basic::Request.new(env)
|
||||
|
||||
# Need this patch due to the rails mount
|
||||
|
||||
# Need this if under RELATIVE_URL_ROOT
|
||||
# unless Gitlab.config.gitlab.relative_url_root.empty?
|
||||
# # If website is mounted using relative_url_root need to remove it first
|
||||
# @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'')
|
||||
# else
|
||||
# @env['PATH_INFO'] = @request.path
|
||||
# end
|
||||
|
||||
if repository
|
||||
auth!
|
||||
else
|
||||
render_not_found
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def auth!
|
||||
if @auth.provided?
|
||||
return bad_request unless @auth.basic?
|
||||
|
||||
# Authentication with username and password
|
||||
login, password = @auth.credentials
|
||||
@user = authenticate_user(login, password)
|
||||
|
||||
@env['REMOTE_USER'] = @user.gitolite_identifier if @user
|
||||
end
|
||||
|
||||
if authorized_request?
|
||||
@app.call(@env)
|
||||
else
|
||||
unauthorized
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def authenticate_user(login, password)
|
||||
auth = RedmineGitHosting::Auth.new
|
||||
auth.find(login, password)
|
||||
end
|
||||
|
||||
|
||||
def authorized_request?
|
||||
case git_cmd
|
||||
when *RedmineGitHosting::GitAccess::DOWNLOAD_COMMANDS
|
||||
if @user
|
||||
RedmineGitHosting::GitAccess.new.download_access_check(@user, repository, is_ssl?).allowed?
|
||||
elsif repository.public_project? || repository.public_repo?
|
||||
# Allow clone/fetch for public projects
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
when *RedmineGitHosting::GitAccess::PUSH_COMMANDS
|
||||
# Push requires valid SSL
|
||||
if !is_ssl?
|
||||
logger.error('SmartHttp : your are trying to push data without SSL!, exiting !')
|
||||
false
|
||||
elsif @user
|
||||
RedmineGitHosting::GitAccess.new.upload_access_check(@user, repository).allowed?
|
||||
else
|
||||
false
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def git_cmd
|
||||
if @request.get?
|
||||
@request.params['service']
|
||||
elsif @request.post?
|
||||
File.basename(@request.path)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def repository
|
||||
@repository ||= repository_by_path(@request.path_info)
|
||||
end
|
||||
|
||||
|
||||
def repository_by_path(path)
|
||||
if m = /([^\/]+\/)*?[^\/]+\.git/.match(path).to_a
|
||||
repo_path = m.first
|
||||
Repository::Xitolite.find_by_path(repo_path, loose: true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def is_ssl?
|
||||
@request.ssl? || https_headers? || x_forwarded_proto_headers? || x_forwarded_ssl_headers?
|
||||
end
|
||||
|
||||
|
||||
def https_headers?
|
||||
@request.env['HTTPS'].to_s == 'on'
|
||||
end
|
||||
|
||||
|
||||
def x_forwarded_proto_headers?
|
||||
@request.env['HTTP_X_FORWARDED_PROTO'].to_s == 'https'
|
||||
end
|
||||
|
||||
|
||||
def x_forwarded_ssl_headers?
|
||||
@request.env['HTTP_X_FORWARDED_SSL'].to_s == 'on'
|
||||
end
|
||||
|
||||
|
||||
def render_not_found
|
||||
[404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
|
||||
end
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Grack::Auth.included_modules.include?(RedmineGitHosting::Patches::GrackAuthPatch)
|
||||
Grack::Auth.send(:prepend, RedmineGitHosting::Patches::GrackAuthPatch)
|
||||
end
|
|
@ -0,0 +1,132 @@
|
|||
require 'grack/git'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module GrackGitPatch
|
||||
|
||||
def initialize(git_path, repo_path, user)
|
||||
@user = user
|
||||
super(git_path, repo_path)
|
||||
end
|
||||
|
||||
|
||||
# Override original *valid_repo?* method to test directory presence with Sudo
|
||||
def valid_repo?
|
||||
directory_exists?(repo)
|
||||
end
|
||||
|
||||
|
||||
# Override original *command* method to prefix the command with Sudo and other args.
|
||||
#
|
||||
def command(cmd)
|
||||
git_command_with_sudo(cmd)
|
||||
end
|
||||
|
||||
|
||||
# Override original *capture* method because the original IO.popen().read let zombie process behind.
|
||||
# This method is called :
|
||||
# * to get repository Git config (http.uploadpack || http.receivepack)
|
||||
# * to get repository info refs :
|
||||
# 0087deab8f3d612a47e7e153ed21bbc52a480205035a refs/heads/devel report-status delete-refs side-band-64k quiet ofs-delta agent=git/1.9.1
|
||||
# * to get repository refs :
|
||||
# 003f91a7b1dad21020e96d52119c585881c02f2fae45 refs/heads/master
|
||||
|
||||
# Note : *service_rpc* also calls IO.popen but pass a block !.
|
||||
# Passing a block to IO.popen auto-close the pipe/thread.
|
||||
|
||||
def capture(command)
|
||||
# Extract Args
|
||||
cmd = command.shift
|
||||
args = command
|
||||
|
||||
begin
|
||||
RedmineGitHosting::Utils::Exec.capture(cmd, args)
|
||||
rescue => e
|
||||
logger.error('Problems while getting SmartHttp params')
|
||||
# Return empty string since the next method will call *chomp* on it
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Override original *popen_options* method.
|
||||
# The original one try to chdir before executing the command by
|
||||
# passing 'chdir: @dir' option to IO.popen.
|
||||
# This is wrong as we can't chdir to Gitolite directory.
|
||||
# Notes : this method is called in *service_rpc* (not overriden)
|
||||
#
|
||||
def popen_options
|
||||
{ unsetenv_others: true }
|
||||
end
|
||||
|
||||
|
||||
# Override original *popen_env* method.
|
||||
# The original one passes useless arg (GL_ID) to IO.popen.
|
||||
# Notes : this method is called in *service_rpc* (not overriden)
|
||||
#
|
||||
def popen_env
|
||||
{ 'PATH' => ENV['PATH'] }
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def directory_exists?(dir)
|
||||
RedmineGitHosting::Commands.sudo_dir_exists?(dir)
|
||||
end
|
||||
|
||||
|
||||
# We sometimes need to add *--git-dir* arg to Git command otherwise
|
||||
# Git looks for the repository in the current path.
|
||||
def git_command_with_sudo(params)
|
||||
if command_require_chdir?(params.last)
|
||||
git_command_with_chdir.concat(params)
|
||||
else
|
||||
git_command_without_chdir.concat(params)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def command_require_chdir?(cmd)
|
||||
cmd == 'update-server-info' || cmd == 'http.receivepack' || cmd == 'http.uploadpack' || cmd == 'rev-parse'
|
||||
end
|
||||
|
||||
|
||||
def git_command_without_chdir
|
||||
RedmineGitHosting::Commands.sudo_git_cmd(smart_http_args)
|
||||
end
|
||||
|
||||
|
||||
def git_command_with_chdir
|
||||
RedmineGitHosting::Commands.sudo_git_args_for_repo(@repo, smart_http_args)
|
||||
end
|
||||
|
||||
|
||||
def smart_http_args
|
||||
[
|
||||
'env',
|
||||
"GL_BINDIR=#{RedmineGitHosting::Config.gitolite_bin_dir}",
|
||||
"GL_LIBDIR=#{RedmineGitHosting::Config.gitolite_lib_dir}",
|
||||
"GL_REPO=#{repository_object.gitolite_repository_name}",
|
||||
"GL_USER=#{@user}"
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def logger
|
||||
RedmineGitHosting.logger
|
||||
end
|
||||
|
||||
|
||||
def repository_object
|
||||
@repository_object ||= Repository::Xitolite.find_by_path(@repo, loose: true)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Grack::Git.included_modules.include?(RedmineGitHosting::Patches::GrackGitPatch)
|
||||
Grack::Git.send(:prepend, RedmineGitHosting::Patches::GrackGitPatch)
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
require 'grack/server'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module GrackServerPatch
|
||||
|
||||
# Override original *get_git* method to set the right path for the repository.
|
||||
# Also pass the *@env['REMOTE_USER']* variable to the Git constructor so we
|
||||
# can pass it to Gitolite hooks later.
|
||||
def get_git(path)
|
||||
path = gitolite_path(path)
|
||||
Grack::Git.new(@config[:git_path], path, @env['REMOTE_USER'])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gitolite_path(path)
|
||||
File.join(RedmineGitHosting::Config.gitolite_home_dir, RedmineGitHosting::Config.gitolite_global_storage_dir, RedmineGitHosting::Config.gitolite_redmine_storage_dir, path)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Grack::Server.included_modules.include?(RedmineGitHosting::Patches::GrackServerPatch)
|
||||
Grack::Server.send(:prepend, RedmineGitHosting::Patches::GrackServerPatch)
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
require_dependency 'group'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module GroupPatch
|
||||
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
# Relations
|
||||
has_many :protected_branches_members, dependent: :destroy, foreign_key: :principal_id
|
||||
has_many :protected_branches, through: :protected_branches_members
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def user_added(user)
|
||||
super
|
||||
protected_branches.each do |pb|
|
||||
RepositoryProtectedBranches::MemberManager.new(pb).add_user_from_group(user, self.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def user_removed(user)
|
||||
super
|
||||
protected_branches.each do |pb|
|
||||
RepositoryProtectedBranches::MemberManager.new(pb).remove_user_from_group(user, self.id)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Group.included_modules.include?(RedmineGitHosting::Patches::GroupPatch)
|
||||
Group.send(:prepend, RedmineGitHosting::Patches::GroupPatch)
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
require_dependency 'issue'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module IssuePatch
|
||||
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
has_one :github_issue, foreign_key: 'issue_id', class_name: 'GithubIssue', dependent: :destroy
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Issue.included_modules.include?(RedmineGitHosting::Patches::IssuePatch)
|
||||
Issue.send(:prepend, RedmineGitHosting::Patches::IssuePatch)
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
require_dependency 'journal'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module JournalPatch
|
||||
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
has_one :github_comment, foreign_key: 'journal_id', class_name: 'GithubComment', dependent: :destroy
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Journal.included_modules.include?(RedmineGitHosting::Patches::JournalPatch)
|
||||
Journal.send(:prepend, RedmineGitHosting::Patches::JournalPatch)
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
require_dependency 'member'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module MemberPatch
|
||||
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
after_commit :update_project
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_project
|
||||
options = { message: "Membership changes on project '#{project}', update!" }
|
||||
gitolite_accessor.update_projects([project.id], options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Member.included_modules.include?(RedmineGitHosting::Patches::MemberPatch)
|
||||
Member.send(:prepend, RedmineGitHosting::Patches::MemberPatch)
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
require_dependency 'project'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module ProjectPatch
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
# Add custom scope
|
||||
scope :active_or_closed, -> { where("status = #{Project::STATUS_ACTIVE} OR status = #{Project::STATUS_CLOSED}") }
|
||||
|
||||
# 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
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Find all repositories owned by project which are Repository::Xitolite
|
||||
def gitolite_repos
|
||||
repositories.select { |x| x.is_a?(Repository::Xitolite) }.sort { |x, y| x.id <=> y.id }
|
||||
end
|
||||
|
||||
|
||||
# Return first repo with a blank identifier (should be only one!)
|
||||
def repo_blank_ident
|
||||
Repository.where("project_id = ? and (identifier = '' or identifier is null)", id).first
|
||||
end
|
||||
|
||||
|
||||
def users_available
|
||||
get_members_available('User')
|
||||
end
|
||||
|
||||
|
||||
def groups_available
|
||||
get_members_available('Group')
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def get_members_available(klass)
|
||||
memberships.active.map(&:principal).select { |m| m.class.name == klass }.uniq.sort
|
||||
end
|
||||
|
||||
|
||||
def additional_constraints_on_identifier
|
||||
if new_record? && !identifier.blank?
|
||||
# Make sure that identifier does not match existing repository identifier
|
||||
errors.add(:identifier, :taken) if Repository.find_by_identifier_and_type(identifier, 'Repository::Xitolite')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Project.included_modules.include?(RedmineGitHosting::Patches::ProjectPatch)
|
||||
Project.send(:prepend, RedmineGitHosting::Patches::ProjectPatch)
|
||||
end
|
|
@ -0,0 +1,132 @@
|
|||
require_dependency 'projects_controller'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module ProjectsControllerPatch
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
helper :bootstrap_kit
|
||||
helper :additionals_clipboardjs
|
||||
helper :extend_projects
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
super
|
||||
# Only create repo if project creation worked
|
||||
create_project_repository if valid_project?
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
if @project.gitolite_repos.detect { |repo| repo.url != repo.gitolite_repository_path || repo.url != repo.root_url }
|
||||
# Hm... something about parent hierarchy changed. Update us and our children
|
||||
move_project_hierarchy
|
||||
else
|
||||
update_project("Set Git daemon for repositories of project : '#{@project}'")
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
# Build repositories list before project destruction.
|
||||
repositories_list = repositories_to_destroy
|
||||
# Destroy project
|
||||
super
|
||||
# Destroy repositories
|
||||
destroy_repositories(repositories_list) if api_request? || params[:confirm]
|
||||
end
|
||||
|
||||
def archive
|
||||
super
|
||||
update_project_hierarchy("User '#{User.current.login}' has archived project '#{@project}', update it !")
|
||||
end
|
||||
|
||||
def unarchive
|
||||
super
|
||||
update_project("User '#{User.current.login}' has unarchived project '#{@project}', update it !")
|
||||
end
|
||||
|
||||
def close
|
||||
super
|
||||
update_project_hierarchy("User '#{User.current.login}' has closed project '#{@project}', update it !")
|
||||
end
|
||||
|
||||
def reopen
|
||||
super
|
||||
update_project_hierarchy("User '#{User.current.login}' has reopened project '#{@project}', update it !")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_project?
|
||||
if Rails::VERSION::MAJOR == 3
|
||||
validate_parent_id && @project.save
|
||||
else
|
||||
@project.save
|
||||
end
|
||||
end
|
||||
|
||||
# Call UseCase object that will complete Project repository creation :
|
||||
# it will create the Repository::Xitolite association, the GitExtra association and then
|
||||
# the repository in Gitolite.
|
||||
#
|
||||
def create_project_repository
|
||||
if @project.module_enabled?('repository') && RedmineGitHosting::Config.all_projects_use_git?
|
||||
if Setting.enabled_scm.include?('Xitolite')
|
||||
Projects::CreateRepository.call(@project)
|
||||
else
|
||||
flash[:error] = l(:error_xitolite_repositories_disabled)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def move_project_hierarchy
|
||||
gitolite_accessor.move_project_hierarchy(@project)
|
||||
end
|
||||
|
||||
def update_project(message)
|
||||
options = { message: message }
|
||||
Projects::Update.call(@project, options)
|
||||
end
|
||||
|
||||
def update_project_hierarchy(message)
|
||||
options = { message: message }
|
||||
gitolite_accessor.update_projects(hierarchy_to_update, options)
|
||||
end
|
||||
|
||||
def hierarchy_to_update
|
||||
# Only take projects that have Git repos.
|
||||
@project.self_and_descendants.uniq.select { |p| p.gitolite_repos.any? }.map(&:id)
|
||||
end
|
||||
|
||||
def destroy_repositories(repositories_list)
|
||||
options = { message: "User '#{User.current.login}' has destroyed project '#{@project}', delete all Gitolite repositories !" }
|
||||
gitolite_accessor.destroy_repositories(repositories_list, options)
|
||||
end
|
||||
|
||||
def repositories_to_destroy
|
||||
destroy_repositories = []
|
||||
|
||||
# Get all projects hierarchy
|
||||
projects = @project.self_and_descendants
|
||||
|
||||
# Only take projects that have Git repos.
|
||||
git_projects = projects.uniq.select { |p| p.gitolite_repos.any? }
|
||||
|
||||
git_projects.reverse.each do |project|
|
||||
project.gitolite_repos.reverse.each do |repository|
|
||||
destroy_repositories << repository.data_for_destruction
|
||||
end
|
||||
end
|
||||
|
||||
destroy_repositories
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless ProjectsController.included_modules.include?(RedmineGitHosting::Patches::ProjectsControllerPatch)
|
||||
ProjectsController.send(:prepend, RedmineGitHosting::Patches::ProjectsControllerPatch)
|
||||
end
|
|
@ -0,0 +1,162 @@
|
|||
require_dependency 'repositories_controller'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module RepositoriesControllerPatch
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
before_action :set_current_tab, only: :edit
|
||||
|
||||
helper :bootstrap_kit
|
||||
helper :additionals_clipboardjs
|
||||
helper :watchers
|
||||
|
||||
# Load ExtendRepositoriesHelper so we can call our
|
||||
# additional methods.
|
||||
helper :extend_repositories
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
if @repository.is_a?(Repository::Xitolite) && @repository.empty?
|
||||
# Fake list of repos
|
||||
@repositories = @project.gitolite_repos
|
||||
render 'git_instructions'
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
super
|
||||
call_use_cases
|
||||
end
|
||||
|
||||
def update
|
||||
super
|
||||
call_use_cases
|
||||
end
|
||||
|
||||
def destroy
|
||||
super
|
||||
call_use_cases
|
||||
end
|
||||
|
||||
# Monkey patch *diff* method to pass the *bypass_cache* flag
|
||||
# on diff download.
|
||||
#
|
||||
def diff
|
||||
if @repository.is_a?(Repository::Xitolite)
|
||||
diff_with_options
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_tab
|
||||
@tab = params[:tab] || ''
|
||||
end
|
||||
|
||||
def call_use_cases
|
||||
return unless @repository.is_a?(Repository::Xitolite)
|
||||
return if @repository.errors.any?
|
||||
|
||||
case action_name
|
||||
when 'create'
|
||||
# Call UseCase object that will complete Repository creation :
|
||||
# it will create GitExtra association and then the repository in Gitolite.
|
||||
Repositories::Create.call(@repository, creation_options)
|
||||
when 'update'
|
||||
gitolite_accessor.update_repository(@repository)
|
||||
when 'destroy'
|
||||
gitolite_accessor.destroy_repository(@repository)
|
||||
end
|
||||
end
|
||||
|
||||
def creation_options
|
||||
{ create_readme_file: create_readme_file?, enable_git_annex: enable_git_annex? }
|
||||
end
|
||||
|
||||
def create_readme_file?
|
||||
Additionals.true? @repository.create_readme
|
||||
end
|
||||
|
||||
def enable_git_annex?
|
||||
Additionals.true? @repository.enable_git_annex
|
||||
end
|
||||
|
||||
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
|
||||
|
||||
# Monkey patch *find_project_repository* method to render Git instructions
|
||||
# if repository has no branch
|
||||
#
|
||||
def find_project_repository
|
||||
@project = Project.find(params[:id])
|
||||
if params[:repository_id].present?
|
||||
@repository = @project.repositories.find_by_identifier_param(params[:repository_id])
|
||||
else
|
||||
@repository = @project.repository
|
||||
end
|
||||
(render_404; return false) unless @repository
|
||||
@path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
|
||||
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
|
||||
@rev_to = params[:rev_to]
|
||||
|
||||
unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
|
||||
raise InvalidRevisionParam if @repository.branches.empty?
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
rescue InvalidRevisionParam
|
||||
# Fake list of repos
|
||||
@repositories = @project.gitolite_repos
|
||||
render 'git_instructions'
|
||||
end
|
||||
|
||||
# This is the original diff method with the *bypass_cache* flag
|
||||
# for diff download. We keep the cache for the diff view.
|
||||
#
|
||||
def diff_with_options
|
||||
if params[:format] == 'diff'
|
||||
@diff = @repository.diff(@path, @rev, @rev_to, bypass_cache: true)
|
||||
(show_error_not_found; return) unless @diff
|
||||
filename = "changeset_r#{@rev}"
|
||||
filename << "_r#{@rev_to}" if @rev_to
|
||||
send_data @diff.join, filename: "#{filename}.diff",
|
||||
type: 'text/x-patch',
|
||||
disposition: 'attachment'
|
||||
else
|
||||
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
|
||||
@diff_type = 'inline' unless %w[inline sbs].include?(@diff_type)
|
||||
|
||||
# Save diff type as user preference
|
||||
if User.current.logged? && @diff_type != User.current.pref[:diff_type]
|
||||
User.current.pref[:diff_type] = @diff_type
|
||||
User.current.preference.save
|
||||
end
|
||||
@cache_key = "repositories/diff/#{@repository.id}/" +
|
||||
Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
|
||||
unless read_fragment(@cache_key)
|
||||
@diff = @repository.diff(@path, @rev, @rev_to)
|
||||
unless @diff
|
||||
show_error_not_found
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
@changeset = @repository.find_changeset_by_name(@rev)
|
||||
@changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
|
||||
@diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
|
||||
render :diff, formats: :html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless RepositoriesController.included_modules.include?(RedmineGitHosting::Patches::RepositoriesControllerPatch)
|
||||
RepositoriesController.send(:prepend, RedmineGitHosting::Patches::RepositoriesControllerPatch)
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
require_dependency 'repositories_helper'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module RepositoriesHelperPatch
|
||||
|
||||
def xitolite_field_tags(form, repository)
|
||||
encoding_field(form, repository) +
|
||||
create_readme_field(form, repository) +
|
||||
enable_git_annex_field(form, repository)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless RepositoriesHelper.included_modules.include?(RedmineGitHosting::Patches::RepositoriesHelperPatch)
|
||||
RepositoriesHelper.send(:prepend, RedmineGitHosting::Patches::RepositoriesHelperPatch)
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
require_dependency 'repository'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module RepositoryPatch
|
||||
|
||||
# This is the (possibly non-unique) basename for the Gitolite repository
|
||||
#
|
||||
def redmine_name
|
||||
identifier.blank? ? project.identifier : identifier
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Repository.included_modules.include?(RedmineGitHosting::Patches::RepositoryPatch)
|
||||
Repository.send(:prepend, RedmineGitHosting::Patches::RepositoryPatch)
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
require_dependency 'roles_controller'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module RolesControllerPatch
|
||||
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
def create
|
||||
super
|
||||
call_gitolite('created')
|
||||
end
|
||||
|
||||
|
||||
def update
|
||||
super
|
||||
call_gitolite('modified')
|
||||
end
|
||||
|
||||
|
||||
def destroy
|
||||
super
|
||||
call_gitolite('deleted')
|
||||
end
|
||||
|
||||
|
||||
def permissions
|
||||
super
|
||||
call_gitolite('modified') if request.post?
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def call_gitolite(message)
|
||||
options = { message: "Role has been #{message}, resync all projects (active or closed)..." }
|
||||
gitolite_accessor.update_projects('active_or_closed', options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless RolesController.included_modules.include?(RedmineGitHosting::Patches::RolesControllerPatch)
|
||||
RolesController.send(:prepend, RedmineGitHosting::Patches::RolesControllerPatch)
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
require_dependency 'setting'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module SettingPatch
|
||||
|
||||
def self.prepended(base)
|
||||
class << base
|
||||
prepend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def check_cache
|
||||
settings_updated_on = Setting.maximum(:updated_on)
|
||||
if settings_updated_on && @cached_cleared_on <= settings_updated_on
|
||||
clear_cache
|
||||
RedmineGitHosting::Config.check_cache
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Setting.included_modules.include?(RedmineGitHosting::Patches::SettingPatch)
|
||||
Setting.send(:prepend, RedmineGitHosting::Patches::SettingPatch)
|
||||
end
|
|
@ -0,0 +1,76 @@
|
|||
require_dependency 'settings_controller'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module SettingsControllerPatch
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
helper :bootstrap_kit
|
||||
helper :gitolite_plugin_settings
|
||||
end
|
||||
end
|
||||
|
||||
def authors
|
||||
@plugin = Redmine::Plugin.find(params[:id])
|
||||
return render_404 unless @plugin.id == :redmine_git_hosting
|
||||
|
||||
@authors = RedmineGitHosting.authors
|
||||
render layout: false
|
||||
end
|
||||
|
||||
def install_gitolite_hooks
|
||||
@plugin = Redmine::Plugin.find(params[:id])
|
||||
return render_404 unless @plugin.id == :redmine_git_hosting
|
||||
|
||||
@gitolite_checks = RedmineGitHosting::Config.install_hooks!
|
||||
end
|
||||
|
||||
def plugin
|
||||
@plugin = Redmine::Plugin.find(params[:id])
|
||||
return super unless @plugin.id == :redmine_git_hosting
|
||||
|
||||
if request.post?
|
||||
handle_settings_update
|
||||
redirect_to plugin_settings_path(@plugin)
|
||||
else
|
||||
@partial = @plugin.settings[:partial]
|
||||
@settings = Setting.send "plugin_#{@plugin.id}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_settings_update
|
||||
# Create FormObject
|
||||
settings_form = PluginSettingsForm.new(@plugin)
|
||||
|
||||
# Strip *rescue* hash from params as we don't want to save them
|
||||
options = params[:settings].delete(:rescue) { {} }
|
||||
|
||||
# Validate form
|
||||
if settings_form.submit(params[:settings])
|
||||
# Backup old settings
|
||||
old_settings = Setting.send("plugin_#{@plugin.id}")
|
||||
|
||||
# Save settings for real
|
||||
Setting.send "plugin_#{@plugin.id}=", settings_form.params
|
||||
|
||||
# Execute post actions
|
||||
execute_post_actions(old_settings, options)
|
||||
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
else
|
||||
flash[:error] = settings_form.errors.full_messages.join('<br>')
|
||||
end
|
||||
end
|
||||
|
||||
def execute_post_actions(old_settings, opts = {})
|
||||
Settings::Apply.call(old_settings, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless SettingsController.included_modules.include?(RedmineGitHosting::Patches::SettingsControllerPatch)
|
||||
SettingsController.send(:prepend, RedmineGitHosting::Patches::SettingsControllerPatch)
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
require_dependency 'sys_controller'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module SysControllerPatch
|
||||
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
def fetch_changesets
|
||||
# Flush GitCache
|
||||
gitolite_accessor.flush_git_cache
|
||||
|
||||
super
|
||||
|
||||
# Purge RecycleBin
|
||||
gitolite_accessor.purge_recycle_bin
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless SysController.included_modules.include?(RedmineGitHosting::Patches::SysControllerPatch)
|
||||
SysController.send(:prepend, RedmineGitHosting::Patches::SysControllerPatch)
|
||||
end
|
|
@ -0,0 +1,105 @@
|
|||
require_dependency 'user'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module UserPatch
|
||||
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
# Virtual attribute
|
||||
attr_accessor :status_has_changed
|
||||
|
||||
# Relations
|
||||
has_many :gitolite_public_keys, dependent: :destroy
|
||||
|
||||
has_many :protected_branches_members, dependent: :destroy, foreign_key: :principal_id
|
||||
has_many :protected_branches, through: :protected_branches_members
|
||||
|
||||
# Callbacks
|
||||
after_save :check_if_status_changed
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns a unique identifier for this user to use for gitolite keys.
|
||||
# As login names may change (i.e., user renamed), we use the user id
|
||||
# with its login name as a prefix for readibility.
|
||||
def gitolite_identifier
|
||||
identifier = [RedmineGitHosting::Config.gitolite_identifier_prefix, stripped_login]
|
||||
identifier.concat(['_', id]) unless RedmineGitHosting::Config.gitolite_identifier_strip_user_id?
|
||||
identifier.join
|
||||
end
|
||||
|
||||
|
||||
def gitolite_projects
|
||||
projects.uniq.select { |p| p.gitolite_repos.any? }
|
||||
end
|
||||
|
||||
|
||||
# Syntaxic sugar
|
||||
def status_has_changed?
|
||||
status_has_changed
|
||||
end
|
||||
|
||||
|
||||
def allowed_to_manage_repository?(repository)
|
||||
!roles_for_project(repository.project).select { |role| role.allowed_to?(:manage_repository) }.empty?
|
||||
end
|
||||
|
||||
|
||||
def allowed_to_commit?(repository)
|
||||
allowed_to?(:commit_access, repository.project)
|
||||
end
|
||||
|
||||
|
||||
def allowed_to_clone?(repository)
|
||||
allowed_to?(:view_changesets, repository.project)
|
||||
end
|
||||
|
||||
|
||||
def allowed_to_create_ssh_keys?
|
||||
allowed_to?(:create_gitolite_ssh_key, nil, global: true)
|
||||
end
|
||||
|
||||
|
||||
def allowed_to_download?(repository)
|
||||
git_allowed_to?(:download_git_revision, repository)
|
||||
end
|
||||
|
||||
|
||||
def git_allowed_to?(permission, repository)
|
||||
if repository.project.active?
|
||||
allowed_to?(permission, repository.project)
|
||||
else
|
||||
allowed_to?(permission, nil, global: true)
|
||||
end
|
||||
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_status_changed
|
||||
if status_changed?
|
||||
self.status_has_changed = true
|
||||
else
|
||||
self.status_has_changed = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def stripped_login
|
||||
login.underscore.gsub(/[^0-9a-zA-Z]/, '_')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless User.included_modules.include?(RedmineGitHosting::Patches::UserPatch)
|
||||
User.send(:prepend, RedmineGitHosting::Patches::UserPatch)
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
require_dependency 'users_controller'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module UsersControllerPatch
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
def self.prepended(base)
|
||||
base.send :helper, GitHostingUsersHelper
|
||||
base.class_eval do
|
||||
helper :gitolite_public_keys
|
||||
helper :bootstrap_kit
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
# Set public key values for view
|
||||
set_public_key_values
|
||||
super
|
||||
end
|
||||
|
||||
def update
|
||||
# Set public key values for view (in case of invalid form)
|
||||
set_public_key_values
|
||||
super
|
||||
|
||||
# Update projects if needed
|
||||
update_projects if @user.status_has_changed?
|
||||
end
|
||||
|
||||
def destroy
|
||||
# Build SSH keys list before user destruction.
|
||||
ssh_keys_list = ssh_keys_to_destroy
|
||||
|
||||
# Destroy user
|
||||
super
|
||||
|
||||
# Destroy SSH keys
|
||||
destroy_ssh_keys(ssh_keys_list)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Add in values for viewing public keys:
|
||||
def set_public_key_values
|
||||
@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 update_projects
|
||||
gitolite_accessor.update_projects(projects_to_update, message: "Status of '#{@user.login}' has changed, update projects")
|
||||
end
|
||||
|
||||
def projects_to_update
|
||||
@user.gitolite_projects.map(&:id)
|
||||
end
|
||||
|
||||
def ssh_keys_to_destroy
|
||||
@user.gitolite_public_keys.map(&:data_for_destruction)
|
||||
end
|
||||
|
||||
def destroy_ssh_keys(ssh_keys_list)
|
||||
RedmineGitHosting.logger.info("User '#{@user.login}' has been deleted from Redmine, delete membership and SSH keys !")
|
||||
ssh_keys_list.each do |ssh_key|
|
||||
gitolite_accessor.destroy_ssh_key(ssh_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless UsersController.included_modules.include?(RedmineGitHosting::Patches::UsersControllerPatch)
|
||||
UsersController.send(:prepend, RedmineGitHosting::Patches::UsersControllerPatch)
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
require_dependency 'watchers_controller'
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module WatchersControllerPatch
|
||||
|
||||
include RedmineGitHosting::GitoliteAccessor::Methods
|
||||
|
||||
def create
|
||||
super
|
||||
update_repository(@watched)
|
||||
end
|
||||
|
||||
|
||||
def destroy
|
||||
super
|
||||
update_repository(@watched)
|
||||
end
|
||||
|
||||
|
||||
def watch
|
||||
super
|
||||
update_repository(@watchables.first)
|
||||
end
|
||||
|
||||
|
||||
def unwatch
|
||||
super
|
||||
update_repository(@watchables.first)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def update_repository(repo)
|
||||
return if !repo.is_a?(Repository::Xitolite)
|
||||
options = { message: "Watcher changes on repository '#{repo}', update!" }
|
||||
gitolite_accessor.update_repository(repo, options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless WatchersController.included_modules.include?(RedmineGitHosting::Patches::WatchersControllerPatch)
|
||||
WatchersController.send(:prepend, RedmineGitHosting::Patches::WatchersControllerPatch)
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
require_dependency 'watchers_helper'
|
||||
|
||||
# This patch fix http://www.redmine.org/issues/12348
|
||||
|
||||
module RedmineGitHosting
|
||||
module Patches
|
||||
module WatchersHelperPatch
|
||||
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
alias_method :watcher_css_without_git_hosting, :watcher_css
|
||||
alias_method :watcher_css, :watcher_css_with_git_hosting
|
||||
|
||||
alias_method :watchers_list_without_git_hosting, :watchers_list
|
||||
alias_method :watchers_list, :watchers_list_with_git_hosting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def watcher_css_with_git_hosting(objects, &block)
|
||||
watcher_css_without_git_hosting(objects, &block).gsub('/', '-')
|
||||
end
|
||||
|
||||
|
||||
def watchers_list_with_git_hosting(object, &block)
|
||||
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".gsub('/', '_').to_sym, object.project)
|
||||
content = ''.html_safe
|
||||
lis = object.watcher_users.collect do |user|
|
||||
s = ''.html_safe
|
||||
s << avatar(user, :size => "16").to_s
|
||||
s << link_to_user(user, :class => 'user')
|
||||
if remove_allowed
|
||||
url = {:controller => 'watchers',
|
||||
:action => 'destroy',
|
||||
:object_type => object.class.to_s.underscore,
|
||||
:object_id => object.id,
|
||||
:user_id => user}
|
||||
s << ' '
|
||||
s << link_to(image_tag('delete.png'), url,
|
||||
:remote => true, :method => 'delete', :class => "delete")
|
||||
end
|
||||
content << content_tag('li', s, :class => "user-#{user.id}")
|
||||
end
|
||||
content.present? ? content_tag('ul', content, :class => 'watchers') : content
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless WatchersHelper.included_modules.include?(RedmineGitHosting::Patches::WatchersHelperPatch)
|
||||
WatchersHelper.send(:prepend, RedmineGitHosting::Patches::WatchersHelperPatch)
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
module RedmineGitHosting
|
||||
class PluginAuthor
|
||||
|
||||
attr_reader :author
|
||||
|
||||
|
||||
def initialize(author)
|
||||
@author = author
|
||||
end
|
||||
|
||||
|
||||
def name
|
||||
RedmineGitHosting::Utils::Git.author_name(author)
|
||||
end
|
||||
|
||||
|
||||
def email
|
||||
RedmineGitHosting::Utils::Git.author_email(author).downcase
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
module RedmineGitHosting
|
||||
module Plugins
|
||||
extend self
|
||||
|
||||
def execute(step, repository, opts = {})
|
||||
Plugins::GitolitePlugin.all_plugins.each do |plugin|
|
||||
plugin.new(repository, opts).send(step) if plugin.method_defined?(step)
|
||||
end
|
||||
rescue => e
|
||||
RedmineGitHosting.logger.error e.message
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
module RedmineGitHosting::Plugins::Extenders
|
||||
class BaseExtender < RedmineGitHosting::Plugins::GitolitePlugin
|
||||
|
||||
attr_reader :repository
|
||||
attr_reader :recovered
|
||||
attr_reader :gitolite_repo_name
|
||||
attr_reader :gitolite_repo_path
|
||||
attr_reader :git_default_branch
|
||||
attr_reader :options
|
||||
|
||||
|
||||
def initialize(repository, options = {})
|
||||
@repository = repository
|
||||
@recovered = options.delete(:recovered) { false }
|
||||
@gitolite_repo_name = repository.gitolite_repository_name
|
||||
@gitolite_repo_path = repository.gitolite_repository_path
|
||||
@git_default_branch = repository.git_default_branch
|
||||
@options = options
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def recovered?
|
||||
recovered
|
||||
end
|
||||
|
||||
|
||||
def installable?
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
module RedmineGitHosting::Plugins::Extenders
|
||||
class BranchUpdater < BaseExtender
|
||||
attr_reader :update_default_branch
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@update_default_branch = options.delete(:update_default_branch) { false }
|
||||
end
|
||||
|
||||
def post_update
|
||||
# Update default branch if needed
|
||||
do_update_default_branch if update_default_branch?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_default_branch?
|
||||
Additionals.true? update_default_branch
|
||||
end
|
||||
|
||||
def do_update_default_branch
|
||||
sudo_git('symbolic-ref', 'HEAD', new_default_branch)
|
||||
rescue RedmineGitHosting::GitHosting::GitHostingException
|
||||
logger.error("Error while updating default branch for repository '#{gitolite_repo_name}'")
|
||||
else
|
||||
logger.info("Default branch successfully updated for repository '#{gitolite_repo_name}'")
|
||||
repository.empty_cache!
|
||||
end
|
||||
|
||||
def new_default_branch
|
||||
"refs/heads/#{git_default_branch}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
module RedmineGitHosting::Plugins::Extenders
|
||||
class ConfigKeyDeletor < BaseExtender
|
||||
|
||||
attr_reader :delete_git_config_key
|
||||
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@delete_git_config_key = options.delete(:delete_git_config_key) { '' }
|
||||
end
|
||||
|
||||
|
||||
def post_update
|
||||
# Delete hook param if needed
|
||||
delete_hook_param unless delete_git_config_key.nil? || delete_git_config_key.empty?
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def delete_hook_param
|
||||
begin
|
||||
sudo_git('config', '--local', '--unset', delete_git_config_key)
|
||||
rescue RedmineGitHosting::Error::GitoliteCommandException => e
|
||||
logger.error("Error while deleting Git config key '#{delete_git_config_key}' for repository '#{gitolite_repo_name}'")
|
||||
else
|
||||
logger.info("Git config key '#{delete_git_config_key}' successfully deleted for repository '#{gitolite_repo_name}'")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue