stagit

static git page generator  https://git.ce9e.org
git clone https://git.ce9e.org/stagit.git

commit
f60aee3a165ae1cf788e09218f18275a97e8d033
parent
90dd920e85e8208caef5fb79439fa3b6d1881d66
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2020-03-28 17:12
add gitolite functionality

Diffstat

A gitolite/post-update.py 14 ++++++++++++++
A gitolite/shell.py 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
A gitolite/stagit.py 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A gitolite/update.py 38 ++++++++++++++++++++++++++++++++++++++

4 files changed, 288 insertions, 0 deletions


diff --git a/gitolite/post-update.py b/gitolite/post-update.py

@@ -0,0 +1,14 @@
   -1     1 #!/usr/bin/env python3
   -1     2 # see man githooks
   -1     3 
   -1     4 import os
   -1     5 
   -1     6 import stagit
   -1     7 
   -1     8 
   -1     9 repo = os.environ['STAGIT_REPO']
   -1    10 
   -1    11 if repo == stagit.ADMIN_REPO:
   -1    12 	stagit.update_admin()
   -1    13 else:
   -1    14 	stagit.render_html(repo)

diff --git a/gitolite/shell.py b/gitolite/shell.py

@@ -0,0 +1,51 @@
   -1     1 #!/usr/bin/env python3
   -1     2 # see man git-shell
   -1     3 
   -1     4 import os
   -1     5 import sys
   -1     6 import re
   -1     7 import subprocess
   -1     8 
   -1     9 import stagit
   -1    10 
   -1    11 SOC_RE = (
   -1    12 	r"^(git-receive-pack|git-upload-pack|git-upload-archive) "
   -1    13 	r"'?/?([a-z0-9_:-]+)(\.git)?/?'?$"
   -1    14 )
   -1    15 
   -1    16 
   -1    17 def parse_soc():
   -1    18 	cmd, repo = os.environ['SSH_ORIGINAL_COMMAND'].split()
   -1    19 
   -1    20 	soc = re.match(SOC_RE, os.environ['SSH_ORIGINAL_COMMAND'])
   -1    21 	if not soc:
   -1    22 		sys.stderr.write('invalid command\n')
   -1    23 		sys.exit(1)
   -1    24 	cmd, repo, _ = soc.groups()
   -1    25 	return cmd, repo
   -1    26 
   -1    27 
   -1    28 if __name__ == '__main__':
   -1    29 	user = sys.argv[1]
   -1    30 
   -1    31 	if 'SSH_ORIGINAL_COMMAND' not in os.environ:
   -1    32 		for repo in stagit.config.iter_user_repos(user):
   -1    33 			print(repo)
   -1    34 		sys.exit(0)
   -1    35 
   -1    36 	cmd, repo = parse_soc()
   -1    37 
   -1    38 	access = 'w' if cmd == 'git-receive-pack' else 'r'
   -1    39 	if not stagit.config.has_perm(repo, user, access, None):
   -1    40 		sys.stderr.write('access denied\n')
   -1    41 		sys.exit(1)
   -1    42 
   -1    43 	sys.exit(subprocess.call(
   -1    44 		[*cmd.split('-', 1), repo],
   -1    45 		cwd=stagit.REPO_DIR,
   -1    46 		env={
   -1    47 			**os.environ,
   -1    48 			'STAGIT_USER': user,
   -1    49 			'STAGIT_REPO': repo,
   -1    50 		},
   -1    51 	))

diff --git a/gitolite/stagit.py b/gitolite/stagit.py

@@ -0,0 +1,185 @@
   -1     1 import os
   -1     2 import sys
   -1     3 import shutil
   -1     4 import subprocess
   -1     5 import configparser
   -1     6 
   -1     7 USER = 'git'
   -1     8 ADMIN_REPO = 'stagit-admin'
   -1     9 
   -1    10 WWW_DIR = '/var/www/git/'
   -1    11 REPO_DIR = os.path.expanduser('~/repos/')
   -1    12 ADMIN_DIR = os.path.expanduser('~/.stagit/')
   -1    13 
   -1    14 SHELL = '/usr/lib/stagit/shell'
   -1    15 UPDATE_HOOK = '/usr/lib/stagit/hooks/update'
   -1    16 POST_UPDATE_HOOK = '/usr/lib/stagit/hooks/post-update'
   -1    17 CONF = '/usr/lib/stagit/conf.skel'
   -1    18 
   -1    19 KEY_OPTS = 'no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty'
   -1    20 KEY_TPL = 'command="%s %%s",%s %%s' % (SHELL, KEY_OPTS)
   -1    21 
   -1    22 
   -1    23 if os.environ.get('USER') != USER:
   -1    24 	sys.stderr.write('wrong user\n')
   -1    25 	sys.exit(1)
   -1    26 
   -1    27 
   -1    28 def get_repo_dir(repo):
   -1    29 	return os.path.join(REPO_DIR, repo + '.git')
   -1    30 
   -1    31 
   -1    32 class Config:
   -1    33 	def __init__(self):
   -1    34 		self._cache = None
   -1    35 
   -1    36 	@property
   -1    37 	def config(self):
   -1    38 		if not self._cache:
   -1    39 			self._cache = configparser.ConfigParser()
   -1    40 			self._cache.read(os.path.join(ADMIN_DIR, 'conf'))
   -1    41 		return self._cache
   -1    42 
   -1    43 	def invalidate(self):
   -1    44 		self._cache = None
   -1    45 
   -1    46 	def _iter_sets(self, repo, prefix):
   -1    47 		try:
   -1    48 			for key, value in self.config[repo].items():
   -1    49 				if key[0] == prefix:
   -1    50 					yield key, set(value.split())
   -1    51 		except KeyError:
   -1    52 			pass
   -1    53 
   -1    54 	def has_perm(self, repo, user, access, ref):
   -1    55 		users = set([user, '@all'])
   -1    56 		repos = set([repo, '@all'])
   -1    57 		for key, values in self._iter_sets('GROUPS', '@'):
   -1    58 			if user in values:
   -1    59 				users.add(key)
   -1    60 			if repo in values:
   -1    61 				repos.add(key)
   -1    62 
   -1    63 		ret = False
   -1    64 		for r in repos:
   -1    65 			for key, values in self._iter_sets(r, '!'):
   -1    66 				if users.isdisjoint(values):
   -1    67 					continue
   -1    68 				refex, perm = key.rsplit('!', 1)
   -1    69 				if ref and refex and refex[1:] != ref:
   -1    70 					continue
   -1    71 				if perm == '-':
   -1    72 					return False
   -1    73 				elif access in perm:
   -1    74 					ret = True
   -1    75 		return ret
   -1    76 
   -1    77 	def is_public(self, repo):
   -1    78 		return self.has_perm(repo, '@public', 'r', None)
   -1    79 
   -1    80 	def iter_repos(self):
   -1    81 		for key in self.config:
   -1    82 			if key[0] != '@' and key not in ['GROUPS', 'DEFAULT']:
   -1    83 				yield key
   -1    84 
   -1    85 	def iter_user_repos(self, user):
   -1    86 		for repo in self.iter_repos():
   -1    87 			if self.has_perm(repo, user, 'r', None):
   -1    88 				yield repo
   -1    89 
   -1    90 
   -1    91 config = Config()
   -1    92 
   -1    93 
   -1    94 def update_admin_dir():
   -1    95 	subprocess.call(
   -1    96 		['git', 'checkout', '-f', '--quiet'],
   -1    97 		cwd=get_repo_dir(ADMIN_REPO),
   -1    98 		env={
   -1    99 			**os.environ,
   -1   100 			'GIT_WORK_TREE': ADMIN_DIR,
   -1   101 		},
   -1   102 	)
   -1   103 	config.invalidate()
   -1   104 
   -1   105 
   -1   106 def update_keys():
   -1   107 	keydir = os.path.join(ADMIN_DIR, 'keydir')
   -1   108 	lines = []
   -1   109 
   -1   110 	for fn in os.listdir(keydir):
   -1   111 		if fn.endswith('.pub'):
   -1   112 			path = os.path.join(keydir, fn)
   -1   113 			name = fn[:-4]
   -1   114 			with open(path) as fh:
   -1   115 				lines.append(KEY_TPL % (name, fh.read()))
   -1   116 
   -1   117 	with open(os.path.expanduser('~/.ssh/authorized_keys'), 'w') as fh:
   -1   118 		for line in lines:
   -1   119 			fh.write(line)
   -1   120 
   -1   121 
   -1   122 def update_daemon_export_ok(repo):
   -1   123 	export_ok = os.path.join(get_repo_dir(repo), 'git-daemon-export-ok')
   -1   124 	if config.is_public(repo):
   -1   125 		with open(export_ok, 'a'):
   -1   126 			os.utime(export_ok, None)
   -1   127 	elif os.path.exists(export_ok):
   -1   128 		os.unlink(export_ok)
   -1   129 
   -1   130 
   -1   131 def update_repo(repo):
   -1   132 	path = get_repo_dir(repo)
   -1   133 	if not os.path.exists(path):
   -1   134 		os.makedirs(path, exist_ok=True)
   -1   135 		subprocess.check_call(['git', 'init', '--bare'], cwd=path)
   -1   136 	shutil.copy(UPDATE_HOOK, os.path.join(path, 'hooks', 'update'))
   -1   137 	shutil.copy(POST_UPDATE_HOOK, os.path.join(path, 'hooks', 'post-update'))
   -1   138 
   -1   139 
   -1   140 def update_admin():
   -1   141 	update_admin_dir()
   -1   142 	update_keys()
   -1   143 	for repo in config.iter_repos():
   -1   144 		update_repo(repo)
   -1   145 		update_daemon_export_ok(repo)
   -1   146 
   -1   147 
   -1   148 def render_html(repo):
   -1   149 	if config.is_public(repo):
   -1   150 		target_dir = os.path.join(WWW_DIR, repo)
   -1   151 		os.makedirs(target_dir, exist_ok=True)
   -1   152 		subprocess.check_call(['stagit', get_repo_dir(repo)], cwd=target_dir)
   -1   153 
   -1   154 
   -1   155 def bootstrap(keyfile):
   -1   156 	update_repo(ADMIN_REPO)
   -1   157 	repodir = get_repo_dir(ADMIN_REPO)
   -1   158 
   -1   159 	if not os.path.exists(ADMIN_DIR):
   -1   160 		workdir = os.path.join(REPO_DIR, '.bootstrap')
   -1   161 		keydir = os.path.join(workdir, 'keydir')
   -1   162 		os.mkdir(workdir)
   -1   163 		os.mkdir(keydir)
   -1   164 		shutil.copy(CONF, os.path.join(workdir, 'conf'))
   -1   165 		shutil.copy(keyfile, os.path.join(workdir, 'keydir', 'admin.pub'))
   -1   166 
   -1   167 		subprocess.call(
   -1   168 			['git', 'add', 'conf', 'keydir/admin.pub'],
   -1   169 			cwd=repodir, env={**os.environ, 'GIT_WORK_TREE': workdir},
   -1   170 		)
   -1   171 		subprocess.call(
   -1   172 			['git', 'commit', '-m', 'bootstrap'],
   -1   173 			cwd=repodir, env={**os.environ, 'GIT_WORK_TREE': workdir},
   -1   174 		)
   -1   175 
   -1   176 		os.unlink(os.path.join(workdir, 'conf'))
   -1   177 		os.unlink(os.path.join(workdir, 'keydir', 'admin.pub'))
   -1   178 		os.rmdir(os.path.join(workdir, 'keydir'))
   -1   179 		os.rmdir(os.path.join(workdir))
   -1   180 
   -1   181 	update_admin()
   -1   182 
   -1   183 
   -1   184 if __name__ == '__main__':
   -1   185 	bootstrap(sys.argv[1])

diff --git a/gitolite/update.py b/gitolite/update.py

@@ -0,0 +1,38 @@
   -1     1 #!/usr/bin/env python3
   -1     2 # see man githooks
   -1     3 
   -1     4 import os
   -1     5 import sys
   -1     6 import subprocess
   -1     7 
   -1     8 import stagit
   -1     9 
   -1    10 
   -1    11 def get_access(repo, ref, oldrev, newrev):
   -1    12 	# detect tags
   -1    13 	if ref.startswith('refs/tags/'):
   -1    14 		return '+'
   -1    15 
   -1    16 	# detect create/delete
   -1    17 	if '0' * 40 in [oldrev, newrev]:
   -1    18 		return '+'
   -1    19 
   -1    20 	# detect force push
   -1    21 	mergebase = subprocess.check_output(
   -1    22 		['git', 'merge-base', oldrev, newrev], cwd=stagit.get_repo_dir(repo),
   -1    23 	)
   -1    24 	if oldrev != mergebase.decode('ascii').rstrip():
   -1    25 		return '+'
   -1    26 
   -1    27 	return 'w'
   -1    28 
   -1    29 
   -1    30 if __name__ == '__main__':
   -1    31 	user = os.environ['STAGIT_USER']
   -1    32 	repo = os.environ['STAGIT_REPO']
   -1    33 	ref, oldrev, newrev = sys.argv[1:]
   -1    34 
   -1    35 	access = get_access(repo, ref, oldrev, newrev)
   -1    36 	if not stagit.config.has_perm(repo, user, access, ref):
   -1    37 		sys.stderr.write('access denied\n')
   -1    38 		sys.exit(1)