Ticket #986: trac_mysql_r2644.patch
| File trac_mysql_r2644.patch, 27.4 kB (added by trac@…, 3 years ago) |
|---|
-
trac/db_default.py
18 18 from trac.db import Table, Column, Index 19 19 20 20 # Database version identifier. Used for automatic upgrades. 21 db_version = 1 621 db_version = 17 22 22 23 23 def __mkreports(reports): 24 24 """Utility function used to create report data in same syntax as the … … 83 83 Column('author'), 84 84 Column('message'), 85 85 Index(['time'])], 86 Table('node_change', key=('rev', 'path', 'change '))[86 Table('node_change', key=('rev', 'path', 'change_type'))[ 87 87 Column('rev'), 88 88 Column('path'), 89 Column(' kind', size=1),90 Column('change ', size=1),89 Column('node_type', size=1), 90 Column('change_type', size=1), 91 91 Column('base_path'), 92 92 Column('base_rev'), 93 93 Index(['rev'])], … … 437 437 ) 438 438 439 439 default_components = ('trac.About', 'trac.attachment', 440 'trac.db.mysql_backend', 440 441 'trac.db.postgres_backend', 'trac.db.sqlite_backend', 441 442 'trac.mimeview.enscript', 'trac.mimeview.patch', 442 443 'trac.mimeview.php', 'trac.mimeview.rst', -
trac/ticket/api.py
167 167 return 168 168 db = self.env.get_db_cnx() 169 169 sql, args = query_to_sql(db, query, 'b.newvalue') 170 sql2, args2 = query_to_sql(db, query, 'summary||keywords||description||reporter||cc') 170 search_fields = ['summary', 'keywords', 'description', 'reporter', 'cc'] 171 sql_seq = [] 172 for f in search_fields: 173 subsql, subarg = query_to_sql(db, query, f) 174 sql_seq.append(subsql) 175 args += subarg 176 sql2 = ' OR '.join(sql_seq) 171 177 cursor = db.cursor() 172 cursor.execute("SELECT DISTINCT a.summary,a.description,a.reporter, "173 "a.keywords,a.id,a.time FROM ticket a " 174 "LEFT JOIN ticket_change b ON a.id = b.ticket " 175 "WHERE (b.field='comment' AND %s ) OR %s" % (sql, sql2) ,176 args + args2)178 sql3 = "SELECT DISTINCT a.summary,a.description,a.reporter, " + \ 179 "a.keywords,a.id,a.time FROM ticket a " + \ 180 "LEFT JOIN ticket_change b ON a.id = b.ticket " + \ 181 "WHERE (b.field='comment' AND %s ) OR %s" % (sql, sql2) 182 cursor.execute(sql3, args) 177 183 for summary,desc,author,keywords,tid,date in cursor: 178 184 yield (self.env.href.ticket(tid), 179 185 '#%d: %s' % (tid, util.escape(util.shorten_line(summary))), -
trac/db/tests/api.py
39 39 'host': 'localhost', 'port': 9431, 40 40 'path': '/trac'}), 41 41 _parse_db_str('postgres://john:letmein@localhost:9431/trac')) 42 def test_mysql_simple(self): 43 self.assertEqual(('mysql', {'host': 'localhost', 'path': '/trac'}), 44 _parse_db_str('mysql://localhost/trac')) 42 45 43 46 def test_mysql_with_creds(self): 47 self.assertEqual(('mysql', {'user': 'john', 'password': 'letmein', 48 'host': 'localhost', 'port': 3306, 49 'path': '/trac'}), 50 _parse_db_str('mysql://john:letmein@localhost:3306/trac')) 44 51 def suite(): 45 52 return unittest.makeSuite(ParseConnectionStringTestCase,'test') 46 53 -
trac/db/mysql_backend.py
1 # -*- coding: iso8859-1 -*- 2 # 3 # Copyright (C) 2005 Edgewall Software 4 # Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de> 5 # Copyright (C) 2005 Jeff Weiss <trac@jeffweiss.org> 6 # All rights reserved. 7 # 8 # This software is licensed as described in the file COPYING, which 9 # you should have received as part of this distribution. The terms 10 # are also available at http://trac.edgewall.com/license.html. 11 # 12 # This software consists of voluntary contributions made by many 13 # individuals. For the exact contribution history, see the revision 14 # history and logs, available at http://projects.edgewall.com/trac/. 15 # 16 # Derived from postgres_backend.py 17 # Author: Christopher Lenz <cmlenz@gmx.de> 18 # Author: Jeff Weiss <trac@jeffweiss.org> 19 20 from trac.core import * 21 from trac.db.api import IDatabaseConnector 22 from trac.db.util import ConnectionWrapper 23 24 import MySQLdb 25 26 class MySQLConnector(Component): 27 """MySQL database support. Still extremely experimental! 28 Currently only supports database urls of the following formats: 29 mysql://user:password@host/database 30 mysql://user:password@host:port/database 31 """ 32 33 implements(IDatabaseConnector) 34 35 def get_supported_schemes(self): 36 return [('mysql', 1)] 37 38 def get_connection(self, path, user=None, password=None, host=None, 39 port=None, params={}): 40 return MySQLConnection(path, user, password, host, port, params) 41 42 def init_db(self, path, user=None, password=None, host=None, port=None, 43 params={}): 44 cnx = self.get_connection(path, user, password, host, port, params) 45 cursor = cnx.cursor() 46 from trac.db_default import schema 47 for table in schema: 48 for stmt in self.to_sql(table): 49 self.env.log.debug(stmt) 50 cursor.execute(stmt) 51 cnx.commit() 52 53 def to_sql(self, table): 54 sql = ["CREATE TABLE %s (" % table.name] 55 coldefs = [] 56 textkeys = [] 57 textcols = [] 58 mytablekey = [] 59 num_key_text = 0 60 for column in table.columns: 61 ctype = column.type 62 if column.auto_increment: 63 ctype = "INT AUTO_INCREMENT" 64 # Overide the column type because otherwise it will screw 65 # up the rest of the code for indexes and constraints 66 column.type = 'int' 67 if column.name in table.key: 68 mytablekey.append(column.name) 69 if column.type == 'text': 70 num_key_text += 1 71 textkeys.append(column.name) 72 if column.type == 'text': 73 textcols.append(column.name) 74 coldefs.append(" `%s` %s" % (column.name, ctype)) 75 if len(table.key) >= 1: 76 if num_key_text == 0: 77 num_key_text = 1 78 size = 767 / num_key_text 79 keysize = "(%s)" % size 80 for key in textkeys: 81 if key in mytablekey: 82 mytablekey.remove(key) 83 sizedkey = '`'+key+'`' + keysize 84 mytablekey.append(sizedkey) 85 coldefs.append(" CONSTRAINT %s_pk PRIMARY KEY (%s)" 86 % (table.name, ','.join(mytablekey))) 87 sql.append(',\n'.join(coldefs) + '\n)') 88 yield '\n'.join(sql) 89 # This does not work because what I need to do is not add something about 90 # the index size for the text fields because MySQL apparrently can't index 91 # on the entire length of the text field 92 for index in table.indices: 93 myidxcols = set([]) 94 processedidx = set([]) 95 for col in index.columns: 96 if col in textcols: 97 myidxcols.add(col) 98 myidx = set(index.columns) - myidxcols 99 for col in myidx: 100 escaped_col = '`'+col+'`' 101 processedidx.add(escaped_col) 102 if len(myidxcols) > 0: 103 size = 767 / len(myidxcols) 104 idxsize = "(%s)" % size 105 for col in myidxcols: 106 sizedidx = '`'+col+'`'+idxsize 107 processedidx.add(sizedidx) 108 yield "CREATE INDEX %s_%s_idx ON %s (%s)" % (table.name, 109 '_'.join(index.columns), table.name, ','.join(processedidx)) 110 111 112 class MySQLConnection(ConnectionWrapper): 113 """Connection wrapper for MySQL.""" 114 115 poolable = True 116 117 def __init__(self, path, usr=None, password=None, hst=None, prt=None, 118 params={}): 119 if prt: 120 cnx = MySQLdb.connect(db=path[1:], user=usr, passwd=password, host=hst, port=prt) 121 else: 122 cnx = MySQLdb.connect(db=path[1:], user=usr, passwd=password, host=hst) 123 124 ConnectionWrapper.__init__(self, cnx) 125 126 def cast(self, column, type): 127 # Temporary hack needed for the union of selects in the search module 128 return 'CAST(%s AS %s)' % (column, type) 129 130 def like(self): 131 # Temporary hack needed for the case-insensitive string matching in the 132 # search module 133 return 'LIKE' 134 135 def get_last_id(self, cursor, table, column='id'): 136 sql = "SELECT MAX(%s) FROM %s" % (column, table) 137 cursor.execute(sql) 138 return cursor.fetchone()[0] -
trac/versioncontrol/api.py
132 132 DIRECTORY = "dir" 133 133 FILE = "file" 134 134 135 def __init__(self, path, rev, kind):136 assert kind in (Node.DIRECTORY, Node.FILE), "Unknown node kind %s" % kind135 def __init__(self, path, rev, node_type): 136 assert node_type in (Node.DIRECTORY, Node.FILE), "Unknown node node_type %s" % node_type 137 137 self.path = str(path) 138 138 self.rev = rev 139 self. kind = kind139 self.node_type = node_type 140 140 141 141 def get_content(self): 142 142 """ … … 187 187 raise NotImplementedError 188 188 last_modified = property(lambda x: x.get_last_modified()) 189 189 190 isdir = property(lambda x: x. kind== Node.DIRECTORY)191 isfile = property(lambda x: x. kind== Node.FILE)190 isdir = property(lambda x: x.node_type == Node.DIRECTORY) 191 isfile = property(lambda x: x.node_type == Node.FILE) 192 192 193 193 194 194 class Changeset(object): … … 210 210 211 211 def get_changes(self): 212 212 """ 213 Generator that produces a (path, kind, change, base_rev, base_path)213 Generator that produces a (path, node_type, change_type, base_rev, base_path) 214 214 tuple for every change in the changeset, where change can be one of 215 215 Changeset.ADD, Changeset.COPY, Changeset.DELETE, Changeset.EDIT or 216 Changeset.MOVE, and kindis one of Node.FILE or Node.DIRECTORY.216 Changeset.MOVE, and node_type is one of Node.FILE or Node.DIRECTORY. 217 217 """ 218 218 raise NotImplementedError 219 219 -
trac/versioncontrol/tests/svn_fs.py
117 117 node = self.repos.get_node('/trunk') 118 118 self.assertEqual('trunk', node.name) 119 119 self.assertEqual('/trunk', node.path) 120 self.assertEqual(Node.DIRECTORY, node. kind)120 self.assertEqual(Node.DIRECTORY, node.node_type) 121 121 self.assertEqual(14, node.rev) 122 122 self.assertEqual(1133340423L, node.last_modified) 123 123 node = self.repos.get_node('/trunk/README.txt') 124 124 self.assertEqual('README.txt', node.name) 125 125 self.assertEqual('/trunk/README.txt', node.path) 126 self.assertEqual(Node.FILE, node. kind)126 self.assertEqual(Node.FILE, node.node_type) 127 127 self.assertEqual(3, node.rev) 128 128 self.assertEqual(1112361898, node.last_modified) 129 129 … … 131 131 node = self.repos.get_node('/trunk', 1) 132 132 self.assertEqual('trunk', node.name) 133 133 self.assertEqual('/trunk', node.path) 134 self.assertEqual(Node.DIRECTORY, node. kind)134 self.assertEqual(Node.DIRECTORY, node.node_type) 135 135 self.assertEqual(1, node.rev) 136 136 self.assertEqual(1112349652, node.last_modified) 137 137 node = self.repos.get_node('/trunk/README.txt', 2) 138 138 self.assertEqual('README.txt', node.name) 139 139 self.assertEqual('/trunk/README.txt', node.path) 140 self.assertEqual(Node.FILE, node. kind)140 self.assertEqual(Node.FILE, node.node_type) 141 141 self.assertEqual(2, node.rev) 142 142 self.assertEqual(1112361138, node.last_modified) 143 143 … … 370 370 node = self.repos.get_node('/dir1') 371 371 self.assertEqual('dir1', node.name) 372 372 self.assertEqual('/dir1', node.path) 373 self.assertEqual(Node.DIRECTORY, node. kind)373 self.assertEqual(Node.DIRECTORY, node.node_type) 374 374 self.assertEqual(5, node.rev) 375 375 self.assertEqual(1112372739, node.last_modified) 376 376 node = self.repos.get_node('/README.txt') 377 377 self.assertEqual('README.txt', node.name) 378 378 self.assertEqual('/README.txt', node.path) 379 self.assertEqual(Node.FILE, node. kind)379 self.assertEqual(Node.FILE, node.node_type) 380 380 self.assertEqual(3, node.rev) 381 381 self.assertEqual(1112361898, node.last_modified) 382 382 … … 384 384 node = self.repos.get_node('/dir1', 4) 385 385 self.assertEqual('dir1', node.name) 386 386 self.assertEqual('/dir1', node.path) 387 self.assertEqual(Node.DIRECTORY, node. kind)387 self.assertEqual(Node.DIRECTORY, node.node_type) 388 388 self.assertEqual(4, node.rev) 389 389 self.assertEqual(1112370155, node.last_modified) 390 390 node = self.repos.get_node('/README.txt', 2) 391 391 self.assertEqual('README.txt', node.name) 392 392 self.assertEqual('/README.txt', node.path) 393 self.assertEqual(Node.FILE, node. kind)393 self.assertEqual(Node.FILE, node.node_type) 394 394 self.assertEqual(2, node.rev) 395 395 self.assertEqual(1112361138, node.last_modified) 396 396 -
trac/versioncontrol/tests/cache.py
66 66 self.assertEquals(('0', 41000, '', ''), cursor.fetchone()) 67 67 self.assertEquals(('1', 42000, 'joe', 'Import'), cursor.fetchone()) 68 68 self.assertEquals(None, cursor.fetchone()) 69 cursor.execute("SELECT rev,path, kind,change,base_path,base_rev "69 cursor.execute("SELECT rev,path,node_type,change_type,base_path,base_rev " 70 70 "FROM node_change") 71 71 self.assertEquals(('1', 'trunk', 'D', 'A', None, None), 72 72 cursor.fetchone()) … … 80 80 "VALUES (0,41000,'','')") 81 81 cursor.execute("INSERT INTO revision (rev,time,author,message) " 82 82 "VALUES (1,42000,'joe','Import')") 83 cursor.executemany("INSERT INTO node_change (rev,path, kind,change,"83 cursor.executemany("INSERT INTO node_change (rev,path,node_type,change_type," 84 84 "base_path,base_rev) VALUES ('1',%s,%s,%s,%s,%s)", 85 85 [('trunk', 'D', 'A', None, None), 86 86 ('trunk/README', 'F', 'A', None, None)]) … … 99 99 cursor.execute("SELECT time,author,message FROM revision WHERE rev='2'") 100 100 self.assertEquals((42042, 'joe', 'Update'), cursor.fetchone()) 101 101 self.assertEquals(None, cursor.fetchone()) 102 cursor.execute("SELECT path, kind,change,base_path,base_rev "102 cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 103 103 "FROM node_change WHERE rev='2'") 104 104 self.assertEquals(('trunk/README', 'F', 'E', 'trunk/README', '1'), 105 105 cursor.fetchone()) … … 111 111 "VALUES (0,41000,'','')") 112 112 cursor.execute("INSERT INTO revision (rev,time,author,message) " 113 113 "VALUES (1,42000,'joe','Import')") 114 cursor.executemany("INSERT INTO node_change (rev,path, kind,change,"114 cursor.executemany("INSERT INTO node_change (rev,path,node_type,change_type," 115 115 "base_path,base_rev) VALUES ('1',%s,%s,%s,%s,%s)", 116 116 [('trunk', 'D', 'A', None, None), 117 117 ('trunk/README', 'F', 'A', None, None)]) -
trac/versioncontrol/web_ui/changeset.py
181 181 182 182 edits = [] 183 183 idx = 0 184 for path, kind, change, base_path, base_rev in chgset.get_changes():185 info = {'change': change }184 for path, node_type, change_type, base_path, base_rev in chgset.get_changes(): 185 info = {'change': change_type} 186 186 if base_path: 187 187 info['path.old'] = base_path 188 188 info['rev.old'] = base_rev … … 193 193 info['rev.new'] = chgset.rev 194 194 info['browser_href.new'] = self.env.href.browser(path, 195 195 rev=chgset.rev) 196 if change in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE):197 edits.append((idx, path, kind, base_path, base_rev))196 if change_type in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE): 197 edits.append((idx, path, node_type, base_path, base_rev)) 198 198 req.hdf['changeset.changes.%d' % idx] = info 199 199 idx += 1 200 200 … … 202 202 in self.config.get('browser', 'hide_properties', 203 203 'svk:merge').split(',')] 204 204 205 for idx, path, kind, base_path, base_rev in edits:205 for idx, path, node_type, base_path, base_rev in edits: 206 206 old_node = repos.get_node(base_path or path, base_rev) 207 207 new_node = repos.get_node(path, chgset.rev) 208 208 … … 224 224 del changed_props[k] 225 225 req.hdf['changeset.changes.%d.props' % idx] = changed_props 226 226 227 if kind== Node.DIRECTORY:227 if node_type == Node.DIRECTORY: 228 228 continue 229 229 230 230 # Content changes … … 272 272 'filename=Changeset%s.diff' % req.args.get('rev')) 273 273 req.end_headers() 274 274 275 for path, kind, change, base_path, base_rev in chgset.get_changes():276 if change == Changeset.ADD:275 for path, node_type, change_type, base_path, base_rev in chgset.get_changes(): 276 if change_type == Changeset.ADD: 277 277 old_node = None 278 278 else: 279 279 old_node = repos.get_node(base_path or path, base_rev) 280 if change == Changeset.DELETE:280 if change_type == Changeset.DELETE: 281 281 new_node = None 282 282 else: 283 283 new_node = repos.get_node(path, chgset.rev) … … 285 285 # TODO: Property changes 286 286 287 287 # Content changes 288 if kind== 'dir':288 if node_type == 'dir': 289 289 continue 290 290 291 291 default_charset = self.config.get('trac', 'default_charset') … … 345 345 346 346 buf = StringIO() 347 347 zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 348 for path, kind, change, base_path, base_rev in chgset.get_changes():349 if kind == Node.FILE and change != Changeset.DELETE:348 for path, node_type, change_type, base_path, base_rev in chgset.get_changes(): 349 if node_type == Node.FILE and change_type != Changeset.DELETE: 350 350 node = repos.get_node(path, chgset.rev) 351 351 zipinfo = ZipInfo() 352 352 zipinfo.filename = node.path … … 390 390 return 391 391 authzperm = SubversionAuthorizer(self.env, req.authname) 392 392 db = self.env.get_db_cnx() 393 sql, args = query_to_sql(db, query, 'message||author') 393 query_fields = ['message', 'author'] 394 sql_seq = [] 395 args = [] 396 for f in query_fields: 397 subsql, subarg = query_to_sql(db, query, f) 398 sql_seq.append(subsql) 399 args += subarg 400 sql = ' OR '.join(sql_seq) 394 401 cursor = db.cursor() 395 402 cursor.execute("SELECT rev,time,author,message " 396 403 "FROM revision WHERE " + sql, args) -
trac/versioncontrol/cache.py
18 18 from trac.versioncontrol import Changeset, Node, Repository, Authorizer 19 19 20 20 21 _ kindmap = {'D': Node.DIRECTORY, 'F': Node.FILE}22 _ actionmap = {'A': Changeset.ADD, 'C': Changeset.COPY,21 _node_typemap = {'D': Node.DIRECTORY, 'F': Node.FILE} 22 _change_typemap = {'A': Changeset.ADD, 'C': Changeset.COPY, 23 23 'D': Changeset.DELETE, 'E': Changeset.EDIT, 24 24 'M': Changeset.MOVE} 25 25 … … 64 64 authz = self.repos.authz 65 65 self.repos.authz = Authorizer() # remove permission checking 66 66 67 kindmap = dict(zip(_kindmap.values(), _kindmap.keys()))68 actionmap = dict(zip(_actionmap.values(), _actionmap.keys()))67 node_typemap = dict(zip(_node_typemap.values(), _node_typemap.keys())) 68 change_typemap = dict(zip(_change_typemap.values(), _change_typemap.keys())) 69 69 self.log.info("Syncing with repository (%s to %s)" 70 70 % (youngest_stored, self.repos.youngest_rev)) 71 71 if youngest_stored: … … 78 78 "VALUES (%s,%s,%s,%s)", (str(current_rev), 79 79 changeset.date, changeset.author, 80 80 changeset.message)) 81 for path, kind,action,base_path,base_rev in changeset.get_changes():81 for path,node_type,change_type,base_path,base_rev in changeset.get_changes(): 82 82 self.log.debug("Caching node change in [%s]: %s" 83 % (current_rev, (path, kind, action,83 % (current_rev, (path, node_type, change_type, 84 84 base_path, base_rev))) 85 kind = kindmap[kind]86 action = actionmap[action]87 cursor.execute("INSERT INTO node_change (rev,path, kind,"88 "change ,base_path,base_rev) "85 node_type = node_typemap[node_type] 86 change_type = change_typemap[change_type] 87 cursor.execute("INSERT INTO node_change (rev,path,node_type," 88 "change_type,base_path,base_rev) " 89 89 "VALUES (%s,%s,%s,%s,%s,%s)", 90 (str(current_rev), path, kind, action,90 (str(current_rev), path, node_type, change_type, 91 91 base_path, base_rev)) 92 92 current_rev = self.repos.next_rev(current_rev) 93 93 self.db.commit() … … 141 141 142 142 def get_changes(self): 143 143 cursor = self.db.cursor() 144 cursor.execute("SELECT path, kind,change,base_path,base_rev "144 cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 145 145 "FROM node_change WHERE rev=%s " 146 146 "ORDER BY path", (self.rev,)) 147 for path, kind, change, base_path, base_rev in cursor:147 for path, node_type, change_type, base_path, base_rev in cursor: 148 148 if not self.authz.has_permission(path): 149 149 # FIXME: what about the base_path? 150 150 continue 151 kind = _kindmap[kind]152 change = _actionmap[change]153 yield path, kind, change, base_path, base_rev151 node_type = _node_typemap[node_type] 152 change_type = _change_typemap[change_type] 153 yield path, node_type, change_type, base_path, base_rev -
trac/upgrades/db17.py
1 from trac.db import Table, Column, Index, DatabaseManager 2 3 def do_upgrade(env, ver, cursor): 4 cursor.execute("CREATE TEMP TABLE node_change_old AS SELECT * FROM node_change") 5 cursor.execute("DROP TABLE node_change") 6 7 db = env.get_db_cnx() 8 node_change_table = Table('node_change', key=('rev', 'path', 'change_type'))[ 9 Column('rev'), 10 Column('path'), 11 Column('node_type', size=1), 12 Column('change_type', size=1), 13 Column('base_path'), 14 Column('base_rev'), 15 Index(['rev'])] 16 db_backend, _ = DatabaseManager(env)._get_connector() 17 for stmt in db_backend.to_sql(node_change_table): 18 cursor.execute(stmt) 19 20 cursor.execute("INSERT INTO node_change (rev,path,node_type,change_type,base_path,base_rev) " 21 "SELECT rev,path,kind,change,base_path,base_rev " 22 "FROM node_change_old") -
trac/web/session.py
181 181 # changed as to minimize the purging. 182 182 mintime = now - PURGE_AGE 183 183 self.env.log.debug('Purging old, expired, sessions.') 184 cursor.execute("DELETE FROM session WHERE authenticated=0 AND " 185 "sid IN (SELECT sid FROM session WHERE " 186 "var_name='last_visit' AND var_value < %s)", 184 # This is a temporary hack because MySQL doesn't support 185 # a delete statement with the subquery coming from the same 186 # table 187 cursor.execute("SELECT sid from session WHERE " 188 "var_name='last_visit' AND var_value < %s", 187 189 (mintime,)) 188 190 sids = cursor.fetchall() 191 if sids and len(sids) >= 1: 192 vals = [] 193 sids_args = [] 194 for t in sids: 195 vals.append(t[0]) 196 sids_args.append("sid = %s") 197 sql = "DELETE FROM session WHERE authenticated = 0 AND (%s)" % ' OR '.join(sids_args) 198 cursor.execute(sql, vals) 189 199 db.commit() -
templates/changeset.cs
70 70 </div> 71 71 </form><?cs /if ?> 72 72 73 <?cs def:node_change(item,cl, kind) ?><?cs73 <?cs def:node_change(item,cl,node_type) ?><?cs 74 74 set:ndiffs = len(item.diff) ?><?cs 75 75 set:nprops = len(item.props) ?> 76 76 <div class="<?cs var:cl ?>"></div><?cs … … 81 81 <a title="Show entry in browser" href="<?cs 82 82 var:item.browser_href.new ?>"><?cs var:item.path.new ?></a><?cs 83 83 /if ?> 84 <span class="comment">(<?cs var: kind?>)</span><?cs84 <span class="comment">(<?cs var:node_type ?>)</span><?cs 85 85 if:item.path.old && item.change == 'copy' || item.change == 'move' ?> 86 <small><em>(<?cs var: kind?> from <a href="<?cs86 <small><em>(<?cs var:node_type ?> from <a href="<?cs 87 87 var:item.browser_href.old ?>" title="Show original file (rev. <?cs 88 88 var:item.rev.old ?>)"><?cs var:item.path.old ?></a>)</em></small><?cs 89 89 /if ?><?cs
