| 1 |
from trac.core import * |
|---|
| 2 |
from trac.search import ISearchSource, shorten_result |
|---|
| 3 |
from trac.versioncontrol.api import Node |
|---|
| 4 |
from trac.perm import IPermissionRequestor |
|---|
| 5 |
from trac.util import Markup, escape |
|---|
| 6 |
from trac.mimeview.api import Mimeview |
|---|
| 7 |
import re |
|---|
| 8 |
import posixpath |
|---|
| 9 |
import os |
|---|
| 10 |
from fnmatch import fnmatch |
|---|
| 11 |
|
|---|
| 12 |
class TracRepoSearchPlugin(Component): |
|---|
| 13 |
""" Search the source repository. """ |
|---|
| 14 |
implements(ISearchSource, IPermissionRequestor) |
|---|
| 15 |
|
|---|
| 16 |
def _get_filters(self): |
|---|
| 17 |
includes = [glob for glob in self.env.config.get('repo-search', |
|---|
| 18 |
'include', '').split(os.path.pathsep) if glob] |
|---|
| 19 |
excludes = [glob for glob in self.env.config.get('repo-search', |
|---|
| 20 |
'exclude', '').split(os.path.pathsep) if glob] |
|---|
| 21 |
return (includes, excludes) |
|---|
| 22 |
|
|---|
| 23 |
def walk_repo(self, repo): |
|---|
| 24 |
""" Walk all nodes in the repo that match the filters. """ |
|---|
| 25 |
includes, excludes = self._get_filters() |
|---|
| 26 |
|
|---|
| 27 |
def searchable(path): |
|---|
| 28 |
# Exclude paths |
|---|
| 29 |
for exclude in excludes: |
|---|
| 30 |
if fnmatch(path, exclude): |
|---|
| 31 |
return 0 |
|---|
| 32 |
|
|---|
| 33 |
# Include paths |
|---|
| 34 |
for include in includes: |
|---|
| 35 |
if fnmatch(path, include): |
|---|
| 36 |
return 1 |
|---|
| 37 |
|
|---|
| 38 |
return not includes |
|---|
| 39 |
|
|---|
| 40 |
def do_walk(path): |
|---|
| 41 |
node = repo.get_node(path) |
|---|
| 42 |
basename = posixpath.basename(path) |
|---|
| 43 |
|
|---|
| 44 |
if searchable(node.path): |
|---|
| 45 |
yield node |
|---|
| 46 |
|
|---|
| 47 |
if node.kind == Node.DIRECTORY: |
|---|
| 48 |
for subnode in node.get_entries(): |
|---|
| 49 |
for result in do_walk(subnode.path): |
|---|
| 50 |
yield result |
|---|
| 51 |
|
|---|
| 52 |
for node in do_walk('/'): |
|---|
| 53 |
yield node |
|---|
| 54 |
|
|---|
| 55 |
# IPermissionRequestor methods |
|---|
| 56 |
def get_permission_actions(self): |
|---|
| 57 |
yield 'REPO_SEARCH' |
|---|
| 58 |
|
|---|
| 59 |
# ISearchSource methods |
|---|
| 60 |
def get_search_filters(self, req): |
|---|
| 61 |
if req.perm.has_permission('REPO_SEARCH'): |
|---|
| 62 |
yield ('repo', 'Source Repository', 0) |
|---|
| 63 |
|
|---|
| 64 |
def get_search_results(self, req, query, filters): |
|---|
| 65 |
if 'repo' not in filters: |
|---|
| 66 |
return |
|---|
| 67 |
repo = self.env.get_repository(req.authname) |
|---|
| 68 |
if not isinstance(query, list): |
|---|
| 69 |
query = query.split() |
|---|
| 70 |
query = [q.lower() for q in query] |
|---|
| 71 |
db = self.env.get_db_cnx() |
|---|
| 72 |
include, excludes = self._get_filters() |
|---|
| 73 |
|
|---|
| 74 |
to_unicode = Mimeview(self.env).to_unicode |
|---|
| 75 |
|
|---|
| 76 |
# Use indexer if possible, otherwise fall back on brute force search. |
|---|
| 77 |
try: |
|---|
| 78 |
from tracreposearch.indexer import Indexer |
|---|
| 79 |
self.indexer = Indexer(self.env) |
|---|
| 80 |
self.indexer.reindex() |
|---|
| 81 |
walker = lambda repo, query: [repo.get_node(filename) for filename |
|---|
| 82 |
in self.indexer.find_words(query)] |
|---|
| 83 |
except TracError, e: |
|---|
| 84 |
self.env.log.warning(e) |
|---|
| 85 |
self.env.log.warning('Falling back on full repository walk') |
|---|
| 86 |
def full_walker(repo, query): |
|---|
| 87 |
for node in self.walk_repo(repo): |
|---|
| 88 |
# Search content |
|---|
| 89 |
matched = 1 |
|---|
| 90 |
content = node.get_content() |
|---|
| 91 |
if not content: |
|---|
| 92 |
continue |
|---|
| 93 |
content = to_unicode(content.read().lower(), node.get_content_type()) |
|---|
| 94 |
for term in query: |
|---|
| 95 |
if term not in content: |
|---|
| 96 |
matched = 0 |
|---|
| 97 |
break |
|---|
| 98 |
if matched: |
|---|
| 99 |
yield node |
|---|
| 100 |
|
|---|
| 101 |
walker = full_walker |
|---|
| 102 |
|
|---|
| 103 |
if not req.perm.has_permission('REPO_SEARCH'): |
|---|
| 104 |
return |
|---|
| 105 |
|
|---|
| 106 |
def match_name(name): |
|---|
| 107 |
for term in query: |
|---|
| 108 |
if term not in name: |
|---|
| 109 |
return 0 |
|---|
| 110 |
return 1 |
|---|
| 111 |
|
|---|
| 112 |
for node in walker(repo, query): |
|---|
| 113 |
change = repo.get_changeset(node.rev) |
|---|
| 114 |
if node.kind == Node.DIRECTORY: |
|---|
| 115 |
yield (self.env.href.browser(node.path), |
|---|
| 116 |
node.path, change.date, change.author, |
|---|
| 117 |
'Directory') |
|---|
| 118 |
else: |
|---|
| 119 |
found = 0 |
|---|
| 120 |
content = to_unicode(node.get_content().read(), node.get_content_type()) |
|---|
| 121 |
for n, line in enumerate(content.splitlines()): |
|---|
| 122 |
line = line.lower() |
|---|
| 123 |
for q in query: |
|---|
| 124 |
idx = line.find(q) |
|---|
| 125 |
if idx != -1: |
|---|
| 126 |
found = n + 1 |
|---|
| 127 |
break |
|---|
| 128 |
if found: |
|---|
| 129 |
break |
|---|
| 130 |
|
|---|
| 131 |
yield (self.env.href.browser(node.path) + (found and '#L%i' % found or ''), |
|---|
| 132 |
node.path, change.date, change.author, |
|---|
| 133 |
shorten_result(content, query)) |
|---|