diff -Nru wikkid-0.1+bzr69/bin/wikkid-serve wikkid-0.2+74+62~ubuntu15.04.1/bin/wikkid-serve --- wikkid-0.1+bzr69/bin/wikkid-serve 2011-03-09 21:34:46.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/bin/wikkid-serve 2015-11-05 11:47:33.000000000 +0000 @@ -18,8 +18,6 @@ import optparse import sys -from bzrlib.workingtree import WorkingTree - from wikkid import version from wikkid.app import WikkidApp from wikkid.context import ( @@ -28,8 +26,6 @@ DEFAULT_PORT, ExecutionContext, ) -from wikkid.filestore.bzr import FileStore -from wikkid.user.bzr import LocalBazaarUserMiddleware def setup_logging(): @@ -48,6 +44,8 @@ usage = "Usage: %prog [options] <wiki-branch>" parser = optparse.OptionParser( usage=usage, description="Run a Wikkid Wiki server.", version=version) + parser.add_option('--format', type='choice', default='bzr', + choices=['bzr', 'git'], help=("Default repository format to use.")) parser.add_option( '--host', type='string', default=DEFAULT_HOST, help=('The interface to listen on. Defaults to %r' % DEFAULT_HOST)) @@ -58,12 +56,16 @@ '--default-format', type='string', default=DEFAULT_FORMAT, help=("Specify the default wiki format to use. Defaults to %r" % DEFAULT_FORMAT)) + parser.add_option( + '--script-name', + help=('The SCRIPT_NAME for the environment. This is the prefix for the URLs')) options, args = parser.parse_args(sys.argv[1:]) execution_context = ExecutionContext( host=options.host, port=options.port, - default_format=options.default_format) + default_format=options.default_format, + script_name=options.script_name) if len(args) == 0: print "No branch location specified.\n" @@ -78,12 +80,24 @@ logger = logging.getLogger('wikkid') logger.setLevel(logging.INFO) - working_tree = WorkingTree.open(branch) - logger.info('Using: %s', working_tree) - filestore = FileStore(working_tree) + if options.format == 'bzr': + from bzrlib.workingtree import WorkingTree + from wikkid.filestore.bzr import FileStore + from wikkid.user.bzr import LocalBazaarUserMiddleware + + working_tree = WorkingTree.open(branch) + logger.info('Using: %s', working_tree) + filestore = FileStore(working_tree) + elif options.format == 'git': + from wikkid.filestore.git import FileStore + from wikkid.user.git import LocalGitUserMiddleware + filestore = FileStore.from_path(branch) app = WikkidApp(filestore=filestore, execution_context=execution_context) - app = LocalBazaarUserMiddleware(app, working_tree.branch) + if options.format == 'bzr': + app = LocalBazaarUserMiddleware(app, working_tree.branch) + elif options.format == 'git': + app = LocalGitUserMiddleware(app, filestore.repo) from wsgiref.simple_server import make_server httpd = make_server(options.host, options.port, app) logger.info('Serving on http://%s:%s', options.host, options.port) diff -Nru wikkid-0.1+bzr69/debian/bzr-builddeb.conf wikkid-0.2+74+62~ubuntu15.04.1/debian/bzr-builddeb.conf --- wikkid-0.1+bzr69/debian/bzr-builddeb.conf 2012-02-28 12:29:56.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/debian/bzr-builddeb.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -[BUILDDEB] -upstream-branch = lp:wikkid -export-upstream-revision = tag:wikkid-$UPSTREAM_VERSION diff -Nru wikkid-0.1+bzr69/debian/bzr-builder.manifest wikkid-0.2+74+62~ubuntu15.04.1/debian/bzr-builder.manifest --- wikkid-0.1+bzr69/debian/bzr-builder.manifest 1970-01-01 00:00:00.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/debian/bzr-builder.manifest 2015-11-05 11:47:35.000000000 +0000 @@ -0,0 +1,3 @@ +# bzr-builder format 0.3 deb-version {debupstream}+74+62 +lp:wikkid revid:jelmer@samba.org-20140116000627-lf3123sbxvvb5rq8 +merge packaging lp:~thumper/wikkid/debian revid:tim@penhey.net-20120517122554-11mr6z06xihgchz8 diff -Nru wikkid-0.1+bzr69/debian/changelog wikkid-0.2+74+62~ubuntu15.04.1/debian/changelog --- wikkid-0.1+bzr69/debian/changelog 2012-02-28 12:32:53.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/debian/changelog 2015-11-05 11:47:35.000000000 +0000 @@ -1,50 +1,24 @@ -wikkid (0.1+bzr69-1) unstable; urgency=low +wikkid (0.2+74+62~ubuntu15.04.1) vivid; urgency=low - * Bump standards version to 3.9.3 (no changes) - * New upstream snapshot. - * Fix running of the test suite during build. - * Add build dependency on python-bzrlib.tests, required by newer - versions of bzr. - * Depend on python-bzrlib rather than bzr itself, which is not - strictly necessary. - * Use machine-parseable copyright format. + * Auto build. - -- Jelmer Vernooij <jelmer@debian.org> Tue, 28 Feb 2012 13:29:30 +0100 + -- orchestra <fuhongwei141@gmail.com> Thu, 05 Nov 2015 11:47:35 +0000 -wikkid (0.1+bzr58-1.1) unstable; urgency=low +wikkid (0.2) lucid; urgency=low - * Non-maintainer upload. - * Rebuild to add Python 2.7 support + * Changed from twisted.web to webob WSGI for serving the branches + * Show who last modified the file on the wiki page + * Add a Home.txt so wikkid works nicely on itself + * Fix the setup packaging dependencies + * Add textile and markdown formatters + * Add the ability to use a branch rather than a working tree + * Adds a simple man page for wikkid-serve + * Fixed for python 2.7 - -- Piotr Ożarowski <piotr@debian.org> Sun, 08 May 2011 16:46:02 +0200 + -- Tim Penhey <tim@penhey.net> Fri, 1 Jul 2011 16:47:00 -0000 -wikkid (0.1+bzr58-1) unstable; urgency=low +wikkid (0.1-1~ppa) lucid; urgency=low - * Switch to dh_python2. Closes: #617146 - * Switch to debhelper 7, drop cdbs. - * New upstream snapshot. - + Fixes support for newer versions of Python. + * Initial release of Wikkid Wiki. Very basic wiki functionality. - -- Jelmer Vernooij <jelmer@debian.org> Thu, 10 Mar 2011 00:16:48 +0100 - -wikkid (0.1+bzr57-1) unstable; urgency=low - - * New upstream snapshot. - * Bump standards version to 3.9.1 (no changes). - * Add simple manual page for wikkid-serve. - - -- Jelmer Vernooij <jelmer@debian.org> Sat, 29 Jan 2011 14:57:54 +0100 - -wikkid (0.1+bzr49-1) unstable; urgency=low - - * Upload to unstable. - * Move to section web. Closes: #586974 - * New upstream snapshot. - - -- Jelmer Vernooij <jelmer@debian.org> Sat, 09 Oct 2010 14:45:13 +0200 - -wikkid (0.1-1) experimental; urgency=low - - * Initial release. Closes: #584579 - - -- Jelmer Vernooij <jelmer@debian.org> Wed, 26 May 2010 16:41:10 +0200 + -- Tim Penhey <tim@penhey.net> Mon, 24 May 2010 20:51:01 -0400 diff -Nru wikkid-0.1+bzr69/debian/control wikkid-0.2+74+62~ubuntu15.04.1/debian/control --- wikkid-0.1+bzr69/debian/control 2012-02-28 12:30:10.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/debian/control 2015-11-05 11:47:34.000000000 +0000 @@ -1,43 +1,25 @@ Source: wikkid -Section: web +Maintainer: Wikkid Developers <wikkid-dev@lists.launchpad.net> +Section: python Priority: optional -Maintainer: Debian Bazaar Maintainers <pkg-bazaar-maint@lists.alioth.debian.org> -Uploaders: Jelmer Vernooij <jelmer@debian.org> -Homepage: http://launchpad.net/wikkid -Build-Depends: bzr (>= 2.0~), - debhelper (>= 7.0.50~), - python (>= 2.6.6-3), - python-beautifulsoup, - python-bzrlib.tests | bzr (<< 2.4.0~beta1-2), - python-distribute, - python-docutils, - python-jinja2, +Build-Depends: python-setuptools (>= 0.6b3), + debhelper (>= 7), + python (>= 2.6.6-3~), python-lxml, - python-pygments, - python-testtools (>= 0.9.2), - python-twisted-core, - python-twisted-web, - python-webob (>= 1.0), - python-zope.interface -Standards-Version: 3.9.3 -X-Python-Version: >= 2.5 -Vcs-Bzr: http://bzr.debian.org/pkg-bazaar/wikkid/unstable -Vcs-Browser: http://bzr.debian.org/lh/pkg-bazaar/wikkid/unstable + python-testtools, + python-webob, + python-bs4, + python-bzrlib.tests | bzr (<< 2.4.0~beta1-2) +Standards-Version: 3.9.1 +X-Python-Version: (>= 2.6) -Package: wikkid +Package: python-wikkid Architecture: all -Depends: python-bzrlib (>= 2.0~), - python-jinja2, - python-pygments, - python-twisted-core, - python-twisted-web, - python-webob, - python-zope.interface, - ${misc:Depends}, - ${python:Depends} -Enhances: bzr -Recommends: bzr -Description: Bazaar-backed wiki - A wiki that is backed by Bazaar that allows local branching of the wiki for - later merging. It doesn't have any page locks and uses Bazaar's three way - merging. +Depends: ${python:Depends}, + python-docutils, + python-jinja2, + python-pygments, + python-twisted, + python-webob, + python-zope.interface +Description: A wiki that is backed by Bazaar that allows local branching diff -Nru wikkid-0.1+bzr69/debian/copyright wikkid-0.2+74+62~ubuntu15.04.1/debian/copyright --- wikkid-0.1+bzr69/debian/copyright 2012-02-28 12:29:09.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/debian/copyright 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: wikkid -Upstream-Contact: Tim Penhey -Source: https://launchpad.net/wikkid/ - -Files: debian/* -Copyright: 2009 Jelmer Vernooij <jelmer@debian.org> -Copyright: 2010 Barry Warsaw -Comment: - This package was debianized by Jelmer Vernooij - <jelmer@debian.org> on Wed May 26 19:41:28 CEST 2010, partially based on - the Ubuntu PPA packaging by Barry Warsaw. -License: GPL-3+ - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License with - the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL; - if not, write to the Free Software Foundation, Inc., 51 Franklin St, - Fifth Floor, Boston, MA 02110-1301, USA. - . - On Debian systems, the full text of the GPL can be found in - /usr/share/common-licenses/GPL - -Files: * -Copyright: 2010 Wikkid Developers -Comment: - Upstream authors: - Tim Penhey <tim@penhey.net> -License: AGPL-3 - GNU Affero General Public License version 3 - . - This program is free software: you can redistribute it and/or modify it - under the terms of the GNU Affero General Public License version 3, - as published by the Free Software Foundation. - . - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranties of - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - PURPOSE. See the GNU Affero General Public License for more details. - . - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. diff -Nru wikkid-0.1+bzr69/debian/rules wikkid-0.2+74+62~ubuntu15.04.1/debian/rules --- wikkid-0.1+bzr69/debian/rules 2012-02-28 12:27:07.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/debian/rules 2015-11-05 11:47:34.000000000 +0000 @@ -1,10 +1,7 @@ #!/usr/bin/make -f -%: - dh --with python2 --buildsystem=python_distutils $* +#exports specified using stdeb Setup-Env-Vars: +#export DH_OPTIONS=--buildsystem=python_distutils -ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) -override_dh_auto_test: - PYTHONPATH=. python -m wikkid.tests.test_app - $(MAKE) check -endif +%: + dh $@ --with python2 diff -Nru wikkid-0.1+bzr69/debian/source/format wikkid-0.2+74+62~ubuntu15.04.1/debian/source/format --- wikkid-0.1+bzr69/debian/source/format 2010-06-22 16:18:50.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/debian/source/format 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -3.0 (quilt) diff -Nru wikkid-0.1+bzr69/debian/watch wikkid-0.2+74+62~ubuntu15.04.1/debian/watch --- wikkid-0.1+bzr69/debian/watch 2012-02-28 12:29:35.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/debian/watch 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -version=3 -https://launchpad.net/wikkid/+download https://launchpad.net/wikkid/.*/Wikkid-(.+).tar.gz diff -Nru wikkid-0.1+bzr69/setup.py wikkid-0.2+74+62~ubuntu15.04.1/setup.py --- wikkid-0.1+bzr69/setup.py 2011-06-29 09:21:55.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/setup.py 2015-11-05 11:47:33.000000000 +0000 @@ -24,6 +24,7 @@ include_package_data=True, install_requires=[ 'docutils', + 'dulwich', 'jinja2', 'pygments', 'twisted', @@ -31,7 +32,8 @@ 'zope.interface', ], test_requires=[ - 'beautifulsoup', + 'bs4', + 'bzrlib.tests', 'testtools', ], test_suite='wikkid.tests', diff -Nru wikkid-0.1+bzr69/wikkid/app.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/app.py --- wikkid-0.1+bzr69/wikkid/app.py 2010-11-22 00:00:31.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/app.py 2015-11-05 11:47:33.000000000 +0000 @@ -11,6 +11,7 @@ import mimetypes import os.path import urllib +from wsgiref.util import shift_path_info from bzrlib import urlutils from webob import Request, Response @@ -59,35 +60,70 @@ self.skin = Skin(skin_name) self.logger = logging.getLogger('wikkid') - def __call__(self, environ, start_response): - """The WSGI bit.""" + def preprocess_environ(self, environ): request = Request(environ) + path = urllib.unquote(request.path) + script_name = self.execution_context.script_name + # Firstly check to see if the path is the same as the script_name + if (path != script_name and + not path.startswith(script_name + '/')): + raise HTTPNotFound() + + shifted_prefix = '' + while shifted_prefix != script_name: + shifted = shift_path_info(environ) + shifted_prefix = '{0}/{1}'.format(shifted_prefix, shifted) + # Now we are just interested in the path_info having ignored the + # script name. + path = urllib.unquote(request.path_info) + if path == '': + path = '/' # Explicitly be the root (we need the /) + return request, path + + def _get_view(self, request, path): + """Get the view for the path specified.""" + resource_path, action = parse_url(path) + model = self.resource_factory.get_resource_at_path(resource_path) + return get_view(model, action, request, self.execution_context) + def process_call(self, environ): + """The actual implementation of dealing with the call.""" # TODO: reject requests that aren't GET or POST - path = urllib.unquote(request.path) + try: + request, path = self.preprocess_environ(environ) + except HTTPException, e: + return e + if path == '/favicon.ico': if self.skin.favicon is not None: - response = serve_file(self.skin.favicon) + return serve_file(self.skin.favicon) else: - response = HTTPNotFound() - elif path.startswith('/static/'): + return HTTPNotFound() + + if path.startswith('/static/'): if self.skin.static_dir is not None: static_dir = self.skin.static_dir.rstrip(os.sep) + os.sep static_file = os.path.abspath( urlutils.joinpath(static_dir, path[8:])) if static_file.startswith(static_dir): - response = serve_file(static_file) + return serve_file(static_file) else: - response = HTTPNotFound() + return HTTPNotFound() else: - response = HTTPNotFound() - else: - resource_path, action = parse_url(path) - model = self.resource_factory.get_resource_at_path(resource_path) - try: - view = get_view(model, action, request, self.execution_context) - response = view.render(self.skin) - except HTTPException, e: - response = e + return HTTPNotFound() + + try: + view = self._get_view(request, path) + return view.render(self.skin) + except HTTPException, e: + return e + + def get_view(self, environ): + """Allow an app user to get the wikkid view for a particular call.""" + request, path = self.preprocess_environ(environ) + return self._get_view(request, path) + def __call__(self, environ, start_response): + """The WSGI bit.""" + response = self.process_call(environ) return response(environ, start_response) diff -Nru wikkid-0.1+bzr69/wikkid/context.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/context.py --- wikkid-0.1+bzr69/wikkid/context.py 2010-11-22 00:00:31.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/context.py 2015-11-05 11:47:33.000000000 +0000 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2010 Wikkid Developers. +# Copyright (C) 2010-2012 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). @@ -13,9 +13,13 @@ class ExecutionContext(object): - """Store run-time execution context data.""" + """Store run-time execution context data. - def __init__(self, host=None, port=None, default_format=None): + This is the Encapsulate Context pattern. + """ + + def __init__(self, host=None, port=None, default_format=None, + script_name=None): """Create an execution context for the application. :param host: The hostname that content is being served from. @@ -32,3 +36,8 @@ self.host = host self.port = port self.default_format = default_format + # TODO: make sure the script_name if set starts with a slash and + # doesn't finish with one. + if script_name is None: + script_name = '' + self.script_name = script_name.rstrip('/') diff -Nru wikkid-0.1+bzr69/wikkid/dispatcher.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/dispatcher.py --- wikkid-0.1+bzr69/wikkid/dispatcher.py 2010-11-12 09:00:42.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/dispatcher.py 2015-11-05 11:47:33.000000000 +0000 @@ -53,7 +53,7 @@ # Don't register. return key = (interface, view_name) - assert key not in _VIEW_REGISTRY, "key already registered: %r" % key + assert key not in _VIEW_REGISTRY, "key already registered: %r" % (key,) _VIEW_REGISTRY[key] = view_class if default_view: _VIEW_REGISTRY[(interface, None)] = view_class diff -Nru wikkid-0.1+bzr69/wikkid/filestore/basefile.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/filestore/basefile.py --- wikkid-0.1+bzr69/wikkid/filestore/basefile.py 2010-05-12 10:39:13.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/filestore/basefile.py 2015-11-05 11:47:33.000000000 +0000 @@ -16,9 +16,8 @@ class BaseFile(object): """Provide common fields and methods and properties for files.""" - def __init__(self, path, file_id): + def __init__(self, path): self.path = path - self.file_id = file_id self.base_name = urlutils.basename(path) self._mimetype = mimetypes.guess_type(self.base_name)[0] diff -Nru wikkid-0.1+bzr69/wikkid/filestore/bzr.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/filestore/bzr.py --- wikkid-0.1+bzr69/wikkid/filestore/bzr.py 2011-06-30 17:24:38.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/filestore/bzr.py 2015-11-05 11:47:33.000000000 +0000 @@ -243,7 +243,8 @@ implements(IFile) def __init__(self, filestore, path, file_id): - BaseFile.__init__(self, path, file_id) + BaseFile.__init__(self, path) + self.file_id = file_id self.filestore = filestore # This isn't entirely necessary. self.tree = self.filestore.tree @@ -258,18 +259,22 @@ def _get_filetype(self): """Work out the filetype based on the mimetype if possible.""" - is_directory = ('directory' == self.tree.kind(self.file_id)) - if is_directory: - return FileType.DIRECTORY - else: - if self._mimetype is None: - binary = self._is_binary - else: - binary = not self._mimetype.startswith('text/') - if binary: - return FileType.BINARY_FILE + try: + self.tree.lock_read() + is_directory = ('directory' == self.tree.kind(self.file_id)) + if is_directory: + return FileType.DIRECTORY else: - return FileType.TEXT_FILE + if self._mimetype is None: + binary = self._is_binary + else: + binary = not self._mimetype.startswith('text/') + if binary: + return FileType.BINARY_FILE + else: + return FileType.TEXT_FILE + finally: + self.tree.unlock() def get_content(self): if self.file_id is None: diff -Nru wikkid-0.1+bzr69/wikkid/filestore/git.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/filestore/git.py --- wikkid-0.1+bzr69/wikkid/filestore/git.py 1970-01-01 00:00:00.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/filestore/git.py 2015-11-05 11:47:33.000000000 +0000 @@ -0,0 +1,205 @@ +# +# Copyright (C) 2012 Wikkid Developers. +# +# This software is licensed under the GNU Affero General Public License +# version 3 (see the file LICENSE). + +"""A git filestore using Dulwich. + +""" + +import datetime +import mimetypes + +from dulwich.objects import Blob, Tree, ZERO_SHA +from dulwich.object_store import tree_lookup_path +from dulwich.repo import Repo +from dulwich.walk import Walker +import posixpath +import stat + +from zope.interface import implements + +from wikkid.errors import FileExists, UpdateConflicts +from wikkid.interface.filestore import FileType, IFile, IFileStore + + +class FileStore(object): + """A filestore that just uses an internal map to store data.""" + + implements(IFileStore) + + @classmethod + def from_path(cls, path): + return cls(Repo(path)) + + def __init__(self, repo, ref='HEAD'): + """Repo is a dulwich repository.""" + self.repo = repo + self.ref = ref + + @property + def store(self): + return self.repo.object_store + + def _get_root(self, revision=None): + if revision is None: + try: + revision = self.repo.refs[self.ref] + except KeyError: + revision = ZERO_SHA + try: + return (revision, self.repo[revision].tree) + except KeyError: + return None, None + + def get_file(self, path): + """Return an object representing the file.""" + commit_id, root_id = self._get_root() + if root_id is None: + return None + try: + (mode, sha) = tree_lookup_path(self.store.__getitem__, + root_id, path) + except KeyError: + return None + return File(self.store, mode, sha, path, commit_id) + + def update_file(self, path, content, user, parent_revision, + commit_message=None): + """The `user` is updating the file at `path` with `content`.""" + commit_id, root_id = self._get_root() + if root_id is None: + root_tree = Tree() + else: + root_tree = self.store[root_id] + # Find all tree objects involved + tree = root_tree + trees = [root_tree] + elements = path.strip(posixpath.sep).split(posixpath.sep) + for el in elements[:-1]: + try: + (mode, sha) = tree[el] + except KeyError: + tree = Tree() + else: + if not stat.S_ISDIR(mode): + raise FileExists( + "File %s exists and is not a directory" % el) + tree = self.store[sha] + trees.append(tree) + if elements[-1] in tree: + (old_mode, old_sha) = tree[elements[-1]] + if stat.S_ISDIR(old_mode): + raise FileExists("File %s exists and is a directory" % path) + if old_sha != parent_revision and parent_revision is not None: + raise UpdateConflicts("File conflict %s != %s" % (old_sha, + parent_revision), old_sha) + blob = Blob.from_string(content.encode("utf-8")) + child = (stat.S_IFREG | 0644, blob.id) + self.store.add_object(blob) + assert len(trees) == len(elements) + for tree, name in zip(reversed(trees), reversed(elements)): + assert name != "" + tree[name] = child + self.store.add_object(tree) + child = (stat.S_IFDIR, tree.id) + if commit_message is None: + commit_message = "" + self.repo.do_commit(ref=self.ref, message=commit_message, author=user, + tree=tree.id) + + def list_directory(self, directory_path): + """Return a list of File objects for in the directory path. + + If the path doesn't exist, returns None. If the path exists but is + empty, an empty list is returned. Otherwise a list of File objects in + that directory. + """ + if directory_path is None: + directory_path = '' + else: + directory_path = directory_path.strip(posixpath.sep) + commit_id, root_id = self._get_root() + if directory_path == '': + sha = root_id + mode = stat.S_IFDIR + else: + if root_id is None: + return None + try: + (mode, sha) = tree_lookup_path(self.store.__getitem__, + root_id, directory_path) + except KeyError: + return None + if mode is not None and stat.S_ISDIR(mode): + ret = [] + for (name, mode, sha) in self.store[sha].iteritems(): + ret.append( + File(self.store, mode, sha, posixpath.join(directory_path, name), commit_id)) + return ret + else: + return None + + +class File(object): + """A Git file object.""" + + implements(IFile) + + def __init__(self, store, mode, sha, path, commit_sha): + self.store = store + self.mode = mode + self.sha = sha + self.path = path + self.commit_sha = commit_sha + self.base_name = posixpath.basename(path) + self.mimetype = mimetypes.guess_type(self.base_name)[0] + + @property + def file_type(self): + """Work out the filetype based on the mimetype if possible.""" + if self._is_directory: + return FileType.DIRECTORY + else: + if self.mimetype is None: + binary = self._is_binary + else: + binary = not self.mimetype.startswith('text/') + if binary: + return FileType.BINARY_FILE + else: + return FileType.TEXT_FILE + + def get_content(self): + o = self.store[self.sha] + if isinstance(o, Blob): + return o.data + else: + return None + + @property + def _is_directory(self): + return stat.S_ISDIR(self.mode) + + @property + def _is_binary(self): + return '\0' in self.get_content() + + def _get_last_modified_commit(self): + walker = Walker(self.store, include=[self.commit_sha], + paths=[self.path]) + return iter(walker).next().commit + + @property + def last_modified_in_revision(self): + return self.sha + + @property + def last_modified_by(self): + return self._get_last_modified_commit().author + + @property + def last_modified_date(self): + c = self._get_last_modified_commit() + return datetime.datetime.utcfromtimestamp(c.author_time) diff -Nru wikkid-0.1+bzr69/wikkid/filestore/volatile.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/filestore/volatile.py --- wikkid-0.1+bzr69/wikkid/filestore/volatile.py 2010-06-07 08:56:13.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/filestore/volatile.py 2015-11-05 11:47:33.000000000 +0000 @@ -113,7 +113,8 @@ implements(IFile) def __init__(self, path, content, file_id, user): - BaseFile.__init__(self, path, file_id) + BaseFile.__init__(self, path) + self.file_id = file_id self.content = content self.last_modified_in_revision = None self.last_modified_by = user diff -Nru wikkid-0.1+bzr69/wikkid/interface/filestore.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/interface/filestore.py --- wikkid-0.1+bzr69/wikkid/interface/filestore.py 2010-06-07 08:24:06.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/interface/filestore.py 2015-11-05 11:47:33.000000000 +0000 @@ -70,9 +70,6 @@ base_name = Attribute("The last part of the path.") - file_id = Attribute( - "The unique identifier for the file in the filestore.") - file_type = Attribute("Soon to be a Choice with a lazr.enum.") mimetype = Attribute( diff -Nru wikkid-0.1+bzr69/wikkid/skin/default/base.html wikkid-0.2+74+62~ubuntu15.04.1/wikkid/skin/default/base.html --- wikkid-0.1+bzr69/wikkid/skin/default/base.html 2010-07-13 12:49:52.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/skin/default/base.html 2015-11-05 11:47:33.000000000 +0000 @@ -5,14 +5,14 @@ <title>{% block title %}{% endblock %} - Wikkid</title> <link type="text/css" rel="stylesheet" media="screen, print" - href="/static/default.css" /> - <link rel="shortcut icon" href="/favicon.ico" /> + href="{{ request.script_name }}/static/default.css" /> + <link rel="shortcut icon" href="{{ request.script_name }}/favicon.ico" /> {% endblock %} </head> <body> <div id="container"> <div id="header"> - <div class="wikkidlogo"><a href="/"><strong>Wikkid Wiki</strong></a></div> + <div class="wikkidlogo"><a href="{{ request.script_name }}/"><strong>Wikkid Wiki</strong></a></div> <div id="logged"> {% if view.user %} @@ -44,7 +44,7 @@ </div> {% block footer %}{% endblock %} <div id="footer"> - © 2010 + © 2010-2012 <a href="https://launchpad.net/~wikkid">Wikkid Hackers</a>, All rights reserved. </div> diff -Nru wikkid-0.1+bzr69/wikkid/tests/factory.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/factory.py --- wikkid-0.1+bzr69/wikkid/tests/factory.py 2010-06-15 09:05:29.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/factory.py 2015-11-05 11:47:33.000000000 +0000 @@ -26,7 +26,7 @@ class ViewTestCase(FactoryTestCase): """A factory test case that can create views.""" - def get_view(self, factory, path, name=None): + def get_view(self, factory, path, name=None, base_url=None): info = factory.get_resource_at_path(path) - request = Request.blank(path) + request = Request.blank(path, base_url=base_url) return get_view(info, name, request) diff -Nru wikkid-0.1+bzr69/wikkid/tests/filestore.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/filestore.py --- wikkid-0.1+bzr69/wikkid/tests/filestore.py 2010-11-08 00:00:06.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/filestore.py 2015-11-05 11:47:33.000000000 +0000 @@ -42,6 +42,7 @@ filestore = self.make_filestore( [('README', 'Content'), ('lib/', None), + ('lib/foo', 'dummy data'), ('image.jpg', 'pretend image'), ('binary-file', 'a\0binary\0file'), ('simple.txt', 'A text file'), @@ -57,6 +58,7 @@ filestore = self.make_filestore( [('README', 'Content'), ('lib/', None), + ('lib/data', 'dummy data'), ('image.jpg', 'pretend image'), ('binary-file', 'a\0binary\0file'), ('simple.txt', 'A text file'), diff -Nru wikkid-0.1+bzr69/wikkid/tests/__init__.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/__init__.py --- wikkid-0.1+bzr69/wikkid/tests/__init__.py 2010-06-09 11:30:07.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/__init__.py 2015-11-05 11:47:33.000000000 +0000 @@ -36,10 +36,12 @@ ] names = [ 'app', + 'context', 'model_factory', 'volatile_filestore', 'bzr_filestore', 'bzr_user', + 'git_filestore', 'view_dispatcher', 'model', ] diff -Nru wikkid-0.1+bzr69/wikkid/tests/test_app.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/test_app.py --- wikkid-0.1+bzr69/wikkid/tests/test_app.py 2011-04-23 08:18:58.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/test_app.py 2015-11-05 11:47:33.000000000 +0000 @@ -1,5 +1,5 @@ # -# Copyright (C) 2010 Wikkid Developers. +# Copyright (C) 2010-2012 Wikkid Developers. # # This software is licensed under the GNU Affero General Public License # version 3 (see the file LICENSE). @@ -8,28 +8,35 @@ import os.path +from testtools.matchers import IsInstance from webob.request import environ_from_url from wikkid.app import WikkidApp +from wikkid.context import ExecutionContext from wikkid.filestore.volatile import FileStore +from wikkid.view.missing import MissingPage +from wikkid.view.root import RootPage +from wikkid.view.wiki import WikiPage from wikkid.tests import TestCase class TestApp(TestCase): + def assert_not_found(self, status, headers): + self.assertEqual("404 Not Found", status) + + def assert_ok(self, status, headers): + self.assertEqual("200 OK", status) + def test_traverse_above_static_not_possible_with_relative_path(self): """ Traversal above the static folder, by forging a malicious request with a relative path for example, is not possible. """ environ = environ_from_url("/static/../page.html") - - def start_response(status, headers): - self.assertEqual("404 Not Found", status) - filestore = FileStore() app = WikkidApp(filestore) - app(environ, start_response) + app(environ, self.assert_not_found) def test_traverse_above_static_not_possible_with_absolute_path(self): """ @@ -38,10 +45,57 @@ """ this_file = os.path.abspath(__file__) environ = environ_from_url("/static/" + this_file) + filestore = FileStore() + app = WikkidApp(filestore) + app(environ, self.assert_not_found) - def start_response(status, headers): - self.assertEqual("404 Not Found", status) + def test_getting_static_style_css_works(self): + environ = environ_from_url("/static/default.css") filestore = FileStore() app = WikkidApp(filestore) - app(environ, start_response) + app(environ, self.assert_ok) + + def test_getting_static_style_css_works_with_script_name(self): + + environ = environ_from_url("/test/static/default.css") + filestore = FileStore() + context = ExecutionContext(script_name="/test") + app = WikkidApp(filestore, execution_context=context) + app(environ, self.assert_ok) + + def test_getting_static_style_css_works_with_script_name_multiple_segments(self): + environ = environ_from_url("/p/project-name/wiki/static/default.css") + filestore = FileStore() + context = ExecutionContext(script_name="/p/project-name/wiki") + app = WikkidApp(filestore, execution_context=context) + app(environ, self.assert_ok) + + def test_getting_anything_outside_script_name_fails(self): + environ = environ_from_url("/foo/bar") + filestore = FileStore() + context = ExecutionContext(script_name="/test") + app = WikkidApp(filestore, execution_context=context) + app(environ, self.assert_not_found) + + def assertUrlIsView(self, url, view_type, + script_name=None, store_content=None): + environ = environ_from_url(url) + filestore = FileStore(store_content) + context = ExecutionContext(script_name=script_name) + app = WikkidApp(filestore, execution_context=context) + view = app.get_view(environ) + self.assertThat(view, IsInstance(view_type)) + + def test_home_redirect_url_matches_script_name(self): + self.assertUrlIsView("/test", RootPage, "/test") + self.assertUrlIsView("/test", RootPage, "/test/") + self.assertUrlIsView("/test/", RootPage, "/test") + self.assertUrlIsView("/test/", RootPage, "/test/") + + def test_get_view(self): + self.assertUrlIsView("/Home", MissingPage) + + def test_get_home_view(self): + content = [('Home.txt', 'Welcome Home.')] + self.assertUrlIsView("/Home", WikiPage, store_content=content) diff -Nru wikkid-0.1+bzr69/wikkid/tests/test_context.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/test_context.py --- wikkid-0.1+bzr69/wikkid/tests/test_context.py 1970-01-01 00:00:00.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/test_context.py 2015-11-05 11:47:33.000000000 +0000 @@ -0,0 +1,27 @@ +# +# Copyright (C) 2010-2012 Wikkid Developers. +# +# This software is licensed under the GNU Affero General Public License +# version 3 (see the file LICENSE). + +"""Tests for method and classes in wikkid.context.""" + +from testtools.matchers import Equals + +from wikkid.context import ExecutionContext +from wikkid.tests import TestCase + + +class TestContext(TestCase): + + def test_empty_script_name(self): + context = ExecutionContext() + self.assertThat(context.script_name, Equals('')) + + def test_script_name(self): + context = ExecutionContext(script_name='/foo') + self.assertThat(context.script_name, Equals('/foo')) + + def test_script_name_strips_trailing_slash(self): + context = ExecutionContext(script_name='/foo/') + self.assertThat(context.script_name, Equals('/foo')) diff -Nru wikkid-0.1+bzr69/wikkid/tests/test_git_filestore.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/test_git_filestore.py --- wikkid-0.1+bzr69/wikkid/tests/test_git_filestore.py 1970-01-01 00:00:00.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/test_git_filestore.py 2015-11-05 11:47:33.000000000 +0000 @@ -0,0 +1,55 @@ +# +# Copyright (C) 2012 Wikkid Developers. +# +# This software is licensed under the GNU Affero General Public License +# version 3 (see the file LICENSE). + +"""Tests for the wikkid.filestore.git.FileStore.""" + +from dulwich.repo import MemoryRepo + +from textwrap import dedent + +from wikkid.errors import UpdateConflicts +from wikkid.filestore.git import ( + FileStore, + ) +from wikkid.tests import ProvidesMixin, TestCase +from wikkid.tests.filestore import TestFileStore + + +class TestGitFileStore(TestCase, ProvidesMixin, TestFileStore): + """Tests for the git filestore and files.""" + + def make_filestore(self, contents=None): + repo = MemoryRepo() + fs = FileStore(repo) + if contents: + for (path, contents) in contents: + if contents is None: + # Directory + continue + fs.update_file(path, contents, + user="Somebody <test@example.com>", + parent_revision=None, + commit_message="Added by make_filestore") + return fs + + def test_empty(self): + # Empty files do not have line endings, but they can be saved + # nonetheless. + filestore = self.make_filestore( + [('test.txt', 'several\nlines\nof\ncontent')]) + f = filestore.get_file('test.txt') + base_rev = f.last_modified_in_revision + filestore.update_file( + 'test.txt', '', 'Test Author <test@example.com>', base_rev) + curr = filestore.get_file('test.txt') + self.assertEqual('', curr.get_content()) + + def test_listing_directory_empty(self): + filestore = self.make_filestore( + [('empty/', None), + ]) + listing = filestore.list_directory('empty') + self.assertIs(None, listing) diff -Nru wikkid-0.1+bzr69/wikkid/tests/views/test_breadcrumbs.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/views/test_breadcrumbs.py --- wikkid-0.1+bzr69/wikkid/tests/views/test_breadcrumbs.py 2010-06-15 09:05:29.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/views/test_breadcrumbs.py 2015-11-05 11:47:33.000000000 +0000 @@ -107,3 +107,16 @@ ('wiki root', '/+listing'), ('SomePage', '/SomePage/+listing'), ('SubPage', '/SomePage/SubPage')]) + + def test_directory_breadcrumbs_nested_with_script_name(self): + # For each directory after the root, a listing crumb is added. + # Names are not wiki expanded. + factory = self.make_factory([ + ('SomePage/SubPage/Nested.txt', 'some text')]) + view = self.get_view(factory, '/SomePage/SubPage', 'listing', '/p/wiki') + self.assertBreadcrumbs( + view, + [('Home', '/p/wiki/Home'), + ('wiki root', '/p/wiki/+listing'), + ('SomePage', '/p/wiki/SomePage/+listing'), + ('SubPage', '/p/wiki/SomePage/SubPage')]) diff -Nru wikkid-0.1+bzr69/wikkid/tests/views/test_root.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/views/test_root.py --- wikkid-0.1+bzr69/wikkid/tests/views/test_root.py 2010-06-17 10:45:52.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/views/test_root.py 2015-11-05 11:47:33.000000000 +0000 @@ -6,16 +6,19 @@ """Test views for the root object.""" +from bs4 import BeautifulSoup +from testtools.matchers import Equals from webob.exc import HTTPSeeOther +from wikkid.skin.loader import Skin from wikkid.tests.factory import ViewTestCase class TestRootViews(ViewTestCase): """Test the views on the root object.""" - def test_last_modified_by(self): - """Test that the last committer is displayed properly""" + def test_root_redirects(self): + """Going to / redirects to the Home page.""" factory = self.make_factory() view = self.get_view(factory, '/') error = self.assertRaises( @@ -23,3 +26,31 @@ view.render, None) self.assertEqual('/Home', error.headers['Location']) + + def test_root_redirects_with_script_name(self): + """Redirection works and respects the script name""" + factory = self.make_factory() + view = self.get_view(factory, '/', base_url='/p/test') + error = self.assertRaises( + HTTPSeeOther, + view.render, + None) + self.assertEqual('/p/test/Home', error.headers['Location']) + + def test_home_rendering(self): + """Render the home page and test the elements.""" + factory = self.make_factory() + view = self.get_view(factory, '/Home') + content = view.render(Skin('default')) + soup = BeautifulSoup(content.text) + [style] = soup.find_all('link', {'rel':'stylesheet'}) + self.assertThat(style['href'], Equals('/static/default.css')) + + def test_home_rendering_with_script_name(self): + """Render the home page and test the elements.""" + factory = self.make_factory() + view = self.get_view(factory, '/Home', base_url='/p/test') + content = view.render(Skin('default')) + soup = BeautifulSoup(content.text) + [style] = soup.find_all('link', {'rel':'stylesheet'}) + self.assertThat(style['href'], Equals('/p/test/static/default.css')) diff -Nru wikkid-0.1+bzr69/wikkid/tests/views/test_urls.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/views/test_urls.py --- wikkid-0.1+bzr69/wikkid/tests/views/test_urls.py 2010-06-16 10:29:35.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/tests/views/test_urls.py 2015-11-05 11:47:33.000000000 +0000 @@ -6,6 +6,9 @@ """Tests the edit views.""" +from testtools.matchers import Equals +from webob import Request + from wikkid.tests import TestCase from wikkid.tests.factory import FactoryTestCase from wikkid.view.urls import canonical_url, parse_url @@ -14,84 +17,86 @@ class TestCanonicalUrl(FactoryTestCase): """Test the wikkid.view.base.canonical_url.""" + def assertUrl(self, resource, url, view_name=None, base_url=None): + request = Request.blank('/', base_url=base_url) + self.assertThat(canonical_url(resource, request, view_name), + Equals(url)) + def test_root(self): factory = self.make_factory() root = factory.get_resource_at_path('/') - self.assertEqual('/', canonical_url(root)) + self.assertUrl(root, '/') def test_root_listing(self): factory = self.make_factory() root = factory.get_resource_at_path('/') - self.assertEqual('/+listing', canonical_url(root, 'listing')) + self.assertUrl(root, '/+listing', view_name='listing') def test_default(self): factory = self.make_factory([ ('Home.txt', 'Some content'), ]) root = factory.get_resource_at_path('/') - self.assertEqual('/Home', canonical_url(root.default_resource)) + self.assertUrl(root.default_resource, '/Home') def test_default_view(self): factory = self.make_factory([ ('Home.txt', 'Some content'), ]) root = factory.get_resource_at_path('/') - self.assertEqual( - '/Home/+edit', - canonical_url(root.default_resource, 'edit')) + self.assertUrl(root.default_resource, '/Home/+edit', view_name='edit') def test_wiki_page(self): factory = self.make_factory([ ('SomeDir/SomePage.txt', 'Some content'), ]) page = factory.get_resource_at_path('/SomeDir/SomePage') - self.assertEqual('/SomeDir/SomePage', canonical_url(page)) + self.assertUrl(page, '/SomeDir/SomePage') def test_wiki_page_view(self): factory = self.make_factory([ ('SomeDir/SomePage.txt', 'Some content'), ]) page = factory.get_resource_at_path('/SomeDir/SomePage') - self.assertEqual( - '/SomeDir/SomePage/+edit', canonical_url(page, 'edit')) + self.assertUrl(page, '/SomeDir/SomePage/+edit', view_name='edit') def test_wiki_page_full_url(self): factory = self.make_factory([ ('SomeDir.txt', 'Some content'), ]) page = factory.get_resource_at_path('/SomeDir.txt') - self.assertEqual('/SomeDir', canonical_url(page)) + self.assertUrl(page, '/SomeDir') def test_wiki_page_full_url_with_view(self): factory = self.make_factory([ ('SomeDir.txt', 'Some content'), ]) page = factory.get_resource_at_path('/SomeDir.txt') - self.assertEqual('/SomeDir/+edit', canonical_url(page, 'edit')) + self.assertUrl(page, '/SomeDir/+edit', view_name='edit') def test_other_file(self): factory = self.make_factory([ ('simple.py', '#!/usr/bin/python'), ]) page = factory.get_resource_at_path('/simple.py') - self.assertEqual('/simple.py', canonical_url(page)) + self.assertUrl(page, '/simple.py') def test_other_file_view(self): factory = self.make_factory([ ('simple.py', '#!/usr/bin/python'), ]) page = factory.get_resource_at_path('/simple.py') - self.assertEqual('/simple.py/+edit', canonical_url(page, 'edit')) + self.assertUrl(page, '/simple.py/+edit', view_name='edit') def test_missing(self): factory = self.make_factory() - root = factory.get_resource_at_path('/MissingPage') - self.assertEqual('/MissingPage', canonical_url(root)) + missing = factory.get_resource_at_path('/MissingPage') + self.assertUrl(missing, '/MissingPage') def test_missing_view(self): factory = self.make_factory() - root = factory.get_resource_at_path('/MissingPage') - self.assertEqual('/MissingPage/+edit', canonical_url(root, 'edit')) + missing = factory.get_resource_at_path('/MissingPage') + self.assertUrl(missing, '/MissingPage/+edit', view_name='edit') class TestParseUrl(TestCase): diff -Nru wikkid-0.1+bzr69/wikkid/user/git.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/user/git.py --- wikkid-0.1+bzr69/wikkid/user/git.py 1970-01-01 00:00:00.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/user/git.py 2015-11-05 11:47:33.000000000 +0000 @@ -0,0 +1,74 @@ +# +# Copyright (C) 2010 Wikkid Developers +# +# This software is licensed under the GNU Affero General Public License +# version 3 (see the file LICENSE). + +"""A user factory and user class which uses the git identity from the +local Git config.""" + +import email +import logging + +from webob import Request +from zope.interface import implements + +from wikkid.interface.user import IUser, IUserFactory +from wikkid.user.baseuser import BaseUser + + +def create_git_user_from_author_string(author): + name, address = email.Utils.parseaddr(author) + if name: + display_name = name + else: + display_name = address + return User(address, display_name, author) + + +class LocalGitUserMiddleware(object): + """A middleware to inject a user into the environment.""" + + def __init__(self, app, repo): + self.app = app + config = repo.get_config_stack() + email = config.get(("user", ), "email") + name = config.get(("user", ), "name") + self.user = User(email, name, "%s <%s>" % (name, email)) + + def __call__(self, environ, start_response): + environ['wikkid.user'] = self.user + req = Request(environ) + resp = req.get_response(self.app) + return resp(environ, start_response) + + +class UserFactory(object): + """Generate a user from local bazaar config.""" + + implements(IUserFactory) + + def __init__(self, branch): + """Use the user config from the branch.""" + config = branch.get_config() + self.user = create_git_user_from_author_string(config.username()) + logger = logging.getLogger('wikkid') + logger.info( + 'Using git identity: "%s", "%s"', + self.user.display_name, self.user.email) + + def create(self, request): + """Create a User.""" + return self.user + + +class User(BaseUser): + """A user from the local bazaar config.""" + + implements(IUser) + + def __init__(self, email, display_name, committer_id): + self.email = email + self.display_name = display_name + self.committer_id = committer_id + diff -Nru wikkid-0.1+bzr69/wikkid/view/base.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/base.py --- wikkid-0.1+bzr69/wikkid/view/base.py 2010-11-22 09:23:40.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/base.py 2015-11-05 11:47:33.000000000 +0000 @@ -29,8 +29,8 @@ class Breadcrumb(object): """Breadcrumbs exist to give the user quick links up the path chain.""" - def __init__(self, context, view=None, title=None): - self.path = canonical_url(context, view) + def __init__(self, context, request, view=None, title=None): + self.path = canonical_url(context, request, view) if title is None: self.title = title_for_filename(context.base_name) else: @@ -54,14 +54,14 @@ self.logger = logging.getLogger('wikkid') def _create_breadcrumbs(self): - crumbs = [Breadcrumb(self.context)] + crumbs = [Breadcrumb(self.context, self.request)] current = self.context.parent while not IRootResource.providedBy(current): - crumbs.append(Breadcrumb(current)) + crumbs.append(Breadcrumb(current, self.request)) current = current.parent # And add in the default page if the context isn't the default. if not IDefaultPage.providedBy(self.context): - crumbs.append(Breadcrumb(current.default_resource)) + crumbs.append(Breadcrumb(current.default_resource, self.request)) return reversed(crumbs) def initialize(self): @@ -89,6 +89,9 @@ def before_render(self): """A hook to do things before rendering.""" + def canonical_url(self, context, view=None): + return canonical_url(context, self.request, view) + def template_args(self): """Needs to be implemented in the derived classes. @@ -99,7 +102,7 @@ 'user': self.user, 'context': self.context, 'request': self.request, - 'canonical_url': canonical_url, + 'canonical_url': self.canonical_url, } def _render(self, skin): @@ -139,12 +142,13 @@ view = None while not IRootResource.providedBy(current): crumbs.append(Breadcrumb( - current, view, title=current.base_name)) + current, self.request, view, title=current.base_name)) current = current.parent # Add listings to subsequent urls. view = 'listing' # Add in the root dir. - crumbs.append(Breadcrumb(current, 'listing', title='wiki root')) + crumbs.append(Breadcrumb(current, self.request, 'listing', + title='wiki root')) # And add in the default page. - crumbs.append(Breadcrumb(current.default_resource)) + crumbs.append(Breadcrumb(current.default_resource, self.request)) return reversed(crumbs) diff -Nru wikkid-0.1+bzr69/wikkid/view/directory.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/directory.py --- wikkid-0.1+bzr69/wikkid/view/directory.py 2010-06-23 10:57:14.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/directory.py 2015-11-05 11:47:33.000000000 +0000 @@ -14,9 +14,9 @@ class ListingItem(object): """An item to be shown in the directory listing.""" - def __init__(self, context, view, css_class, name=None): + def __init__(self, context, request, view, css_class, name=None): self.context = context - self.url = canonical_url(self.context, view) + self.url = canonical_url(self.context, request, view) if name is None: name = context.base_name self.name = name @@ -53,11 +53,11 @@ if self.context.path != '/': parent = self.context.parent items.append( - ListingItem(parent, 'listing', 'up', name='..')) + ListingItem(parent, self.request, 'listing', 'up', name='..')) for item in sorted(directories, key=sort_key): - items.append(ListingItem(item, 'listing', 'directory')) + items.append(ListingItem(item, self.request, 'listing', 'directory')) for item in sorted(files, key=sort_key): - items.append(ListingItem(item, None, 'file')) + items.append(ListingItem(item, self.request, None, 'file')) self.items = items @property diff -Nru wikkid-0.1+bzr69/wikkid/view/edit.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/edit.py --- wikkid-0.1+bzr69/wikkid/view/edit.py 2010-06-17 10:45:52.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/edit.py 2015-11-05 11:47:33.000000000 +0000 @@ -7,7 +7,6 @@ """View base class for editing text pages.""" from wikkid.view.base import BaseView -from wikkid.view.urls import canonical_url from wikkid.view.utils import expand_wiki_name @@ -24,9 +23,9 @@ @property def save_url(self): """The link for the cancel button.""" - return canonical_url(self.context, 'save') + return self.canonical_url(self.context, 'save') @property def cancel_url(self): """The link for the cancel button.""" - return self.context.preferred_path + return self.canonical_url(self.context) diff -Nru wikkid-0.1+bzr69/wikkid/view/root.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/root.py --- wikkid-0.1+bzr69/wikkid/view/root.py 2010-06-17 10:45:52.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/root.py 2015-11-05 11:47:33.000000000 +0000 @@ -22,5 +22,5 @@ def _render(self, skin): """Redirect to Home (or the default page).""" default_resource = self.context.default_resource - preferred = default_resource.preferred_path - raise HTTPSeeOther(location=preferred) + location = self.canonical_url(default_resource) + raise HTTPSeeOther(location=location) diff -Nru wikkid-0.1+bzr69/wikkid/view/textfile.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/textfile.py --- wikkid-0.1+bzr69/wikkid/view/textfile.py 2011-04-23 08:49:27.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/textfile.py 2015-11-05 11:47:33.000000000 +0000 @@ -70,7 +70,8 @@ self.context.put_bytes( content, self.user.committer_id, rev_id, description) - raise HTTPSeeOther(location=self.context.path) + location = self.canonical_url(self.context) + raise HTTPSeeOther(location=location) except UpdateConflicts, e: # Show the edit page again. logger = logging.getLogger('wikkid') diff -Nru wikkid-0.1+bzr69/wikkid/view/urls.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/urls.py --- wikkid-0.1+bzr69/wikkid/view/urls.py 2010-06-16 10:29:35.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/urls.py 2015-11-05 11:47:33.000000000 +0000 @@ -24,12 +24,12 @@ return (path, None) -def canonical_url(context, view=None): +def canonical_url(context, request, view=None): """The one true URL for the context object.""" path = context.preferred_path if view is None: - return path + return '{0}{1}'.format(request.script_name, path) else: if path == '/': path = '' - return '{0}/+{1}'.format(path, view) + return '{0}{1}/+{2}'.format(request.script_name, path, view) diff -Nru wikkid-0.1+bzr69/wikkid/view/wiki.py wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/wiki.py --- wikkid-0.1+bzr69/wikkid/view/wiki.py 2011-04-23 08:49:27.000000000 +0000 +++ wikkid-0.2+74+62~ubuntu15.04.1/wikkid/view/wiki.py 2015-11-05 11:47:33.000000000 +0000 @@ -46,6 +46,7 @@ """ preferred = self.context.preferred_path if self.context.path != preferred: - raise HTTPTemporaryRedirect(location=preferred) + location = self.canonical_url(self.context) + raise HTTPTemporaryRedirect(location=location) else: return super(WikiPage, self)._render(skin)