Edgewall Software

Ticket #1242: xref_cs1343.diff

File xref_cs1343.diff, 59.6 KB (added by cboos@…, 4 years ago)

The xref patch (on [1343])

  • scripts/trac-admin

     
    3030import sqlite 
    3131import StringIO 
    3232 
     33import trac.Environment # has to be imported before Xref 
    3334from trac import perm 
    3435from trac import util 
    3536from trac import sync 
     37from trac import Xref 
     38from trac import Href 
    3639import trac.siteconfig 
    37 import trac.Environment 
    3840 
    3941def my_sum(list): 
    4042    """Python2.1 doesn't have sum()""" 
     
    272274            docs = (self._help_about + self._help_help + 
    273275                    self._help_initenv + self._help_hotcopy + 
    274276                    self._help_resync + self._help_upgrade + 
     277                    self._help_xref + 
    275278                    self._help_wiki + 
    276279#                    self._help_config + self._help_wiki + 
    277280                    self._help_permission + self._help_component + 
     
    542545            print ' Installing wiki pages' 
    543546            cursor = cnx.cursor() 
    544547            self._do_wiki_load(trac.siteconfig.__default_wiki_dir__,cursor) 
     548            print ' Cross-referencing' 
     549            self.__env.href = Href('trac-admin::initenv') 
     550            self.__env._wiki_pages = {} 
     551            Xref.rebuild_cross_references(self.__env, cnx) 
    545552 
    546553            print ' Indexing repository' 
    547             sync.sync(cnx, rep, fs_ptr, pool) 
     554            sync.sync(self.__env, cnx, rep, fs_ptr, pool) 
    548555        except Exception, e: 
    549556            print 'Failed to initialize database.', e 
    550557            sys.exit(2) 
     
    592599        pool = core.svn_pool_create(None) 
    593600 
    594601        self.db_open() # We need to call this function to open the env, really stupid 
     602        self.__env.href = Href.Href('trac-admin::resync') # We need this for the WikiFormatter 
     603        self.__env._wiki_pages = {} 
    595604 
    596605        # Remove any trailing slash or else subversion might abort 
    597606        repository_dir = self.__env.get_config('trac', 'repository_dir') 
     
    605614        print 'resyncing...' 
    606615        self.db_execsql("DELETE FROM revision") 
    607616        self.db_execsql("DELETE FROM node_change") 
    608         sync.sync(cnx, rep, fs_ptr, pool) 
     617        sync.sync(self.__env, cnx, rep, fs_ptr, pool) 
    609618        print 'done.' 
    610619         
     620    _help_xref = [('xref', 'Regenerate all the cross-reference information between trac objects')] 
     621     
     622    ## XRef 
     623    def do_xref(self, line): 
     624        self.db_open() # We need to call this function to open the env, really stupid 
     625        cnx = self.__env.get_db_cnx() 
     626        print 'cross-referencing... (except changesets: use \'resync\' for that)' 
     627        self.__env.href = Href.Href('trac-admin::xref') 
     628        self.__env._wiki_pages = {} 
     629        Xref.rebuild_cross_references(self.__env, cnx, False) 
     630        print 'done.' 
     631         
    611632    ## Wiki 
    612633    _help_wiki = [('wiki list', 'List wiki pages'), 
    613634                  ('wiki remove <name>', 'Remove wiki page'), 
  • trac/core.py

     
    4040    'search'      : ('Search', 'Search', 0), 
    4141    'report'      : ('Report', 'Report', 0), 
    4242    'ticket'      : ('Ticket', 'TicketModule', 0), 
     43    'bug'         : ('Ticket', 'TicketModule', 0), 
     44    'issue'       : ('Ticket', 'TicketModule', 0), 
     45    'source'      : ('Browser', 'Browser', 1), 
     46    'repos'       : ('Browser', 'Browser', 1), 
    4347    'browser'     : ('Browser', 'Browser', 1), 
    4448    'timeline'    : ('Timeline', 'Timeline', 1), 
    4549    'changeset'   : ('Changeset', 'Changeset', 1), 
     
    4852    'attachment'  : ('File', 'Attachment', 0), 
    4953    'roadmap'     : ('Roadmap', 'Roadmap', 0), 
    5054    'settings'    : ('Settings', 'Settings', 0), 
     55    'xref'        : ('Xref', 'XrefModule', 0), 
     56    'orphans'     : ('Xref', 'XrefModule', 0), 
    5157    'milestone'   : ('Milestone', 'Milestone', 0) 
    5258    } 
    5359 
     
    7480        pool, rep, fs_ptr = open_svn_repos(repos_dir) 
    7581        module.repos = rep 
    7682        module.fs_ptr = fs_ptr 
    77         sync.sync(db, rep, fs_ptr, pool) 
     83        sync.sync(env, db, rep, fs_ptr, pool) 
    7884        module.pool = pool 
    7985 
    8086    return module 
  • trac/db_default.py

     
    2121 
    2222 
    2323# Database version identifier. Used for automatic upgrades. 
    24 db_version = 9 
     24db_version = 10 
    2525 
    2626def __mkreports(reports): 
    2727    """Utility function used to create report data in same syntax as the 
     
    159159         UNIQUE(sid,var_name) 
    160160); 
    161161 
     162CREATE TABLE xref ( 
     163         src_type        text, 
     164         src_id          text, 
     165         relation        text, 
     166         dest_type       text, 
     167         dest_id         text, 
     168         facet           text, 
     169         context         text 
     170); 
     171 
     172 
    162173CREATE INDEX node_change_idx    ON node_change(rev); 
    163174CREATE INDEX ticket_change_idx  ON ticket_change(ticket, time); 
    164175CREATE INDEX wiki_idx           ON wiki(name,version); 
    165176CREATE INDEX session_idx        ON session(sid,var_name); 
     177CREATE INDEX xref_src_idx       ON xref(src_id,src_type); 
     178CREATE INDEX xref_dest_idx      ON xref(dest_id,dest_type); 
    166179""" 
    167180 
    168181## 
  • trac/Milestone.py

     
    2424from trac.Ticket import get_custom_fields, Ticket 
    2525from trac.WikiFormatter import wiki_to_html 
    2626from trac.util import * 
     27from trac.Xref import TracRef 
    2728 
    2829import time 
    2930 
     
    159160        cursor.execute("INSERT INTO milestone (name,due,completed,description) " 
    160161                       "VALUES (%s,%s,%s,%s)", 
    161162                       (name, due, completed, description)) 
     163        TracRef('milestone', name).replace_xrefs_from_wiki(self.env, self.db, 'description', description) 
    162164        self.db.commit() 
    163165        req.redirect(self.env.href.milestone(name)) 
    164166 
     
    182184                                   "WHERE milestone=%s", (id,)) 
    183185            self.log.info('Deleting milestone %s' % id) 
    184186            cursor.execute("DELETE FROM milestone WHERE name=%s", (id,)) 
     187            TracRef('milestone', id).delete_xrefs(self.db, 'description') 
    185188            self.db.commit() 
    186189            req.redirect(self.env.href.roadmap()) 
    187190        else: 
     
    266269        action = req.args.get('action', 'view') 
    267270        id = req.args.get('id') 
    268271 
     272        TracRef('milestone', id).add_backlinks(self.db, req) 
     273 
    269274        if action == 'new': 
    270275            self.perm.assert_permission(perm.MILESTONE_CREATE) 
    271276            self.render_editor(req) 
  • trac/tests/href.py

     
    9494        self.assertEqual('/attachment/ticket/42/foo.txt?format=raw', 
    9595                         self.href.attachment('ticket', '42', 'foo.txt', 'raw')) 
    9696 
     97    def test_xref(self): 
     98        """Testing Href.xref""" 
     99        self.assertEqual('/xref', self.href.xref()) 
     100        self.assertEqual('/xref/wiki', self.href.xref('wiki')) 
     101        self.assertEqual('/xref/wiki/WikiStart', self.href.xref('wiki', 'WikiStart')) 
     102 
     103    def test_orphans(self): 
     104        """Testing Href.orphans""" 
     105        self.assertEqual('/orphans', self.href.orphans()) 
     106 
    97107def suite(): 
    98108    return unittest.makeSuite(HrefTestCase,'test') 
    99109 
  • trac/Report.py

     
    2222from trac import perm, util 
    2323from trac.Module import Module 
    2424from trac.WikiFormatter import wiki_to_html 
     25from trac.Xref import TracRef 
    2526 
    2627import re 
    2728import time 
     
    113114        cursor.execute("INSERT INTO report (title,sql,description) " 
    114115                       "VALUES (%s,%s,%s)", (title, sql, description)) 
    115116        id = self.db.get_last_id() 
     117        TracRef('report', id).replace_xrefs_from_wiki(self.env, self.db, 'description', description) 
    116118        self.db.commit() 
    117119        req.redirect(self.env.href.report(id)) 
    118120 
     
    122124        if not req.args.has_key('cancel'): 
    123125            cursor = self.db.cursor () 
    124126            cursor.execute("DELETE FROM report WHERE id=%s", (id,)) 
     127            TracRef('report', id).delete_xrefs(self.db, 'description') 
    125128            self.db.commit() 
    126129            req.redirect(self.env.href.report()) 
    127130        else: 
     
    401404                                   req.args.get('description', ''), 
    402405                                   req.args.get('sql', '')) 
    403406 
     407        if id != -1: 
     408            TracRef('report', id).add_backlinks(self.db, req) 
     409 
    404410        if id != -1 or action == 'new': 
    405411            self.add_link('up', self.env.href.report(), 'Available Reports') 
    406412 
  • trac/sync.py

     
    2020# Author: Jonas Borgstr�jonas@edgewall.com> 
    2121 
    2222from svn import fs, util, delta, repos, core 
     23from trac.Xref import TracRef 
    2324 
    2425import posixpath 
    2526 
    2627 
    27 def sync(db, repos, fs_ptr, pool): 
     28def sync(env, db, repos, fs_ptr, pool): 
    2829    """ 
    2930    Update the revision and node_change tables to be in sync with 
    3031    the repository. 
     
    5657        cursor.execute ('INSERT INTO revision (rev, time, author, message) ' 
    5758                        'VALUES (%s, %s, %s, %s)', rev + offset, date, 
    5859                        author, message) 
     60        TracRef('changeset', rev + offset).replace_xrefs_from_wiki(env, db, 'content', message) 
    5961        insert_change (subpool, fs_ptr, rev + offset, cursor) 
    6062        core.svn_pool_clear(subpool) 
    6163 
     
    125127                old_path = posixpath.join(old_path, posixpath.split(path)[1]) 
    126128                action = 'A' 
    127129            else: 
    128                 self._save_change(core.svn_node_file, 'A', path)  
     130                self._save_change(core.svn_node_dir, 'A', path)  
    129131                action = None 
    130132 
    131133            if action: 
  • trac/File.py

     
    2525from trac import perm, util 
    2626from trac.Module import Module 
    2727from trac.WikiFormatter import wiki_to_html 
     28from trac.Xref import TracRef 
    2829 
    2930import svn.core 
    3031import svn.fs 
     
    261262 
    262263        self.rev = req.args.get('rev', None) 
    263264        self.path = req.args.get('path', '/') 
     265 
     266        TracRef('source', self.path).add_backlinks(self.db, req) 
     267         
    264268        if not self.rev: 
    265269            rev_specified = 0 
    266270            self.rev = svn.fs.youngest_rev(self.fs_ptr, self.pool) 
  • trac/Log.py

     
    2222from trac import perm, util 
    2323from trac.Module import Module 
    2424from trac.WikiFormatter import wiki_to_oneliner 
     25from trac.Xref import TracRef 
    2526 
    2627import svn.core 
    2728import svn.fs 
     
    121122 
    122123        self.path = req.args.get('path', '/') 
    123124        self.authzperm.assert_permission(self.path) 
     125 
     126        TracRef('source', self.path).add_backlinks(self.db, req) 
     127         
    124128        if req.args.has_key('rev'): 
    125129            try: 
    126130                rev = int(req.args.get('rev')) 
  • trac/upgrades/db10.py

     
     1import time 
     2 
     3sql = """ 
     4-- Initial creation of the general cross-reference table 
     5CREATE TABLE xref ( 
     6         src_type        text, 
     7         src_id          text, 
     8         relation        text, 
     9         dest_type       text, 
     10         dest_id         text, 
     11         facet           text, 
     12         context         text 
     13); 
     14 
     15CREATE INDEX xref_src_idx       ON xref(src_id,src_type); 
     16CREATE INDEX xref_dest_idx      ON xref(dest_id,dest_type); 
     17""" 
     18 
     19def do_upgrade(env, ver, cursor): 
     20    cursor.execute(sql) 
     21 
     22def do_db_upgrade(env, ver, db): 
     23    """Renumbering of ticket comments (using the spare 'oldvalue' field)""" 
     24    cursor = db.cursor() 
     25    update_cursor = db.cursor() 
     26    cursor.execute("SELECT ticket, time, author FROM ticket_change " 
     27                   "WHERE field = 'comment' " 
     28                   "ORDER BY ticket, time, author ") 
     29    previous_ticket = None 
     30    for ticket, time, author in cursor: 
     31        if ticket != previous_ticket: 
     32            previous_ticket = ticket 
     33            n = 1 
     34        update_cursor.execute("UPDATE ticket_change SET " 
     35                              "oldvalue = %s ", 
     36                              (n)) 
     37        n += 1 
     38 
     39         
     40 
     41 
     42 
     43 
     44 
     45 
     46 
     47 
     48 
     49 
     50 
     51 
     52 
     53     
  • trac/Href.py

     
    5656        else: 
    5757            return href_join(self.base, 'browser', path) 
    5858 
     59    def source(self, path, rev=None): 
     60        return self.browser(path, rev) 
     61 
    5962    def login(self): 
    6063        return href_join(self.base, 'login') 
    6164 
     
    176179        if format: 
    177180            href += '?format=%s' % format 
    178181        return href 
     182 
     183    def xref(self, module=None, id=None): 
     184        if module and id: 
     185            href = href_join(self.base, 'xref', str(module), str(id)) 
     186        else: 
     187            href = href_join(self.base, 'xref') 
     188        return href 
     189 
     190    def orphans(self, module=None): 
     191        if module: 
     192            href = href_join(self.base, 'orphans', str(module)) 
     193        else: 
     194            href = href_join(self.base, 'orphans') 
     195        return href 
     196 
  • trac/web/main.py

     
    153153    if match: 
    154154        set_if_missing(args, 'mode', match.group(1)) 
    155155        return args 
    156     match = re.search('^/(ticket|report)(?:/([0-9]+)/*)?', path_info) 
     156    match = re.search('^/(ticket|bug|issue|report)(?:/([0-9]+)/*)?', path_info) 
    157157    if match: 
    158158        set_if_missing(args, 'mode', match.group(1)) 
    159159        if match.group(2): 
    160160            set_if_missing(args, 'id', match.group(2)) 
    161161        return args 
    162     match = re.search('^/(browser|log|file)(?:(/.*))?', path_info) 
     162    match = re.search('^/(browser|source|repos|log|file)(?:(/.*))?', path_info) 
    163163    if match: 
    164164        set_if_missing(args, 'mode', match.group(1)) 
    165165        if match.group(2): 
     
    183183        if match.group(1): 
    184184            set_if_missing(args, 'id', urllib.unquote(match.group(1))) 
    185185        return args 
     186    match = re.search('^/(xref|orphans)(?:/([^/]+))?(?:/(.*)/?)?', path_info) 
     187    if match: 
     188        set_if_missing(args, 'mode', match.group(1)) 
     189        set_if_missing(args, 'type', match.group(2)) 
     190        id = match.group(3) 
     191        if id: 
     192            set_if_missing(args, 'id', urllib.unquote(id)) 
     193        return args 
    186194    return args 
    187195 
    188196def populate_hdf(hdf, env, req=None): 
     
    208216        'login': env.href.login(), 
    209217        'logout': env.href.logout(), 
    210218        'settings': env.href.settings(), 
     219        'xref': env.href.xref(), 
     220        'orphans': env.href.orphans(), 
    211221        'homepage': 'http://trac.edgewall.com/' 
    212222    } 
    213223 
  • trac/Changeset.py

     
    2424from trac.Diff import get_diff_options, hdf_diff, unified_diff 
    2525from trac.Module import Module 
    2626from trac.WikiFormatter import wiki_to_html 
     27from trac.Xref import TracRef 
    2728from trac import authzperm, perm 
    2829 
    2930import svn.core 
     
    435436        else: 
    436437            self.rev = youngest_rev 
    437438 
     439        TracRef('changeset', self.rev).add_backlinks(self.db, req) 
     440 
    438441        self.diff_options = get_diff_options(req) 
    439442        if req.args.has_key('update'): 
    440443            req.redirect(self.env.href.changeset(self.rev)) 
     
    462465        req.hdf['changeset.revision'] = self.rev 
    463466        req.hdf['changeset.changes'] = change_info 
    464467        req.hdf['changeset.href'] = self.env.href.changeset(self.rev) 
    465          
     468 
    466469        if len(change_info) == 0: 
    467470            raise authzperm.AuthzPermissionError() 
    468471         
  • trac/Wiki.py

     
    2424from trac.Module import Module 
    2525from trac.util import escape, TracError, get_reporter_id 
    2626from trac.WikiFormatter import * 
     27from trac.Xref import TracRef 
    2728 
    2829import os 
    2930import time 
     
    5152    Represents a wiki page (new or existing). 
    5253    """ 
    5354 
    54     def __init__(self, name, version, perm_, db): 
    55         self.db = db 
     55    def __init__(self, name, version, perm_, env, db): 
     56        self.env = env 
     57        self.db  = db 
    5658        self.name = name 
    5759        self.perm = perm_ 
    5860        cursor = self.db.cursor () 
     
    108110                            "%s,%s)", (self.name, self.version + 1, 
    109111                            int(time.time()), author, remote_addr, self.text, 
    110112                            comment, self.readonly)) 
     113            TracRef('wiki', self.name).replace_xrefs_from_wiki(self.env, self.db, 'content', self.text) 
    111114            self.db.commit() 
    112115            self.version += 1 
    113116            self.old_readonly = self.readonly 
     
    126129        req.hdf['wiki.page_name'] = escape(pagename) 
    127130        req.hdf['wiki.current_href'] = escape(self.env.href.wiki(pagename)) 
    128131 
     132        self.ref = TracRef('wiki', pagename) 
     133        self.ref.add_backlinks(self.db, req) 
     134 
    129135        if action == 'diff': 
    130136            version = int(req.args.get('version', 0)) 
    131137            self._render_diff(req, pagename, version) 
     
    136142        elif action == 'delete': 
    137143            version = None 
    138144            if req.args.has_key('delete_version'): 
    139                 version = int(req.args['version']) 
     145                version = int(req.args.get('version')) 
    140146            self._delete_page(req, pagename, version) 
    141147        elif action == 'save': 
    142148            if req.args.has_key('cancel'): 
     
    164170            cursor = self.db.cursor() 
    165171            cursor.execute("DELETE FROM wiki WHERE name=%s and version=%s", 
    166172                           (pagename, version)) 
     173            self.ref.delete_xrefs(self.db, 'comment:%s' % version) 
    167174            self.log.info('Deleted version %d of page %s' % (version, pagename)) 
    168175            cursor.execute("SELECT COUNT(*) FROM wiki WHERE name=%s", (pagename,)) 
    169             if not cursor.fetchone(): 
     176            last_version = cursor.fetchone()[0] 
     177            if last_version == 0: 
    170178                page_deleted = 1 
     179            elif version > last_version: # resurrect the previous 'content'  
     180                cursor.execute("SELECT text FROM wiki WHERE name=%s ORDER BY version DESC", 
     181                               (pagename)) 
     182                text = cursor.fetchone()[0] 
     183                self.ref.replace_xrefs_from_wiki(self.env, self.db, 'content', text) 
    171184        else: # Delete a wiki page completely 
    172185            cursor.execute("DELETE FROM wiki WHERE name=%s", (pagename,)) 
    173186            page_deleted = 1 
     
    175188        self.db.commit() 
    176189 
    177190        if page_deleted: 
     191            self.ref.delete_xrefs(self.db) 
    178192            # Delete orphaned attachments 
    179193            for attachment in self.env.get_attachments(self.db, 'wiki', pagename): 
    180194                self.env.delete_attachment(self.db, 'wiki', pagename, 
     
    233247    def _render_editor(self, req, pagename, preview=0): 
    234248        self.perm.assert_permission(perm.WIKI_MODIFY) 
    235249 
    236         page = WikiPage(pagename, None, self.perm, self.db) 
     250        page = WikiPage(pagename, None, self.perm, self.env, self.db) 
    237251        if req.args.has_key('text'): 
    238252            page.set_content(req.args.get('text')) 
    239253        if preview: 
     
    311325            self.add_link('alternate', '?format=txt', 'Plain Text', 
    312326                          'text/plain') 
    313327 
    314         page = WikiPage(pagename, version, self.perm, self.db) 
     328        page = WikiPage(pagename, version, self.perm, self.env, self.db) 
    315329 
    316330        info = { 
    317331            'version': page.version, 
     
    331345    def _save_page(self, req, pagename): 
    332346        self.perm.assert_permission(perm.WIKI_MODIFY) 
    333347 
    334         page = WikiPage(pagename, None, self.perm, self.db) 
     348        page = WikiPage(pagename, None, self.perm, self.env, self.db) 
    335349        if req.args.has_key('text'): 
    336350            page.set_content(req.args.get('text')) 
    337351 
  • trac/Environment.py

     
    2323# 
    2424 
    2525from trac import db, db_default, Logging, Mimeview, util 
     26from trac.Xref import TracRef 
    2627 
     28 
    2729import ConfigParser 
    2830import os 
    2931import shutil 
     
    253255        cursor.execute('INSERT INTO attachment VALUES(%s,%s,%s,%s,%s,%s,%s,%s)', 
    254256                       (type, id, filename, length, int(time.time()), 
    255257                       description, author, ipnr)) 
     258        TracRef(type, id).replace_xrefs_from_wiki(self, cnx, 'attachment:%s' % filename, description) 
    256259        shutil.copyfileobj(attachment.file, fd) 
    257260        self.log.info('New attachment: %s/%s/%s by %s', type, id, filename, author) 
    258261        cnx.commit() 
     
    265268        cursor = cnx.cursor() 
    266269        cursor.execute('DELETE FROM attachment WHERE type=%s AND id=%s AND ' 
    267270                       'filename=%s', (type, id, filename)) 
     271        TracRef(type, id).delete_xrefs(cnx, 'attachment:%s' % filename) 
    268272        os.unlink(path) 
    269273        self.log.info('Attachment removed: %s/%s/%s', type, id, filename) 
    270274        cnx.commit() 
     
    324328                    err = 'No upgrade module for version %i (%s.py)' % (i, upg) 
    325329                    raise EnvironmentError, err 
    326330                d.do_upgrade(self, i, cursor) 
     331                if hasattr(d, 'do_db_upgrade'): # for more complex upgrades, as in 'db9.py' 
     332                    d.do_db_upgrade(self, i, cnx) 
    327333            cursor.execute("UPDATE system SET value=%s WHERE " 
    328334                           "name='database_version'", (db_default.db_version)) 
    329335            self.log.info('Upgraded db version from %d to %d', 
  • trac/Xref.py

     
     1# -*- coding: iso8859-1 -*- 
     2# 
     3# Copyright (C) 2005 Edgewall Software 
     4# Copyright (C) 2005 Christian Boos <cboos@wanadoo.fr> 
     5# 
     6# Trac is free software; you can redistribute it and/or 
     7# modify it under the terms of the GNU General Public License as 
     8# published by the Free Software Foundation; either version 2 of the 
     9# License, or (at your option) any later version. 
     10# 
     11# Trac is distributed in the hope that it will be useful, 
     12# but WITHOUT ANY WARRANTY; without even the implied warranty of 
     13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
     14# General Public License for more details. 
     15# 
     16# You should have received a copy of the GNU General Public License 
     17# along with this program; if not, write to the Free Software 
     18# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
     19# 
     20# Author: Christian Boos <cboos@wanadoo.fr> 
     21 
     22""" 
     23 
     24  This module implements a general cross-reference facility for Trac, 
     25  as suggested in #1242. 
     26 
     27  Currently, cross-references between the following Trac objects are supported: 
     28   * Wiki page 
     29   * Ticket 
     30   * Changeset 
     31   * Report 
     32   * Milestone 
     33   * Source (only as targets for now) 
     34 
     35  Basically, two kinds of references are supported: 
     36   * ''implicit references between objects'' 
     37     Implicit references are created for every TracLinks that can be found 
     38     in (any of) the wiki text of a Trac object. 
     39     Indeed, some objects may have separately editable wiki texts, 
     40     each of them being a ''facet'' of this object. 
     41     (TODO: generic fine grained anchoring: <object href>#<facet> should go to the facet) 
     42   * ''explicit relation between objects'' 
     43     An explicit relation must be created explicitely as such, 
     44     by some programmatic mean. 
     45     Currently, some of the ticket fields are setting up explicit relationships. 
     46 
     47   Note that implicit references always have an empty 'relation', 
     48   whereas explicit references may use the 'facet' for informative purpose. 
     49 
     50""" 
     51 
     52 
     53from trac.Module import Module 
     54from trac.util import escape 
     55from trac.WikiFormatter import XRefFormatter 
     56 
     57__all__ = ['TracRef', 'rebuild_cross_references'] 
     58 
     59 
     60how_much_context = 40 
     61 
     62 
     63class TracRef: 
     64    """