File: //usr/sbin/git-start
#!/usr/bin/env python3
import sys
import os
import re
import shlex
import subprocess
from os import path
_PATH_PREFIX = '/srv/data/lamp0'
class FatalError(Exception):
"""Fatal error exception"""
_message = 'unknown error'
def __init__(self, message_=None):
super(FatalError, self).__init__(message_ or self._message)
class ArgumentError(FatalError):
"""Argument error exception"""
_message = 'bad argument'
class RepositoryNotFoundError(FatalError):
"""Repository not found error exception"""
_message = 'repository not found'
class Vhost(object):
_VHOST_ROOT = path.join(_PATH_PREFIX, 'web/vhosts')
def __init__(self, name):
self._name = name
self._path = path.join(Vhost._VHOST_ROOT, self._name)
def exists(self):
return path.exists(self._path)
def is_name_valid(self):
"""checks hostname pattern without final dot"""
if len(self._name) > 255:
return False
allowed = re.compile(r'(?!-)[a-z\d\-]{1,63}(?<!-)\Z')
return all(allowed.match(x) for x in self._name.split('.'))
@property
def name(self):
return self._name
class GitRepository(object):
_GIT_REPO_ROOT = path.join(_PATH_PREFIX, 'vcs/git')
def __init__(self, name):
self._name = name.strip('/')
if not self._name.endswith('.git'):
raise ArgumentError
self._vhost = Vhost(self._name[:-4])
if not self._vhost.is_name_valid():
raise ArgumentError
self._path = path.join(self._GIT_REPO_ROOT, self._name)
def init(self):
if path.exists(self._path):
return
if not self._vhost.exists():
raise RepositoryNotFoundError
self._init()
@property
def path(self):
return self._path
def _init(self):
commands = [
[ # git init
'/usr/bin/git',
'init',
'-q',
'--shared=group',
'--bare',
self._path
],
[ # allow using the force
'/usr/bin/git',
'-C',
self._path,
'config',
'receive.denyNonFastForwards',
'false'
],
]
try:
ret = 0
for command in commands:
ret += subprocess.call(command, close_fds=True)
except OSError as err:
errno, strerror = err.args
raise FatalError('%s: %s (%d)' % (command, strerror, errno))
else:
if ret:
sys.exit(ret % 256 or 1) # Ensure we do not exit with 256
self._write_gitweb_file('description', '%s project' % (self._vhost.name))
self._write_gitweb_file('cloneurl', self._name)
def _write_gitweb_file(self, file_, content):
filename = path.join(self._path, file_)
with open(filename, 'w') as f:
f.write(content)
f.write('\n')
class GitCommand(object):
def __init__(self):
if len(sys.argv) != 3 or sys.argv[1] != '-c':
raise ArgumentError
args = shlex.split(sys.argv[2])
if len(args) < 2:
raise ArgumentError
self._cmd = args[0]
self._repo = GitRepository(args[1])
self._opt_args = args[2:]
def execute(self):
os.environ['HOME'] = '/srv/admin/lamp0/vcs/git'
args = ['/usr/bin/git-shell', '-c', self._git_shell_command()]
try:
os.execvp(args[0], args)
except OSError as err:
errno, strerror = err.args
raise FatalError('%s: %s (%d)' % (args[0], strerror, errno))
@property
def repository(self):
return self._repo
def _git_shell_command(self):
args = [self._repo.path]
args.extend(self._opt_args)
return '%s %s' % (self._cmd,
' '.join("'%s'" % (arg) for arg in args))
if __name__ == '__main__':
try:
cmd = GitCommand()
cmd.repository.init()
cmd.execute()
except FatalError as message:
sys.exit('fatal: %s' % (message))