Ticket #1242: xref_cs1343.diff
| File xref_cs1343.diff, 59.6 KB (added by cboos@…, 7 years ago) |
|---|
-
scripts/trac-admin
30 30 import sqlite 31 31 import StringIO 32 32 33 import trac.Environment # has to be imported before Xref 33 34 from trac import perm 34 35 from trac import util 35 36 from trac import sync 37 from trac import Xref 38 from trac import Href 36 39 import trac.siteconfig 37 import trac.Environment38 40 39 41 def my_sum(list): 40 42 """Python2.1 doesn't have sum()""" … … 272 274 docs = (self._help_about + self._help_help + 273 275 self._help_initenv + self._help_hotcopy + 274 276 self._help_resync + self._help_upgrade + 277 self._help_xref + 275 278 self._help_wiki + 276 279 # self._help_config + self._help_wiki + 277 280 self._help_permission + self._help_component + … … 542 545 print ' Installing wiki pages' 543 546 cursor = cnx.cursor() 544 547 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) 545 552 546 553 print ' Indexing repository' 547 sync.sync( cnx, rep, fs_ptr, pool)554 sync.sync(self.__env, cnx, rep, fs_ptr, pool) 548 555 except Exception, e: 549 556 print 'Failed to initialize database.', e 550 557 sys.exit(2) … … 592 599 pool = core.svn_pool_create(None) 593 600 594 601 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 = {} 595 604 596 605 # Remove any trailing slash or else subversion might abort 597 606 repository_dir = self.__env.get_config('trac', 'repository_dir') … … 605 614 print 'resyncing...' 606 615 self.db_execsql("DELETE FROM revision") 607 616 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) 609 618 print 'done.' 610 619 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 611 632 ## Wiki 612 633 _help_wiki = [('wiki list', 'List wiki pages'), 613 634 ('wiki remove <name>', 'Remove wiki page'), -
trac/core.py
40 40 'search' : ('Search', 'Search', 0), 41 41 'report' : ('Report', 'Report', 0), 42 42 'ticket' : ('Ticket', 'TicketModule', 0), 43 'bug' : ('Ticket', 'TicketModule', 0), 44 'issue' : ('Ticket', 'TicketModule', 0), 45 'source' : ('Browser', 'Browser', 1), 46 'repos' : ('Browser', 'Browser', 1), 43 47 'browser' : ('Browser', 'Browser', 1), 44 48 'timeline' : ('Timeline', 'Timeline', 1), 45 49 'changeset' : ('Changeset', 'Changeset', 1), … … 48 52 'attachment' : ('File', 'Attachment', 0), 49 53 'roadmap' : ('Roadmap', 'Roadmap', 0), 50 54 'settings' : ('Settings', 'Settings', 0), 55 'xref' : ('Xref', 'XrefModule', 0), 56 'orphans' : ('Xref', 'XrefModule', 0), 51 57 'milestone' : ('Milestone', 'Milestone', 0) 52 58 } 53 59 … … 74 80 pool, rep, fs_ptr = open_svn_repos(repos_dir) 75 81 module.repos = rep 76 82 module.fs_ptr = fs_ptr 77 sync.sync( db, rep, fs_ptr, pool)83 sync.sync(env, db, rep, fs_ptr, pool) 78 84 module.pool = pool 79 85 80 86 return module -
trac/db_default.py
21 21 22 22 23 23 # Database version identifier. Used for automatic upgrades. 24 db_version = 924 db_version = 10 25 25 26 26 def __mkreports(reports): 27 27 """Utility function used to create report data in same syntax as the … … 159 159 UNIQUE(sid,var_name) 160 160 ); 161 161 162 CREATE 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 162 173 CREATE INDEX node_change_idx ON node_change(rev); 163 174 CREATE INDEX ticket_change_idx ON ticket_change(ticket, time); 164 175 CREATE INDEX wiki_idx ON wiki(name,version); 165 176 CREATE INDEX session_idx ON session(sid,var_name); 177 CREATE INDEX xref_src_idx ON xref(src_id,src_type); 178 CREATE INDEX xref_dest_idx ON xref(dest_id,dest_type); 166 179 """ 167 180 168 181 ## -
trac/Milestone.py
24 24 from trac.Ticket import get_custom_fields, Ticket 25 25 from trac.WikiFormatter import wiki_to_html 26 26 from trac.util import * 27 from trac.Xref import TracRef 27 28 28 29 import time 29 30 … … 159 160 cursor.execute("INSERT INTO milestone (name,due,completed,description) " 160 161 "VALUES (%s,%s,%s,%s)", 161 162 (name, due, completed, description)) 163 TracRef('milestone', name).replace_xrefs_from_wiki(self.env, self.db, 'description', description) 162 164 self.db.commit() 163 165 req.redirect(self.env.href.milestone(name)) 164 166 … … 182 184 "WHERE milestone=%s", (id,)) 183 185 self.log.info('Deleting milestone %s' % id) 184 186 cursor.execute("DELETE FROM milestone WHERE name=%s", (id,)) 187 TracRef('milestone', id).delete_xrefs(self.db, 'description') 185 188 self.db.commit() 186 189 req.redirect(self.env.href.roadmap()) 187 190 else: … … 266 269 action = req.args.get('action', 'view') 267 270 id = req.args.get('id') 268 271 272 TracRef('milestone', id).add_backlinks(self.db, req) 273 269 274 if action == 'new': 270 275 self.perm.assert_permission(perm.MILESTONE_CREATE) 271 276 self.render_editor(req) -
trac/tests/href.py
94 94 self.assertEqual('/attachment/ticket/42/foo.txt?format=raw', 95 95 self.href.attachment('ticket', '42', 'foo.txt', 'raw')) 96 96 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 97 107 def suite(): 98 108 return unittest.makeSuite(HrefTestCase,'test') 99 109 -
trac/Report.py
22 22 from trac import perm, util 23 23 from trac.Module import Module 24 24 from trac.WikiFormatter import wiki_to_html 25 from trac.Xref import TracRef 25 26 26 27 import re 27 28 import time … … 113 114 cursor.execute("INSERT INTO report (title,sql,description) " 114 115 "VALUES (%s,%s,%s)", (title, sql, description)) 115 116 id = self.db.get_last_id() 117 TracRef('report', id).replace_xrefs_from_wiki(self.env, self.db, 'description', description) 116 118 self.db.commit() 117 119 req.redirect(self.env.href.report(id)) 118 120 … … 122 124 if not req.args.has_key('cancel'): 123 125 cursor = self.db.cursor () 124 126 cursor.execute("DELETE FROM report WHERE id=%s", (id,)) 127 TracRef('report', id).delete_xrefs(self.db, 'description') 125 128 self.db.commit() 126 129 req.redirect(self.env.href.report()) 127 130 else: … … 401 404 req.args.get('description', ''), 402 405 req.args.get('sql', '')) 403 406 407 if id != -1: 408 TracRef('report', id).add_backlinks(self.db, req) 409 404 410 if id != -1 or action == 'new': 405 411 self.add_link('up', self.env.href.report(), 'Available Reports') 406 412 -
trac/sync.py
20 20 # Author: Jonas Borgström <jonas@edgewall.com> 21 21 22 22 from svn import fs, util, delta, repos, core 23 from trac.Xref import TracRef 23 24 24 25 import posixpath 25 26 26 27 27 def sync( db, repos, fs_ptr, pool):28 def sync(env, db, repos, fs_ptr, pool): 28 29 """ 29 30 Update the revision and node_change tables to be in sync with 30 31 the repository. … … 56 57 cursor.execute ('INSERT INTO revision (rev, time, author, message) ' 57 58 'VALUES (%s, %s, %s, %s)', rev + offset, date, 58 59 author, message) 60 TracRef('changeset', rev + offset).replace_xrefs_from_wiki(env, db, 'content', message) 59 61 insert_change (subpool, fs_ptr, rev + offset, cursor) 60 62 core.svn_pool_clear(subpool) 61 63 … … 125 127 old_path = posixpath.join(old_path, posixpath.split(path)[1]) 126 128 action = 'A' 127 129 else: 128 self._save_change(core.svn_node_ file, 'A', path)130 self._save_change(core.svn_node_dir, 'A', path) 129 131 action = None 130 132 131 133 if action: -
trac/File.py
25 25 from trac import perm, util 26 26 from trac.Module import Module 27 27 from trac.WikiFormatter import wiki_to_html 28 from trac.Xref import TracRef 28 29 29 30 import svn.core 30 31 import svn.fs … … 261 262 262 263 self.rev = req.args.get('rev', None) 263 264 self.path = req.args.get('path', '/') 265 266 TracRef('source', self.path).add_backlinks(self.db, req) 267 264 268 if not self.rev: 265 269 rev_specified = 0 266 270 self.rev = svn.fs.youngest_rev(self.fs_ptr, self.pool) -
trac/Log.py
22 22 from trac import perm, util 23 23 from trac.Module import Module 24 24 from trac.WikiFormatter import wiki_to_oneliner 25 from trac.Xref import TracRef 25 26 26 27 import svn.core 27 28 import svn.fs … … 121 122 122 123 self.path = req.args.get('path', '/') 123 124 self.authzperm.assert_permission(self.path) 125 126 TracRef('source', self.path).add_backlinks(self.db, req) 127 124 128 if req.args.has_key('rev'): 125 129 try: 126 130 rev = int(req.args.get('rev')) -
trac/upgrades/db10.py
1 import time 2 3 sql = """ 4 -- Initial creation of the general cross-reference table 5 CREATE 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 15 CREATE INDEX xref_src_idx ON xref(src_id,src_type); 16 CREATE INDEX xref_dest_idx ON xref(dest_id,dest_type); 17 """ 18 19 def do_upgrade(env, ver, cursor): 20 cursor.execute(sql) 21 22 def 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
56 56 else: 57 57 return href_join(self.base, 'browser', path) 58 58 59 def source(self, path, rev=None): 60 return self.browser(path, rev) 61 59 62 def login(self): 60 63 return href_join(self.base, 'login') 61 64 … … 176 179 if format: 177 180 href += '?format=%s' % format 178 181 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
153 153 if match: 154 154 set_if_missing(args, 'mode', match.group(1)) 155 155 return args 156 match = re.search('^/(ticket| report)(?:/([0-9]+)/*)?', path_info)156 match = re.search('^/(ticket|bug|issue|report)(?:/([0-9]+)/*)?', path_info) 157 157 if match: 158 158 set_if_missing(args, 'mode', match.group(1)) 159 159 if match.group(2): 160 160 set_if_missing(args, 'id', match.group(2)) 161 161 return args 162 match = re.search('^/(browser| log|file)(?:(/.*))?', path_info)162 match = re.search('^/(browser|source|repos|log|file)(?:(/.*))?', path_info) 163 163 if match: 164 164 set_if_missing(args, 'mode', match.group(1)) 165 165 if match.group(2): … … 183 183 if match.group(1): 184 184 set_if_missing(args, 'id', urllib.unquote(match.group(1))) 185 185 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 186 194 return args 187 195 188 196 def populate_hdf(hdf, env, req=None): … … 208 216 'login': env.href.login(), 209 217 'logout': env.href.logout(), 210 218 'settings': env.href.settings(), 219 'xref': env.href.xref(), 220 'orphans': env.href.orphans(), 211 221 'homepage': 'http://trac.edgewall.com/' 212 222 } 213 223 -
trac/Changeset.py
24 24 from trac.Diff import get_diff_options, hdf_diff, unified_diff 25 25 from trac.Module import Module 26 26 from trac.WikiFormatter import wiki_to_html 27 from trac.Xref import TracRef 27 28 from trac import authzperm, perm 28 29 29 30 import svn.core … … 435 436 else: 436 437 self.rev = youngest_rev 437 438 439 TracRef('changeset', self.rev).add_backlinks(self.db, req) 440 438 441 self.diff_options = get_diff_options(req) 439 442 if req.args.has_key('update'): 440 443 req.redirect(self.env.href.changeset(self.rev)) … … 462 465 req.hdf['changeset.revision'] = self.rev 463 466 req.hdf['changeset.changes'] = change_info 464 467 req.hdf['changeset.href'] = self.env.href.changeset(self.rev) 465 468 466 469 if len(change_info) == 0: 467 470 raise authzperm.AuthzPermissionError() 468 471 -
trac/Wiki.py
24 24 from trac.Module import Module 25 25 from trac.util import escape, TracError, get_reporter_id 26 26 from trac.WikiFormatter import * 27 from trac.Xref import TracRef 27 28 28 29 import os 29 30 import time … … 51 52 Represents a wiki page (new or existing). 52 53 """ 53 54 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 56 58 self.name = name 57 59 self.perm = perm_ 58 60 cursor = self.db.cursor () … … 108 110 "%s,%s)", (self.name, self.version + 1, 109 111 int(time.time()), author, remote_addr, self.text, 110 112 comment, self.readonly)) 113 TracRef('wiki', self.name).replace_xrefs_from_wiki(self.env, self.db, 'content', self.text) 111 114 self.db.commit() 112 115 self.version += 1 113 116 self.old_readonly = self.readonly … … 126 129 req.hdf['wiki.page_name'] = escape(pagename) 127 130 req.hdf['wiki.current_href'] = escape(self.env.href.wiki(pagename)) 128 131 132 self.ref = TracRef('wiki', pagename) 133 self.ref.add_backlinks(self.db, req) 134 129 135 if action == 'diff': 130 136 version = int(req.args.get('version', 0)) 131 137 self._render_diff(req, pagename, version) … … 136 142 elif action == 'delete': 137 143 version = None 138 144 if req.args.has_key('delete_version'): 139 version = int(req.args ['version'])145 version = int(req.args.get('version')) 140 146 self._delete_page(req, pagename, version) 141 147 elif action == 'save': 142 148 if req.args.has_key('cancel'): … … 164 170 cursor = self.db.cursor() 165 171 cursor.execute("DELETE FROM wiki WHERE name=%s and version=%s", 166 172 (pagename, version)) 173 self.ref.delete_xrefs(self.db, 'comment:%s' % version) 167 174 self.log.info('Deleted version %d of page %s' % (version, pagename)) 168 175 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: 170 178 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) 171 184 else: # Delete a wiki page completely 172 185 cursor.execute("DELETE FROM wiki WHERE name=%s", (pagename,)) 173 186 page_deleted = 1 … … 175 188 self.db.commit() 176 189 177 190 if page_deleted: 191 self.ref.delete_xrefs(self.db) 178 192 # Delete orphaned attachments 179 193 for attachment in self.env.get_attachments(self.db, 'wiki', pagename): 180 194 self.env.delete_attachment(self.db, 'wiki', pagename, … … 233 247 def _render_editor(self, req, pagename, preview=0): 234 248 self.perm.assert_permission(perm.WIKI_MODIFY) 235 249 236 page = WikiPage(pagename, None, self.perm, self. db)250 page = WikiPage(pagename, None, self.perm, self.env, self.db) 237 251 if req.args.has_key('text'): 238 252 page.set_content(req.args.get('text')) 239 253 if preview: … … 311 325 self.add_link('alternate', '?format=txt', 'Plain Text', 312 326 'text/plain') 313 327 314 page = WikiPage(pagename, version, self.perm, self. db)328 page = WikiPage(pagename, version, self.perm, self.env, self.db) 315 329 316 330 info = { 317 331 'version': page.version, … … 331 345 def _save_page(self, req, pagename): 332 346 self.perm.assert_permission(perm.WIKI_MODIFY) 333 347 334 page = WikiPage(pagename, None, self.perm, self. db)348 page = WikiPage(pagename, None, self.perm, self.env, self.db) 335 349 if req.args.has_key('text'): 336 350 page.set_content(req.args.get('text')) 337 351 -
trac/Environment.py
23 23 # 24 24 25 25 from trac import db, db_default, Logging, Mimeview, util 26 from trac.Xref import TracRef 26 27 28 27 29 import ConfigParser 28 30 import os 29 31 import shutil … … 253 255 cursor.execute('INSERT INTO attachment VALUES(%s,%s,%s,%s,%s,%s,%s,%s)', 254 256 (type, id, filename, length, int(time.time()), 255 257 description, author, ipnr)) 258 TracRef(type, id).replace_xrefs_from_wiki(self, cnx, 'attachment:%s' % filename, description) 256 259 shutil.copyfileobj(attachment.file, fd) 257 260 self.log.info('New attachment: %s/%s/%s by %s', type, id, filename, author) 258 261 cnx.commit() … … 265 268 cursor = cnx.cursor() 266 269 cursor.execute('DELETE FROM attachment WHERE type=%s AND id=%s AND ' 267 270 'filename=%s', (type, id, filename)) 271 TracRef(type, id).delete_xrefs(cnx, 'attachment:%s' % filename) 268 272 os.unlink(path) 269 273 self.log.info('Attachment removed: %s/%s/%s', type, id, filename) 270 274 cnx.commit() … … 324 328 err = 'No upgrade module for version %i (%s.py)' % (i, upg) 325 329 raise EnvironmentError, err 326 330 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) 327 333 cursor.execute("UPDATE system SET value=%s WHERE " 328 334 "name='database_version'", (db_default.db_version)) 329 335 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 53 from trac.Module import Module 54 from trac.util import escape 55 from trac.WikiFormatter import XRefFormatter 56 57 __all__ = ['TracRef', 'rebuild_cross_references'] 58 59 60 how_much_context = 40 61 62 63 class TracRef: 64 """ 65 A TracRef encapsulate the identity of a Trac Object (changeset, 66 wiki page, ticket, ...) and can be used to manage the relationships 67 to other Trac Objects. 68 69 The cross-reference information is stored in the XREF table 70 (see trac/db_default.py). 71 72 Besides the obvious 'type' and 'id' information for the source and the 73 destination objects, there is also: 74 * facet: the location of the cross-reference within the source object. 75 * context: the wikitext surrounding the cross-reference, if any 76 * relation: the explicit nature of the relationship. 77 """ 78 79 def __init__(self, type, id): 80 self.type = type 81 self.id = id 82 if type == 'source': # TODO: oo-ify 83 self.id = id.strip('/') 84 85 def name(self): 86 if self.type == 'wiki': # TODO: oo-ify 87 # TODO: use canonical name if it doesn't follow the WikiPageNames conventions 88 return escape(self.id) 89 elif self.type == 'ticket': 90 return 'Ticket #%s' % self.id 91 elif self.type == 'changeset': 92 return 'Changeset [%s]' % self.id 93 elif self.type == 'report': 94 return 'Report {%s}' % self.id 95 else: 96 return self.type + ':' + escape(self.id) 97 98 def icon(self): 99 if self.type == 'ticket': # TODO: oo-ify 100 return 'newticket' 101 else: 102 return self.type 103 104 def href(self, env): 105 m = getattr(env.href, self.type) 106 if m: 107 return m(self.id) 108 else: 109 return env.href.wiki() 110 111 # -- used by other Modules 112 113 def add_backlinks(self, db, req): 114 req.hdf['xref_count'] = self.count_sources(db) 115 116 def replace_relation(self, db, relation, dest, facet='', context=''): 117 """ 118 Replace the related object for the given 'relation'. 119 The 'facet' and 'context' are only used to recreate the new relation. 120 Best suited for 1 to 1 relationships. 121 """ 122 print "- %s:%s --[%s]--> *:*" % (self.type, self.id, relation) 123 cursor = db.cursor() 124 cursor.execute("DELETE FROM xref " 125 "WHERE src_id = %s AND src_type = %s " 126 "AND relation = %s ", 127 (self.id, self.type, relation)) 128 self.insert_xref(db, relation, dest, facet, context) 129 130 def replace_xrefs_from_wiki(self, env, db, facet, wikitext): 131 """ 132 Remove then re-create the cross-references for the given facet. 133 """ 134 self.delete_xrefs(db, facet) 135 self._create_xrefs_from_wiki(env, db, facet, wikitext) 136 137 138 def _create_xrefs_from_wiki(self, env, db, facet, wikitext): 139 """ 140 Parse the given 'wikitext' in order to generate all the cross-reference. 141 It is assumed that self is the source object for these references. 142 """ 143 XRefFormatter(env, db, False).format(wikitext, self, facet) 144 145 def delete_xrefs(self, db, facet=None): 146 """ 147 Remove all the cross-references having this reference as a source. 148 Only delete the cross-references for the 'facet', if given. 149 """ 150 cursor = db.cursor() 151 if facet: 152 facet_clause = "AND facet = %s" 153 tuple = (self.id, self.type, facet) 154 print "- %s:%s --[*]--> *:* (in %s)" % (self.type, self.id, facet) 155 else: 156 facet_clause = "" 157 tuple = (self.id, self.type) 158 print "- %s:%s --[*]--> *:*" % (self.type, self.id) 159 cursor.execute("DELETE FROM xref " 160 "WHERE src_id = %s AND src_type = %s " + facet_clause, 161 tuple) 162 163 164 def count_sources(self, db): 165 return self._count(db, 'dest') 166 167 def count_destinations(self, db): 168 return self._count(db, 'src') 169 170 def _count(self, db, base): 171 cursor = db.cursor() 172 cursor.execute("SELECT count(*) FROM xref " 173 "WHERE <base>_id = %s AND <base>_type = %s ".replace('<base>', base), 174 (self.id, self.type)) 175 return cursor.fetchone()[0] 176 177 178 def find_sources(self, db, relation=None): 179 """ 180 Retrieve all the incoming relationships for this object. 181 If 'relation' is given, only the sources for the given relation 182 are retrieved, otherwise all relations are searched, even implicit ones. 183 """ 184 return self._find(db, 'dest', 'src', relation) 185 186 def find_destinations(self, db, relation=None): 187 """ 188 Retrieve all the outgoing relationships for this object. 189 If 'relation' is given, only the targets for the given relation 190 are retrieved, otherwise all relations are searched, even implicit ones. 191 """ 192 return self._find(db, 'src', 'dest', relation) 193 194 def _find(self, db, base, other, relation): 195 cursor = db.cursor() 196 if relation: 197 relation_clause = "AND relation = %s" 198 tuple = (self.id, self.type, relation) 199 else: 200 relation_clause = "" 201 tuple = (self.id, self.type) 202 cursor.execute(("SELECT <other>_type, <other>_id, relation, facet, context " 203 "FROM xref WHERE <base>_id = %s AND <base>_type = %s " 204 ).replace('<base>', base).replace('<other>', other) + relation_clause, 205 tuple) 206 return cursor 207 208 # -- used by the WikiFormatter.XRefFormatter: 209 210 def insert_xref(self, db, relation, dest, facet, context): 211 print "+ %s:%s --[%s]--> %s:%s (in %s %s)" % (self.type, self.id, 212 relation, 213 dest.type, dest.id, 214 facet, context) 215 cursor = db.cursor() 216 cursor.execute("INSERT INTO xref VALUES (%s,%s,%s,%s,%s,%s,%s)", 217 (self.type, self.id, relation, dest.type, dest.id, facet, context)) 218 219 def extract_context(self, text, start, end): 220 start_ellipsis = end_ellipsis = '...' 221 start = start - how_much_context 222 if start < 0: 223 start = 0 224 start_ellipsis = '' 225 end = end + how_much_context 226 if end > len(text): 227 end = len(text) 228 end_ellipsis = '' 229 return start_ellipsis + text[start:end] + end_ellipsis 230 231 232 233 234 235 def rebuild_cross_references(env, db, do_changesets=True): 236 """ 237 Rebuild all cross-references in the given environment. 238 239 As an option, the rebuilding of the references found in 240 changesets can be skipped, as this is done by a 'resync' 241 operation, which is what is advised to do in trac-admin. 242 """ 243 cursor = db.cursor() 244 xreffmt = XRefFormatter(env, db, False) 245 246 def add_xrefs(src, facet, text): 247 xreffmt.format(text, src, facet) 248 249 # -- wiki objects 250 cursor.execute("SELECT name, text, comment, version FROM wiki " 251 "ORDER BY name, version DESC") 252 previous_page = None 253 src = None 254 for name, text, comment, version in cursor: 255 if name != previous_page: 256 previous_page = name 257 src = TracRef('wiki', name) 258 src.delete_xrefs(db) 259 add_xrefs(src, 'content', text) 260 add_xrefs(src, 'comment:%d' % version, comment) 261 262 # -- ticket objects 263 # -- -- description 264 cursor.execute("SELECT id, description FROM ticket") 265 for id, description in cursor: 266 src = TracRef('ticket', id) 267 src.delete_xrefs(db) 268 add_xrefs(src, 'description', description) 269 # -- -- comments 270 comment_cursor = db.cursor() 271 comment_cursor.execute("SELECT oldvalue, newvalue FROM ticket_change " 272 "WHERE ticket = %s AND field = 'comment'", 273 (id)) 274 for n, value in comment_cursor: 275 add_xrefs(src, 'comment:%s' % n, value) 276 # -- -- custom fields 277 # (uncomment when the custom fields will support wiki formatting) 278 # ... "SELECT name, value FROM ticket_custom" 279 280 # -- changeset objects 281 if do_changesets: 282 cursor.execute("SELECT rev, message FROM revision") 283 for rev, message in cursor: 284 src = TracRef('changeset', rev) 285 src.delete_xrefs(db) 286 add_xrefs(src, 'content', message) 287 288 # -- report objects 289 cursor.execute("SELECT id, description FROM report") 290 for id, description in cursor: 291 src = TracRef('report', id) 292 src.delete_xrefs(db) 293 add_xrefs(src, 'description', description) 294 295 # -- milestone objects 296 cursor.execute("SELECT name, description FROM milestone") 297 for name, description in cursor: 298 src = TracRef('milestone', name) 299 src.delete_xrefs(db) 300 add_xrefs(src, 'description', description) 301 302 # -- attachment object 303 cursor.execute("SELECT type, id, description, filename FROM attachment") 304 for type, id, description, filename in cursor: 305 add_xrefs(TracRef(type, id), 'attachment:%s' % filename, description) 306 307 db.commit() 308 309 310 311 312 def find_orphaned_objects(db): 313 # Most interesting 'orphans' order: wikis, tickets, milestones then reports and changesets 314 queries = [ 315 "SELECT DISTINCT(name), 'wiki' FROM wiki " 316 "WHERE name NOT IN (SELECT DISTINCT(dest_id) FROM xref WHERE dest_type = 'wiki') " 317 , 318 "SELECT id, 'ticket' FROM ticket " 319 "WHERE id NOT IN (SELECT DISTINCT(dest_id) FROM xref WHERE dest_type = 'ticket') " 320 , 321 "SELECT name, 'milestone' FROM milestone " 322 "WHERE name NOT IN (SELECT DISTINCT(dest_id) FROM xref WHERE dest_type = 'milestone') " 323 , 324 "SELECT id, 'report' FROM report " 325 "WHERE id NOT IN (SELECT DISTINCT(dest_id) FROM xref WHERE dest_type = 'report') " 326 , 327 "SELECT rev, 'changeset' FROM revision " 328 "WHERE rev NOT IN (SELECT DISTINCT(dest_id) FROM xref WHERE dest_type = 'changeset') " 329 , 330 # Not sure about this one: it works, but produces a huge list (good for testing, though :) 331 # "SELECT DISTINCT(name), 'source' FROM node_change " 332 # "WHERE name NOT IN (SELECT DISTINCT(dest_id) FROM xref WHERE dest_type = 'source') " 333 ] 334 cursor = db.cursor() 335 cursor.execute(" UNION ALL ".join(queries)) 336 return cursor 337 338 339 340 341 class XrefModule(Module): 342 template_name = 'xref.cs' 343 344 def render(self, req): 345 mode = req.args.get('mode', 'xref') 346 req.hdf['xref.mode'] = mode 347 if mode == 'orphans': 348 self._orphans(req) 349 self.template_name = 'orphans.cs' 350 else: 351 direction = req.args.get('direction','back') 352 if direction == 'forward': 353 req.hdf['xref.direction.name'] = 'Forward Link' 354 base = self._base(req) 355 self._references(req, base.find_destinations(self.db)) 356 else: # direction == back 357 req.hdf['xref.direction.back'] = 1 358 req.hdf['xref.direction.name'] = 'Backlink' 359 base = self._base(req) 360 self._references(req, base.find_sources(self.db)) 361 362 def _base(self, req): 363 type = req.args.get('type', 'wiki') 364 id = req.args.get('id', 'WikiStart') 365 base = TracRef(type, id) 366 req.hdf['title'] = req.hdf['xref.direction.name'] + ' for ' + base.name() 367 req.hdf['xref.base.type'] = type 368 req.hdf['xref.base.id'] = escape(id) 369 req.hdf['xref.base.name'] = base.name() 370 req.hdf['xref.base.icon'] = base.icon() 371 req.hdf['xref.base.href'] = base.href(self.env) 372 req.hdf['xref.current_href'] = escape(self.env.href.xref(type, id)) 373 return base 374 375 def _references(self, req, refs): 376 links = [] 377 relations = [] 378 for type, id, relation, facet, context in refs: 379 other_ref = TracRef(type, id) 380 dict = {'type' : type, 381 'id' : id, 382 'name' : other_ref.name(), 383 'icon' : other_ref.icon(), 384 'href' : other_ref.href(self.env), 385 'relation' : relation, 386 'facet' : facet, 387 'context' : context} 388 if relation: 389 relations.append(dict) 390 else: 391 links.append(dict) 392 req.hdf['xref.links'] = links 393 req.hdf['xref.relations'] = relations 394 395 def _orphans(self, req): 396 req.hdf['title'] = 'Orphaned objects' 397 orphans = [] 398 for id, type in find_orphaned_objects(self.db): 399 ref = TracRef(type, id) 400 obj = {'type' : type, 401 'id' : id, 402 'name' : ref.name(), 403 'icon' : ref.icon(), 404 'href' : ref.href(self.env)} 405 orphans.append(obj) 406 req.hdf['orphans'] = orphans 407 -
trac/Ticket.py
23 23 from trac.Module import Module 24 24 from trac.WikiFormatter import wiki_to_html 25 25 from trac.Notify import TicketNotifyEmail 26 from trac.Xref import TracRef 26 27 27 28 import time 28 29 import string … … 37 38 'reporter', 'owner', 'cc', 'url', 'version', 'status', 38 39 'resolution', 'keywords', 'summary', 'description', 39 40 'changetime'] 41 field_xrefs = { 'description' : ('implicit',), 42 'summary' : ('implicit',), 43 'milestone' : ('1 to 1', 'milestone'), 44 'component' : ('1 to 1', 'wiki') } 40 45 41 46 def __init__(self, *args): 42 47 UserDict.__init__(self) 43 48 self._old = {} 49 self.ref = TracRef('ticket', -1) 44 50 if len(args) == 2: 45 51 self._fetch_ticket(*args) 46 52 … … 65 71 raise util.TracError('Ticket %d does not exist.' % id, 66 72 'Invalid Ticket Number') 67 73 68 self['id'] = id74 self['id'] = self.ref.id = id 69 75 for i in range(len(Ticket.std_fields)): 70 76 self[Ticket.std_fields[i]] = row[i] or '' 71 77 … … 91 97 if not dict.has_key(name): 92 98 self[name] = '0' 93 99 94 def insert(self, db):100 def insert(self, env, db): 95 101 """Add ticket to database""" 96 102 assert not self.has_key('id') 103 assert self.ref.id == -1 97 104 98 105 # Add a timestamp 99 106 now = int(time.time()) … … 107 114 % (','.join(std_fields), 108 115 ','.join(['%s'] * len(std_fields))), 109 116 map(lambda n, self=self: self[n], std_fields)) 110 id = db.get_last_id()117 self['id'] = self.ref.id = db.get_last_id() 111 118 119 for name in Ticket.field_xrefs.keys(): 120 self.xref_field(env, db, name, self[name], 121 time.strftime('%c', time.localtime(now))) 122 112 123 custom_fields = filter(lambda n: n[:7] == 'custom_', self.keys()) 113 124 for name in custom_fields: 114 125 cursor.execute("INSERT INTO ticket_custom(ticket,name,value) " 115 "VALUES(%s,%s,%s)", (id, name[7:], self[name])) 126 "VALUES(%s,%s,%s)", (self.ref.id, name[7:], self[name])) 127 # TODO: support xrefs in custom fields too... 116 128 db.commit() 117 self['id'] = id118 129 self._forget_changes() 119 return id130 return self.ref.id 120 131 121 def save_changes(self, db, author, comment, when = 0):132 def save_changes(self, env, db, author, comment, when = 0): 122 133 """Store ticket changes in the database. 123 134 The ticket must already exist in the database.""" 124 135 assert self.has_key('id') … … 161 172 fname = name 162 173 cursor.execute("UPDATE ticket SET %s=%s WHERE id=%s", 163 174 (fname, self[name], id)) 175 self.xref_field(env, db, fname, self[name], 176 time.strftime('%c', time.localtime(when))) 164 177 cursor.execute("INSERT INTO ticket_change " 165 178 "(ticket,time,author,field,oldvalue,newvalue) " 166 179 "VALUES (%s, %s, %s, %s, %s, %s)", 167 180 (id, when, author, fname, self._old[name], 168 181 self[name])) 169 182 if comment: 183 cursor.execute("SELECT count(*) FROM ticket_change " 184 "WHERE ticket = %s", # AND oldvalue LIKE '%%%s.' parent (threads) 185 (id)) 186 n = cursor.fetchone()[0] + 1 170 187 cursor.execute("INSERT INTO ticket_change " 171 188 "(ticket,time,author,field,oldvalue,newvalue) " 172 "VALUES (%s,%s,%s,'comment','',%s)", 173 (id, when, author, comment)) 189 "VALUES (%s,%s,%s,'comment',%s,%s)", 190 (id, when, author, n, comment)) 191 self.ref.replace_xrefs_from_wiki(env, db, 'comment:%d' % n, comment) 174 192 175 193 cursor.execute("UPDATE ticket SET changetime=%s WHERE id=%s", 176 194 (when, id)) … … 208 226 log.append((int(row[0]), row[1], row[2], row[3] or '', row[4] or '')) 209 227 return log 210 228 229 def xref_field(self, env, db, name, value, context=''): 230 """ 231 Create cross-references and relationships for this ticket. 232 """ 233 assert self.ref.id != -1 234 xref_kind = Ticket.field_xrefs[name] 235 if xref_kind: 236 if xref_kind[0] == 'implicit': 237 self.ref.replace_xrefs_from_wiki(env, db, name, self[name]) 238 elif xref_kind[0] == '1 to 1': 239 self.ref.replace_relation(db, 'has-%s' % name, TracRef(xref_kind[1], value), 240 'field', context) 211 241 242 243 212 244 def get_custom_fields(env): 213 245 cfg = env.get_config_items('ticket-custom') 214 246 if not cfg: … … 296 328 owner = cursor.fetchone()[0] 297 329 ticket['owner'] = owner 298 330 299 tktid = ticket.insert(self. db)331 tktid = ticket.insert(self.env, self.db) 300 332 301 333 # Notify 302 334 try: … … 399 431 ticket.populate(req.args) 400 432 401 433 now = int(time.time()) 402 ticket.save_changes(self.db, req.args.get('author', req.authname), 434 ticket.save_changes(self.env, self.db, 435 req.args.get('author', req.authname), 403 436 req.args.get('comment'), when=now) 404 437 405 438 try: … … 511 544 ticket = Ticket(self.db, id) 512 545 reporter_id = util.get_reporter_id(req) 513 546 547 TracRef('ticket', id).add_backlinks(self.db, req) 548 514 549 if preview: 515 550 # Use user supplied values 516 551 for field in Ticket.std_fields: -
trac/Browser.py
22 22 from trac import perm, util 23 23 from trac.Module import Module 24 24 from trac.WikiFormatter import wiki_to_oneliner 25 from trac.Xref import TracRef 25 26 26 27 import svn.core 27 28 import svn.fs … … 165 166 desc = req.args.has_key('desc') 166 167 167 168 self.authzperm.assert_permission (path) 168 169 170 TracRef('source', path).add_backlinks(self.db, req) 171 169 172 if not rev: 170 173 rev_specified = 0 171 174 rev = svn.fs.youngest_rev(self.fs_ptr, self.pool) -
trac/WikiFormatter.py
27 27 28 28 import util 29 29 30 30 31 __all__ = ['Formatter', 'OneLinerFormatter', 'wiki_to_html', 'wiki_to_oneliner'] 31 32 32 33 … … 51 52 52 53 _open_tags = [] 53 54 env = None 55 xref = None 54 56 absurls = 0 55 57 56 58 def __init__(self, env, db, absurls=0): … … 65 67 # Check for preceding escape character '!' 66 68 if match[0] == '!': 67 69 return match[1:] 70 if self.xref: # remember the context 71 self.context = self.xref.extract_context(self.text, fullmatch.start(), fullmatch.end()) 68 72 return getattr(self, '_' + itype + '_formatter')(match, fullmatch) 69 73 70 74 def tag_open_p(self, tag): … … 162 166 else: 163 167 return '<a href="%s">%s</a>' % (url, text) 164 168 169 def make_xref(self,type,id): 170 """ 171 Create a new cross-reference for the given destination type and id. 172 All the source information (TracRef, facet and context) is 173 already known at this point. 174 """ 175 if self.xref: 176 from trac.Xref import TracRef 177 self.xref.insert_xref(self.db, '', TracRef(type, id), self.facet, self.context) 178 165 179 def _make_wiki_link(self, page, text): 166 180 anchor = '' 167 181 if page.find('#') != -1: 168 182 anchor = page[page.find('#'):] 169 183 page = page[:page.find('#')] 184 self.make_xref('wiki', page) 170 185 if not self.env._wiki_pages.has_key(page): 171 186 return '<a class="missing wiki" href="%s" rel="nofollow">%s?</a>' \ 172 187 % (self._href.wiki(page) + anchor, text) … … 175 190 % (self._href.wiki(page) + anchor, text) 176 191 177 192 def _make_changeset_link(self, rev, text): 193 self.make_xref('changeset', rev) 178 194 cursor = self.db.cursor() 179 195 cursor.execute('SELECT message FROM revision WHERE rev=%s', (rev,)) 180 196 row = cursor.fetchone() … … 187 203 % (self._href.changeset(rev), text) 188 204 189 205 def _make_ticket_link(self, id, text): 206 self.make_xref('ticket', id) 190 207 cursor = self.db.cursor() 191 208 cursor.execute("SELECT summary,status FROM ticket WHERE id=%s", (id,)) 192 209 row = cursor.fetchone() … … 202 219 return '<a class="missing ticket" href="%s" rel="nofollow">%s</a>' \ 203 220 % (self._href.ticket(id), text) 204 221 _make_bug_link = _make_ticket_link # alias 222 _make_issue_link = _make_ticket_link # alias 205 223 206 224 def _make_milestone_link(self, name, text): 225 self.make_xref('milestone', name) 207 226 return '<a class="milestone" href="%s">%s</a>' \ 208 227 % (self._href.milestone(name), text) 209 228 210 229 def _make_report_link(self, id, text): 230 self.make_xref('report', id) 211 231 return '<a class="report" href="%s">%s</a>' \ 212 232 % (self._href.report(id), text) 213 233 … … 221 241 if match: 222 242 path = match.group(1) 223 243 rev = match.group(2) 244 self.make_xref('source', path) 224 245 if rev: 225 246 return '<a class="source" href="%s">%s</a>' \ 226 247 % (self._href.browser(path, rev), text) … … 231 252 _make_repos_link = _make_source_link # alias 232 253 233 254 255 class XRefFormatter(CommonFormatter): 256 """ 257 A special version of the wiki formatter that only cares about Trac objects. 258 This version is used for generating cross-references (see Xref.py). 259 """ 260 261 _rules = CommonFormatter._rules 262 263 _compiled_rules = re.compile('(?:' + string.join(_rules, '|') + ')') 264 265 def format(self, text, xref, facet): 266 if not text: 267 return 268 self.xref = xref # remember the source's TracRef 269 self.facet = facet # remember the source's facet 270 self._open_tags = [] 271 272 self.in_code_block = 0 273 274 rules = self._compiled_rules 275 276 for line in text.splitlines(): 277 # Handle code block 278 if self.in_code_block or line.strip() == '{{{': 279 self.handle_code_block(line) 280 continue 281 # Handle Horizontal ruler 282 elif line[0:4] == '----': 283 continue 284 # Handle new paragraph 285 elif line == '': 286 continue 287 288 self.text = util.escape(line) 289 # Throw a bunch of regexps on the problem 290 result = re.sub(rules, self.replace, self.text) 291 292 def handle_code_block(self, line): 293 if line.strip() == '{{{': 294 self.in_code_block += 1 295 elif line.strip() == '}}}': 296 self.in_code_block -= 1 297 298 234 299 class OneLinerFormatter(CommonFormatter): 235 300 """ 236 301 A special version of the wiki formatter that only implement a … … 251 316 252 317 rules = self._compiled_rules 253 318 254 result = re.sub(rules, self.replace, util.escape(text.strip())) 319 text = util.escape(text.strip()) 320 result = re.sub(rules, self.replace, text) 255 321 # Close all open 'one line'-tags 256 322 result += self.close_tag(None) 257 323 out.write(result) … … 630 696 return out.getvalue() 631 697 632 698 633 def wiki_to_oneliner(wikitext, env, db, absurls=0):699 def wiki_to_oneliner(wikitext, env, db, absurls=0): 634 700 out = StringIO.StringIO() 635 701 OneLinerFormatter(env, db, absurls).format(wikitext, out) 636 702 return out.getvalue() -
templates/report.cs
4 4 5 5 <div id="ctxtnav" class="nav"> 6 6 <h2>Report Navigation</h2> 7 <ul> 8 <?cs if report.edit_href || report.copy_href || report.delete_href ?> 7 <ul><?cs 8 call:backlinks("report", report.id) ?><?cs 9 if report.edit_href || report.copy_href || report.delete_href ?> 9 10 <li><b>This report:</b> 10 <ul> 11 <?cs if report.edit_href 12 ?><li <?cs if !report.delete_href && !report.copy_href ?>class="last"<?cs /if 13 ?>><a href="<?cs var:report.edit_href ?>">Edit</a></li><?cs 14 /if ?><?cs 15 if report.copy_href ?><li <?cs if !report.delete_href ?>class="last"<?cs /if 16 ?>><a href="<?cs var:report.copy_href ?>">Copy</a></li><?cs /if ?><?cs 17 if report.delete_href ?><li class="last"><a href="<?cs var:report.delete_href ?>">Delete</a></li><?cs /if ?></ul></li> 18 <?cs /if ?> 19 <?cs if:report.create_href ?> 20 <li><a href="<?cs var:report.create_href ?>">New Report</a></li> 21 <?cs /if ?> 11 <ul><?cs 12 if report.edit_href ?> 13 <li <?cs if !report.delete_href && !report.copy_href ?>class="last"<?cs /if ?>> 14 <a href="<?cs var:report.edit_href ?>">Edit</a> 15 </li><?cs 16 /if ?><?cs 17 if report.copy_href ?><li <?cs if !report.delete_href ?>class="last"<?cs /if ?>> 18 <a href="<?cs var:report.copy_href ?>">Copy</a> 19 </li><?cs 20 /if ?><?cs 21 if report.delete_href ?> 22 <li class="last"><a href="<?cs var:report.delete_href ?>">Delete</a></li><?cs 23 /if ?> 24 </ul> 25 </li><?cs 26 /if ?><?cs 27 if:report.create_href ?> 28 <li><a href="<?cs var:report.create_href ?>">New Report</a></li><?cs 29 /if ?> 22 30 <li class="last"><a href="<?cs var:$trac.href.query ?>">Custom Query</a></li> 23 31 </ul> 24 32 </div> -
templates/file.cs
3 3 <?cs include "macros.cs"?> 4 4 5 5 <div id="ctxtnav" class="nav"> 6 <?cs if:args.mode != 'attachment' && trac.acl.LOG_VIEW ?><ul> 7 <li class="last"><a href="<?cs var:file.logurl ?>">Revision Log</a></li> 8 </ul><?cs /if ?> 6 <ul><?cs 7 call:backlinks("source", file.path) ?><?cs 8 if:args.mode != 'attachment' && trac.acl.LOG_VIEW ?> 9 <li class="last"><a href="<?cs var:file.logurl ?>">Revision Log</a></li><?cs 10 /if ?> 11 </ul> 9 12 </div> 10 13 11 14 <div id="content" class="file"> -
templates/log.cs
3 3 <?cs include "macros.cs"?> 4 4 5 5 <div id="ctxtnav" class="nav"> 6 <ul> 6 <ul><?cs 7 call:backlinks("source", log.path) ?> 7 8 <li class="last"><a href="<?cs 8 9 var:log.items.0.file_href ?>">View Latest Revision</a></li> 9 10 </ul> -
templates/ticket.cs
5 5 <div id="ctxtnav" class="nav"> 6 6 <h2>Ticket Navigation</h2> 7 7 <ul><?cs 8 call:backlinks("ticket", ticket.id) ?><?cs 8 9 if:len(links.prev) ?> 9 10 <li class="first<?cs if:!len(links.up) && !len(links.next) ?> last<?cs /if ?>"> 10 11 ← <a href="<?cs var:links.prev.0.href ?>" title="<?cs -
templates/browser.cs
3 3 <?cs include "macros.cs"?> 4 4 5 5 <div id="ctxtnav" class="nav"> 6 <ul> 6 <ul><?cs 7 call:backlinks("source", browser.path) ?> 7 8 <li class="last"><a href="<?cs var:browser.log_href ?>">Revision Log</a></li> 8 9 </ul> 9 10 </div> -
templates/macros.cs
176 176 </div><?cs 177 177 /each ?><?cs 178 178 /def ?> 179 180 <?cs def:backlinks(type,id) ?><?cs 181 if:$xref_count == #0 ?><?cs 182 elif:$xref_count == #1 ?> 183 <li><a href="<?cs var:trac.href.xref ?>/<?cs var:type ?>/<?cs var:id ?>" 184 title="One backlink">Backlink</a></li><?cs 185 else ?> 186 <li><a href="<?cs var:trac.href.xref ?>/<?cs var:type ?>/<?cs var:id ?>" 187 title="<?cs var:xref_count ?> backlinks">Backlinks</a></li><?cs 188 /if ?><?cs 189 /def ?> 190 -
templates/milestone.cs
2 2 <?cs include:"header.cs"?> 3 3 <?cs include:"macros.cs"?> 4 4 5 <div id="ctxtnav" class="nav"></div> 5 <div id="ctxtnav" class="nav"> 6 <h2>Milestone Navigation</h2> 7 <ul><?cs 8 call:backlinks("milestone", milestone.name) ?> 9 </ul> 10 </div> 6 11 7 12 <div id="content" class="milestone"> 8 13 <?cs if:milestone.mode == "new" ?> -
templates/changeset.cs
5 5 6 6 <div id="ctxtnav" class="nav"> 7 7 <h2>Changeset Navigation</h2> 8 <ul><?cs 8 <ul><?cs 9 call:backlinks("changeset", changeset.revision) ?><?cs 9 10 if:len(links.prev) ?> 10 11 <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>"> 11 12 <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs -
templates/wiki.cs
4 4 5 5 <div id="ctxtnav" class="nav"> 6 6 <h2>Wiki Navigation</h2> 7 <ul> 7 <ul><?cs 8 call:backlinks("wiki", wiki.page_name) ?> 8 9 <li><a href="<?cs var:$trac.href.wiki ?>">Start Page</a></li> 9 10 <li><a href="<?cs var:$trac.href.wiki ?>/TitleIndex">Title Index</a></li> 10 <li><a href="<?cs var:$trac.href.wiki ?>/RecentChanges">Recent Changes</a></li> 11 <?csif:wiki.history_href ?>12 <li class="last"><a href="<?cs var:wiki.history_href ?>">Page History</a></li> 13 <?cselse ?>14 <li class="last">Page History</li> 15 <?cs/if ?>11 <li><a href="<?cs var:$trac.href.wiki ?>/RecentChanges">Recent Changes</a></li><?cs 12 if:wiki.history_href ?> 13 <li class="last"><a href="<?cs var:wiki.history_href ?>">Page History</a></li><?cs 14 else ?> 15 <li class="last">Page History</li><?cs 16 /if ?> 16 17 </ul> 17 18 <hr /> 18 19 </div> -
templates/xref.cs
1 <?cs set:html.stylesheet = 'css/timeline.css' ?> 2 <?cs include "header.cs" ?> 3 <?cs include "macros.cs" ?> 4 5 <div id="ctxtnav" class="nav"> 6 <h2>Xref Navigation</h2> 7 <ul><?cs 8 if:xref.direction.back ?> 9 <li><a href="<?cs var:$trac.current_href ?>?direction=forward" 10 title="Show a summary of Trac Objects referenced by <?cs var:base.name ?>"> 11 Forward Links</a> 12 </li><?cs 13 else ?> 14 <li><a href="<?cs var:$trac.current_href ?>?direction=back" 15 title="Show the Trac Objects referencing <?cs var:base.name ?>" 16 Backward Links</a> 17 </li><?cs 18 /if ?> 19 <li><a href="<?cs var:$trac.href.orphans ?>" 20 title="Show Trac Objects which are not referenced"> 21 Orphaned Objects</a> 22 </li> 23 </ul> 24 <hr /> 25 </div> 26 <div id="content" class="wiki"> 27 28 <?cs def:anchor(xref) ?> 29 <a href="<?cs var:xref.href ?>"><em><?cs var:xref.name ?></em></a><?cs 30 /def ?> 31 32 <?cs set:nlinks = len(xref.links) ?> 33 <?cs set:nrelations = len(xref.relations) ?> 34 35 <?cs if:$nlinks + $nrelations == #0 ?> 36 <h1>No <?cs var:xref.direction.name ?>s for <?cs call:anchor(xref.base) ?></h1><?cs 37 elif: $nlinks + $nrelations == #1 ?> 38 <h1>One <?cs var:xref.direction.name ?> for <?cs call:anchor(xref.base) ?></h1><?cs 39 else ?> 40 <h1><?cs var:xref.count ?> <?cs var:xref.direction.name ?>s for <?cs call:anchor(xref.base) ?></h1><?cs 41 /if ?> 42 43 <?cs if:$nrelations > #0 ?> 44 <h2>Relationships of <?cs var:xref.base.name ?></h2> 45 <dl><?cs 46 each:item = xref.relations ?> 47 <dt class="<?cs var:item.icon ?>"> 48 <a href="<?cs var:item.href ?>"><?cs 49 if:xref.direction.back ?> 50 <em><?cs var:item.name ?></em> 51 <strong><?cs var:item.relation ?></strong> 52 <?cs var:xref.base.name ?><?cs 53 else ?> 54 <?cs var:xref.base.name ?> 55 <strong><?cs var:item.relation ?></strong> 56 <em><?cs var:item.name ?></em><?cs 57 /if ?> 58 </a> 59 </dt> 60 <dd><?cs var:item.context ?></dd><?cs 61 /each ?> 62 </dl> 63 <?cs /if ?> 64 65 <?cs if:$nlinks > #0 ?> 66 <?cs if:xref.direction.back ?> 67 <h2><?cs var:xref.base.name ?> is referenced in the following Trac Objects:</h2><?cs 68 else ?> 69 <h2><?cs var:xref.base.name ?> references the following Trac Objects:</h2><?cs 70 /if ?> 71 <dl><?cs 72 set:previous_name = "" ?><?cs 73 each:item = xref.links ?><?cs 74 if item.name != previous_name ?><?cs 75 set previous_name = item.name ?> 76 <dt class="<?cs var:item.icon ?>"><?cs call:anchor(item) ?></dt><?cs 77 /if ?> 78 <dd><?cs var:item.context ?> <em>(in the <?cs var:item.facet?>)</em></dd><?cs 79 /each ?> 80 </dl> 81 <?cs /if ?> 82 83 </div> 84 85 <?cs include "footer.cs" ?> -
templates/orphans.cs
1 <?cs set:html.stylesheet = 'css/timeline.css' ?> 2 <?cs include "header.cs" ?> 3 <?cs include "macros.cs" ?> 4 5 <div id="ctxtnav" class="nav"> 6 <h2>Xref Navigation</h2> 7 <ul> 8 </ul> 9 <hr /> 10 </div> 11 <div id="content" class="wiki"> 12 13 <?cs def:anchor(xref) ?> 14 <a href="<?cs var:xref.href ?>"><em><?cs var:xref.name ?></em></a><?cs 15 /def ?> 16 17 <h1><?cs var:len(orphans) ?> Orphaned Trac Objects</h1> 18 <p>The following objects are not referenced by any other objects</p> 19 <dl><?cs 20 set:previous_type = "" ?><?cs 21 each:item = orphans ?><?cs 22 if:item.type != previous_type ?><?cs 23 set:previous_type = item.type ?> 24 <h2>Orphaned <?cs var:item.type ?> objects:</h2><?cs 25 /if ?> 26 <dt class="<?cs var:item.icon ?>"><?cs call:anchor(item) ?></dt><?cs 27 /each ?> 28 </dl> 29 30 </div> 31 32 <?cs include "footer.cs" ?>
