Edgewall Software

Ticket #199: diff_module_alpha1.diff

File diff_module_alpha1.diff, 28.9 KB (added by cboos, 3 years ago)

For your testing pleasure, this is my initial attempt. It's not 100% robust, but it does show the new interface and ideas (see follow-up comment)

  • htdocs/css/browser.css

     
    4545#dirlist td.name a, #dirlist td.rev a { border-bottom: none; display: block } 
    4646#dirlist td.change * { font-size: 9px } 
    4747 
     48/* Log */ 
     49#diff { margin: 0; } 
     50 
    4851/* Styles for the revision log table 
    4952   (extends the styles for "table.listing") */ 
    5053#chglist { margin-top: 0 } 
  • trac/db_default.py

     
    458458 
    459459default_components = ('trac.About', 'trac.attachment', 'trac.Browser', 
    460460                      'trac.Changeset', 'trac.Search', 'trac.Settings', 
     461                      'trac.Diff', 
    461462                      'trac.ticket.query', 'trac.ticket.report', 
    462463                      'trac.Roadmap', 
    463464                      'trac.ticket.web_ui', 'trac.Timeline', 'trac.wiki.web_ui', 
  • trac/versioncontrol/svn_fs.py

     
    2727import os.path 
    2828import time 
    2929import weakref 
     30import posixpath 
    3031 
    3132from svn import fs, repos, core, delta 
    3233 
     
    176177        self.close() 
    177178 
    178179    def normalize_path(self, path): 
    179         return path == '/' and path or path.strip('/') 
     180        return path == '/' and path or path and path.strip('/') or '' 
    180181 
    181182    def normalize_rev(self, rev): 
    182183        try: 
     
    361362            props[name] = str(value) # Make sure the value is a proper string 
    362363        return props 
    363364 
     365    def get_diff(self, older_node, ignore_ancestry=1): 
     366        if self.isfile and not older_node.isfile: 
     367            raise TracError, "Trying to diff a %s with a %s." % (self.kind, older_node.kind) 
     368        editor = DiffChangeEditor() 
     369        e_ptr, e_baton = delta.make_editor(editor, self.pool) 
     370        old_root = fs.revision_root(self.fs_ptr, older_node.created_rev, self.pool) 
     371        new_root = fs.revision_root(self.fs_ptr, self.created_rev, self.pool) 
     372        if older_node.isfile: 
     373            old_dir, old_entry = posixpath.split(older_node.created_path) 
     374        else: 
     375            old_dir, old_entry = older_node.created_path, '' 
     376        def authz_cb(root, path, pool): return 1 
     377        text_deltas = 0 # as this is currently redone in Diff.py...  
     378        repos.svn_repos_dir_delta(old_root, old_dir, old_entry, 
     379                                  new_root, self.created_path, e_ptr, e_baton, authz_cb, 
     380                                  text_deltas, self.isdir, 0, ignore_ancestry, self.pool) 
     381        for d in editor.deltas: 
     382            yield d 
     383 
    364384    def get_content_length(self): 
    365385        if self.isdir: 
    366386            return None 
     
    450470 
    451471    def _get_prop(self, name): 
    452472        return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool) 
     473 
     474 
     475# 
     476# Delta editor for diffs between arbitrary nodes (recycling my old code for #295 :) ) 
     477# 
     478# Note 1: the 'copyfrom_path' and 'copyfrom_rev' information is not used 
     479#         because 'repos.svn_repos_dir_delta' *doesn't* provide it. 
     480# 
     481# Note 2: the 'dir_baton' is the path of the parent directory 
     482# 
     483 
     484class DiffChangeEditor(delta.Editor):  
     485 
     486    def __init__(self): 
     487        self.deltas = [] 
     488        self.skip_dir_prop_change = 0 
     489 
     490    def _norm(self, path): 
     491        """Path are normalized to __not__ have a leading slash""" 
     492        return path == '/' and path or path and path.strip('/') or '' 
     493     
     494    # -- svn.delta.Editor callbacks 
     495 
     496    def open_root(self, base_revision, dir_pool): 
     497        return '/' 
     498 
     499    def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev, dir_pool): 
     500        self.deltas.append((path, Node.DIRECTORY, Changeset.ADD)) 
     501        # don't create an additional 'Changeset.EDIT' entry for this directory 
     502        # in case there's also a dir property change: 
     503        self.skip_dir_prop_change = 1  
     504        return path 
     505 
     506    def open_directory(self, path, dir_baton, base_revision, dir_pool): 
     507        self.deltas.append((path, Node.DIRECTORY, Changeset.EDIT)) 
     508        self.skip_dir_prop_change = 0 
     509        return path 
     510 
     511    def change_dir_prop(self, dir_baton, name, value, pool): 
     512        if self.skip_dir_prop_change: 
     513            return 
     514        self.deltas.append((dir_baton, Node.DIRECTORY, Changeset.EDIT)) 
     515        self.skip_dir_prop_change = 1 
     516 
     517    def close_directory(self, dir_baton): 
     518        self.skip_dir_prop_change = 0 
     519 
     520    def delete_entry(self, path, revision, dir_baton, pool): 
     521        self.deltas.append((path, Node.FILE, Changeset.DELETE)) # should be Node.UNKNOWN 
     522 
     523    def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision, dir_pool): 
     524        self.deltas.append((self._norm(path), Node.FILE, Changeset.ADD)) 
     525 
     526    def open_file(self, path, dir_baton, dummy_rev, file_pool): 
     527        self.deltas.append((self._norm(path), Node.FILE, Changeset.EDIT)) 
     528 
  • trac/versioncontrol/main.py

     
    162162        """ 
    163163        raise NotImplementedError 
    164164 
     165    def get_diff(self, older_node, ignore_ancestry=1): 
     166        """ 
     167        Generator that yields (path, kind, chg) tuples representing 
     168        the diffs to this node, from the older node given as parameter. 
     169        """ 
     170        raise NotImplementedError 
     171 
    165172    def get_content_length(self): 
    166173        raise NotImplementedError 
    167174    content_length = property(lambda x: x.get_content_length()) 
  • trac/Diff.py

     
    2424from __future__ import generators 
    2525import time 
    2626import re 
     27import posixpath 
    2728 
    2829from trac import mimeview, perm, util 
    2930from trac.core import * 
    30 from trac.Timeline import ITimelineEventProvider 
    3131from trac.versioncontrol import Changeset, Node 
    3232from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff 
    3333from trac.web.chrome import add_link, add_stylesheet 
     
    3535from trac.wiki import wiki_to_html, wiki_to_oneliner 
    3636 
    3737 
    38 class ChangesetModule(Component): 
     38class Diff(dict): 
     39    def __getattr__(self,str): 
     40        return self[str] 
     41     
    3942 
    40     implements(IRequestHandler, ITimelineEventProvider) 
     43class DiffModule(Component): 
    4144 
     45    implements(IRequestHandler) 
     46 
    4247    # IRequestHandler methods 
    4348 
    4449    def match_request(self, req): 
    45         match = re.match(r'/changeset/([0-9]+)$', req.path_info) 
     50        print 'match_request in DiffModule' 
     51        match = re.match(r'/diff(?:(/.*)|$)', req.path_info) 
    4652        if match: 
    47             req.args['rev'] = match.group(1) 
     53            req.args['path'] = match.group(1) 
    4854            return 1 
    4955 
    5056    def process_request(self, req): 
    5157        req.perm.assert_permission(perm.CHANGESET_VIEW) 
    5258 
    53         rev = req.args.get('rev') 
     59        path = req.args.get('path') 
    5460        repos = self.env.get_repository(req.authname) 
     61        path = repos.normalize_path(path) 
     62        rev = req.args.get('rev', repos.youngest_rev) # 'path history' mode 
     63        old = req.args.get('old')                     # 'arbitrary diff' mode 
     64        new = req.args.get('new') 
     65        old_path = req.args.get('old_path', path) 
    5566 
    5667        diff_options = get_diff_options(req) 
    5768        if req.args.has_key('update'): 
    58             req.redirect(self.env.href.changeset(rev)) 
     69            if old or new: 
     70                req.redirect(self.env.href.diff(path, new=new, old_path=old_path, old=old)) 
     71            else: 
     72                req.redirect(self.env.href.diff(path, rev=rev)) 
    5973 
    60         chgset = repos.get_changeset(rev) 
    61         req.check_modified(chgset.date, 
    62                            diff_options[0] + ''.join(diff_options[1])) 
     74        if old or new: 
     75            chgset = None 
     76            if not new: 
     77                new = repos.next_rev(old) # FIXME: must lookup the next entry in node history 
     78            elif not old: 
     79                old = repos.previous_rev(new) 
     80            if not old_path: 
     81                old_path = path 
     82            diff = Diff(old_path=old_path, old_rev=old, 
     83                        new_path=path, new_rev=new) 
     84        else: 
     85            chgset = repos.get_changeset(rev) 
     86            diff = Diff(old_path=path, old_rev=repos.previous_rev(rev), 
     87                        new_path=path, new_rev=rev) 
     88         
     89        # TODO: 
     90#         req.check_modified(chgset.date, 
     91#                            diff_options[0] + ''.join(diff_options[1])) 
    6392 
    6493        format = req.args.get('format') 
    6594        if format == 'diff': 
    66             self._render_diff(req, repos, chgset, diff_options) 
     95            self._render_diff(req, repos, diff, chgset, diff_options) 
    6796            return 
    6897        elif format == 'zip': 
    69             self._render_zip(req, repos, chgset) 
     98            self._render_zip(req, repos, diff, chgset) 
    7099            return 
    71100 
    72         self._render_html(req, repos, chgset, diff_options) 
     101        self._render_html(req, repos, diff, chgset, diff_options) 
    73102        add_link(req, 'alternate', '?format=diff', 'Unified Diff', 
    74103                 'text/plain', 'diff') 
    75104        add_link(req, 'alternate', '?format=zip', 'Zip Archive', 
    76105                 'application/zip', 'zip') 
    77106        add_stylesheet(req, 'changeset.css') 
    78107        add_stylesheet(req, 'diff.css') 
    79         return 'changeset.cs', None 
     108        return 'diff.cs', None 
    80109 
    81     # ITimelineEventProvider methods 
    82110 
    83     def get_timeline_filters(self, req): 
    84         if req.perm.has_permission(perm.CHANGESET_VIEW): 
    85             yield ('changeset', 'Repository checkins') 
    86  
    87     def get_timeline_events(self, req, start, stop, filters): 
    88         if 'changeset' in filters: 
    89             absurls = req.args.get('format') == 'rss' # Kludge 
    90             show_files = int(self.config.get('timeline', 
    91                                              'changeset_show_files')) 
    92             db = self.env.get_db_cnx() 
    93             repos = self.env.get_repository() 
    94             rev = repos.youngest_rev 
    95             while rev: 
    96                 chgset = repos.get_changeset(rev) 
    97                 if chgset.date < start: 
    98                     return 
    99                 if chgset.date < stop: 
    100                     if absurls: 
    101                         href = self.env.abs_href.changeset(chgset.rev) 
    102                     else: 
    103                         href = self.env.href.changeset(chgset.rev) 
    104                     title = 'Changeset <em>[%s]</em> by %s' % ( 
    105                             util.escape(chgset.rev), util.escape(chgset.author)) 
    106                     message = wiki_to_oneliner(util.shorten_line(chgset.message or '--'), 
    107                                                self.env, db, absurls=absurls) 
    108                     if show_files: 
    109                         files = [] 
    110                         for chg in chgset.get_changes(): 
    111                             if show_files > 0 and len(files) >= show_files: 
    112                                 files.append('...') 
    113                                 break 
    114                             files.append('<span class="%s">%s</span>' 
    115                                          % (chg[2], util.escape(chg[0]))) 
    116                         message = '<span class="changes">' + ', '.join(files) +\ 
    117                                   '</span>: ' + message 
    118                     yield 'changeset', href, title, chgset.date, chgset.author,\ 
    119                           message 
    120                 rev = repos.previous_rev(rev) 
    121  
    122111    # Internal methods 
    123112 
    124     def _render_html(self, req, repos, chgset, diff_options): 
     113    def _render_html(self, req, repos, diff, chgset, diff_options): 
    125114        """HTML version""" 
    126         req.hdf['title'] = '[%s]' % chgset.rev 
    127         req.hdf['changeset'] = { 
    128             'revision': chgset.rev, 
    129             'time': time.strftime('%c', time.localtime(chgset.date)), 
    130             'author': util.escape(chgset.author or 'anonymous'), 
    131             'message': wiki_to_html(chgset.message or '--', self.env, req, 
    132                                     escape_newlines=True) 
    133         } 
     115        req.hdf['diff'] = diff 
     116        req.hdf['diff.href'] = { 
     117            'new_rev': self.env.href.changeset(diff.new_rev), 
     118            'old_rev': self.env.href.changeset(diff.old_rev), 
     119            'new_path': self.env.href.browser(diff.new_path, rev=diff.new_rev), 
     120            'old_path': self.env.href.changeset(diff.old_path, rev=diff.old_rev) 
     121            } 
     122        if chgset: # 'path history' mode 
     123            req.hdf['title'] = 'Changes for %s at Revision %s' % (diff.new_path, chgset.rev) 
     124            req.hdf['changeset'] = { 
     125                'revision': chgset.rev, 
     126                'time': time.strftime('%c', time.localtime(chgset.date)), 
     127                'author': util.escape(chgset.author or 'anonymous'), 
     128                'message': wiki_to_html(chgset.message or '--', self.env, req, 
     129                                        escape_newlines=True) 
     130                } 
    134131 
    135         oldest_rev = repos.oldest_rev 
    136         if chgset.rev != oldest_rev: 
    137             add_link(req, 'first', self.env.href.changeset(oldest_rev), 
    138                      'Changeset %s' % oldest_rev) 
    139             previous_rev = repos.previous_rev(chgset.rev) 
    140             add_link(req, 'prev', self.env.href.changeset(previous_rev), 
    141                      'Changeset %s' % previous_rev) 
    142         youngest_rev = repos.youngest_rev 
    143         if str(chgset.rev) != str(youngest_rev): 
    144             next_rev = repos.next_rev(chgset.rev) 
    145             add_link(req, 'next', self.env.href.changeset(next_rev), 
    146                      'Changeset %s' % next_rev) 
    147             add_link(req, 'last', self.env.href.changeset(youngest_rev), 
    148                      'Changeset %s' % youngest_rev) 
     132            oldest_rev = repos.oldest_rev 
     133            if chgset.rev != oldest_rev: 
     134                add_link(req, 'first', self.env.href.diff(diff.old_path, rev=oldest_rev), 
     135                         'Changeset %s' % oldest_rev) # FIXME (use the history) 
     136                previous_rev = repos.previous_rev(chgset.rev) 
     137                add_link(req, 'prev', self.env.href.diff(diff.old_path, rev=previous_rev), 
     138                         'Changeset %s' % previous_rev) 
     139            youngest_rev = repos.youngest_rev 
     140            if str(chgset.rev) != str(youngest_rev): 
     141                next_rev = repos.next_rev(chgset.rev) 
     142                add_link(req, 'next', self.env.href.diff(diff.new_path, rev=next_rev), 
     143                         'Changeset %s' % next_rev) 
     144                add_link(req, 'last', self.env.href.diff(diff.new_path, rev=youngest_rev), 
     145                         'Changeset %s' % youngest_rev) 
     146        elif diff.new_path == diff.old_path: # 'diff between 2 revisions' mode 
     147            req.hdf['title'] = 'Diff for %s between Revisions %s and %s' \ 
     148                               % (diff.new_path, diff.old_rev, diff.new_rev) 
     149        else:                           # 'arbitrary diff' mode 
     150            req.hdf['title'] = 'Diff between %s at Revision %s and %s at Revision %s' \ 
     151                               % (diff.old_path, diff.old_rev, 
     152                                  diff.new_path, diff.new_rev) 
    149153 
     154        ## FIXME: move the get_diff at the level of the repos 
     155        try: 
     156            old_root_node = repos.get_node(diff.old_path, diff.old_rev) 
     157        except TracError: 
     158            old_root_node = None 
     159        try: 
     160            new_root_node = repos.get_node(diff.new_path, diff.new_rev) 
     161        except TracError: 
     162            new_root_node = None 
     163 
     164        if not old_root_node and not new_root_node: 
     165            raise TracError, 'Invalid diff request' # FIXME be more explicit 
     166        elif not old_root_node: 
     167            raise TracError, 'Unsupported diff request' # FIXME: add everything 
     168        elif not new_root_node: 
     169            raise TracError, 'Unsupported diff request' # FIXME: del everything 
     170 
     171        if old_root_node.isdir and new_root_node.isdir: 
     172            old_base = diff.old_path 
     173            new_base = diff.new_path 
     174        elif old_root_node.isfile and new_root_node.isfile: 
     175            old_base = posixpath.split(diff.old_path)[0] 
     176            new_base = posixpath.split(diff.new_path)[0] 
     177        else: 
     178            raise TracError, "Trying to diff a %s with a %s." \ 
     179                  % (old_root_node.kind, new_root_node.kind) 
    150180        edits = [] 
    151181        idx = 0 
    152         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
     182        for path, kind, change in new_root_node.get_diff(old_root_node): 
     183            old_path = posixpath.join(old_base, path) 
     184            new_path = posixpath.join(new_base, path) 
     185            print 'delta %d: %s %s delta from %s@%s to %s@%s' \ 
     186                  % (idx, change, kind, old_path, diff.old_rev, new_path, diff.new_rev) 
     187            old_node = repos.get_node(old_path, diff.old_rev) 
     188            new_node = repos.get_node(new_path, diff.new_rev) 
    153189            info = {'change': change} 
     190            base_path = old_path 
     191            base_rev = diff.old_rev 
     192            path = new_path 
     193            rev = diff.new_rev 
    154194            if base_path: 
    155195                info['path.old'] = base_path 
    156196                info['rev.old'] = base_rev 
     
    158198                                                                 rev=base_rev) 
    159199            if path: 
    160200                info['path.new'] = path 
    161                 info['rev.new'] = chgset.rev 
     201                info['rev.new'] = rev 
    162202                info['browser_href.new'] = self.env.href.browser(path, 
    163                                                                  rev=chgset.rev) 
    164             if change in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE): 
    165                 edits.append((idx, path, kind, base_path, base_rev)) 
    166             req.hdf['changeset.changes.%d' % idx] = info 
    167             idx += 1 
    168  
    169         for idx, path, kind, base_path, base_rev in edits: 
    170             old_node = repos.get_node(base_path or path, base_rev) 
    171             new_node = repos.get_node(path, chgset.rev) 
    172  
     203                                                                 rev=rev) 
     204            req.hdf['diff.changes.%d' % idx] = info 
     205         
    173206            # Property changes 
    174207            old_props = old_node.get_properties() 
    175208            new_props = new_node.get_properties() 
     
    183216                for k,v in new_props.items(): 
    184217                    if not k in old_props: 
    185218                        changed_props[k] = {'new': v} 
    186                 req.hdf['changeset.changes.%d.props' % idx] = changed_props 
     219                req.hdf['diff.changes.%d.props' % idx] = changed_props 
    187220 
    188221            if kind == Node.DIRECTORY: 
    189222                continue 
    190223 
    191224            # Content changes 
    192225            default_charset = self.config.get('trac', 'default_charset') 
    193             old_content = old_node.get_content().read() 
     226            old_content = old_node.get_content().read()             
    194227            if mimeview.is_binary(old_content): 
    195228                continue 
    196229            charset = mimeview.get_charset(old_node.content_type) or \ 
     
    217250                                   ignore_blank_lines='-B' in diff_options[1], 
    218251                                   ignore_case='-i' in diff_options[1], 
    219252                                   ignore_space_changes='-b' in diff_options[1]) 
    220                 req.hdf['changeset.changes.%d.diff' % idx] = changes 
     253                req.hdf['diff.changes.%d.diff' % idx] = changes 
     254            idx += 1 
    221255 
    222     def _render_diff(self, req, repos, chgset, diff_options): 
     256 
     257    def _render_diff(self, req, repos, chgset, diff_options): # TODO 
    223258        """Raw Unified Diff version""" 
    224259        req.send_response(200) 
    225260        req.send_header('Content-Type', 'text/plain;charset=utf-8') 
     
    284319                                         ignore_space_changes='-b' in diff_options[1]): 
    285320                    req.write(line + util.CRLF) 
    286321 
    287     def _render_zip(self, req, repos, chgset): 
     322    def _render_zip(self, req, repos, chgset): # TODO 
    288323        """ZIP archive with all the added and/or modified files.""" 
    289324        req.send_response(200) 
    290325        req.send_header('Content-Type', 'application/zip') 
  • trac/Browser.py

     
    118118            'props': dict([(util.escape(name), util.escape(value)) 
    119119                           for name, value in node.get_properties().items()]), 
    120120            'href': self.env.href.browser(path,rev=rev or repos.youngest_rev), 
     121            'diff_href': self.env.href.diff(path,rev=rev or repos.youngest_rev), 
    121122            'log_href': self.env.href.log(path) 
    122123        } 
    123124 
     
    268269        stop_rev = req.args.get('stop_rev') 
    269270        verbose = req.args.get('verbose') 
    270271        limit = int(req.args.get('limit') or 100) 
     272        diff = req.args.get('diff') 
     273        old = req.args.get('old') 
     274        new = req.args.get('new') 
    271275 
     276        repos = self.env.get_repository(req.authname) 
     277        normpath = repos.normalize_path(path) 
     278        rev = str(repos.normalize_rev(rev)) 
     279        old = old or str(repos.previous_rev(rev)) 
     280        new = new or rev 
     281 
    272282        req.hdf['title'] = path + ' (log)' 
    273283        req.hdf['log'] = { 
    274284            'path': path, 
     
    276286            'verbose': verbose, 
    277287            'stop_rev': stop_rev, 
    278288            'browser_href': self.env.href.browser(path, rev=rev), 
    279             'log_href': self.env.href.log(path, rev=rev) 
     289            'log_href': self.env.href.log(path, rev=rev), 
     290            'select_diff_href': self.env.href.log(path, rev=rev, diff=1), 
     291            'diff': diff, 
     292            'diff_href': self.env.href.diff(path, old=old, new=new), 
     293            'old': old, 
     294            'new': new 
    280295        } 
    281296 
    282297        path_links = _get_path_links(self.env.href, path, rev) 
     
    284299        if path_links: 
    285300            add_link(req, 'up', path_links[-1]['href'], 'Parent directory') 
    286301 
    287         repos = self.env.get_repository(req.authname) 
    288         normpath = repos.normalize_path(path) 
    289         rev = str(repos.normalize_rev(rev)) 
    290  
    291302        # 'node' or 'path' history: use get_node()/get_history() or get_path_history() 
    292303        if mode != 'path_history': 
    293304            try: 
  • templates/log.cs

     
    22<?cs include "macros.cs"?> 
    33 
    44<div id="ctxtnav" class="nav"> 
    5  <ul> 
    6   <li class="last"><a href="<?cs 
    7     var:log.browser_href ?>">View Latest Revision</a></li><?cs 
     5 <ul><?cs  
     6  if:!log.diff ?> 
     7   <li class="first"> 
     8    <a href="<?cs var:log.select_diff_href ?>">Select for Diff</a> 
     9   </li><?cs 
     10  /if ?> 
     11  <li class="last"> 
     12   <a href="<?cs var:log.browser_href ?>">View Latest Revision</a> 
     13  </li><?cs 
    814  if:len(chrome.links.prev) ?> 
    915   <li class="first<?cs if:!len(chrome.links.next) ?> last<?cs /if ?>"> 
    1016    &larr; <a href="<?cs var:chrome.links.prev.0.href ?>" title="<?cs 
     
    7480   </dl> 
    7581  </div> 
    7682 </div> 
    77  <table id="chglist" class="listing"> 
    78   <thead> 
    79    <tr> 
     83 <table id="chglist" class="listing"><?cs 
     84   if:log.diff ?> 
     85    <form action="<?cs var:log.diff_href ?>" method="get"><?cs 
     86   /if ?> 
     87  <thead><?cs 
     88   if:log.diff ?> 
     89    <tr> 
     90     <th colspan="2"> 
     91      <div class="buttons"> 
     92       <input type="submit" id="diff" value="Diff"  
     93        title="Diff from Old Revision to New Revision (select them below)" /> 
     94      </div> 
     95     </th> 
     96    </tr><?cs 
     97   /if ?> 
     98   <tr><?cs  
     99    if:log.diff ?> 
     100     <th>Old</th> 
     101     <th>New</th><?cs 
     102    /if ?> 
    80103    <th class="change"></th> 
    81104    <th class="data">Date</th> 
    82105    <th class="rev">Rev</th> 
     
    98121    elif:log.mode == "path_history" ?><?cs 
    99122      set:indent = #1 ?><?cs 
    100123    /if ?> 
    101     <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"> 
     124    <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"><?cs 
     125     if:log.diff ?> 
     126     <td><input type="radio" name="old" value="<?cs var:item.rev ?>" <?cs 
     127          if:item.rev == #log.old ?> checked="checked" <?cs /if ?> /></td> 
     128     <td><input type="radio" name="new" value="<?cs var:item.rev ?>" <?cs 
     129          if:item.rev == #log.new ?> checked="checked" <?cs /if ?> /></td><?cs 
     130     /if ?> 
    102131     <td class="change" style="padding-left:<?cs var:indent ?>em"> 
    103132      <a title="View log starting at this revision" href="<?cs var:item.log_href ?>"> 
    104133       <div class="<?cs var:item.change ?>"></div> 
     
    116145     <td class="summary"><?cs var:log.changes[item.rev].message ?></td> 
    117146    </tr><?cs 
    118147   /each ?> 
    119   </tbody> 
     148  </tbody><?cs 
     149   if:log.diff ?> 
     150    </form><?cs 
     151   /if ?> 
    120152 </table><?cs 
    121153 if:len(links.prev) || len(links.next) ?><div id="paging" class="nav"><ul><?cs 
    122154  if:len(links.prev) ?><li class="first<?cs 
  • templates/browser.cs

     
    33 
    44<div id="ctxtnav" class="nav"> 
    55 <ul> 
     6  <li class="first"><a href="<?cs var:browser.diff_href ?>">Diff to previous</a></li> 
    67  <li class="last"><a href="<?cs var:browser.log_href ?>">Revision Log</a></li> 
    78 </ul> 
    89</div> 
  • templates/diff.cs

     
    22<?cs include "macros.cs"?> 
    33 
    44<div id="ctxtnav" class="nav"> 
    5  <h2>Changeset Navigation</h2><?cs 
     5 <h2>Diff Navigation</h2><?cs 
    66 with:links = chrome.links ?> 
    77  <ul><?cs 
    88   if:len(links.prev) ?> 
    99    <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>"> 
    1010     <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs 
    11        var:links.prev.0.title ?>">Previous Changeset</a> 
     11       var:links.prev.0.title ?>">Previous Diff</a> 
    1212    </li><?cs 
    1313   /if ?><?cs 
    1414   if:len(links.next) ?> 
    1515    <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last"> 
    1616     <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs 
    17        var:links.next.0.title ?>">Next Changeset</a> 
     17       var:links.next.0.title ?>">Next Diff</a> 
    1818    </li><?cs 
    1919   /if ?> 
    2020  </ul><?cs 
     
    2222</div> 
    2323 
    2424<div id="content" class="changeset"> 
    25 <h1>Changeset <?cs var:changeset.revision ?></h1> 
     25<h1><?cs 
     26 if:len(changeset) > #0 ?> 
     27  Changes for <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     28   <?cs var:diff.new_path ?></a>  
     29  in Revision <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     30   <?cs var:diff.new_rev ?></a><?cs 
     31 elif:diff.new_path == diff.old_path ?> 
     32  Differences for <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     33   <?cs var:diff.new_path ?></a>  
     34  between Revisions <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>"> 
     35   <?cs var:diff.old_rev ?></a> 
     36  and <a title="Show full changese