Create leading directories when creating missing repos in gitosis-serve.

Creation is in gitosis.serve and not directly in repository.init(),
because that's the location that can tell what part of the directory
tree is allowed to be missing. Made the reconstructed git command
include the extension as that was easier to do.

haveAccess return value is now tuple, to preserve information on what
parts of the path can be missing.
This commit is contained in:
Tommi Virtanen 2007-11-15 20:56:15 +02:00
parent ea9ee97046
commit a2e5470426
5 changed files with 85 additions and 28 deletions

View file

@ -10,8 +10,8 @@ def haveAccess(config, user, mode, path):
Note for read-only access, the caller should check for write Note for read-only access, the caller should check for write
access too. access too.
Returns ``None`` for no access, or the physical repository path Returns ``None`` for no access, or a tuple of toplevel directory
for access granted to that repository. containing repositories and a relative path to the physical repository.
""" """
log = logging.getLogger('gitosis.access.haveAccess') log = logging.getLogger('gitosis.access.haveAccess')
@ -85,10 +85,4 @@ def haveAccess(config, user, mode, path):
prefix=prefix, prefix=prefix,
path=mapping, path=mapping,
)) ))
mapping = os.path.join(prefix, mapping) return (prefix, mapping)
log.debug(
'New path is %(path)r'
% dict(
path=mapping,
))
return mapping

View file

@ -20,6 +20,19 @@ def init(
template=None, template=None,
_git=None, _git=None,
): ):
"""
Create a git repository at C{path} (if missing).
Leading directories of C{path} must exist.
@param path: Path of repository create.
@type path: str
@param template: Template directory, to pass to C{git init}.
@type template: str
"""
if _git is None: if _git is None:
_git = 'git' _git = 'git'

View file

@ -11,6 +11,7 @@ import sys, os, re
from gitosis import access from gitosis import access
from gitosis import repository from gitosis import repository
from gitosis import app from gitosis import app
from gitosis import util
ALLOW_RE = re.compile("^'(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)'$") ALLOW_RE = re.compile("^'(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)'$")
@ -93,20 +94,29 @@ def serve(
# didn't have write access and tried to write # didn't have write access and tried to write
raise WriteAccessDenied() raise WriteAccessDenied()
assert not newpath.endswith('.git'), \ (topdir, relpath) = newpath
'git extension should have been stripped: %r' % newpath assert not relpath.endswith('.git'), \
repopath = '%s.git' % newpath 'git extension should have been stripped: %r' % relpath
if (not os.path.exists(repopath) repopath = '%s.git' % relpath
fullpath = os.path.join(topdir, repopath)
if (not os.path.exists(fullpath)
and verb in COMMANDS_WRITE): and verb in COMMANDS_WRITE):
# it doesn't exist on the filesystem, but the configuration # it doesn't exist on the filesystem, but the configuration
# refers to it, we're serving a write request, and the user is # refers to it, we're serving a write request, and the user is
# authorized to do that: create the repository on the fly # authorized to do that: create the repository on the fly
repository.init(path=repopath)
# create leading directories
p = topdir
for segment in repopath.split(os.sep)[:-1]:
p = os.path.join(p, segment)
util.mkdir(p, 0750)
repository.init(path=fullpath)
# put the verb back together with the new path # put the verb back together with the new path
newcmd = "%(verb)s '%(newpath)s'" % dict( newcmd = "%(verb)s '%(path)s'" % dict(
verb=verb, verb=verb,
newpath=newpath, path=fullpath,
) )
return newcmd return newcmd

View file

@ -15,7 +15,7 @@ def test_write_yes_simple():
cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'members', 'jdoe')
cfg.set('group fooers', 'writable', 'foo/bar') cfg.set('group fooers', 'writable', 'foo/bar')
eq(access.haveAccess(config=cfg, user='jdoe', mode='writable', path='foo/bar'), eq(access.haveAccess(config=cfg, user='jdoe', mode='writable', path='foo/bar'),
'repositories/foo/bar') ('repositories', 'foo/bar'))
def test_write_no_simple_wouldHaveReadonly(): def test_write_no_simple_wouldHaveReadonly():
cfg = RawConfigParser() cfg = RawConfigParser()
@ -31,7 +31,7 @@ def test_write_yes_map():
cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'members', 'jdoe')
cfg.set('group fooers', 'map writable foo/bar', 'quux/thud') cfg.set('group fooers', 'map writable foo/bar', 'quux/thud')
eq(access.haveAccess(config=cfg, user='jdoe', mode='writable', path='foo/bar'), eq(access.haveAccess(config=cfg, user='jdoe', mode='writable', path='foo/bar'),
'repositories/quux/thud') ('repositories', 'quux/thud'))
def test_write_no_map_wouldHaveReadonly(): def test_write_no_map_wouldHaveReadonly():
cfg = RawConfigParser() cfg = RawConfigParser()
@ -52,7 +52,7 @@ def test_read_yes_simple():
cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'members', 'jdoe')
cfg.set('group fooers', 'readonly', 'foo/bar') cfg.set('group fooers', 'readonly', 'foo/bar')
eq(access.haveAccess(config=cfg, user='jdoe', mode='readonly', path='foo/bar'), eq(access.haveAccess(config=cfg, user='jdoe', mode='readonly', path='foo/bar'),
'repositories/foo/bar') ('repositories', 'foo/bar'))
def test_read_yes_simple_wouldHaveWritable(): def test_read_yes_simple_wouldHaveWritable():
cfg = RawConfigParser() cfg = RawConfigParser()
@ -68,7 +68,7 @@ def test_read_yes_map():
cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'members', 'jdoe')
cfg.set('group fooers', 'map readonly foo/bar', 'quux/thud') cfg.set('group fooers', 'map readonly foo/bar', 'quux/thud')
eq(access.haveAccess(config=cfg, user='jdoe', mode='readonly', path='foo/bar'), eq(access.haveAccess(config=cfg, user='jdoe', mode='readonly', path='foo/bar'),
'repositories/quux/thud') ('repositories', 'quux/thud'))
def test_read_yes_map_wouldHaveWritable(): def test_read_yes_map_wouldHaveWritable():
cfg = RawConfigParser() cfg = RawConfigParser()
@ -87,7 +87,7 @@ def test_base_global_absolute():
cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud') cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud')
eq(access.haveAccess( eq(access.haveAccess(
config=cfg, user='jdoe', mode='writable', path='foo/bar'), config=cfg, user='jdoe', mode='writable', path='foo/bar'),
'/a/leading/path/baz/quux/thud') ('/a/leading/path', 'baz/quux/thud'))
def test_base_global_relative(): def test_base_global_relative():
cfg = RawConfigParser() cfg = RawConfigParser()
@ -98,7 +98,7 @@ def test_base_global_relative():
cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud') cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud')
eq(access.haveAccess( eq(access.haveAccess(
config=cfg, user='jdoe', mode='writable', path='foo/bar'), config=cfg, user='jdoe', mode='writable', path='foo/bar'),
'some/relative/path/baz/quux/thud') ('some/relative/path', 'baz/quux/thud'))
def test_base_global_relative_simple(): def test_base_global_relative_simple():
cfg = RawConfigParser() cfg = RawConfigParser()
@ -109,7 +109,7 @@ def test_base_global_relative_simple():
cfg.set('group fooers', 'readonly', 'foo xyzzy bar') cfg.set('group fooers', 'readonly', 'foo xyzzy bar')
eq(access.haveAccess( eq(access.haveAccess(
config=cfg, user='jdoe', mode='readonly', path='xyzzy'), config=cfg, user='jdoe', mode='readonly', path='xyzzy'),
'some/relative/path/xyzzy') ('some/relative/path', 'xyzzy'))
def test_base_global_unset(): def test_base_global_unset():
cfg = RawConfigParser() cfg = RawConfigParser()
@ -119,7 +119,7 @@ def test_base_global_unset():
cfg.set('group fooers', 'readonly', 'foo xyzzy bar') cfg.set('group fooers', 'readonly', 'foo xyzzy bar')
eq(access.haveAccess( eq(access.haveAccess(
config=cfg, user='jdoe', mode='readonly', path='xyzzy'), config=cfg, user='jdoe', mode='readonly', path='xyzzy'),
'repositories/xyzzy') ('repositories', 'xyzzy'))
def test_base_local(): def test_base_local():
cfg = RawConfigParser() cfg = RawConfigParser()
@ -129,7 +129,7 @@ def test_base_local():
cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud') cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud')
eq(access.haveAccess( eq(access.haveAccess(
config=cfg, user='jdoe', mode='writable', path='foo/bar'), config=cfg, user='jdoe', mode='writable', path='foo/bar'),
'some/relative/path/baz/quux/thud') ('some/relative/path', 'baz/quux/thud'))
def test_dotgit(): def test_dotgit():
# a .git extension is always allowed to be added # a .git extension is always allowed to be added
@ -138,4 +138,4 @@ def test_dotgit():
cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'members', 'jdoe')
cfg.set('group fooers', 'writable', 'foo/bar') cfg.set('group fooers', 'writable', 'foo/bar')
eq(access.haveAccess(config=cfg, user='jdoe', mode='writable', path='foo/bar.git'), eq(access.haveAccess(config=cfg, user='jdoe', mode='writable', path='foo/bar.git'),
'repositories/foo/bar') ('repositories', 'foo/bar'))

View file

@ -115,7 +115,7 @@ def test_simple_read():
user='jdoe', user='jdoe',
command="git-upload-pack 'foo'", command="git-upload-pack 'foo'",
) )
eq(got, "git-upload-pack '%s/foo'" % tmp) eq(got, "git-upload-pack '%s/foo.git'" % tmp)
def test_simple_write(): def test_simple_write():
tmp = util.maketemp() tmp = util.maketemp()
@ -131,7 +131,7 @@ def test_simple_write():
user='jdoe', user='jdoe',
command="git-receive-pack 'foo'", command="git-receive-pack 'foo'",
) )
eq(got, "git-receive-pack '%s/foo'" % tmp) eq(got, "git-receive-pack '%s/foo.git'" % tmp)
def test_push_inits_if_needed(): def test_push_inits_if_needed():
# a push to a non-existent repository (but where config authorizes # a push to a non-existent repository (but where config authorizes
@ -169,6 +169,46 @@ def test_push_inits_if_needed_haveExtension():
eq(os.listdir(tmp), ['foo.git']) eq(os.listdir(tmp), ['foo.git'])
assert os.path.isfile(os.path.join(tmp, 'foo.git', 'HEAD')) assert os.path.isfile(os.path.join(tmp, 'foo.git', 'HEAD'))
def test_push_inits_subdir_parent_missing():
tmp = util.maketemp()
cfg = RawConfigParser()
cfg.add_section('gitosis')
cfg.set('gitosis', 'repositories', tmp)
cfg.add_section('group foo')
cfg.set('group foo', 'members', 'jdoe')
cfg.set('group foo', 'writable', 'foo/bar')
serve.serve(
cfg=cfg,
user='jdoe',
command="git-receive-pack 'foo/bar.git'",
)
eq(os.listdir(tmp), ['foo'])
foo = os.path.join(tmp, 'foo')
util.check_mode(foo, 0750, is_dir=True)
eq(os.listdir(foo), ['bar.git'])
assert os.path.isfile(os.path.join(tmp, 'foo', 'bar.git', 'HEAD'))
def test_push_inits_subdir_parent_exists():
tmp = util.maketemp()
foo = os.path.join(tmp, 'foo')
# silly mode on purpose; not to be touched
os.mkdir(foo, 0751)
cfg = RawConfigParser()
cfg.add_section('gitosis')
cfg.set('gitosis', 'repositories', tmp)
cfg.add_section('group foo')
cfg.set('group foo', 'members', 'jdoe')
cfg.set('group foo', 'writable', 'foo/bar')
serve.serve(
cfg=cfg,
user='jdoe',
command="git-receive-pack 'foo/bar.git'",
)
eq(os.listdir(tmp), ['foo'])
util.check_mode(foo, 0751, is_dir=True)
eq(os.listdir(foo), ['bar.git'])
assert os.path.isfile(os.path.join(tmp, 'foo', 'bar.git', 'HEAD'))
def test_push_inits_if_needed_existsWithExtension(): def test_push_inits_if_needed_existsWithExtension():
tmp = util.maketemp() tmp = util.maketemp()
os.mkdir(os.path.join(tmp, 'foo.git')) os.mkdir(os.path.join(tmp, 'foo.git'))