- 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)