Ticket #7723: 7723-1-multirepos-cache-r7928.patch
| File 7723-1-multirepos-cache-r7928.patch, 35.0 KB (added by cboos, 3 years ago) |
|---|
-
trac/admin/console.py
MultiRepos: added initial support for multiple cached repositories. - introduction of a new ''repository'' command in TracAdmin - `resync` subcommand is now `repository resync` - new subcommand `changeset` for new changeset notification (the name of this subcommand is likely to change in follow-up revisions) - the `RepositoryManager` now makes a distinction between "real" repositories and mere aliases to real repositories. Only real repositories get notified of new changesets and get resynced. - a `Repository` can now report its actual base, if it's a scoped repository. This enable to identify different scoped repositories sharing the same base. Note that a scoped repository is a real repository and if its type requires it (e.g. svn), it will get cached on its own. Note also that '''a schema upgrade is needed''' (database_version = 22). The `node_change` and `revision` tables gain a new `repos` column of type text. Patch written by Remy Blank on #7723. Review, nit-picking and summary by me. diff --git a/trac/admin/console.py b/trac/admin/console.py
a b 50 50 try: 51 51 import readline 52 52 delims = readline.get_completer_delims() 53 for c in '-/ ':53 for c in '-/()': 54 54 delims = delims.replace(c, '') 55 55 readline.set_completer_delims(delims) 56 56 except ImportError: -
trac/admin/tests/console-tests.txt
diff --git a/trac/admin/tests/console-tests.txt b/trac/admin/tests/console-tests.txt
a b 5 5 6 6 Invoking trac-admin without command starts interactive mode. 7 7 8 help Show documentation 9 initenv Create and initialize a new environment 10 component add Add a new component 11 component chown Change component ownership 12 component list Show available components 13 component remove Remove/uninstall a component 14 component rename Rename a component 15 config get Get the value of the given option in "trac.ini" 16 config remove Remove the specified option from "trac.ini" 17 config set Set the value for the given option in "trac.ini" 18 deploy Extract static resources from Trac and all plugins 19 hotcopy Make a hot backup copy of an environment 20 milestone add Add milestone 21 milestone completed Set milestone complete date 22 milestone due Set milestone due date 23 milestone list Show milestones 24 milestone remove Remove milestone 25 milestone rename Rename milestone 26 permission add Add a new permission rule 27 permission list List permission rules 28 permission remove Remove a permission rule 29 priority add Add a priority value option 30 priority change Change a priority value 31 priority list Show possible ticket priorities 32 priority order Move a priority value up or down in the list 33 priority remove Remove a priority value 34 resolution add Add a resolution value option 35 resolution change Change a resolution value 36 resolution list Show possible ticket resolutions 37 resolution order Move a resolution value up or down in the list 38 resolution remove Remove a resolution value 39 resync Re-synchronize trac with the repository 40 severity add Add a severity value option 41 severity change Change a severity value 42 severity list Show possible ticket severities 43 severity order Move a severity value up or down in the list 44 severity remove Remove a severity value 45 ticket remove Remove ticket 46 ticket_type add Add a ticket type 47 ticket_type change Change a ticket type 48 ticket_type list Show possible ticket types 49 ticket_type order Move a ticket type up or down in the list 50 ticket_type remove Remove a ticket type 51 upgrade Upgrade database to current version 52 version add Add version 53 version list Show versions 54 version remove Remove version 55 version rename Rename version 56 version time Set version date 57 wiki dump Export all wiki pages to files named by title 58 wiki export Export wiki page to file or stdout 59 wiki import Import wiki page from file or stdin 60 wiki list List wiki pages 61 wiki load Import all wiki pages from directory 62 wiki remove Remove wiki page 63 wiki upgrade Upgrade default wiki pages to current version 8 help Show documentation 9 initenv Create and initialize a new environment 10 component add Add a new component 11 component chown Change component ownership 12 component list Show available components 13 component remove Remove/uninstall a component 14 component rename Rename a component 15 config get Get the value of the given option in "trac.ini" 16 config remove Remove the specified option from "trac.ini" 17 config set Set the value for the given option in "trac.ini" 18 deploy Extract static resources from Trac and all plugins 19 hotcopy Make a hot backup copy of an environment 20 milestone add Add milestone 21 milestone completed Set milestone complete date 22 milestone due Set milestone due date 23 milestone list Show milestones 24 milestone remove Remove milestone 25 milestone rename Rename milestone 26 permission add Add a new permission rule 27 permission list List permission rules 28 permission remove Remove a permission rule 29 priority add Add a priority value option 30 priority change Change a priority value 31 priority list Show possible ticket priorities 32 priority order Move a priority value up or down in the list 33 priority remove Remove a priority value 34 repository changeset Notify trac about new changesets 35 repository resync Re-synchronize trac with repositories 36 resolution add Add a resolution value option 37 resolution change Change a resolution value 38 resolution list Show possible ticket resolutions 39 resolution order Move a resolution value up or down in the list 40 resolution remove Remove a resolution value 41 severity add Add a severity value option 42 severity change Change a severity value 43 severity list Show possible ticket severities 44 severity order Move a severity value up or down in the list 45 severity remove Remove a severity value 46 ticket remove Remove ticket 47 ticket_type add Add a ticket type 48 ticket_type change Change a ticket type 49 ticket_type list Show possible ticket types 50 ticket_type order Move a ticket type up or down in the list 51 ticket_type remove Remove a ticket type 52 upgrade Upgrade database to current version 53 version add Add version 54 version list Show versions 55 version remove Remove version 56 version rename Rename version 57 version time Set version date 58 wiki dump Export all wiki pages to files named by title 59 wiki export Export wiki page to file or stdout 60 wiki import Import wiki page from file or stdin 61 wiki list List wiki pages 62 wiki load Import all wiki pages from directory 63 wiki remove Remove wiki page 64 wiki upgrade Upgrade default wiki pages to current version 64 65 ===== test_config_get ===== 65 66 Test project 66 67 ===== test_config_set ===== -
trac/db_default.py
diff --git a/trac/db_default.py b/trac/db_default.py
a b 17 17 from trac.db import Table, Column, Index 18 18 19 19 # Database version identifier. Used for automatic upgrades. 20 db_version = 2 120 db_version = 22 21 21 22 22 def __mkreports(reports): 23 23 """Utility function used to create report data in same syntax as the … … 82 82 Index(['time'])], 83 83 84 84 # Version control cache 85 Table('revision', key='rev')[ 85 Table('repository', key=('id', 'name'))[ 86 Column('id'), 87 Column('name'), 88 Column('value')], 89 Table('revision', key=('repos', 'rev'))[ 90 Column('repos'), 86 91 Column('rev'), 87 92 Column('time', type='int'), 88 93 Column('author'), 89 94 Column('message'), 90 Index(['time'])], 91 Table('node_change', key=('rev', 'path', 'change_type'))[ 95 Index(['repos', 'time'])], 96 Table('node_change', key=('repos', 'rev', 'path', 'change_type'))[ 97 Column('repos'), 92 98 Column('rev'), 93 99 Column('path'), 94 100 Column('node_type', size=1), 95 101 Column('change_type', size=1), 96 102 Column('base_path'), 97 103 Column('base_rev'), 98 Index(['re v'])],104 Index(['repos', 'rev'])], 99 105 100 106 # Ticket system 101 107 Table('ticket', key='id')[ … … 384 390 ('system', 385 391 ('name', 'value'), 386 392 (('database_version', str(db_version)), 387 ('initial_database_version', str(db_version)), 388 ('youngest_rev', ''))), 393 ('initial_database_version', str(db_version)))), 389 394 ('report', 390 395 ('author', 'title', 'query', 'description'), 391 396 __mkreports(get_reports(db)))) -
trac/versioncontrol/admin.py
diff --git a/trac/versioncontrol/admin.py b/trac/versioncontrol/admin.py
a b 15 15 16 16 from trac.admin import IAdminCommandProvider 17 17 from trac.core import * 18 from trac.util.text import print out18 from trac.util.text import printerr, printout 19 19 from trac.util.translation import _, ngettext 20 from trac.versioncontrol import RepositoryManager 20 21 21 22 22 23 class VersionControlAdmin(Component): … … 27 28 # IAdminCommandProvider methods 28 29 29 30 def get_admin_commands(self): 30 yield ('resync', '[rev]', 31 """Re-synchronize trac with the repository 31 yield ('repository changeset', '<repos> <rev> [rev] [...]', 32 'Notify trac about new changesets', 33 self._complete_repos, self._do_changeset) 34 yield ('repository resync', '<repos> [rev]', 35 """Re-synchronize trac with repositories 32 36 33 37 When [rev] is specified, only that revision is synchronized. 34 38 Otherwise, the complete revision history is synchronized. Note 35 39 that this operation can take a long time to complete. 40 41 To synchronize all repositories, specify "*" as the repository. 36 42 """, 37 None, self._do_resync)43 self._complete_repos, self._do_resync) 38 44 39 def _do_resync(self, rev=None): 40 if rev: 41 self.env.get_repository().sync_changeset(rev) 42 printout(_('%(rev)s resynced.', rev=rev)) 43 return 45 def _complete_repos(self, args): 46 if len(args) == 1: 47 rm = RepositoryManager(self.env) 48 return [reponame or '(default)' for reponame 49 in rm.get_all_repositories()] 50 51 def _do_changeset(self, reponame, *revs): 52 rm = RepositoryManager(self.env) 53 rm.notify_changesets_added(reponame, revs, None) 54 55 def _do_resync(self, reponame, rev=None): 56 rm = RepositoryManager(self.env) 57 if reponame == '*': 58 if rev is not None: 59 raise TracError(_('Cannot synchronize a single revision ' 60 'on multiple repositories')) 61 repositories = rm.get_real_repositories(None) 62 else: 63 if reponame == '(default)': 64 reponame = '' 65 repos = rm.get_repository(reponame, None) 66 if repos is None: 67 raise TracError(_("Unknown repository '%(reponame)s'", 68 reponame=reponame or '(default)')) 69 if rev is not None: 70 repos.sync_changeset(rev) 71 printout(_('%(rev)s resynced on %(reponame)s.', rev=rev, 72 reponame=repos.reponame or '(default)')) 73 return 74 repositories = [repos] 75 44 76 from trac.versioncontrol.cache import CACHE_METADATA_KEYS 45 printout(_('Resyncing repository history... '))46 77 db = self.env.get_db_cnx() 47 78 cursor = db.cursor() 48 cursor.execute("DELETE FROM revision") 49 cursor.execute("DELETE FROM node_change") 50 cursor.executemany("DELETE FROM system WHERE name=%s", 51 [(k,) for k in CACHE_METADATA_KEYS]) 52 cursor.executemany("INSERT INTO system (name, value) VALUES (%s, %s)", 53 [(k, '') for k in CACHE_METADATA_KEYS]) 54 db.commit() 55 repos = self.env.get_repository().sync(self._resync_feedback) 56 cursor.execute("SELECT count(rev) FROM revision") 57 for cnt, in cursor: 58 printout(ngettext('%(num)s revision cached.', 59 '%(num)s revisions cached.', num=cnt)) 79 for repos in sorted(repositories, key=lambda r: r.reponame): 80 reponame = repos.reponame 81 printout(_('Resyncing repository history for %(reponame)s... ', 82 reponame=reponame or '(default)')) 83 cursor.execute("DELETE FROM revision WHERE repos=%s", (reponame,)) 84 cursor.execute("DELETE FROM node_change " 85 "WHERE repos=%s", (reponame,)) 86 cursor.executemany("DELETE FROM repository " 87 "WHERE id=%s AND name=%s", 88 [(reponame, k) for k in CACHE_METADATA_KEYS]) 89 cursor.executemany("INSERT INTO repository (id, name, value) " 90 "VALUES (%s, %s, %s)", [(reponame, k, '') 91 for k in CACHE_METADATA_KEYS]) 92 db.commit() 93 repos.sync(self._resync_feedback) 94 cursor.execute("SELECT count(rev) FROM revision WHERE repos=%s", 95 (reponame,)) 96 for cnt, in cursor: 97 printout(ngettext('%(num)s revision cached.', 98 '%(num)s revisions cached.', num=cnt)) 60 99 printout(_('Done.')) 61 100 62 101 def _resync_feedback(self, rev): -
trac/versioncontrol/api.py
diff --git a/trac/versioncontrol/api.py b/trac/versioncontrol/api.py
a b 55 55 """Return a Repository instance for the given repository type and dir. 56 56 """ 57 57 58 58 59 class IRepositoryProvider(Interface): 59 60 """Provide known named instances of Repository.""" 60 61 … … 73 74 """ 74 75 75 76 77 class IRepositoryChangeListener(Interface): 78 """Listen for changes in repositories.""" 79 80 def changeset_added(self, repos, changeset): 81 """Called after a changeset has been added to a repository.""" 82 83 76 84 class RepositoryManager(Component): 77 """Component registering the supported version control systems ,85 """Component registering the supported version control systems. 78 86 79 87 It provides easy access to the configured implementation. 80 88 """ … … 83 91 84 92 connectors = ExtensionPoint(IRepositoryConnector) 85 93 providers = ExtensionPoint(IRepositoryProvider) 94 change_listeners = ExtensionPoint(IRepositoryChangeListener) 86 95 87 96 repository_type = Option('trac', 'repository_type', 'svn', 88 97 """Default repository connector type. (''since 0.10'')""") … … 184 193 # Public API methods 185 194 186 195 def get_repository(self, reponame, authname): 187 """Retrieve the appropriate Repository for the given name 196 """Retrieve the appropriate Repository for the given name. 188 197 189 198 :param reponame: the key for specifying the repository. 190 199 If no name is given, take the the default … … 195 204 """ 196 205 repoinfo = self.get_all_repositories().get(reponame, {}) 197 206 if repoinfo and 'alias' in repoinfo: 198 repoinfo = self.get_all_repositories().get(repoinfo['alias']) 207 reponame = repoinfo['alias'] 208 repoinfo = self.get_all_repositories().get(reponame) 199 209 if repoinfo: 200 210 rdir = repoinfo.get('dir') 201 211 rtype = repoinfo.get('type', self.repository_type) … … 251 261 return (reponame, self.get_repository(reponame, authname), path or '/') 252 262 253 263 def get_default_repository(self, context): 254 """Recover the appropriate trepository from the current context.264 """Recover the appropriate repository from the current context. 255 265 256 266 Lookup the closest source or changeset resource in the context 257 267 hierarchy and return the name of its associated repository. … … 273 283 else: 274 284 self._all_repositories[reponame] = info 275 285 return self._all_repositories 286 287 def get_real_repositories(self, authname): 288 """Return a list of all real repositories (i.e. excluding aliases).""" 289 repositories = set() 290 for reponame in self.get_all_repositories(): 291 try: 292 repos = self.get_repository(reponame, authname) 293 if repos is not None: 294 repositories.add(repos) 295 except TracError: 296 "Skip invalid repositories" 297 return repositories 276 298 299 def notify_changesets_added(self, reponame, revs, authname): 300 """Notify repositories and change listeners about added changesets.""" 301 self.log.debug('Notification on %s for changesets %r' 302 % (reponame, revs)) 303 304 # Notify a repository by name, and all repositories with the same 305 # base, or all repositories by base 306 repos = self.get_repository(reponame, None) 307 if repos: 308 base = repos.get_base() 309 else: 310 base = reponame 311 repositories = [each for each in self.get_real_repositories(authname) 312 if each.get_base() == base] 313 for repos in sorted(repositories, key=lambda r: r.reponame): 314 repos.sync() 315 for rev in revs: 316 try: 317 changeset = repos.get_changeset(rev) 318 except NoSuchChangeset: 319 continue 320 self.log.debug('Notifying %s for revision %s' 321 % (repos.reponame, rev)) 322 for listener in self.change_listeners: 323 listener.changeset_added(repos, changeset) 324 277 325 def shutdown(self, tid=None): 278 326 if tid: 279 327 assert tid == threading._get_ident() … … 345 393 """Close the connection to the repository.""" 346 394 raise NotImplementedError 347 395 396 def get_base(self): 397 """Return the name of the base repository for this repository. 398 399 This function returns the name of the base repository to which scoped 400 repositories belong. For non-scoped repositories, it returns the 401 repository name. 402 """ 403 return self.name 404 348 405 def clear(self, youngest_rev=None): 349 406 """Clear any data that may have been cached in instance properties. 350 407 … … 457 514 return row and row[0] or None 458 515 459 516 def get_path_history(self, path, rev=None, limit=None): 460 """Retrieve all the revisions containing this path 517 """Retrieve all the revisions containing this path. 461 518 462 519 If given, `rev` is used as a starting point (i.e. no revision 463 520 ''newer'' than `rev` should be returned). … … 636 693 return [] 637 694 638 695 def get_changes(self): 639 """Generator that produces a tuple for every change in the changeset 696 """Generator that produces a tuple for every change in the changeset. 640 697 641 698 The tuple will contain `(path, kind, change, base_path, base_rev)`, 642 699 where `change` can be one of Changeset.ADD, Changeset.COPY, -
trac/versioncontrol/cache.py
diff --git a/trac/versioncontrol/cache.py b/trac/versioncontrol/cache.py
a b 41 41 has_linear_changesets = False 42 42 43 43 def __init__(self, getdb, repos, authz, log): 44 self.repos = repos 44 45 Repository.__init__(self, repos.name, authz, log) 45 46 if callable(getdb): 46 47 self.getdb = getdb 47 48 else: 48 49 self.getdb = lambda: getdb 49 self.repos = repos50 50 51 def _set_reponame(self, value): 52 self.repos.reponame = value 53 54 reponame = property(fget=lambda self: self.repos.reponame, 55 fset=_set_reponame) 56 51 57 def close(self): 52 58 self.repos.close() 53 59 60 def get_base(self): 61 return self.repos.get_base() 62 54 63 def get_quickjump_entries(self, rev): 55 64 for category, name, path, rev in self.repos.get_quickjump_entries(rev): 56 65 yield category, name, path, rev … … 63 72 db = self.getdb() 64 73 cursor = db.cursor() 65 74 cursor.execute("SELECT rev FROM revision " 66 "WHERE time >= %s AND time < %s"75 "WHERE repos=%s AND time >= %s AND time < %s" 67 76 "ORDER BY time DESC, rev DESC", 68 (to_timestamp(start), to_timestamp(stop))) 77 (self.reponame, to_timestamp(start), 78 to_timestamp(stop))) 69 79 for rev, in cursor: 70 80 try: 71 81 if self.authz.has_permission_for_changeset(rev): … … 78 88 db = self.getdb() 79 89 cursor = db.cursor() 80 90 cursor.execute("UPDATE revision SET time=%s, author=%s, message=%s " 81 "WHERE re v=%s", (to_timestamp(cset.date),82 cset.author, cset.message,83 (str(cset.rev))))91 "WHERE repos=%s AND rev=%s", 92 (to_timestamp(cset.date), cset.author, cset.message, 93 self.reponame, str(cset.rev))) 84 94 db.commit() 85 95 86 96 def sync(self, feedback=None): 87 97 db = self.getdb() 88 98 cursor = db.cursor() 89 cursor.execute("SELECT name, value FROM system WHERE name IN (%s)" % 90 ','.join(["'%s'" % key for key in CACHE_METADATA_KEYS])) 99 cursor.execute("SELECT name, value FROM repository " 100 "WHERE id=%%s AND name IN (%s)" % 101 ','.join(['%s'] * len(CACHE_METADATA_KEYS)), 102 (self.reponame,) + CACHE_METADATA_KEYS) 91 103 metadata = {} 92 104 for name, value in cursor: 93 105 metadata[name] = value … … 103 115 "'trac-admin resync' operation is needed.")) 104 116 elif repository_dir is None: # 105 117 self.log.info('Storing initial "repository_dir": %s' % self.name) 106 cursor.execute("INSERT INTO system (name,value) VALUES (%s,%s)", 107 (CACHE_REPOSITORY_DIR, self.name,)) 118 cursor.execute("INSERT INTO repository (id,name,value) " 119 "VALUES (%s,%s,%s)", 120 (self.reponame, CACHE_REPOSITORY_DIR, self.name)) 108 121 else: # 'repository_dir' cleared by a resync 109 122 self.log.info('Resetting "repository_dir": %s' % self.name) 110 cursor.execute("UPDATE system SET value=%s WHERE name=%s", 111 (self.name, CACHE_REPOSITORY_DIR)) 112 113 db.commit() # save metadata changes made up to now 123 cursor.execute("UPDATE repository SET value=%s " 124 "WHERE id=%s AND name=%s", 125 (self.name, self.reponame, CACHE_REPOSITORY_DIR)) 114 126 115 127 # -- retrieve the youngest revision in the repository 116 128 self.repos.clear() 117 129 repos_youngest = self.repos.youngest_rev 118 130 119 131 # -- retrieve the youngest revision cached so far 120 if CACHE_YOUNGEST_REV not in metadata: 121 raise TracError(_('Missing "youngest_rev" in cache metadata')) 122 123 self.youngest = metadata[CACHE_YOUNGEST_REV] 132 self.youngest = metadata.get(CACHE_YOUNGEST_REV) 133 if self.youngest is None: 134 cursor.execute("INSERT INTO repository (id,name,value) " 135 "VALUES (%s,%s,%s)", 136 (self.reponame, CACHE_YOUNGEST_REV, '')) 137 138 db.commit() # save metadata changes made up to now 124 139 125 140 if self.youngest: 126 141 self.youngest = self.repos.normalize_rev(self.youngest) … … 156 171 return 157 172 158 173 # 0. first check if there's no (obvious) resync in progress 159 cursor.execute("SELECT rev FROM revision WHERE rev=%s", 160 (str(next_youngest),)) 174 cursor.execute("SELECT rev FROM revision " 175 "WHERE repos=%s AND rev=%s", 176 (self.reponame, str(next_youngest))) 161 177 for rev, in cursor: 162 178 # already there, but in progress, so keep ''previous'' 163 179 # notion of 'youngest' … … 182 198 cset = self.repos.get_changeset(next_youngest) 183 199 try: 184 200 cursor.execute("INSERT INTO revision " 185 " (re v,time,author,message) "186 "VALUES (%s,%s,%s,%s )",187 (s tr(next_youngest),201 " (repos,rev,time,author,message) " 202 "VALUES (%s,%s,%s,%s,%s)", 203 (self.reponame, str(next_youngest), 188 204 to_timestamp(cset.date), 189 205 cset.author, cset.message)) 190 206 except Exception, e: # *another* 1.1. resync attempt won … … 206 222 kind = kindmap[kind] 207 223 action = actionmap[action] 208 224 cursor.execute("INSERT INTO node_change " 209 " (re v,path,node_type,change_type,"210 " base_path,base_rev) "211 "VALUES (%s,%s,%s,%s,%s,%s )",212 (s tr(next_youngest),225 " (repos,rev,path,node_type," 226 " change_type,base_path,base_rev) " 227 "VALUES (%s,%s,%s,%s,%s,%s,%s)", 228 (self.reponame, str(next_youngest), 213 229 path, kind, action, bpath, brev)) 214 230 215 231 # 1.3. iterate (1.1 should always succeed now) … … 218 234 219 235 # 1.4. update 'youngest_rev' metadata 220 236 # (minimize possibility of failures at point 0.) 221 cursor.execute("UPDATE system SET value=%s WHERE name=%s", 222 (str(self.youngest), CACHE_YOUNGEST_REV)) 237 cursor.execute("UPDATE repository SET value=%s " 238 "WHERE id=%s AND name=%s", 239 (str(self.youngest), self.reponame, 240 CACHE_YOUNGEST_REV)) 223 241 db.commit() 224 242 225 243 # 1.5. provide some feedback … … 258 276 def _next_prev_rev(self, direction, rev, path=''): 259 277 db = self.getdb() 260 278 # the changeset revs are sequence of ints: 261 sql = "SELECT rev FROM node_change WHERE " + \279 sql = "SELECT rev FROM node_change WHERE repos=%s AND " + \ 262 280 db.cast('rev', 'int') + " " + direction + " %s" 263 args = [ rev]281 args = [self.reponame, rev] 264 282 265 283 if path: 266 284 path = path.lstrip('/') … … 316 334 db = self.getdb() 317 335 cursor = db.cursor() 318 336 cursor.execute("SELECT time,author,message FROM revision " 319 "WHERE rev=%s", (str(rev),)) 337 "WHERE repos=%s AND rev=%s", 338 (self.repos.reponame, str(rev))) 320 339 row = cursor.fetchone() 321 340 if row: 322 341 _date, author, message = row … … 330 349 db = self.getdb() 331 350 cursor = db.cursor() 332 351 cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 333 "FROM node_change WHERE re v=%s "334 "ORDER BY path", (s tr(self.rev),))352 "FROM node_change WHERE repos=%s AND rev=%s " 353 "ORDER BY path", (self.repos.reponame, str(self.rev))) 335 354 for path, kind, change, base_path, base_rev in cursor: 336 355 if not self.authz.has_permission(posixpath.join(self.scope, 337 356 path.strip('/'))): -
trac/versioncontrol/svn_fs.py
diff --git a/trac/versioncontrol/svn_fs.py b/trac/versioncontrol/svn_fs.py
a b 431 431 self.fs_ptr = repos.svn_repos_fs(self.repos) 432 432 433 433 uuid = fs.get_uuid(self.fs_ptr, self.pool()) 434 self.base = 'svn:%s:%s' % (uuid, _from_svn(root_path_utf8)) 434 435 name = 'svn:%s:%s' % (uuid, _from_svn(path_utf8)) 435 436 436 437 Repository.__init__(self, name, authz, log) … … 484 485 def close(self): 485 486 self.repos = self.fs_ptr = self.pool = None 486 487 488 def get_base(self): 489 return self.base 490 487 491 def _get_tags_or_branches(self, paths): 488 492 """Retrieve known branches or tags.""" 489 493 for path in self.options.get(paths, []): -
trac/versioncontrol/tests/cache.py
diff --git a/trac/versioncontrol/tests/cache.py b/trac/versioncontrol/tests/cache.py
a b 32 32 self.db = InMemoryDatabase() 33 33 self.log = logger_factory('test') 34 34 cursor = self.db.cursor() 35 cursor.execute("INSERT INTO system (name, value) VALUES (%s,%s)", 36 ('youngest_rev', '')) 35 cursor.execute("INSERT INTO repository (id, name, value) " 36 "VALUES (%s,%s,%s)", 37 ('test-repos', 'youngest_rev', '')) 37 38 38 39 def test_initial_sync_with_empty_repos(self): 39 40 t = datetime(2001, 1, 1, 1, 1, 1, 0, utc) … … 91 92 t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) 92 93 t3 = datetime(2003, 1, 1, 1, 1, 1, 0, utc) 93 94 cursor = self.db.cursor() 94 cursor.execute("INSERT INTO revision (rev,time,author,message) " 95 "VALUES (0,%s,'','')", (to_timestamp(t1),)) 96 cursor.execute("INSERT INTO revision (rev,time,author,message) " 97 "VALUES (1,%s,'joe','Import')", (to_timestamp(t2),)) 98 cursor.executemany("INSERT INTO node_change (rev,path,node_type," 99 "change_type,base_path,base_rev) " 100 "VALUES ('1',%s,%s,%s,%s,%s)", 95 cursor.execute("INSERT INTO revision (repos,rev,time,author,message) " 96 "VALUES ('test-repos',0,%s,'','')", 97 (to_timestamp(t1),)) 98 cursor.execute("INSERT INTO revision (repos,rev,time,author,message) " 99 "VALUES ('test-repos',1,%s,'joe','Import')", 100 (to_timestamp(t2),)) 101 cursor.executemany("INSERT INTO node_change (repos,rev,path," 102 "node_type,change_type,base_path,base_rev) " 103 "VALUES ('test-repos','1',%s,%s,%s,%s,%s)", 101 104 [('trunk', 'D', 'A', None, None), 102 105 ('trunk/README', 'F', 'A', None, None)]) 103 cursor.execute("UPDATE system SET value='1' WHERE name='youngest_rev'") 106 cursor.execute("UPDATE repository SET value='1' " 107 "WHERE id='test-repos' AND name='youngest_rev'") 104 108 105 109 changes = [('trunk/README', Node.FILE, Changeset.EDIT, 'trunk/README', 1)] 106 110 changeset = Mock(Changeset, 2, 'Update', 'joe', t3, … … 128 132 t1 = datetime(2001, 1, 1, 1, 1, 1, 0, utc) 129 133 t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) 130 134 cursor = self.db.cursor() 131 cursor.execute("INSERT INTO revision (rev,time,author,message) " 132 "VALUES (0,%s,'','')", (to_timestamp(t1),)) 133 cursor.execute("INSERT INTO revision (rev,time,author,message) " 134 "VALUES (1,%s,'joe','Import')", (to_timestamp(t2),)) 135 cursor.executemany("INSERT INTO node_change (rev,path,node_type," 136 "change_type,base_path,base_rev) " 137 "VALUES ('1',%s,%s,%s,%s,%s)", 135 cursor.execute("INSERT INTO revision (repos,rev,time,author,message) " 136 "VALUES ('test-repos',0,%s,'','')", 137 (to_timestamp(t1),)) 138 cursor.execute("INSERT INTO revision (repos,rev,time,author,message) " 139 "VALUES ('test-repos',1,%s,'joe','Import')", 140 (to_timestamp(t2),)) 141 cursor.executemany("INSERT INTO node_change (repos,rev,path," 142 "node_type,change_type,base_path,base_rev) " 143 "VALUES ('test-repos','1',%s,%s,%s,%s,%s)", 138 144 [('trunk', 'D', 'A', None, None), 139 145 ('trunk/README', 'F', 'A', None, None)]) 140 cursor.execute("UPDATE system SET value='1' WHERE name='youngest_rev'") 146 cursor.execute("UPDATE repository SET value='1' " 147 "WHERE id='test-repos' AND name='youngest_rev'") 141 148 142 149 repos = Mock(Repository, 'test-repos', None, self.log, 143 150 get_changeset=lambda x: None, -
trac/versioncontrol/web_ui/changeset.py
diff --git a/trac/versioncontrol/web_ui/changeset.py b/trac/versioncontrol/web_ui/changeset.py
a b 908 908 909 909 single = rev_a == rev_b 910 910 if reponame: 911 title = ngettext('Changeset in %(repo)s', 'Changesets in %(repo)s', 911 title = ngettext('Changeset in %(repo)s ', 912 'Changesets in %(repo)s ', 912 913 single and 1 or 2, repo=reponame) 913 914 else: 914 title = ngettext('Changeset ', 'Changesets', single and 1 or 2)915 title = ngettext('Changeset ', 'Changesets ', single and 1 or 2) 915 916 if single: 916 title = tag(title, tag.em(' [%s]' % rev_a))917 title = tag(title, tag.em('[%s]' % rev_a)) 917 918 else: 918 title = tag(title, tag.em(' [%s-%s]' % (rev_a, rev_b)))919 title = tag(title, tag.em('[%s-%s]' % (rev_a, rev_b))) 919 920 if field == 'title': 920 921 return title 921 922 elif field == 'summary':
