--- 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: