From a2e54704265b58abaa7a6edd0b64548974c69334 Mon Sep 17 00:00:00 2001 From: Tommi Virtanen Date: Thu, 15 Nov 2007 20:56:15 +0200 Subject: [PATCH] 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. --- gitosis/access.py | 12 +++------- gitosis/repository.py | 13 +++++++++++ gitosis/serve.py | 24 ++++++++++++++------ gitosis/test/test_access.py | 20 ++++++++--------- gitosis/test/test_serve.py | 44 +++++++++++++++++++++++++++++++++++-- 5 files changed, 85 insertions(+), 28 deletions(-) diff --git a/gitosis/access.py b/gitosis/access.py index 21724e0..c95c842 100644 --- a/gitosis/access.py +++ b/gitosis/access.py @@ -10,8 +10,8 @@ def haveAccess(config, user, mode, path): Note for read-only access, the caller should check for write access too. - Returns ``None`` for no access, or the physical repository path - for access granted to that repository. + Returns ``None`` for no access, or a tuple of toplevel directory + containing repositories and a relative path to the physical repository. """ log = logging.getLogger('gitosis.access.haveAccess') @@ -85,10 +85,4 @@ def haveAccess(config, user, mode, path): prefix=prefix, path=mapping, )) - mapping = os.path.join(prefix, mapping) - log.debug( - 'New path is %(path)r' - % dict( - path=mapping, - )) - return mapping + return (prefix, mapping) diff --git a/gitosis/repository.py b/gitosis/repository.py index 8746144..4b7ff2b 100644 --- a/gitosis/repository.py +++ b/gitosis/repository.py @@ -20,6 +20,19 @@ def init( template=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: _git = 'git' diff --git a/gitosis/serve.py b/gitosis/serve.py index 8ada5d3..e781dea 100644 --- a/gitosis/serve.py +++ b/gitosis/serve.py @@ -11,6 +11,7 @@ import sys, os, re from gitosis import access from gitosis import repository from gitosis import app +from gitosis import util ALLOW_RE = re.compile("^'(?P[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 raise WriteAccessDenied() - assert not newpath.endswith('.git'), \ - 'git extension should have been stripped: %r' % newpath - repopath = '%s.git' % newpath - if (not os.path.exists(repopath) + (topdir, relpath) = newpath + assert not relpath.endswith('.git'), \ + 'git extension should have been stripped: %r' % relpath + repopath = '%s.git' % relpath + fullpath = os.path.join(topdir, repopath) + if (not os.path.exists(fullpath) and verb in COMMANDS_WRITE): # it doesn't exist on the filesystem, but the configuration # refers to it, we're serving a write request, and the user is # 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 - newcmd = "%(verb)s '%(newpath)s'" % dict( + newcmd = "%(verb)s '%(path)s'" % dict( verb=verb, - newpath=newpath, + path=fullpath, ) return newcmd diff --git a/gitosis/test/test_access.py b/gitosis/test/test_access.py index 66e799c..9f9d81a 100644 --- a/gitosis/test/test_access.py +++ b/gitosis/test/test_access.py @@ -15,7 +15,7 @@ def test_write_yes_simple(): cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'writable', '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(): cfg = RawConfigParser() @@ -31,7 +31,7 @@ def test_write_yes_map(): cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'map writable foo/bar', 'quux/thud') eq(access.haveAccess(config=cfg, user='jdoe', mode='writable', path='foo/bar'), - 'repositories/quux/thud') + ('repositories', 'quux/thud')) def test_write_no_map_wouldHaveReadonly(): cfg = RawConfigParser() @@ -52,7 +52,7 @@ def test_read_yes_simple(): cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'readonly', '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(): cfg = RawConfigParser() @@ -68,7 +68,7 @@ def test_read_yes_map(): cfg.set('group fooers', 'members', 'jdoe') cfg.set('group fooers', 'map readonly foo/bar', 'quux/thud') eq(access.haveAccess(config=cfg, user='jdoe', mode='readonly', path='foo/bar'), - 'repositories/quux/thud') + ('repositories', 'quux/thud')) def test_read_yes_map_wouldHaveWritable(): cfg = RawConfigParser() @@ -87,7 +87,7 @@ def test_base_global_absolute(): cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud') eq(access.haveAccess( 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(): cfg = RawConfigParser() @@ -98,7 +98,7 @@ def test_base_global_relative(): cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud') eq(access.haveAccess( 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(): cfg = RawConfigParser() @@ -109,7 +109,7 @@ def test_base_global_relative_simple(): cfg.set('group fooers', 'readonly', 'foo xyzzy bar') eq(access.haveAccess( config=cfg, user='jdoe', mode='readonly', path='xyzzy'), - 'some/relative/path/xyzzy') + ('some/relative/path', 'xyzzy')) def test_base_global_unset(): cfg = RawConfigParser() @@ -119,7 +119,7 @@ def test_base_global_unset(): cfg.set('group fooers', 'readonly', 'foo xyzzy bar') eq(access.haveAccess( config=cfg, user='jdoe', mode='readonly', path='xyzzy'), - 'repositories/xyzzy') + ('repositories', 'xyzzy')) def test_base_local(): cfg = RawConfigParser() @@ -129,7 +129,7 @@ def test_base_local(): cfg.set('group fooers', 'map writable foo/bar', 'baz/quux/thud') eq(access.haveAccess( config=cfg, user='jdoe', mode='writable', path='foo/bar'), - 'some/relative/path/baz/quux/thud') + ('some/relative/path', 'baz/quux/thud')) def test_dotgit(): # 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', 'writable', 'foo/bar') eq(access.haveAccess(config=cfg, user='jdoe', mode='writable', path='foo/bar.git'), - 'repositories/foo/bar') + ('repositories', 'foo/bar')) diff --git a/gitosis/test/test_serve.py b/gitosis/test/test_serve.py index 08a4e67..2372429 100644 --- a/gitosis/test/test_serve.py +++ b/gitosis/test/test_serve.py @@ -115,7 +115,7 @@ def test_simple_read(): user='jdoe', 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(): tmp = util.maketemp() @@ -131,7 +131,7 @@ def test_simple_write(): user='jdoe', 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(): # 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']) 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(): tmp = util.maketemp() os.mkdir(os.path.join(tmp, 'foo.git'))