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">
-    &copy; 2010
+    &copy; 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)