Edgewall Software

Ticket #7723: 7723-metadata-cache-r8097.patch

File 7723-metadata-cache-r8097.patch, 16.2 KB (added by rblank, 3 years ago)

Updated caching of repository metadata to new caching infrastructure.

  • trac/versioncontrol/admin.py

    diff --git a/trac/versioncontrol/admin.py b/trac/versioncontrol/admin.py
    a b  
    201201        from trac.versioncontrol.cache import CACHE_METADATA_KEYS 
    202202        db = self.env.get_db_cnx() 
    203203        cursor = db.cursor() 
    204         inval = False 
    205204        for repos in sorted(repositories, key=lambda r: r.reponame): 
    206205            reponame = repos.reponame 
    207206            printout(_('Resyncing repository history for %(reponame)s... ', 
     
    219218                                   [(reponame, k, '')  
    220219                                    for k in CACHE_METADATA_KEYS]) 
    221220                db.commit() 
    222             if repos.sync(self._sync_feedback): 
    223                 inval = True 
     221            repos.sync(self._sync_feedback) 
    224222            cursor.execute("SELECT count(rev) FROM revision WHERE repos=%s", 
    225223                           (reponame,)) 
    226224            for cnt, in cursor: 
    227225                printout(ngettext('%(num)s revision cached.', 
    228226                                  '%(num)s revisions cached.', num=cnt)) 
    229         if inval: 
    230             self.config.touch()     # FIXME: Brute force 
    231227        printout(_('Done.')) 
    232228 
    233229    def _sync_feedback(self, rev): 
  • trac/versioncontrol/api.py

    diff --git a/trac/versioncontrol/api.py b/trac/versioncontrol/api.py
    a b  
    391391            self.log.warn("Found no repositories matching '%s' base.", 
    392392                          base or reponame) 
    393393            return 
    394         inval = False 
    395394        for repos in sorted(repositories, key=lambda r: r.reponame): 
    396             if repos.sync(): 
    397                 inval = True 
    398             else: 
    399                 self.log.debug("Repository %s already up-to-date.", repos.name) 
     395            repos.sync() 
    400396            for rev in revs: 
    401397                args = [] 
    402398                if event == 'changeset_modified': 
     
    405401                    changeset = repos.get_changeset(rev) 
    406402                except NoSuchChangeset: 
    407403                    continue 
    408                 inval = inval or (event == 'changeset_modified') 
    409404                self.log.debug("Event %s on %s for revision %s", 
    410                                event, repos.reponame, rev) 
     405                               event, repos.reponame or '(default)', rev) 
    411406                for listener in self.change_listeners: 
    412407                    getattr(listener, event)(repos, changeset, *args) 
    413          
    414         if inval: 
    415             self.log.debug("Invalidating youngest_rev cache") 
    416             self.config.touch()     # FIXME: Brute force method 
    417408     
    418409    def shutdown(self, tid=None): 
    419410        if tid: 
     
    510501        The backend will call this function for each `rev` it decided to 
    511502        synchronize, once the synchronization changes are committed to the  
    512503        cache. 
    513          
    514         Return True if any changes have been committed to the cache. 
    515504        """ 
    516         return False 
     505        pass 
    517506 
    518507    def sync_changeset(self, rev): 
    519508        """Resync the repository cache for the given `rev`, if relevant. 
  • trac/versioncontrol/cache.py

    diff --git a/trac/versioncontrol/cache.py b/trac/versioncontrol/cache.py
    a b  
    1818import posixpath 
    1919from datetime import datetime 
    2020 
     21from trac.cache import CacheProxy 
    2122from trac.core import TracError 
    2223from trac.util.datefmt import utc, to_timestamp 
    2324from trac.util.translation import _ 
     
    4041 
    4142    has_linear_changesets = False 
    4243 
    43     def __init__(self, getdb, repos, authz, log): 
     44    def __init__(self, env, repos, authz, log): 
     45        self.env = env 
    4446        self.repos = repos 
     47        self.metadata = CacheProxy(self.__class__.__module__ + '.' 
     48                                   + self.__class__.__name__ + '.metadata:' 
     49                                   + self.repos.reponame, self._metadata, 
     50                                   self.env) 
    4551        Repository.__init__(self, repos.name, authz, log) 
    46         if callable(getdb): 
    47             self.getdb = getdb 
    48         else: 
    49             self.getdb = lambda: getdb 
    5052 
    5153    def _set_reponame(self, value): 
    5254        self.repos.reponame = value 
     55        self.metadata.id = self.__class__.__module__ + '.' \ 
     56                           + self.__class__.__name__ + '.metadata:' \ 
     57                           + value 
    5358     
    5459    reponame = property(fget=lambda self: self.repos.reponame, 
    5560                        fset=_set_reponame) 
     
    6671 
    6772    def get_changeset(self, rev): 
    6873        return CachedChangeset(self.repos, self.repos.normalize_rev(rev), 
    69                                self.getdb, self.authz) 
     74                               self.env, self.authz) 
    7075 
    7176    def get_changesets(self, start, stop): 
    72         db = self.getdb() 
     77        db = self.env.get_db_cnx() 
    7378        cursor = db.cursor() 
    7479        cursor.execute("SELECT rev FROM revision " 
    7580                       "WHERE repos=%s AND time >= %s AND time < %s" 
     
    8590 
    8691    def sync_changeset(self, rev): 
    8792        cset = self.repos.get_changeset(rev) 
    88         db = self.getdb() 
     93        db = self.env.get_db_cnx() 
    8994        cursor = db.cursor() 
    9095        cursor.execute("SELECT time,author,message FROM revision " 
    9196                       "WHERE repos=%s AND rev=%s", 
     
    102107        db.commit() 
    103108        return old_changeset 
    104109         
    105     # @cached? => move to RepositoryManager 
    106     def metadata(self, db=None): 
    107         if not db: 
    108             db = self.getdb() 
     110    def _metadata(self, db): 
     111        """Retrieve data for the cached `metadata` attribute.""" 
    109112        cursor = db.cursor() 
    110113        cursor.execute("SELECT name, value FROM repository " 
    111114                       "WHERE id=%%s AND name IN (%s)" %  
    112115                       ','.join(['%s'] * len(CACHE_METADATA_KEYS)), 
    113116                       (self.reponame,) + CACHE_METADATA_KEYS) 
    114         metadata = {} 
    115         for name, value in cursor: 
    116             metadata[name] = value 
    117         return metadata 
     117        return dict(cursor) 
    118118 
    119119    def sync(self, feedback=None): 
    120         db = self.getdb() 
     120        db = self.env.get_db_cnx() 
    121121        cursor = db.cursor() 
    122         metadata = self.metadata(db) 
     122        metadata = self.metadata.get(db) 
     123        do_commit = False 
    123124         
    124125        # -- check that we're populating the cache for the correct repository 
    125126        repository_dir = metadata.get(CACHE_REPOSITORY_DIR) 
     
    135136            cursor.execute("INSERT INTO repository (id,name,value) " 
    136137                           "VALUES (%s,%s,%s)", 
    137138                           (self.reponame, CACHE_REPOSITORY_DIR, self.name)) 
     139            do_commit = True 
    138140        else: # 'repository_dir' cleared by a resync 
    139141            self.log.info('Resetting "repository_dir": %s' % self.name) 
    140142            cursor.execute("UPDATE repository SET value=%s " 
    141143                           "WHERE id=%s AND name=%s", 
    142144                           (self.name, self.reponame, CACHE_REPOSITORY_DIR)) 
     145            do_commit = True 
    143146 
    144147        # -- retrieve the youngest revision in the repository 
    145148        self.repos.clear() 
    146149        repos_youngest = self.repos.youngest_rev 
    147150 
    148151        # -- retrieve the youngest revision cached so far 
    149         self.youngest = metadata.get(CACHE_YOUNGEST_REV) 
    150         if self.youngest is None: 
     152        youngest = metadata.get(CACHE_YOUNGEST_REV) 
     153        if youngest is None: 
    151154            cursor.execute("INSERT INTO repository (id,name,value) " 
    152155                           "VALUES (%s,%s,%s)", 
    153156                           (self.reponame, CACHE_YOUNGEST_REV, '')) 
     157            do_commit = True 
    154158 
    155         db.commit() # save metadata changes made up to now 
     159        if do_commit: 
     160            self.metadata.invalidate(db) 
     161            db.commit() # save metadata changes made up to now 
    156162 
    157         if self.youngest: 
    158             self.youngest = self.repos.normalize_rev(self.youngest) 
    159             if not self.youngest: 
     163        if youngest: 
     164            youngest = self.repos.normalize_rev(youngest) 
     165            if not youngest: 
    160166                self.log.debug('normalize_rev failed (youngest_rev=%r)' % 
    161167                               self.youngest_rev) 
    162168        else: 
    163169            self.log.debug('cache metadata undefined (youngest_rev=%r)' % 
    164170                           self.youngest_rev) 
    165             self.youngest = None 
     171            youngest = None 
    166172 
    167173        # -- compare them and try to resync if different 
    168         if self.youngest != repos_youngest: 
     174        if youngest != repos_youngest: 
    169175            self.log.info("repos rev [%s] != cached rev [%s]" % 
    170                           (repos_youngest, self.youngest)) 
    171             if self.youngest: 
    172                 next_youngest = self.repos.next_rev(self.youngest) 
     176                          (repos_youngest, youngest)) 
     177            if youngest: 
     178                next_youngest = self.repos.next_rev(youngest) 
    173179            else: 
    174180                next_youngest = None 
    175181                try: 
     
    183189                    next_youngest = self.repos.normalize_rev(next_youngest) 
    184190                except TracError: 
    185191                    # can't normalize oldest_rev: repository was empty 
    186                     return False 
     192                    return 
    187193 
    188194            if next_youngest is None: # nothing to cache yet 
    189                 return False 
     195                return 
    190196 
    191197            # 0. first check if there's no (obvious) resync in progress 
    192198            cursor.execute("SELECT rev FROM revision " 
     
    195201            for rev, in cursor: 
    196202                # already there, but in progress, so keep ''previous'' 
    197203                # notion of 'youngest' 
    198                 self.repos.clear(youngest_rev=self.youngest) 
    199                 return False 
     204                self.repos.clear(youngest_rev=youngest) 
     205                return 
    200206 
    201207            # 1. prepare for resyncing 
    202208            #    (there still might be a race condition at this point) 
     
    226232                                         (next_youngest, e)) 
    227233                        # also potentially in progress, so keep ''previous'' 
    228234                        # notion of 'youngest' 
    229                         self.repos.clear(youngest_rev=self.youngest) 
     235                        self.repos.clear(youngest_rev=youngest) 
    230236                        db.rollback() 
    231                         return False 
     237                        return 
    232238 
    233239                    # 1.2. now *only* one process was able to get there 
    234240                    #      (i.e. there *shouldn't* be any race condition here) 
     
    247253                                        path, kind, action, bpath, brev)) 
    248254 
    249255                    # 1.3. iterate (1.1 should always succeed now) 
    250                     self.youngest = next_youngest                     
     256                    youngest = next_youngest                     
    251257                    next_youngest = self.repos.next_rev(next_youngest) 
    252258 
    253259                    # 1.4. update 'youngest_rev' metadata  
    254260                    #      (minimize possibility of failures at point 0.) 
    255261                    cursor.execute("UPDATE repository SET value=%s " 
    256262                                   "WHERE id=%s AND name=%s", 
    257                                    (str(self.youngest), self.reponame, 
     263                                   (str(youngest), self.reponame, 
    258264                                    CACHE_YOUNGEST_REV)) 
     265                    self.metadata.invalidate(db) 
    259266                    db.commit() 
    260267 
    261268                    # 1.5. provide some feedback 
    262269                    if feedback: 
    263                         feedback(self.youngest) 
    264                  
    265                 return True 
     270                        feedback(youngest) 
    266271            finally: 
    267272                # 3. restore permission checking (after 1.) 
    268273                self.repos.authz = authz 
     
    277282        return self.repos.oldest_rev 
    278283 
    279284    def get_youngest_rev(self): 
    280         if not hasattr(self, 'youngest'): 
    281             metadata = self.metadata() 
    282             self.youngest = metadata.get(CACHE_YOUNGEST_REV) 
    283         return self.youngest 
     285        return self.metadata.get().get(CACHE_YOUNGEST_REV) 
    284286 
    285287    def previous_rev(self, rev, path=''): 
    286288        if self.has_linear_changesets: 
     
    295297            return self.repos.next_rev(rev, path) 
    296298 
    297299    def _next_prev_rev(self, direction, rev, path=''): 
    298         db = self.getdb() 
     300        db = self.env.get_db_cnx() 
    299301        # the changeset revs are sequence of ints: 
    300302        sql = "SELECT rev FROM node_change WHERE repos=%s AND " + \ 
    301303              db.cast('rev', 'int') + " " + direction + " %s" 
     
    348350 
    349351class CachedChangeset(Changeset): 
    350352 
    351     def __init__(self, repos, rev, getdb, authz): 
     353    def __init__(self, repos, rev, env, authz): 
    352354        self.repos = repos 
    353         self.getdb = getdb 
     355        self.env = env 
    354356        self.authz = authz 
    355         db = self.getdb() 
     357        db = self.env.get_db_cnx() 
    356358        cursor = db.cursor() 
    357359        cursor.execute("SELECT time,author,message FROM revision " 
    358360                       "WHERE repos=%s AND rev=%s", 
     
    367369        self.scope = getattr(repos, 'scope', '') 
    368370 
    369371    def get_changes(self): 
    370         db = self.getdb() 
     372        db = self.env.get_db_cnx() 
    371373        cursor = db.cursor() 
    372374        cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 
    373375                       "FROM node_change WHERE repos=%s AND rev=%s " 
  • trac/versioncontrol/svn_fs.py

    diff --git a/trac/versioncontrol/svn_fs.py b/trac/versioncontrol/svn_fs.py
    a b  
    289289        if type == 'direct-svnfs': 
    290290            repos = fs_repos 
    291291        else: 
    292             repos = CachedRepository(self.env.get_db_cnx, fs_repos, None, 
    293                                      self.log) 
     292            repos = CachedRepository(self.env, fs_repos, None, self.log) 
    294293            repos.has_linear_changesets = True 
    295294        # FIXME: convert SubversionAuthorizer to a PermissionPolicy 
    296295        if 'authname' in options: 
  • trac/versioncontrol/tests/cache.py

    diff --git a/trac/versioncontrol/tests/cache.py b/trac/versioncontrol/tests/cache.py
    a b  
    1717from datetime import datetime 
    1818 
    1919from trac.log import logger_factory 
    20 from trac.test import Mock, InMemoryDatabase 
     20from trac.test import EnvironmentStub, Mock, InMemoryDatabase 
    2121from trac.util.datefmt import to_timestamp, utc 
    2222from trac.versioncontrol import Repository, Changeset, Node, NoSuchChangeset 
    2323from trac.versioncontrol.cache import CachedRepository 
     
    2929class CacheTestCase(unittest.TestCase): 
    3030 
    3131    def setUp(self): 
    32         self.db = InMemoryDatabase() 
    33         self.log = logger_factory('test') 
     32        self.env = EnvironmentStub() 
     33        self.db = self.env.get_db_cnx() 
     34        self.log = self.env.log 
    3435        cursor = self.db.cursor() 
    3536        cursor.execute("INSERT INTO repository (id, name, value) " 
    3637                       "VALUES (%s,%s,%s)", 
     
    4748                     get_youngest_rev=lambda: 0, 
    4849                     normalize_rev=no_changeset, 
    4950                     next_rev=lambda x: None) 
    50         cache = CachedRepository(self.db, repos, None, self.log) 
     51        cache = CachedRepository(self.env, repos, None, self.log) 
    5152        cache.sync() 
    5253 
    5354        cursor = self.db.cursor() 
     
    7172                     get_youngest_rev=lambda: 1, 
    7273                     normalize_rev=lambda x: x, 
    7374                     next_rev=lambda x: int(x) == 0 and 1 or None) 
    74         cache = CachedRepository(self.db, repos, None, self.log) 
     75        cache = CachedRepository(self.env, repos, None, self.log) 
    7576        cache.sync() 
    7677 
    7778        cursor = self.db.cursor() 
     
    115116                     get_oldest_rev=lambda: 0, 
    116117                     normalize_rev=lambda x: x,                     
    117118                     next_rev=lambda x: x and int(x) == 1 and 2 or None) 
    118         cache = CachedRepository(self.db, repos, None, self.log) 
     119        cache = CachedRepository(self.env, repos, None, self.log) 
    119120        cache.sync() 
    120121 
    121122        cursor = self.db.cursor() 
     
    152153                     get_oldest_rev=lambda: 0, 
    153154                     next_rev=lambda x: None, 
    154155                     normalize_rev=lambda rev: rev) 
    155         cache = CachedRepository(self.db, repos, None, self.log) 
     156        cache = CachedRepository(self.env, repos, None, self.log) 
    156157        self.assertEqual('1', cache.youngest_rev) 
    157158        changeset = cache.get_changeset(1) 
    158159        self.assertEqual('joe', changeset.author)