Edgewall Software

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  
    5050        try: 
    5151            import readline 
    5252            delims = readline.get_completer_delims() 
    53             for c in '-/': 
     53            for c in '-/()': 
    5454                delims = delims.replace(c, '') 
    5555            readline.set_completer_delims(delims) 
    5656        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  
    55 
    66Invoking trac-admin without command starts interactive mode. 
    77 
    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 
     8help                  Show documentation 
     9initenv               Create and initialize a new environment 
     10component add         Add a new component 
     11component chown       Change component ownership 
     12component list        Show available components 
     13component remove      Remove/uninstall a component 
     14component rename      Rename a component 
     15config get            Get the value of the given option in "trac.ini" 
     16config remove         Remove the specified option from "trac.ini" 
     17config set            Set the value for the given option in "trac.ini" 
     18deploy                Extract static resources from Trac and all plugins 
     19hotcopy               Make a hot backup copy of an environment 
     20milestone add         Add milestone 
     21milestone completed   Set milestone complete date 
     22milestone due         Set milestone due date 
     23milestone list        Show milestones 
     24milestone remove      Remove milestone 
     25milestone rename      Rename milestone 
     26permission add        Add a new permission rule 
     27permission list       List permission rules 
     28permission remove     Remove a permission rule 
     29priority add          Add a priority value option 
     30priority change       Change a priority value 
     31priority list         Show possible ticket priorities 
     32priority order        Move a priority value up or down in the list 
     33priority remove       Remove a priority value 
     34repository changeset  Notify trac about new changesets 
     35repository resync     Re-synchronize trac with repositories 
     36resolution add        Add a resolution value option 
     37resolution change     Change a resolution value 
     38resolution list       Show possible ticket resolutions 
     39resolution order      Move a resolution value up or down in the list 
     40resolution remove     Remove a resolution value 
     41severity add          Add a severity value option 
     42severity change       Change a severity value 
     43severity list         Show possible ticket severities 
     44severity order        Move a severity value up or down in the list 
     45severity remove       Remove a severity value 
     46ticket remove         Remove ticket 
     47ticket_type add       Add a ticket type 
     48ticket_type change    Change a ticket type 
     49ticket_type list      Show possible ticket types 
     50ticket_type order     Move a ticket type up or down in the list 
     51ticket_type remove    Remove a ticket type 
     52upgrade               Upgrade database to current version 
     53version add           Add version 
     54version list          Show versions 
     55version remove        Remove version 
     56version rename        Rename version 
     57version time          Set version date 
     58wiki dump             Export all wiki pages to files named by title 
     59wiki export           Export wiki page to file or stdout 
     60wiki import           Import wiki page from file or stdin 
     61wiki list             List wiki pages 
     62wiki load             Import all wiki pages from directory 
     63wiki remove           Remove wiki page 
     64wiki upgrade          Upgrade default wiki pages to current version 
    6465===== test_config_get ===== 
    6566Test project 
    6667===== test_config_set ===== 
  • trac/db_default.py

    diff --git a/trac/db_default.py b/trac/db_default.py
    a b  
    1717from trac.db import Table, Column, Index 
    1818 
    1919# Database version identifier. Used for automatic upgrades. 
    20 db_version = 21 
     20db_version = 22 
    2121 
    2222def __mkreports(reports): 
    2323    """Utility function used to create report data in same syntax as the 
     
    8282        Index(['time'])], 
    8383 
    8484    # 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'), 
    8691        Column('rev'), 
    8792        Column('time', type='int'), 
    8893        Column('author'), 
    8994        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'), 
    9298        Column('rev'), 
    9399        Column('path'), 
    94100        Column('node_type', size=1), 
    95101        Column('change_type', size=1), 
    96102        Column('base_path'), 
    97103        Column('base_rev'), 
    98         Index(['rev'])], 
     104        Index(['repos', 'rev'])], 
    99105 
    100106    # Ticket system 
    101107    Table('ticket', key='id')[ 
     
    384390           ('system', 
    385391             ('name', 'value'), 
    386392               (('database_version', str(db_version)), 
    387                 ('initial_database_version', str(db_version)), 
    388                 ('youngest_rev', ''))), 
     393                ('initial_database_version', str(db_version)))), 
    389394           ('report', 
    390395             ('author', 'title', 'query', 'description'), 
    391396               __mkreports(get_reports(db)))) 
  • trac/versioncontrol/admin.py

    diff --git a/trac/versioncontrol/admin.py b/trac/versioncontrol/admin.py
    a b  
    1515 
    1616from trac.admin import IAdminCommandProvider 
    1717from trac.core import * 
    18 from trac.util.text import printout 
     18from trac.util.text import printerr, printout 
    1919from trac.util.translation import _, ngettext 
     20from trac.versioncontrol import RepositoryManager 
    2021 
    2122 
    2223class VersionControlAdmin(Component): 
     
    2728    # IAdminCommandProvider methods 
    2829     
    2930    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 
    3236                
    3337               When [rev] is specified, only that revision is synchronized. 
    3438               Otherwise, the complete revision history is synchronized. Note 
    3539               that this operation can take a long time to complete. 
     40                
     41               To synchronize all repositories, specify "*" as the repository. 
    3642               """, 
    37                None, self._do_resync) 
     43               self._complete_repos, self._do_resync) 
    3844     
    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         
    4476        from trac.versioncontrol.cache import CACHE_METADATA_KEYS 
    45         printout(_('Resyncing repository history... ')) 
    4677        db = self.env.get_db_cnx() 
    4778        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)) 
    6099        printout(_('Done.')) 
    61100 
    62101    def _resync_feedback(self, rev): 
  • trac/versioncontrol/api.py

    diff --git a/trac/versioncontrol/api.py b/trac/versioncontrol/api.py
    a b  
    5555        """Return a Repository instance for the given repository type and dir. 
    5656        """ 
    5757 
     58 
    5859class IRepositoryProvider(Interface): 
    5960    """Provide known named instances of Repository.""" 
    6061 
     
    7374        """ 
    7475 
    7576 
     77class 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 
    7684class RepositoryManager(Component): 
    77     """Component registering the supported version control systems, 
     85    """Component registering the supported version control systems. 
    7886 
    7987    It provides easy access to the configured implementation. 
    8088    """ 
     
    8391 
    8492    connectors = ExtensionPoint(IRepositoryConnector) 
    8593    providers = ExtensionPoint(IRepositoryProvider) 
     94    change_listeners = ExtensionPoint(IRepositoryChangeListener) 
    8695 
    8796    repository_type = Option('trac', 'repository_type', 'svn', 
    8897        """Default repository connector type. (''since 0.10'')""") 
     
    184193    # Public API methods 
    185194 
    186195    def get_repository(self, reponame, authname): 
    187         """Retrieve the appropriate Repository for the given name 
     196        """Retrieve the appropriate Repository for the given name. 
    188197 
    189198           :param reponame: the key for specifying the repository. 
    190199                            If no name is given, take the the default  
     
    195204        """ 
    196205        repoinfo = self.get_all_repositories().get(reponame, {}) 
    197206        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) 
    199209        if repoinfo: 
    200210            rdir = repoinfo.get('dir') 
    201211            rtype = repoinfo.get('type', self.repository_type) 
     
    251261        return (reponame, self.get_repository(reponame, authname), path or '/') 
    252262 
    253263    def get_default_repository(self, context): 
    254         """Recover the appropriatet repository from the current context. 
     264        """Recover the appropriate repository from the current context. 
    255265 
    256266        Lookup the closest source or changeset resource in the context  
    257267        hierarchy and return the name of its associated repository. 
     
    273283                    else: 
    274284                        self._all_repositories[reponame] = info 
    275285        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 
    276298 
     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     
    277325    def shutdown(self, tid=None): 
    278326        if tid: 
    279327            assert tid == threading._get_ident() 
     
    345393        """Close the connection to the repository.""" 
    346394        raise NotImplementedError 
    347395 
     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         
    348405    def clear(self, youngest_rev=None): 
    349406        """Clear any data that may have been cached in instance properties. 
    350407 
     
    457514        return row and row[0] or None 
    458515 
    459516    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. 
    461518 
    462519        If given, `rev` is used as a starting point (i.e. no revision 
    463520        ''newer'' than `rev` should be returned). 
     
    636693        return [] 
    637694         
    638695    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. 
    640697 
    641698        The tuple will contain `(path, kind, change, base_path, base_rev)`, 
    642699        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  
    4141    has_linear_changesets = False 
    4242 
    4343    def __init__(self, getdb, repos, authz, log): 
     44        self.repos = repos 
    4445        Repository.__init__(self, repos.name, authz, log) 
    4546        if callable(getdb): 
    4647            self.getdb = getdb 
    4748        else: 
    4849            self.getdb = lambda: getdb 
    49         self.repos = repos 
    5050 
     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     
    5157    def close(self): 
    5258        self.repos.close() 
    5359 
     60    def get_base(self): 
     61        return self.repos.get_base() 
     62         
    5463    def get_quickjump_entries(self, rev): 
    5564        for category, name, path, rev in self.repos.get_quickjump_entries(rev): 
    5665            yield category, name, path, rev 
     
    6372        db = self.getdb() 
    6473        cursor = db.cursor() 
    6574        cursor.execute("SELECT rev FROM revision " 
    66                        "WHERE time >= %s AND time < %s " 
     75                       "WHERE repos=%s AND time >= %s AND time < %s" 
    6776                       "ORDER BY time DESC, rev DESC", 
    68                        (to_timestamp(start), to_timestamp(stop))) 
     77                       (self.reponame, to_timestamp(start), 
     78                        to_timestamp(stop))) 
    6979        for rev, in cursor: 
    7080            try: 
    7181                if self.authz.has_permission_for_changeset(rev): 
     
    7888        db = self.getdb() 
    7989        cursor = db.cursor() 
    8090        cursor.execute("UPDATE revision SET time=%s, author=%s, message=%s " 
    81                        "WHERE rev=%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))) 
    8494        db.commit() 
    8595         
    8696    def sync(self, feedback=None): 
    8797        db = self.getdb() 
    8898        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) 
    91103        metadata = {} 
    92104        for name, value in cursor: 
    93105            metadata[name] = value 
     
    103115                                  "'trac-admin resync' operation is needed.")) 
    104116        elif repository_dir is None: #  
    105117            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)) 
    108121        else: # 'repository_dir' cleared by a resync 
    109122            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)) 
    114126 
    115127        # -- retrieve the youngest revision in the repository 
    116128        self.repos.clear() 
    117129        repos_youngest = self.repos.youngest_rev 
    118130 
    119131        # -- 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 
    124139 
    125140        if self.youngest: 
    126141            self.youngest = self.repos.normalize_rev(self.youngest) 
     
    156171                return 
    157172 
    158173            # 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))) 
    161177            for rev, in cursor: 
    162178                # already there, but in progress, so keep ''previous'' 
    163179                # notion of 'youngest' 
     
    182198                    cset = self.repos.get_changeset(next_youngest) 
    183199                    try: 
    184200                        cursor.execute("INSERT INTO revision " 
    185                                        " (rev,time,author,message) " 
    186                                        "VALUES (%s,%s,%s,%s)", 
    187                                        (str(next_youngest), 
     201                                       " (repos,rev,time,author,message) " 
     202                                       "VALUES (%s,%s,%s,%s,%s)", 
     203                                       (self.reponame, str(next_youngest), 
    188204                                        to_timestamp(cset.date), 
    189205                                        cset.author, cset.message)) 
    190206                    except Exception, e: # *another* 1.1. resync attempt won  
     
    206222                        kind = kindmap[kind] 
    207223                        action = actionmap[action] 
    208224                        cursor.execute("INSERT INTO node_change " 
    209                                        " (rev,path,node_type,change_type, " 
    210                                        "  base_path,base_rev) " 
    211                                        "VALUES (%s,%s,%s,%s,%s,%s)", 
    212                                        (str(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), 
    213229                                        path, kind, action, bpath, brev)) 
    214230 
    215231                    # 1.3. iterate (1.1 should always succeed now) 
     
    218234 
    219235                    # 1.4. update 'youngest_rev' metadata  
    220236                    #      (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)) 
    223241                    db.commit() 
    224242 
    225243                    # 1.5. provide some feedback 
     
    258276    def _next_prev_rev(self, direction, rev, path=''): 
    259277        db = self.getdb() 
    260278        # 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 " + \ 
    262280              db.cast('rev', 'int') + " " + direction + " %s" 
    263         args = [rev] 
     281        args = [self.reponame, rev] 
    264282 
    265283        if path: 
    266284            path = path.lstrip('/') 
     
    316334        db = self.getdb() 
    317335        cursor = db.cursor() 
    318336        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))) 
    320339        row = cursor.fetchone() 
    321340        if row: 
    322341            _date, author, message = row 
     
    330349        db = self.getdb() 
    331350        cursor = db.cursor() 
    332351        cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 
    333                        "FROM node_change WHERE rev=%s " 
    334                        "ORDER BY path", (str(self.rev),)) 
     352                       "FROM node_change WHERE repos=%s AND rev=%s " 
     353                       "ORDER BY path", (self.repos.reponame, str(self.rev))) 
    335354        for path, kind, change, base_path, base_rev in cursor: 
    336355            if not self.authz.has_permission(posixpath.join(self.scope, 
    337356                                                            path.strip('/'))): 
  • trac/versioncontrol/svn_fs.py

    diff --git a/trac/versioncontrol/svn_fs.py b/trac/versioncontrol/svn_fs.py
    a b  
    431431        self.fs_ptr = repos.svn_repos_fs(self.repos) 
    432432         
    433433        uuid = fs.get_uuid(self.fs_ptr, self.pool()) 
     434        self.base = 'svn:%s:%s' % (uuid, _from_svn(root_path_utf8)) 
    434435        name = 'svn:%s:%s' % (uuid, _from_svn(path_utf8)) 
    435436 
    436437        Repository.__init__(self, name, authz, log) 
     
    484485    def close(self): 
    485486        self.repos = self.fs_ptr = self.pool = None 
    486487 
     488    def get_base(self): 
     489        return self.base 
     490         
    487491    def _get_tags_or_branches(self, paths): 
    488492        """Retrieve known branches or tags.""" 
    489493        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  
    3232        self.db = InMemoryDatabase() 
    3333        self.log = logger_factory('test') 
    3434        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', '')) 
    3738 
    3839    def test_initial_sync_with_empty_repos(self): 
    3940        t = datetime(2001, 1, 1, 1, 1, 1, 0, utc) 
     
    9192        t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) 
    9293        t3 = datetime(2003, 1, 1, 1, 1, 1, 0, utc) 
    9394        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)", 
    101104                           [('trunk', 'D', 'A', None, None), 
    102105                            ('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'") 
    104108 
    105109        changes = [('trunk/README', Node.FILE, Changeset.EDIT, 'trunk/README', 1)] 
    106110        changeset = Mock(Changeset, 2, 'Update', 'joe', t3, 
     
    128132        t1 = datetime(2001, 1, 1, 1, 1, 1, 0, utc) 
    129133        t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) 
    130134        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)", 
    138144                           [('trunk', 'D', 'A', None, None), 
    139145                            ('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'") 
    141148 
    142149        repos = Mock(Repository, 'test-repos', None, self.log, 
    143150                     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  
    908908 
    909909        single = rev_a == rev_b 
    910910        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 ', 
    912913                             single and 1 or 2, repo=reponame) 
    913914        else: 
    914             title = ngettext('Changeset', 'Changesets', single and 1 or 2) 
     915            title = ngettext('Changeset ', 'Changesets ', single and 1 or 2) 
    915916        if single: 
    916             title = tag(title, tag.em(' [%s]' % rev_a)) 
     917            title = tag(title, tag.em('[%s]' % rev_a)) 
    917918        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))) 
    919920        if field == 'title': 
    920921            return title 
    921922        elif field == 'summary':