# User: Vitalie Lazu
# Date: Mar 2008
# Time: 12:08:42 PM
require 'digest/md5'
require 'sorted_inifile'
require 'control_tool'
# http://trac-hacks.org/wiki/GitPlugin
# svn co http://trac-hacks.org/svn/gitplugin/0.10 gitplugin
# cd gitplugin
# python setup.py bdist_egg
# cp egg to /usr/share/trac/plugins
class Git < ControlTool
def check
logger.info("Git check")
logger.warn("Dir '#{repos_dir}' is not writeable") unless test(?w, repos_dir)
logger.warn("Dir '#{git_daemon_dir}' is not writeable") unless test(?w, git_daemon_dir)
logger.warn("File #{gitosis_conf} is not writeable") unless test(?w, gitosis_conf)
logger.info("Git check end")
end
# project_url, allow_anonymous, owner_id, owner_login, ssh_pub_keys
def on_create(opts)
self.settings = opts
raise "NotAllowed" if project_name == "gitosis-admin"
settings['owner_login'] ||= "NotPassed"
dest_dir = dest_dir(settings['project_url'])
if !test(?d, dest_dir)
if init_repo
# http checkout does not work without this, git - 1.5.4.4
#run("git-update-server-info")
if settings['copy_vcs_from']
orig_repo_path = dest_dir(settings['copy_vcs_from'])
if test(?d, orig_repo_path)
cd orig_repo_path
run("git-send-pack --all #{dest_dir}")
end
end
ln_sf "#{base_dir}/../git/git-post-receive.rb", File.join(dest_dir, 'hooks', 'post-receive')
create_breakout_ini(dest_dir, settings)
update_gitosis_conf
return url(dir_suffix(project_name))
else
raise "Can not init repo"
end
else
raise "Dest dir exists"
end
end
def on_destroy(repository_name, suffix = nil)
self.settings ||= {'project_url' => repository_name}
raise "NotAllowed" if project_name == "gitosis-admin"
lock_tmp_file do
ini = SortedIniFile.new(gitosis_conf)
ini["group #{project_name}"] = nil
ini["group read:#{project_name}"] = nil
ini["group write:#{project_name}"] = nil
ini["repo #{project_name}"] = nil
ini.write
commit_gitosis_changes("Removed repository #{project_name}")
end
backup_dir = config['backup_dir'] + '/git'
mkdir_p(backup_dir) unless test(?d, backup_dir)
dir = fix_project_name(repository_name) + suffix
# ruby can not move symlinks
system("/bin/mv #{dest_dir(repository_name)} #{File.join(backup_dir, dir)}")
remove_git_daemon_symlink
end
def upload_pub_keys
@commit_message = "upload_pub_keys for #{project_name}"
update_gitosis_conf
end
def update_permissions(repository_name, allow_anonymous)
self.settings ||= {'project_url' => repository_name, 'allow_anonymous' => allow_anonymous}
@commit_message = "update_permissions for #{project_name}"
update_gitosis_conf
end
# End control center methods
######################################################
def init_repo
mkdir_p repo_path
cd repo_path
run("git --bare init --shared=group")
end
def remove_git_daemon_symlink
rm (git_daemon_repo_path) if test(?l, git_daemon_repo_path)
end
def dest_dir(repository_name)
File.expand_path(File.join(repos_dir, repo_dir_name(repository_name)))
end
def repo_path
dest_dir(settings['project_url'])
end
def repo_dir_name(repository_name)
dir_suffix(fix_project_name(repository_name))
end
def repos_dir
config['git']['base_dir']
end
alias vcs_dir dest_dir
def dir_suffix(dir)
dir + '.git'
end
def project_name
fix_project_name(settings['project_url'])
end
def url(dir)
config['git']['base_url'] + ':' + dir
end
def gitosis_dir
config['git']['gitosis_dir']
end
def gitosis_conf
File.join(gitosis_dir, 'gitosis.conf')
end
def update_gitosis_conf
lock_tmp_file do
ini = SortedIniFile.new(gitosis_conf)
ini["group #{project_name}"] = gitosis_group if settings['ssh_pub_keys']
special_gitosis_group(ini, :read)
special_gitosis_group(ini, :write)
if settings['allow_anonymous']
ini["repo #{project_name}"] = {'daemon' => 'yes'}
ln_sf dest_dir(settings['project_url']), git_daemon_repo_path
else
remove_git_daemon_symlink
end
ini.write
commit_gitosis_changes(@commit_message || "Created repository #{project_name}, owner login/id: #{settings['owner_login']}/#{settings['owner_id']}")
end
end
# Set special group for git repository access
# access -> :read or :write
def special_gitosis_group(ini, access)
key = "ssh_#{access}_keys"
return unless settings[key]
ini_key = "group #{access}:#{project_name}"
if settings[key].size == 0
ini[ini_key] = nil
else
members = []
settings[key].each do |ssh_key|
key_id = "f-" + Digest::MD5.hexdigest(ssh_key)
members << key_id
key_path = File.join(gitosis_dir, 'keydir', "#{key_id}.pub")
write_ssh_key(ssh_key, key_path)
end
ini[ini_key] = { 'members' => members.sort * ' ', access == :read ? 'readonly' : 'writable' => project_name}
end
end
def gitosis_group
# add group
gr = {'writable' => project_name}
users = []
for row in settings['ssh_pub_keys']
user_id, ssh_key = row
users << gitosis_user_id(user_id)
write_ssh_key(ssh_key, user_ssh_key_file(user_id))
end
gr['members'] = users.size > 0 ? users.join(' ') : nil
gr
end
def write_ssh_key(ssh_key, dest_path)
File.open(dest_path, "w") do |f|
f.write(ssh_key.tr("\r\n", '') + "\n")
end
end
def git_daemon_repo_path
"#{git_daemon_dir}/#{repo_dir_name(settings['project_url'])}"
end
def commit_gitosis_changes(msg)
dir = Dir.pwd
cd gitosis_dir
run("git-add .")
run("git-commit -a -m \"#{msg}\"")
run("git-push")
run("git-pull")
cd dir
end
def lock_tmp_file
File.open(File.join(tmp_dir, 'git.lock'), "w") do |f|
if f.flock(File::LOCK_EX)
begin
yield
ensure
f.close
end
end
end
end
def git_daemon_dir
config['git']['git_daemon_dir']
end
def exists_rsa_key?(user_id)
test(?f, user_ssh_key_file(user_id))
end
def user_ssh_key_file(user_id)
File.join(gitosis_dir, 'keydir', "#{gitosis_user_id user_id}.pub")
end
def gitosis_user_id(user_id)
"u-#{user_id}"
end
def disk_space(settings)
dir = dest_dir(settings['dir_name'])
dir_disk_space(dir)
end
end
# Sample Apache conf
#
#ServerName git.host.com
#
#DocumentRoot "/var/www/"
#Alias /git/ "/opt/beta/git/"
#
#
# Order allow,deny
# Allow from all
#
#
#ErrorLog /var/log/tools/git-error.log
#LogLevel debug
#CustomLog /var/log/tools/git-access.log combined
#
#SetHandler mod_python
#PythonDebug On
#
#
# DAV on
# AuthType Basic
# AuthName "Git Restricted Area"
# PythonAccessHandler breakout.svn_access
# PythonAuthenHandler breakout.svn_access
#
#
#