--- a/Allura/allura/model/repo.py
+++ b/Allura/allura/model/repo.py
@@ -1,3 +1,4 @@
+import os
 import re
 import sys
 import logging
@@ -5,11 +6,12 @@
 from itertools import izip, chain
 from datetime import datetime
 from collections import defaultdict
-
-from pylons import g, c
+from difflib import SequenceMatcher
+
+from pylons import c
 import pymongo.errors
 
-from ming import Field, Index, collection
+from ming import Field, collection
 from ming import schema as S
 from ming.base import Object
 from ming.utils import LazyProperty
@@ -31,6 +33,9 @@
 # Used for when we're going to batch queries using $in
 QSIZE = 100
 README_RE = re.compile('^README(\.[^.]*)?$', re.IGNORECASE)
+VIEWABLE_EXTENSIONS = ['.php','.py','.js','.java','.html','.htm','.yaml','.sh',
+    '.rb','.phtml','.txt','.bat','.ps1','.xhtml','.css','.cfm','.jsp','.jspx',
+    '.pl','.php4','.php3','.rhtml','.svg','.markdown','.json','.ini','.tcl','.vbs','.xsl']
 
 # Basic commit information
 CommitDoc = collection(
@@ -56,18 +61,15 @@
 LastCommitDoc = collection(
     'repo_last_commit', project_doc_session,
     Field('_id', str),
-    Field('repo_id', S.ObjectId()),
-    Field('object_id', str),
+    Field('object_id', str, index=True),
     Field('commit_info', dict(
         id=str,
         date=datetime,
         author=str,
         author_email=str,
         author_url=str,
-        href=str,
         shortlink=str,
-        summary=str)),
-    Index('repo_id', 'object_id'))
+        summary=str)))
 
 # List of all trees contained within a commit
 TreesDoc = collection(
@@ -109,14 +111,6 @@
             self.__class__.__name__,
             self._id)
         return id.replace('.', '/')
-
-    @LazyProperty
-    def legacy(self):
-        return Object(object_id=self._id)
-
-    @property
-    def object_id(self):
-        return self._id
 
     @classmethod
     def upsert(cls, id):
@@ -176,12 +170,12 @@
 
     @LazyProperty
     def symbolic_ids(self):
-        return self.repo.symbolics_for_commit(self.legacy)
+        return self.repo.symbolics_for_commit(self)
 
     def url(self):
         if self.repo is None: self.repo = self.guess_repo()
         if self.repo is None: return '#'
-        return self.repo.url_for_commit(self.legacy)
+        return self.repo.url_for_commit(self)
 
     def guess_repo(self):
         for ac in c.project.app_configs:
@@ -231,10 +225,38 @@
     def context(self):
         result = dict(prev=None, next=None)
         if self.parent_ids:
-            result['prev'] = self.query.get(_id=self.parent_ids[0])
+            result['prev'] = self.query.find(dict(_id={'$in': self.parent_ids })).all()
         if self.child_ids:
-            result['next'] = self.query.get(_id=self.child_ids[0])
+            result['next'] = self.query.find(dict(_id={'$in': self.child_ids })).all()
         return result
+
+    @LazyProperty
+    def diffs(self):
+        di = DiffInfoDoc.m.get(_id=self._id)
+        if di is None:
+            return Object(added=[], removed=[], changed=[], copied=[])
+        added = []
+        removed = []
+        changed = []
+        copied = []
+        for change in di.differences:
+            if change.rhs_id is None:
+                removed.append(change.name)
+            elif change.lhs_id is None:
+                added.append(change.name)
+            else:
+                changed.append(change.name)
+        return Object(
+            added=added, removed=removed,
+            changed=changed, copied=copied)
+
+    def get_path(self, path):
+        if path[0] == '/': path = path[1:]
+        parts = path.split('/')
+        cur = self.tree
+        for part in parts:
+            cur = cur[part]
+        return cur
 
 class Tree(RepoObject):
     # Ephemeral attrs
@@ -258,7 +280,8 @@
 
     def __getitem__(self, name):
         obj = self.by_name[name]
-        if obj['type'] == 'blob': return obj
+        if obj['type'] == 'blob':
+            return Blob(self, name, obj['id'])
         obj = self.query.get(_id=obj['id'])
         if obj is None:
             oid = self.repo.compute_tree_new(self.commit, self.path() + name + '/')
@@ -282,13 +305,9 @@
         for x in self.blob_ids:
             if README_RE.match(x.name):
                 name = x.name
-                obj = Object(
-                    object_id=x.id,
-                    path=lambda:self.path() + x['name'],
-                    commit=Object(
-                        object_id=self.commit._id))
-                text = self.repo.open_blob(obj).read()
-                return (x.name, h.really_unicode(text))
+                blob = self[name]
+                return (x.name, h.really_unicode(blob.text))
+        return None, None
 
     def ls(self):
         # Load last commit info
@@ -296,7 +315,7 @@
         lc_index = dict(
             (lc.object_id, lc.commit_info)
             for lc in LastCommitDoc.m.find(dict(
-                    repo_id=self.repo._id,
+                    # repo_id=self.repo._id,
                     object_id={'$in': oids})))
         results = []
         def _get_last_commit(oid):
@@ -311,6 +330,8 @@
                     href=None,
                     shortlink=None,
                     summary=None)
+            if 'href' not in lc:
+                lc['href'] = self.repo.url_for_commit(lc['id'])
             return lc
         for x in sorted(self.tree_ids, key=lambda x:x.name):
             results.append(dict(
@@ -322,7 +343,7 @@
             results.append(dict(
                     kind='FILE',
                     name=x.name,
-                    href=x.name + '/',
+                    href=x.name,
                     last_commit=_get_last_commit(x.id)))
         for x in sorted(self.other_ids, key=lambda x:x.name):
             results.append(dict(
@@ -344,17 +365,118 @@
 
     @LazyProperty
     def by_name(self):
-        d = dict((x.name, x) for x in self.other_ids)
+        d = Object((x.name, x) for x in self.other_ids)
         d.update(
-            (x.name, dict(x, type='tree'))
+            (x.name, Object(x, type='tree'))
             for x in self.tree_ids)
         d.update(
-            (x.name, dict(x, type='blob'))
+            (x.name, Object(x, type='blob'))
             for x in self.blob_ids)
         return d
 
     def is_blob(self, name):
         return self.by_name[name]['type'] == 'blob'
+
+    def get_blob(self, name):
+        x = self.by_name[name]
+        return Blob(self, name, x.id)
+
+class Blob(object):
+    '''Lightweight object representing a file in the repo'''
+
+    def __init__(self, tree, name, _id):
+        self._id = _id
+        self.tree = tree
+        self.name = name
+        self.repo = tree.repo
+        self.commit = tree.commit
+        fn, ext = os.path.splitext(self.name)
+        self.extension = ext or fn
+
+    def path(self):
+        return self.tree.path() + h.really_unicode(self.name)
+
+    def url(self):
+        return self.tree.url() + h.really_unicode(self.name)
+
+    @LazyProperty
+    def prev_commit(self):
+        lc = self.repo.get_last_commit(self)
+        if lc['id']:
+            last_commit = self.repo.commit(lc.id)
+            if last_commit.parent_ids:
+                return self.repo.commit(last_commit.parent_ids[0])
+        return None
+
+    @LazyProperty
+    def next_commit(self):
+        try:
+            path = self.path()
+            cur = self.commit
+            next = cur.context()['next']
+            while next:
+                cur = next[0]
+                next = cur.context()['next']
+                other_blob = cur.get_path(path)
+                if other_blob is None or other_blob._id != self._id:
+                    return cur
+        except:
+            log.exception('Lookup prev_commit')
+            return None
+
+    @LazyProperty
+    def _content_type_encoding(self):
+        return self.repo.guess_type(self.name)
+
+    @LazyProperty
+    def content_type(self):
+        return self._content_type_encoding[0]
+
+    @LazyProperty
+    def content_encoding(self):
+        return self._content_type_encoding[1]
+
+    @property
+    def has_pypeline_view(self):
+        if README_RE.match(self.name) or self.extension in ['.md', '.rst']:
+            return True
+        return False
+
+    @property
+    def has_html_view(self):
+        if self.content_type.startswith('text/') or self.extension in VIEWABLE_EXTENSIONS or \
+            self.extension in self._additional_viewable_extensions:
+            return True
+        return False
+
+    @property
+    def has_image_view(self):
+        return self.content_type.startswith('image/')
+
+    def context(self):
+        path = self.path()
+        prev = self.prev_commit
+        next = self.next_commit
+        if prev is not None: prev = prev.get_path(path)
+        if next is not None: next = next.get_path(path)
+        return dict(
+            prev=prev,
+            next=next)
+
+    def open(self):
+        return self.repo.open_blob(self)
+
+    def __iter__(self):
+        return iter(self.open())
+
+    @LazyProperty
+    def text(self):
+        return self.open().read()
+
+    @classmethod
+    def diff(cls, v0, v1):
+        differ = SequenceMatcher(v0, v1)
+        return differ.get_opcodes()
 
 mapper(Commit, CommitDoc, repository_orm_session)
 mapper(Tree, TreeDoc, repository_orm_session)
@@ -399,7 +521,7 @@
             # remove this commit from its parents children and add any childless
             # parents to the 'ready set'
             new_parent = None
-            for oid in ci_parents[ci]:
+            for oid in ci_parents.get(ci, []):
                 children = ci_children[oid]
                 children.discard(ci)
                 if not children: