Edgewall Software

Ticket #8731: 8731-surrogate-keys-r8662.patch

File 8731-surrogate-keys-r8662.patch, 24.2 KB (added by rblank, 3 years ago)

First experiment with surrogate keys for repositories.

  • trac/admin/tests/console-tests.txt

    diff --git a/trac/admin/tests/console-tests.txt b/trac/admin/tests/console-tests.txt
    a b  
    4141repository alias     Create an alias for a repository 
    4242repository list      List source repositories 
    4343repository remove    Remove a source repository 
    44 repository rename    Rename a source repository 
    4544repository resync    Re-synchronize trac with repositories 
    4645repository set       Set an attribute of a repository 
    4746repository sync      Resume synchronization of repositories 
  • trac/db_default.py

    diff --git a/trac/db_default.py b/trac/db_default.py
    a b  
    8686 
    8787    # Version control cache 
    8888    Table('repository', key=('id', 'name'))[ 
    89         Column('id'), 
     89        Column('id', type='int'), 
    9090        Column('name'), 
    9191        Column('value')], 
    9292    Table('revision', key=('repos', 'rev'))[ 
    93         Column('repos'), 
     93        Column('repos', type='int'), 
    9494        Column('rev'), 
    9595        Column('time', type='int'), 
    9696        Column('author'), 
    9797        Column('message'), 
    9898        Index(['repos', 'time'])], 
    9999    Table('node_change', key=('repos', 'rev', 'path', 'change_type'))[ 
    100         Column('repos'), 
     100        Column('repos', type='int'), 
    101101        Column('rev'), 
    102102        Column('path'), 
    103103        Column('node_type', size=1), 
  • trac/versioncontrol/admin.py

    diff --git a/trac/versioncontrol/admin.py b/trac/versioncontrol/admin.py
    a b  
    126126        cursor = db.cursor() 
    127127        for repos in sorted(repositories, key=lambda r: r.reponame): 
    128128            reponame = repos.reponame 
     129            id = rm.get_repository_id(reponame) 
    129130            printout(_('Resyncing repository history for %(reponame)s... ', 
    130131                       reponame=reponame or '(default)')) 
    131132            if clean: 
    132                 cursor.execute("DELETE FROM revision WHERE repos=%s", 
    133                                (reponame,)) 
    134                 cursor.execute("DELETE FROM node_change " 
    135                                "WHERE repos=%s", (reponame,)) 
     133                cursor.execute("DELETE FROM revision WHERE repos=%s", (id,)) 
     134                cursor.execute("DELETE FROM node_change WHERE repos=%s", (id,)) 
    136135                cursor.executemany("DELETE FROM repository " 
    137136                                   "WHERE id=%s AND name=%s", 
    138                                    [(reponame, k) for k in CACHE_METADATA_KEYS]) 
     137                                   [(id, k) for k in CACHE_METADATA_KEYS]) 
    139138                cursor.executemany("INSERT INTO repository (id, name, value) " 
    140139                                   "VALUES (%s, %s, %s)",  
    141                                    [(reponame, k, '')  
    142                                     for k in CACHE_METADATA_KEYS]) 
     140                                   [(id, k, '') for k in CACHE_METADATA_KEYS]) 
    143141                db.commit() 
    144142            repos.sync(self._sync_feedback) 
    145143            cursor.execute("SELECT count(rev) FROM revision WHERE repos=%s", 
    146                            (reponame,)) 
     144                           (id,)) 
    147145            for cnt, in cursor: 
    148146                printout(ngettext('%(num)s revision cached.', 
    149147                                  '%(num)s revisions cached.', num=cnt)) 
     
    187185                 
    188186                elif db_provider and req.args.get('save'): 
    189187                    # Modify repository 
    190                     changed = False 
    191188                    changes = {} 
    192189                    for field in db_provider.repository_attrs: 
    193190                        value = normalize_whitespace(req.args.get(field)) 
     
    196193                            changes[field] = value 
    197194                    if changes: 
    198195                        db_provider.modify_repository(reponame, changes) 
    199                         changed = True 
    200                     # Rename repository 
     196                        add_notice(req, _('Your changes have been saved.')) 
    201197                    name = req.args.get('name') 
    202                     if name and name != path_info: 
    203                         db_provider.rename_repository(reponame, name) 
    204                         changed = True 
    205                     if changed: 
    206                         add_notice(req, _('Your changes have been saved.')) 
    207198                    if 'dir' in changes: 
    208199                        msg = _('You should now run "trac-admin $ENV ' 
    209200                                'repository resync %(name)s" to synchronize ' 
  • trac/versioncontrol/api.py

    diff --git a/trac/versioncontrol/api.py b/trac/versioncontrol/api.py
    a b  
    9696 
    9797    implements(IRepositoryProvider, IAdminCommandProvider) 
    9898 
    99     repository_attrs = ('alias', 'dir', 'hidden', 'type', 'url') 
     99    repository_attrs = ('alias', 'dir', 'hidden', 'name', 'type', 'url') 
    100100     
    101101    # IRepositoryProvider methods 
    102102 
     
    107107        cursor.execute("SELECT id,name,value FROM repository " 
    108108                       "WHERE name IN (%s)" % ",".join( 
    109109                           "'%s'" % each for each in self.repository_attrs)) 
     110        repos = {} 
     111        for id, name, value in cursor: 
     112            if value is not None: 
     113                repos.setdefault(id, {})[name] = value 
    110114        reponames = {} 
    111         for (id, name, value) in cursor: 
    112             if value is not None: 
    113                 reponames.setdefault(id, {})[name] = value 
     115        for id, info in repos.iteritems(): 
     116            if 'name' in info and 'dir' in info: 
     117                info['id'] = id 
     118                reponames[info['name']] = info 
    114119        return reponames.iteritems() 
    115120 
    116121    # IAdminCommandProvider methods 
     
    125130        yield ('repository remove', '<repos>', 
    126131               'Remove a source repository', 
    127132               self._complete_repos, self._do_remove) 
    128         yield ('repository rename', '<repos> <newname>', 
    129                'Rename a source repository', 
    130                self._complete_repos, self._do_rename) 
    131133        yield ('repository set', '<repos> <key> <value>', 
    132134               """Set an attribute of a repository 
    133135                
     
    169171    def _do_remove(self, reponame): 
    170172        self.remove_repository(reponame) 
    171173     
    172     def _do_rename(self, reponame, newname): 
    173         self.rename_repository(reponame, newname) 
    174      
    175174    def _do_set(self, reponame, key, value): 
    176175        if key not in self.repository_attrs: 
    177176            raise AdminCommandError(_('Invalid key "%(key)s"', key=key)) 
     
    194193            raise TracError(_("The repository type '%(type)s' is not " 
    195194                              "supported", type=type_)) 
    196195        db = self.env.get_db_cnx() 
     196        id = rm.get_repository_id(reponame, db) 
    197197        cursor = db.cursor() 
    198198        cursor.executemany("INSERT INTO repository (id, name, value) " 
    199199                           "VALUES (%s, %s, %s)", 
    200                            [(reponame, 'dir', dir), 
    201                             (reponame, 'type', type_ or '')]) 
     200                           [(id, 'dir', dir), 
     201                            (id, 'type', type_ or '')]) 
    202202        db.commit() 
    203203        rm.reload_repositories() 
    204204     
     
    208208            reponame = '' 
    209209        if target == '(default)': 
    210210            target = '' 
     211        rm = RepositoryManager(self.env) 
    211212        db = self.env.get_db_cnx() 
     213        id = rm.get_repository_id(reponame, db) 
    212214        cursor = db.cursor() 
    213215        cursor.executemany("INSERT INTO repository (id, name, value) " 
    214216                           "VALUES (%s, %s, %s)", 
    215                            [(reponame, 'dir', None), 
    216                             (reponame, 'alias', target)]) 
     217                           [(id, 'dir', None), 
     218                            (id, 'alias', target)]) 
    217219        db.commit() 
    218         RepositoryManager(self.env).reload_repositories() 
     220        rm.reload_repositories() 
    219221     
    220222    def remove_repository(self, reponame): 
    221223        """Remove a repository.""" 
    222224        if reponame == '(default)': 
    223225            reponame = '' 
     226        rm = RepositoryManager(self.env) 
    224227        db = self.env.get_db_cnx() 
     228        id = rm.get_repository_id(reponame, db) 
    225229        cursor = db.cursor() 
    226         cursor.execute("DELETE FROM repository WHERE id=%s", (reponame,)) 
    227         cursor.execute("DELETE FROM revision WHERE repos=%s", (reponame,)) 
    228         cursor.execute("DELETE FROM node_change WHERE repos=%s", (reponame,)) 
     230        cursor.execute("DELETE FROM repository WHERE id=%s", (id,)) 
     231        cursor.execute("DELETE FROM revision WHERE repos=%s", (id,)) 
     232        cursor.execute("DELETE FROM node_change WHERE repos=%s", (id,)) 
    229233        db.commit() 
    230         RepositoryManager(self.env).reload_repositories() 
    231      
    232     def rename_repository(self, reponame, newname): 
    233         """Rename a repository.""" 
    234         if reponame == '(default)': 
    235             reponame = '' 
    236         if newname == '(default)': 
    237             newname = '' 
    238         db = self.env.get_db_cnx() 
    239         cursor = db.cursor() 
    240         cursor.execute("UPDATE repository SET id=%s WHERE id=%s", 
    241                        (newname, reponame)) 
    242         cursor.execute("UPDATE revision SET repos=%s WHERE repos=%s", 
    243                        (newname, reponame)) 
    244         cursor.execute("UPDATE node_change SET repos=%s WHERE repos=%s", 
    245                        (newname, reponame)) 
    246         db.commit() 
    247         RepositoryManager(self.env).reload_repositories() 
     234        rm.reload_repositories() 
    248235     
    249236    def modify_repository(self, reponame, changes): 
    250237        """Modify attributes of a repository.""" 
    251238        if reponame == '(default)': 
    252239            reponame = '' 
     240        rm = RepositoryManager(self.env) 
    253241        db = self.env.get_db_cnx() 
     242        id = rm.get_repository_id(reponame, db) 
    254243        cursor = db.cursor() 
    255244        for (k, v) in changes.iteritems(): 
    256245            if k not in self.repository_attrs: 
    257246                continue 
    258             if k == 'alias' and v == '(default)': 
     247            if k in('alias', 'name') and v == '(default)': 
    259248                v = '' 
    260249            cursor.execute("UPDATE repository SET value=%s " 
    261                            "WHERE id=%s AND name=%s", (v, reponame, k)) 
     250                           "WHERE id=%s AND name=%s", (v, id, k)) 
    262251            cursor.execute("SELECT value FROM repository " 
    263                            "WHERE id=%s AND name=%s", (reponame, k)) 
     252                           "WHERE id=%s AND name=%s", (id, k)) 
    264253            if not cursor.fetchone(): 
    265254                cursor.execute("INSERT INTO repository VALUES (%s, %s, %s)", 
    266                                (reponame, k, v)) 
     255                               (id, k, v)) 
    267256        db.commit() 
    268         RepositoryManager(self.env).reload_repositories() 
     257        rm.reload_repositories() 
    269258 
    270259 
    271260class RepositoryManager(Component): 
     
    445434                        repositories.append(repos) 
    446435        return repositories 
    447436 
     437    def get_repository_id(self, reponame, db=None): 
     438        """Return a unique id for the given repository name.""" 
     439        handle_ta = False 
     440        if db is None: 
     441            db = self.env.get_db_cnx() 
     442            handle_ta = True 
     443        cursor = db.cursor() 
     444        cursor.execute("SELECT id FROM repository " 
     445                       "WHERE name='name' AND value=%s", (reponame,)) 
     446        for id, in cursor: 
     447            return id 
     448        cursor.execute("SELECT COALESCE(MAX(id),0) FROM repository") 
     449        id = cursor.fetchone()[0] + 1 
     450        cursor.execute("INSERT INTO repository VALUES (%s,%s,%s)", 
     451                       (id, 'name', reponame)) 
     452        if handle_ta: 
     453            db.commit() 
     454        return id 
     455     
    448456    def get_repository(self, reponame, authname): 
    449457        """Retrieve the appropriate Repository for the given name. 
    450458 
     
    455463           :return: if no corresponding repository was defined,  
    456464                    simply return `None`. 
    457465        """ 
     466        reponame = reponame or '' 
    458467        repoinfo = self.get_all_repositories().get(reponame, {}) 
    459468        if repoinfo and 'alias' in repoinfo: 
    460469            reponame = repoinfo['alias'] 
     
    489498                connector = self._get_connector(rtype) 
    490499                repos = connector.get_repository(rtype, rdir, repoinfo) 
    491500                repos.reponame = reponame 
     501                repos.id = repoinfo['id'] 
    492502                repositories[reponame] = repos 
    493503            return repos 
    494504        finally: 
     
    538548                        self.log.warn("Discarding duplicate repository '%s'", 
    539549                                      reponame) 
    540550                    else: 
     551                        if 'id' not in info: 
     552                            info['id'] = self.get_repository_id(reponame) 
    541553                        self._all_repositories[reponame] = info 
    542554        return self._all_repositories 
    543555     
     
    673685        self.authz = authz or Authorizer() 
    674686        self.log = log 
    675687        self.reponame = name # overriden by the reponame key used to create it 
     688        self.id = None 
    676689 
    677690    def close(self): 
    678691        """Close the connection to the repository.""" 
  • trac/versioncontrol/cache.py

    diff --git a/trac/versioncontrol/cache.py b/trac/versioncontrol/cache.py
    a b  
    4848        self.repos = repos 
    4949        self.metadata = CacheProxy(self.__class__.__module__ + '.' 
    5050                                   + self.__class__.__name__ + '.metadata:' 
    51                                    + self.repos.reponame, self._metadata, 
     51                                   + str(self.repos.id), self._metadata, 
    5252                                   self.env) 
    5353        Repository.__init__(self, repos.name, authz, log) 
    5454 
    5555    def _set_reponame(self, value): 
    5656        self.repos.reponame = value 
    57         self.metadata.id = self.__class__.__module__ + '.' \ 
    58                            + self.__class__.__name__ + '.metadata:' \ 
    59                            + value 
    6057     
    6158    reponame = property(fget=lambda self: self.repos.reponame, 
    6259                        fset=_set_reponame) 
    6360     
     61    def _set_id(self, value): 
     62        self.repos.id = value 
     63        self.metadata.id = self.__class__.__module__ + '.' \ 
     64                           + self.__class__.__name__ + '.metadata:' \ 
     65                           + str(value) 
     66     
     67    id = property(fget=lambda self: self.repos.id, fset=_set_id) 
     68     
    6469    def close(self): 
    6570        self.repos.close() 
    6671 
     
    8691        cursor.execute("SELECT rev FROM revision " 
    8792                       "WHERE repos=%s AND time >= %s AND time < %s " 
    8893                       "ORDER BY time DESC, rev DESC", 
    89                        (self.reponame, to_timestamp(start), 
     94                       (self.id, to_timestamp(start), 
    9095                        to_timestamp(stop))) 
    9196        for rev, in cursor: 
    9297            try: 
     
    101106        cursor = db.cursor() 
    102107        cursor.execute("SELECT time,author,message FROM revision " 
    103108                       "WHERE repos=%s AND rev=%s", 
    104                        (self.reponame, str(cset.rev))) 
     109                       (self.id, str(cset.rev))) 
    105110        old_changeset = None 
    106111        for time, author, message in cursor: 
    107112            date = datetime.fromtimestamp(time, utc) 
     
    110115        cursor.execute("UPDATE revision SET time=%s, author=%s, message=%s " 
    111116                       "WHERE repos=%s AND rev=%s", 
    112117                       (to_timestamp(cset.date), cset.author, cset.message, 
    113                         self.reponame, str(cset.rev))) 
     118                        self.id, str(cset.rev))) 
    114119        db.commit() 
    115120        return old_changeset 
    116121         
     
    120125        cursor.execute("SELECT name, value FROM repository " 
    121126                       "WHERE id=%%s AND name IN (%s)" %  
    122127                       ','.join(['%s'] * len(CACHE_METADATA_KEYS)), 
    123                        (self.reponame,) + CACHE_METADATA_KEYS) 
     128                       (self.id,) + CACHE_METADATA_KEYS) 
    124129        return dict(cursor) 
    125130 
    126131    def sync(self, feedback=None): 
     
    143148            self.log.info('Storing initial "repository_dir": %s' % self.name) 
    144149            cursor.execute("INSERT INTO repository (id,name,value) " 
    145150                           "VALUES (%s,%s,%s)", 
    146                            (self.reponame, CACHE_REPOSITORY_DIR, self.name)) 
     151                           (self.id, CACHE_REPOSITORY_DIR, self.name)) 
    147152            do_commit = True 
    148153        else: # 'repository_dir' cleared by a resync 
    149154            self.log.info('Resetting "repository_dir": %s' % self.name) 
    150155            cursor.execute("UPDATE repository SET value=%s " 
    151156                           "WHERE id=%s AND name=%s", 
    152                            (self.name, self.reponame, CACHE_REPOSITORY_DIR)) 
     157                           (self.name, self.id, CACHE_REPOSITORY_DIR)) 
    153158            do_commit = True 
    154159 
    155160        # -- retrieve the youngest revision in the repository 
     
    161166        if youngest is None: 
    162167            cursor.execute("INSERT INTO repository (id,name,value) " 
    163168                           "VALUES (%s,%s,%s)", 
    164                            (self.reponame, CACHE_YOUNGEST_REV, '')) 
     169                           (self.id, CACHE_YOUNGEST_REV, '')) 
    165170            do_commit = True 
    166171 
    167172        if do_commit: 
     
    205210            # 0. first check if there's no (obvious) resync in progress 
    206211            cursor.execute("SELECT rev FROM revision " 
    207212                           "WHERE repos=%s AND rev=%s", 
    208                            (self.reponame, str(next_youngest))) 
     213                           (self.id, str(next_youngest))) 
    209214            for rev, in cursor: 
    210215                # already there, but in progress, so keep ''previous'' 
    211216                # notion of 'youngest' 
     
    232237                        cursor.execute("INSERT INTO revision " 
    233238                                       " (repos,rev,time,author,message) " 
    234239                                       "VALUES (%s,%s,%s,%s,%s)", 
    235                                        (self.reponame, str(next_youngest), 
     240                                       (self.id, str(next_youngest), 
    236241                                        to_timestamp(cset.date), 
    237242                                        cset.author, cset.message)) 
    238243                    except Exception, e: # *another* 1.1. resync attempt won  
     
    257262                                       " (repos,rev,path,node_type," 
    258263                                       "  change_type,base_path,base_rev) " 
    259264                                       "VALUES (%s,%s,%s,%s,%s,%s,%s)", 
    260                                        (self.reponame, str(next_youngest), 
     265                                       (self.id, str(next_youngest), 
    261266                                        path, kind, action, bpath, brev)) 
    262267 
    263268                    # 1.3. iterate (1.1 should always succeed now) 
     
    268273                    #      (minimize possibility of failures at point 0.) 
    269274                    cursor.execute("UPDATE repository SET value=%s " 
    270275                                   "WHERE id=%s AND name=%s", 
    271                                    (str(youngest), self.reponame, 
     276                                   (str(youngest), self.id, 
    272277                                    CACHE_YOUNGEST_REV)) 
    273278                    self.metadata.invalidate(db) 
    274279                    db.commit() 
     
    336341        # the changeset revs are sequence of ints: 
    337342        sql = "SELECT rev FROM node_change WHERE repos=%s AND " + \ 
    338343              db.cast('rev', 'int') + " " + direction + " %s" 
    339         args = [self.reponame, rev] 
     344        args = [self.id, rev] 
    340345 
    341346        if path: 
    342347            path = path.lstrip('/') 
     
    406411        cursor = db.cursor() 
    407412        cursor.execute("SELECT time,author,message FROM revision " 
    408413                       "WHERE repos=%s AND rev=%s", 
    409                        (self.repos.reponame, str(rev))) 
     414                       (self.repos.id, str(rev))) 
    410415        row = cursor.fetchone() 
    411416        if row: 
    412417            _date, author, message = row 
     
    421426        cursor = db.cursor() 
    422427        cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 
    423428                       "FROM node_change WHERE repos=%s AND rev=%s " 
    424                        "ORDER BY path", (self.repos.reponame, str(self.rev))) 
     429                       "ORDER BY path", (self.repos.id, str(self.rev))) 
    425430        for path, kind, change, base_path, base_rev in cursor: 
    426431            if not self.authz.has_permission(posixpath.join(self.scope, 
    427432                                                            path.strip('/'))): 
  • trac/versioncontrol/tests/cache.py

    diff --git a/trac/versioncontrol/tests/cache.py b/trac/versioncontrol/tests/cache.py
    a b  
    3333        self.db = self.env.get_db_cnx() 
    3434        self.log = self.env.log 
    3535        cursor = self.db.cursor() 
    36         cursor.execute("INSERT INTO repository (id, name, value) " 
    37                        "VALUES (%s,%s,%s)", 
    38                        ('test-repos', 'youngest_rev', '')) 
     36        cursor.executemany("INSERT INTO repository (id,name,value) " 
     37                           "VALUES (%s,%s,%s)", 
     38                           [(1, 'name', 'test-repos'), 
     39                            (1, 'youngest_rev', '')]) 
    3940 
    4041    def tearDown(self): 
    4142        self.env.reset_db() 
     
    9798        t3 = datetime(2003, 1, 1, 1, 1, 1, 0, utc) 
    9899        cursor = self.db.cursor() 
    99100        cursor.execute("INSERT INTO revision (repos,rev,time,author,message) " 
    100                        "VALUES ('test-repos',0,%s,'','')", 
     101                       "VALUES (1,0,%s,'','')", 
    101102                       (to_timestamp(t1),)) 
    102103        cursor.execute("INSERT INTO revision (repos,rev,time,author,message) " 
    103                        "VALUES ('test-repos',1,%s,'joe','Import')", 
     104                       "VALUES (1,1,%s,'joe','Import')", 
    104105                       (to_timestamp(t2),)) 
    105106        cursor.executemany("INSERT INTO node_change (repos,rev,path," 
    106107                           "node_type,change_type,base_path,base_rev) " 
    107                            "VALUES ('test-repos','1',%s,%s,%s,%s,%s)", 
     108                           "VALUES (1,'1',%s,%s,%s,%s,%s)", 
    108109                           [('trunk', 'D', 'A', None, None), 
    109110                            ('trunk/README', 'F', 'A', None, None)]) 
    110111        cursor.execute("UPDATE repository SET value='1' " 
    111                        "WHERE id='test-repos' AND name='youngest_rev'") 
     112                       "WHERE id=1 AND name='youngest_rev'") 
    112113 
    113114        changes = [('trunk/README', Node.FILE, Changeset.EDIT, 'trunk/README', 1)] 
    114115        changeset = Mock(Changeset, 2, 'Update', 'joe', t3, 
     
    120121                     normalize_rev=lambda x: x,                     
    121122                     next_rev=lambda x: x and int(x) == 1 and 2 or None) 
    122123        cache = CachedRepository(self.env, repos, None, self.log) 
     124        cache.id = 1 
    123125        cache.sync() 
    124126 
    125127        cursor = self.db.cursor() 
     
    137139        t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) 
    138140        cursor = self.db.cursor() 
    139141        cursor.execute("INSERT INTO revision (repos,rev,time,author,message) " 
    140                        "VALUES ('test-repos',0,%s,'','')", 
     142                       "VALUES (1,0,%s,'','')", 
    141143                       (to_timestamp(t1),)) 
    142144        cursor.execute("INSERT INTO revision (repos,rev,time,author,message) " 
    143                        "VALUES ('test-repos',1,%s,'joe','Import')", 
     145                       "VALUES (1,1,%s,'joe','Import')", 
    144146                       (to_timestamp(t2),)) 
    145147        cursor.executemany("INSERT INTO node_change (repos,rev,path," 
    146148                           "node_type,change_type,base_path,base_rev) " 
    147                            "VALUES ('test-repos','1',%s,%s,%s,%s,%s)", 
     149                           "VALUES (1,'1',%s,%s,%s,%s,%s)", 
    148150                           [('trunk', 'D', 'A', None, None), 
    149151                            ('trunk/README', 'F', 'A', None, None)]) 
    150152        cursor.execute("UPDATE repository SET value='1' " 
    151                        "WHERE id='test-repos' AND name='youngest_rev'") 
     153                       "WHERE id=1 AND name='youngest_rev'") 
    152154 
    153155        repos = Mock(Repository, 'test-repos', None, self.log, 
    154156                     get_changeset=lambda x: None, 
     
    157159                     next_rev=lambda x: None, 
    158160                     normalize_rev=lambda rev: rev) 
    159161        cache = CachedRepository(self.env, repos, None, self.log) 
     162        cache.id = 1 
    160163        self.assertEqual('1', cache.youngest_rev) 
    161164        changeset = cache.get_changeset(1) 
    162165        self.assertEqual('joe', changeset.author)