Edgewall Software

Ticket #986: db-changes-986-r2865.2.diff

File db-changes-986-r2865.2.diff, 28.9 kB (added by Manuzhai, 3 years ago)

Replace existing patch by improved version.

  • trac/db_default.py

     
    1818from trac.db import Table, Column, Index 
    1919 
    2020# Database version identifier. Used for automatic upgrades. 
    21 db_version = 16 
     21db_version = 17 
    2222 
    2323def __mkreports(reports): 
    2424    """Utility function used to create report data in same syntax as the 
     
    8383        Column('author'), 
    8484        Column('message'), 
    8585        Index(['time'])], 
    86     Table('node_change', key=('rev', 'path', 'change'))[ 
     86    Table('node_change', key=('rev', 'path', 'change_type'))[ 
    8787        Column('rev'), 
    8888        Column('path'), 
    89         Column('kind', size=1), 
    90         Column('change', size=1), 
     89        Column('node_type', size=1), 
     90        Column('change_type', size=1), 
    9191        Column('base_path'), 
    9292        Column('base_rev'), 
    9393        Index(['rev'])], 
  • trac/versioncontrol/api.py

     
    207207    DIRECTORY = "dir" 
    208208    FILE = "file" 
    209209 
    210     def __init__(self, path, rev, kind): 
    211         assert kind in (Node.DIRECTORY, Node.FILE), "Unknown node kind %s" % kind 
     210    def __init__(self, path, rev, node_type): 
     211        assert node_type in (Node.DIRECTORY, Node.FILE), "Unknown node node_type %s" % node_type 
    212212        self.path = str(path) 
    213213        self.rev = rev 
    214         self.kind = kind 
     214        self.node_type = node_type 
    215215 
    216216    def get_content(self): 
    217217        """ 
     
    274274        raise NotImplementedError 
    275275    last_modified = property(lambda x: x.get_last_modified()) 
    276276 
    277     isdir = property(lambda x: x.kind == Node.DIRECTORY) 
    278     isfile = property(lambda x: x.kind == Node.FILE) 
     277    isdir = property(lambda x: x.node_type == Node.DIRECTORY) 
     278    isfile = property(lambda x: x.node_type == Node.FILE) 
    279279 
    280280 
    281281class Changeset(object): 
     
    297297 
    298298    def get_changes(self): 
    299299        """ 
    300         Generator that produces a (path, kind, change, base_rev, base_path) 
     300        Generator that produces a (path, node_type, change_type, base_rev, base_path) 
    301301        tuple for every change in the changeset, where change can be one of 
    302302        Changeset.ADD, Changeset.COPY, Changeset.DELETE, Changeset.EDIT or 
    303         Changeset.MOVE, and kind is one of Node.FILE or Node.DIRECTORY. 
     303        Changeset.MOVE, and node_type is one of Node.FILE or Node.DIRECTORY. 
    304304        """ 
    305305        raise NotImplementedError 
    306306 
  • trac/versioncontrol/tests/cache.py

     
    6868        self.assertEquals(('0', 41000, '', ''), cursor.fetchone()) 
    6969        self.assertEquals(('1', 42000, 'joe', 'Import'), cursor.fetchone()) 
    7070        self.assertEquals(None, cursor.fetchone()) 
    71         cursor.execute("SELECT rev,path,kind,change,base_path,base_rev " 
     71        cursor.execute("SELECT rev,path,node_type,change_type,base_path,base_rev " 
    7272                       "FROM node_change") 
    7373        self.assertEquals(('1', 'trunk', 'D', 'A', None, None), 
    7474                          cursor.fetchone()) 
     
    8282                       "VALUES (0,41000,'','')") 
    8383        cursor.execute("INSERT INTO revision (rev,time,author,message) " 
    8484                       "VALUES (1,42000,'joe','Import')") 
    85         cursor.executemany("INSERT INTO node_change (rev,path,kind,change," 
     85        cursor.executemany("INSERT INTO node_change (rev,path,node_type,change_type," 
    8686                           "base_path,base_rev) VALUES ('1',%s,%s,%s,%s,%s)", 
    8787                           [('trunk', 'D', 'A', None, None), 
    8888                            ('trunk/README', 'F', 'A', None, None)]) 
     
    101101        cursor.execute("SELECT time,author,message FROM revision WHERE rev='2'") 
    102102        self.assertEquals((42042, 'joe', 'Update'), cursor.fetchone()) 
    103103        self.assertEquals(None, cursor.fetchone()) 
    104         cursor.execute("SELECT path,kind,change,base_path,base_rev " 
     104        cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 
    105105                       "FROM node_change WHERE rev='2'") 
    106106        self.assertEquals(('trunk/README', 'F', 'E', 'trunk/README', '1'), 
    107107                          cursor.fetchone()) 
     
    113113                       "VALUES (0,41000,'','')") 
    114114        cursor.execute("INSERT INTO revision (rev,time,author,message) " 
    115115                       "VALUES (1,42000,'joe','Import')") 
    116         cursor.executemany("INSERT INTO node_change (rev,path,kind,change," 
     116        cursor.executemany("INSERT INTO node_change (rev,path,node_type,change_type," 
    117117                           "base_path,base_rev) VALUES ('1',%s,%s,%s,%s,%s)", 
    118118                           [('trunk', 'D', 'A', None, None), 
    119119                            ('trunk/README', 'F', 'A', None, None)]) 
  • trac/versioncontrol/svn_fs.py

     
    4040        def apr_pool_clear(): pass 
    4141        Editor = object 
    4242    delta = core = dummy_svn() 
    43      
    4443 
    45 _kindmap = {core.svn_node_dir: Node.DIRECTORY, 
    46             core.svn_node_file: Node.FILE} 
    4744 
     45_node_type_map = {core.svn_node_dir: Node.DIRECTORY, 
     46                  core.svn_node_file: Node.FILE} 
    4847 
     48 
    4949application_pool = None 
    50      
     50 
    5151def _get_history(path, authz, fs_ptr, pool, start, end, limit=None): 
    5252    history = [] 
    5353    if hasattr(repos, 'svn_repos_history2'): 
     
    9393    apr_pool_destroy = staticmethod(core.apr_pool_destroy) 
    9494    apr_terminate = staticmethod(core.apr_terminate) 
    9595    apr_pool_clear = staticmethod(core.apr_pool_clear) 
    96      
     96 
    9797    def __init__(self, parent_pool=None): 
    9898        """Create a new memory pool""" 
    9999 
     
    224224                  (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_MICRO) 
    225225 
    226226        self.pool = Pool() 
    227          
     227 
    228228        # Remove any trailing slash or else subversion might abort 
    229229        path = os.path.normpath(path).replace('\\', '/') 
    230230        self.path = repos.svn_repos_find_root_path(path, self.pool()) 
     
    234234 
    235235        self.repos = repos.svn_repos_open(self.path, self.pool()) 
    236236        self.fs_ptr = repos.svn_repos_fs(self.repos) 
    237          
     237 
    238238        uuid = fs.get_uuid(self.fs_ptr, self.pool()) 
    239239        name = 'svn:%s:%s' % (uuid, path) 
    240240 
     
    259259            pool = self.pool 
    260260        rev_root = fs.revision_root(self.fs_ptr, rev, pool()) 
    261261        node_type = fs.check_path(rev_root, self.scope + path, pool()) 
    262         return node_type in _kindmap 
     262        return node_type in _node_type_map 
    263263 
    264264    def normalize_path(self, path): 
    265265        return _normalize_path(path) 
     
    410410            raise TracError, ('The Target for Diff is invalid: path %s' 
    411411                              ' doesn\'t exist in revision %s' \ 
    412412                              % (new_path, new_rev)) 
    413         if new_node.kind != old_node.kind: 
     413        if new_node.node_type != old_node.node_type: 
    414414            raise TracError, ('Diff mismatch: Base is a %s (%s in revision %s) ' 
    415415                              'and Target is a %s (%s in revision %s).' \ 
    416                               % (old_node.kind, old_path, old_rev, 
    417                                  new_node.kind, new_path, new_rev)) 
     416                              % (old_node.node_type, old_path, old_rev, 
     417                                 new_node.node_type, new_path, new_rev)) 
    418418        subpool = Pool(self.pool) 
    419419        if new_node.isdir: 
    420420            editor = DiffChangeEditor() 
     
    434434                                      entry_props, 
    435435                                      ignore_ancestry, 
    436436                                      subpool()) 
    437             for path, kind, change in editor.deltas: 
     437            for path, node_type, change_type in editor.deltas: 
    438438                old_node = new_node = None 
    439                 if change != Changeset.ADD: 
     439                if change_type != Changeset.ADD: 
    440440                    old_node = self.get_node(posixpath.join(old_path, path), 
    441441                                             old_rev) 
    442                 if change != Changeset.DELETE: 
     442                if change_type != Changeset.DELETE: 
    443443                    new_node = self.get_node(posixpath.join(new_path, path), 
    444444                                             new_rev) 
    445445                else: 
    446                     kind = _kindmap[fs.check_path(old_root, 
     446                    node_type = _node_type_map[fs.check_path(old_root, 
    447447                                                  self.scope + old_node.path, 
    448448                                                  subpool())] 
    449                 yield  (old_node, new_node, kind, change) 
     449                yield  (old_node, new_node, node_type, change_type) 
    450450        else: 
    451451            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) 
    452452            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) 
     
    471471 
    472472        self.root = fs.revision_root(fs_ptr, rev, self.pool()) 
    473473        node_type = fs.check_path(self.root, self.scoped_path, self.pool()) 
    474         if not node_type in _kindmap: 
     474        if not node_type in _node_type_map: 
    475475            raise TracError, "No node at %s in revision %s" % (path, rev) 
    476476        self.created_rev = fs.node_created_rev(self.root, self.scoped_path, 
    477477                                               self.pool()) 
     
    484484        #          * the node existed at (created_path,created_rev) 
    485485        # TODO: check node id 
    486486        self.rev = self.created_rev 
    487          
    488         Node.__init__(self, path, self.rev, _kindmap[node_type]) 
    489487 
     488        Node.__init__(self, path, self.rev, _node_type_map[node_type]) 
     489 
    490490    def get_content(self): 
    491491        if self.isdir: 
    492492            return None 
     
    608608                    revroots[b_rev] = b_root 
    609609                change.base_path = fs.node_created_path(b_root, b_path, tmp()) 
    610610                change.base_rev = fs.node_created_rev(b_root, b_path, tmp()) 
    611             kind = _kindmap[change.item_kind] 
     611            kind = _node_type_map[change.item_kind] 
    612612            path = path[len(self.scope) - 1:] 
    613613            base_path = _path_within_scope(self.scope, change.base_path) 
    614614            changes.append([path, kind, action, base_path, change.base_rev]) 
     
    642642# Note 2: the 'dir_baton' is the path of the parent directory 
    643643# 
    644644 
    645 class DiffChangeEditor(delta.Editor):  
     645class DiffChangeEditor(delta.Editor): 
    646646 
    647647    def __init__(self): 
    648648        self.deltas = [] 
    649      
     649 
    650650    # -- svn.delta.Editor callbacks 
    651651 
    652652    def open_root(self, base_revision, dir_pool): 
  • trac/versioncontrol/cache.py

     
    1818from trac.versioncontrol import Changeset, Node, Repository, Authorizer 
    1919 
    2020 
    21 _kindmap = {'D': Node.DIRECTORY, 'F': Node.FILE} 
    22 _actionmap = {'A': Changeset.ADD, 'C': Changeset.COPY, 
     21_node_typemap = {'D': Node.DIRECTORY, 'F': Node.FILE} 
     22_change_typemap = {'A': Changeset.ADD, 'C': Changeset.COPY, 
    2323              'D': Changeset.DELETE, 'E': Changeset.EDIT, 
    2424              'M': Changeset.MOVE} 
    2525 
     
    6464            authz = self.repos.authz 
    6565            self.repos.authz = Authorizer() # remove permission checking 
    6666 
    67             kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 
    68             actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) 
     67            node_typemap = dict(zip(_node_typemap.values(), _node_typemap.keys())) 
     68            change_typemap = dict(zip(_change_typemap.values(), _change_typemap.keys())) 
    6969            self.log.info("Syncing with repository (%s to %s)" 
    7070                          % (youngest_stored, self.repos.youngest_rev)) 
    7171            if youngest_stored: 
     
    8282                               "VALUES (%s,%s,%s,%s)", (str(current_rev), 
    8383                               changeset.date, changeset.author, 
    8484                               changeset.message)) 
    85                 for path,kind,action,base_path,base_rev in changeset.get_changes(): 
     85                for path,node_type,change_type,base_path,base_rev in changeset.get_changes(): 
    8686                    self.log.debug("Caching node change in [%s]: %s" 
    87                                    % (current_rev, (path, kind, action, 
     87                                   % (current_rev, (path, node_type, change_type, 
    8888                                      base_path, base_rev))) 
    89                     kind = kindmap[kind] 
    90                     action = actionmap[action] 
    91                     cursor.execute("INSERT INTO node_change (rev,path,kind," 
    92                                    "change,base_path,base_rev) " 
     89                    node_type = node_typemap[node_type] 
     90                    change_type = change_typemap[change_type] 
     91                    cursor.execute("INSERT INTO node_change (rev,path,node_type," 
     92                                   "change_type,base_path,base_rev) " 
    9393                                   "VALUES (%s,%s,%s,%s,%s,%s)", 
    94                                    (str(current_rev), path, kind, action, 
     94                                   (str(current_rev), path, node_type, change_type, 
    9595                                   base_path, base_rev)) 
    9696                current_rev = self.repos.next_rev(current_rev) 
    9797            self.db.commit() 
     
    148148 
    149149    def get_changes(self): 
    150150        cursor = self.db.cursor() 
    151         cursor.execute("SELECT path,kind,change,base_path,base_rev " 
     151        cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 
    152152                       "FROM node_change WHERE rev=%s " 
    153153                       "ORDER BY path", (self.rev,)) 
    154         for path, kind, change, base_path, base_rev in cursor: 
     154        for path, node_type, change_type, base_path, base_rev in cursor: 
    155155            if not self.authz.has_permission(path): 
    156156                # FIXME: what about the base_path? 
    157157                continue 
    158             kind = _kindmap[kind] 
    159             change = _actionmap[change] 
    160             yield path, kind, change, base_path, base_rev 
     158            node_type = _node_typemap[node_type] 
     159            change_type = _change_typemap[change_type] 
     160            yield path, node_type, change_type, base_path, base_rev 
  • trac/versioncontrol/web_ui/changeset.py

     
    7777    # IRequestHandler methods 
    7878 
    7979    _request_re = re.compile(r"/changeset(?:/([^/]+))?(/.*)?$") 
    80      
     80 
    8181    def match_request(self, req): 
    8282        match = re.match(self._request_re, req.path_info) 
    8383        if match: 
     
    9595         * If `new_path` and `old_path` are equal (or `old_path` is omitted) 
    9696           and `new` and `old` are equal (or `old` is omitted), 
    9797           then we're about to view a revision Changeset: `chgset` is True. 
    98            Furthermore, if the path is not the root, the changeset is  
     98           Furthermore, if the path is not the root, the changeset is 
    9999           ''restricted'' to that path (only the changes affecting that path, 
    100100           its children or its ancestor directories will be shown). 
    101101         * In any other case, the set of changes corresponds to arbitrary 
     
    103103           are equal, the ''restricted'' flag will also be set, meaning in this 
    104104           case that the differences between two revisions are restricted to 
    105105           those occurring on that path. 
    106           
     106 
    107107        In any case, either path@rev pairs must exist. 
    108108        """ 
    109109        req.perm.assert_permission('CHANGESET_VIEW') 
    110          
     110 
    111111        # -- retrieve arguments 
    112112        new_path = req.args.get('new_path') 
    113113        new = req.args.get('new') 
     
    245245                                                        rev=diff.old_rev), 
    246246                      } 
    247247            } 
    248          
     248 
    249249        if chgset: # Changeset Mode (possibly restricted on a path) 
    250250            path, rev = diff.new_path, diff.new_rev 
    251251 
    252             # -- getting the change summary from the Changeset.get_changes  
     252            # -- getting the change summary from the Changeset.get_changes 
    253253            def get_changes(): 
    254254                old_node = new_node = None 
    255                 for npath, kind, change, opath, orev in chgset.get_changes(): 
     255                for npath, node_type, change_type, opath, orev in chgset.get_changes(): 
    256256                    if restricted and \ 
    257257                           not (npath.startswith(path)      # npath is below 
    258258                                or path.startswith(npath)): # npath is above 
     
    261261                        old_node = repos.get_node(opath, orev) 
    262262                    if change != Changeset.DELETE: 
    263263                        new_node = repos.get_node(npath, rev) 
    264                     yield old_node, new_node, kind, change 
    265                      
     264                    yield old_node, new_node, node_type, change_type 
     265 
    266266            def _changeset_title(rev): 
    267267                if restricted: 
    268268                    return 'Changeset %s for %s' % (rev, path) 
     
    314314                    add_link(req, 'next', next_href, _changeset_title(next_rev)) 
    315315 
    316316        else: # Diff Mode 
    317             # -- getting the change summary from the Repository.get_changes  
     317            # -- getting the change summary from the Repository.get_changes 
    318318            def get_changes(): 
    319319                for d in repos.get_changes(**diff): 
    320320                    yield d 
    321                      
     321 
    322322            reverse_href = self.env.href.changeset(diff.old_rev, diff.old_path, 
    323323                                                   old=diff.new_rev, 
    324324                                                   old_path=diff.new_path) 
     
    411411                return [] 
    412412 
    413413        idx = 0 
    414         for old_node, new_node, kind, change in get_changes(): 
    415             if change != Changeset.EDIT: 
     414        for old_node, new_node, node_type, change_type in get_changes(): 
     415            if change_type != Changeset.EDIT: 
    416416                show_entry = True 
    417417            else: 
    418418                show_entry = False 
     
    421421                if props: 
    422422                    req.hdf['changeset.changes.%d.props' % idx] = props 
    423423                    show_entry = True 
    424                 if kind == Node.FILE: 
     424                if node_type == Node.FILE: 
    425425                    diffs = _content_changes(old_node, new_node) 
    426426                    if diffs != []: 
    427427                        if diffs: 
     
    429429                        # elif None (means: manually compare to (previous)) 
    430430                        show_entry = True 
    431431            if show_entry: 
    432                 info = _change_info(old_node, new_node, change) 
     432                info = _change_info(old_node, new_node, change_type) 
    433433                req.hdf['changeset.changes.%d' % idx] = info 
    434434            idx += 1 # the sequence should be immutable 
    435435 
     
    442442        req.end_headers() 
    443443 
    444444        mimeview = Mimeview(self.env) 
    445         for old_node, new_node, kind, change in repos.get_changes(**diff): 
     445        for old_node, new_node, node_type, change_type in repos.get_changes(**diff): 
    446446            # TODO: Property changes 
    447447 
    448448            # Content changes 
    449             if kind == Node.DIRECTORY: 
     449            if node_type == Node.DIRECTORY: 
    450450                continue 
    451451 
    452452            new_content = old_content = '' 
     
    463463                data = new_node.get_content().read() 
    464464                if is_binary(data): 
    465465                    continue 
    466                 new_content = mimeview.to_utf8(data, new_node.content_type)  
     466                new_content = mimeview.to_utf8(data, new_node.content_type) 
    467467                new_node_info = (new_node.path, new_node.rev) 
    468468                new_path = new_node.path 
    469469            else: 
     
    510510 
    511511        buf = StringIO() 
    512512        zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 
    513         for old_node, new_node, kind, change in repos.get_changes(**diff): 
    514             if kind == Node.FILE and change != Changeset.DELETE: 
     513        for old_node, new_node, node_type, change_type in repos.get_changes(**diff): 
     514            if node_type == Node.FILE and change_type != Changeset.DELETE: 
    515515                assert new_node 
    516516                zipinfo = ZipInfo() 
    517517                zipinfo.filename = new_node.path 
     
    523523        buf.seek(0, 2) # be sure to be at the end 
    524524        req.send_header("Content-Length", buf.tell()) 
    525525        req.end_headers() 
    526          
     526 
    527527        req.write(buf.getvalue()) 
    528528 
    529529    def title_for_diff(self, diff): 
     
    582582 
    583583    def get_wiki_syntax(self): 
    584584        yield ( 
    585             # [...] form: start with optional intertrac: [T... or [trac ...  
     585            # [...] form: start with optional intertrac: [T... or [trac ... 
    586586            r"!?\[(?P<it_changeset>%s\s*)?" % Formatter.INTERTRAC_SCHEME + 
    587587            #  digits + optional path for the restricted changeset 
    588             r"\d+(?:/[^\]]*)?\]|"    
     588            r"\d+(?:/[^\]]*)?\]|" 
    589589            # r... form: allow r1 but not r1:2 (handled by the log syntax) 
    590590            r"(?:\b|!)r\d+\b(?!:\d)", 
    591591            lambda x, y, z: 
     
    631631            old, new = pathrev(p1), pathrev(p2) 
    632632            diff = DiffArgs(old_path=old[0], old_rev=old[1], 
    633633                            new_path=new[0], new_rev=new[1]) 
    634         else:  
     634        else: 
    635635            old_path, old_rev = pathrev(params) 
    636636            new_rev = None 
    637637            if old_rev and ':' in old_rev: 
     
    640640                            new_path=old_path, new_rev=new_rev) 
    641641        title = self.title_for_diff(diff) 
    642642        href = formatter.href.changeset(new_path=diff.new_path or None, 
    643                                         new=diff.new_rev,  
     643                                        new=diff.new_rev, 
    644644                                        old_path=diff.old_path or None, 
    645645                                        old=diff.old_rev) 
    646646        return '<a class="changeset" title="%s" href="%s">%s</a>' \ 
    647647               % (title, href, label) 
    648      
     648 
    649649    # ISearchProvider methods 
    650650 
    651651    def get_search_filters(self, req): 
     
    685685        old_path = req.args.get('old_path') 
    686686        old_rev = req.args.get('old_rev') 
    687687 
    688         # -- normalize  
     688        # -- normalize 
    689689        repos = self.env.get_repository(req.authname) 
    690690        new_path = repos.normalize_path(new_path) 
    691691        new_rev = repos.normalize_rev(new_rev) 
     
    695695        authzperm = SubversionAuthorizer(self.env, req.authname) 
    696696        authzperm.assert_permission_for_changeset(new_rev) 
    697697        authzperm.assert_permission_for_changeset(old_rev) 
    698          
     698 
    699699        # -- prepare rendering 
    700700        req.hdf['anydiff'] = { 
    701701            'new_path': new_path, 
  • trac/upgrades/db17.py

     
     1from trac.db import Table, Column, Index, DatabaseManager 
     2 
     3def do_upgrade(env, ver, cursor): 
     4    cursor.execute("CREATE TEMP TABLE node_change_old AS SELECT * FROM node_change") 
     5    cursor.execute("DROP TABLE node_change") 
     6 
     7    db = env.get_db_cnx() 
     8    node_change_table = Table('node_change', key=('rev', 'path', 'change_type'))[ 
     9        Column('rev'), 
     10        Column('path'), 
     11        Column('node_type', size=1), 
     12        Column('change_type', size=1), 
     13        Column('base_path'), 
     14        Column('base_rev'), 
     15        Index(['rev'])] 
     16    db_backend, _ = DatabaseManager(env)._get_connector() 
     17    for stmt in db_backend.to_sql(node_change_table): 
     18        cursor.execute(stmt) 
     19 
     20    cursor.execute("INSERT INTO node_change (rev,path,node_type,change_type,base_path,base_rev) " 
     21                   "SELECT rev,path,kind,change,base_path,base_rev " 
     22                   "FROM node_change_old") 
  • templates/changeset.cs

     
    88   if:changeset.chgset ?><?cs 
    99    if:changeset.restricted ?><?cs 
    1010     set:change = "Change" ?><?cs 
    11     else ?><?cs  
     11    else ?><?cs 
    1212     set:change = "Changeset" ?><?cs 
    1313    /if ?> 
    1414    <li class="first"><?cs 
    15      if:len(links.prev) ?> &larr;  
     15     if:len(links.prev) ?> &larr; 
    1616      <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs 
    17        var:links.prev.0.title ?>">Previous <?cs var:change ?></a> <?cs  
     17       var:links.prev.0.title ?>">Previous <?cs var:change ?></a> <?cs 
    1818     else ?> 
    19       <span class="missing">&larr; Previous <?cs var:change ?></span><?cs  
     19      <span class="missing">&larr; Previous <?cs var:change ?></span><?cs 
    2020     /if ?> 
    2121    </li> 
    2222    <li class="last"><?cs 
    2323     if:len(links.next) ?> 
    2424      <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs 
    25        var:links.next.0.title ?>">Next <?cs var:change ?></a> &rarr; <?cs  
     25       var:links.next.0.title ?>">Next <?cs var:change ?></a> &rarr; <?cs 
    2626     else ?> 
    2727      <span class="missing">Next <?cs var:change ?> &rarr;</span><?cs 
    2828     /if ?>