Edgewall Software

Ticket #2028: trac-diff-for-0.9.patch

File trac-diff-for-0.9.patch, 91.6 KB (added by cboos, 6 years ago)

Adds theTracDiff features on top of trac-0.9

  • htdocs/css/changeset.css

     
    2626 
    2727.diff ul.props { font-size: 90%; list-style: disc; margin: .5em 0 0; padding: 0 .5em 1em 2em } 
    2828.diff ul.props li { margin: 0; padding: 0 } 
     29 
     30 
     31#title dl { 
     32 display: inline; 
     33 font-size: 110% 
     34} 
     35#title dt {  
     36  font-size: 110%; 
     37  font-weight: bold; 
     38  display: inline;  
     39  margin-left: 3em; 
     40} 
     41#title dd {  
     42  display: inline; 
     43  margin-left: 0.4em; 
     44} 
  • htdocs/css/browser.css

     
    4646#dirlist td.rev { text-align: right } 
    4747#dirlist td.change { font-size: 85%; vertical-align: middle; white-space: nowrap } 
    4848 
     49/* Log */ 
     50tr.diff input {  
     51 padding: 0 1em 0 1em; 
     52 margin: 0;  
     53} 
     54 
     55div.buttons { 
     56 clear: left; 
     57} 
     58 
     59#anydiff { 
     60 margin: 0 0 1em; 
     61 float: left; 
     62} 
     63#anydiff form, #anydiff div, #anydiff h2 { 
     64 display: inline; 
     65} 
     66#anydiff input {  
     67 vertical-align: baseline; 
     68 margin: 0 -0.5em 0 1em; 
     69} 
     70 
     71 
    4972/* Styles for the revision log table 
    5073   (extends the styles for "table.listing") */ 
    5174#chglist { margin-top: 0 } 
  • wiki-default/WikiStart

     
    1 = Welcome to Trac 0.9 = 
    2  
    3 Trac is a '''minimalistic''' approach to '''web-based''' management of 
    4 '''software projects'''. Its goal is to simplify effective tracking and handling of software issues, enhancements and overall progress. 
    5  
    6 All aspects of Trac have been designed with the single goal to  
    7 '''help developers write great software''' while '''staying out of the way''' 
    8 and imposing as little as possible on a team's established process and 
    9 culture. 
    10  
    11 As all Wiki pages, this page is editable, this means that you can 
    12 modify the contents of this page simply by using your 
    13 web-browser. Simply click on the "Edit this page" link at the bottom 
    14 of the page. WikiFormatting will give you a detailed description of 
    15 available Wiki formatting commands. 
    16  
    17 "[wiki:TracAdmin trac-admin] ''yourenvdir'' initenv" created 
    18 a new Trac environment, containing a default set of wiki pages and some sample 
    19 data. This newly created environment also contains  
    20 [wiki:TracGuide documentation] to help you get started with your project. 
    21  
    22 You can use [wiki:TracAdmin trac-admin] to configure 
    23 [http://trac.edgewall.com/ Trac] to better fit your project, especially in 
    24 regard to ''components'', ''versions'' and ''milestones''.  
    25  
    26  
    27 TracGuide is a good place to start. 
    28  
    29 Enjoy! [[BR]] 
    30 ''The Trac Team'' 
    31  
    32 == Starting Points == 
    33  
    34  * TracGuide --  Built-in Documentation 
    35  * [http://projects.edgewall.com/trac/ The Trac project] -- Trac Open Source Project 
    36  * [http://projects.edgewall.com/trac/wiki/TracFaq Trac FAQ] -- Frequently Asked Questions 
    37  * TracSupport --  Trac Support 
    38  
    39 For a complete list of local wiki pages, see TitleIndex. 
    40  
    41 Trac is brought to you by [http://www.edgewall.com/ Edgewall Software], 
    42 providing professional Linux and software development services to clients 
    43 worldwide. Visit http://www.edgewall.com/ for more information. 
     1= Welcome to Trac 0.9-trac-diff = 
     2 
     3Trac is a '''minimalistic''' approach to '''web-based''' management of 
     4'''software projects'''. Its goal is to simplify effective tracking and handling of software issues, enhancements and overall progress. 
     5 
     6All aspects of Trac have been designed with the single goal to  
     7'''help developers write great software''' while '''staying out of the way''' 
     8and imposing as little as possible on a team's established process and 
     9culture. 
     10 
     11As all Wiki pages, this page is editable, this means that you can 
     12modify the contents of this page simply by using your 
     13web-browser. Simply click on the "Edit this page" link at the bottom 
     14of the page. WikiFormatting will give you a detailed description of 
     15available Wiki formatting commands. 
     16 
     17"[wiki:TracAdmin trac-admin] ''yourenvdir'' initenv" created 
     18a new Trac environment, containing a default set of wiki pages and some sample 
     19data. This newly created environment also contains  
     20[wiki:TracGuide documentation] to help you get started with your project. 
     21 
     22You can use [wiki:TracAdmin trac-admin] to configure 
     23[http://trac.edgewall.com/ Trac] to better fit your project, especially in 
     24regard to ''components'', ''versions'' and ''milestones''.  
     25 
     26 
     27TracGuide is a good place to start. 
     28 
     29Enjoy! [[BR]] 
     30''The Trac Team'' 
     31 
     32== Starting Points == 
     33 
     34 * TracGuide --  Built-in Documentation 
     35 * [http://projects.edgewall.com/trac/ The Trac project] -- Trac Open Source Project 
     36 * [http://projects.edgewall.com/trac/wiki/TracFaq Trac FAQ] -- Frequently Asked Questions 
     37 * TracSupport --  Trac Support 
     38 
     39For a complete list of local wiki pages, see TitleIndex. 
     40 
     41Trac is brought to you by [http://www.edgewall.com/ Edgewall Software], 
     42providing professional Linux and software development services to clients 
     43worldwide. Visit http://www.edgewall.com/ for more information. 
  • trac/__init__.py

     
    1010""" 
    1111__docformat__ = 'epytext en' 
    1212 
    13 __version__ = '0.9' 
     13__version__ = '0.9-trac-diff' 
    1414__url__ = 'http://trac.edgewall.com/' 
    1515__copyright__ = '(C) 2003-2005 Edgewall Software' 
    1616__license__ = 'BSD' 
  • trac/versioncontrol/diff.py

     
    217217                           ignore_space_changes) 
    218218    for group in _group_opcodes(opcodes, context): 
    219219        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4] 
     220        if i1 == 0 and i2 == 0: 
     221            i1, i2 = -1, -1 # support for 'A'dd changes 
    220222        yield '@@ -%d,%d +%d,%d @@' % (i1 + 1, i2 - i1, j1 + 1, j2 - j1) 
    221223        for tag, i1, i2, j1, j2 in group: 
    222224            if tag == 'equal': 
  • trac/versioncontrol/api.py

     
    122122        'None' is a valid revision value and represents the youngest revision. 
    123123        """ 
    124124        return NotImplementedError 
    125          
    126125 
     126    def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1): 
     127        """ 
     128        Generator that yields change tuples (old_node, new_node, kind, change) 
     129        for each node change between the two arbitrary (path,rev) pairs. 
     130 
     131        The old_node is assumed to be None when the change is an ADD, 
     132        the new_node is assumed to be None when the change is a DELETE. 
     133        """ 
     134        raise NotImplementedError 
     135 
     136 
    127137class Node(object): 
    128138    """ 
    129139    Represents a directory or file in the repository. 
     
    164174        """ 
    165175        raise NotImplementedError 
    166176 
     177    def get_previous(self): 
     178        """ 
     179        Return the (path, rev, chg) tuple corresponding to the previous 
     180        revision for that node. 
     181        """ 
     182        skip = True 
     183        for p in self.get_history(2): 
     184            if skip: 
     185                skip = False 
     186            else: 
     187                return p 
     188 
    167189    def get_properties(self): 
    168190        """ 
    169191        Returns a dictionary containing the properties (meta-data) of the node. 
  • trac/versioncontrol/tests/svn_fs.py

     
    219219        self.assertEqual(('tags/v1', 7, 'unknown'), history.next()) 
    220220        self.assertRaises(StopIteration, history.next) 
    221221 
     222    # Diffs 
     223 
     224    def _cmp_diff(self, expected, got): 
     225        if expected[0]: 
     226            old = self.repos.get_node(*expected[0]) 
     227            self.assertEqual((old.path, old.rev), (got[0].path, got[0].rev)) 
     228        if expected[1]: 
     229            new = self.repos.get_node(*expected[1]) 
     230            self.assertEqual((new.path, new.rev), (got[1].path, got[1].rev)) 
     231        self.assertEqual(expected[2], (got[2], got[3])) 
     232         
     233    def test_diff_file_different_revs(self): 
     234        diffs = self.repos.get_changes('trunk/README.txt', 2, 'trunk/README.txt', 3) 
     235        self._cmp_diff((('trunk/README.txt', 2), 
     236                        ('trunk/README.txt', 3), 
     237                        (Node.FILE, Changeset.EDIT)), diffs.next()) 
     238        self.assertRaises(StopIteration, diffs.next) 
     239 
     240    def test_diff_file_different_files(self): 
     241        diffs = self.repos.get_changes('branches/v1x/README.txt', 12, 
     242                                      'branches/v1x/README2.txt', 12) 
     243        self._cmp_diff((('branches/v1x/README.txt', 12), 
     244                        ('branches/v1x/README2.txt', 12), 
     245                        (Node.FILE, Changeset.EDIT)), diffs.next()) 
     246        self.assertRaises(StopIteration, diffs.next) 
     247 
     248    def test_diff_file_no_change(self): 
     249        diffs = self.repos.get_changes('trunk/README.txt', 7, 
     250                                      'tags/v1/README.txt', 7) 
     251        self.assertRaises(StopIteration, diffs.next) 
     252  
     253    def test_diff_dir_different_revs(self): 
     254        diffs = self.repos.get_changes('trunk', 4, 'trunk', 8) 
     255        self._cmp_diff((None, ('trunk/dir1/dir2', 8), 
     256                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     257        self._cmp_diff((None, ('trunk/dir1/dir3', 8), 
     258                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     259        self._cmp_diff((None, ('trunk/README2.txt', 6), 
     260                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     261        self._cmp_diff((('trunk/dir2', 4), None, 
     262                        (Node.DIRECTORY, Changeset.DELETE)), diffs.next()) 
     263        self._cmp_diff((('trunk/dir3', 4), None, 
     264                        (Node.DIRECTORY, Changeset.DELETE)), diffs.next()) 
     265        self.assertRaises(StopIteration, diffs.next) 
     266 
     267    def test_diff_dir_different_dirs(self): 
     268        diffs = self.repos.get_changes('trunk', 1, 'branches/v1x', 12) 
     269        self._cmp_diff((None, ('branches/v1x/dir1', 12), 
     270                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     271        self._cmp_diff((None, ('branches/v1x/dir1/dir2', 12), 
     272                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     273        self._cmp_diff((None, ('branches/v1x/dir1/dir3', 12), 
     274                        (Node.DIRECTORY, Changeset.ADD)), diffs.next()) 
     275        self._cmp_diff((None, ('branches/v1x/README.txt', 12), 
     276                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     277        self._cmp_diff((None, ('branches/v1x/README2.txt', 12), 
     278                        (Node.FILE, Changeset.ADD)), diffs.next()) 
     279        self.assertRaises(StopIteration, diffs.next) 
     280 
     281    def test_diff_dir_no_change(self): 
     282        diffs = self.repos.get_changes('trunk', 7, 
     283                                      'tags/v1', 7) 
     284        self.assertRaises(StopIteration, diffs.next) 
     285         
     286    # Changesets 
     287 
    222288    def test_changeset_repos_creation(self): 
    223289        chgset = self.repos.get_changeset(0) 
    224290        self.assertEqual(0, chgset.rev) 
  • trac/versioncontrol/svn_fs.py

     
    349349                expect_deletion = True 
    350350                rev = self.previous_rev(rev) 
    351351 
     352    def get_changes(self, old_path, old_rev, new_path, new_rev, 
     353                   ignore_ancestry=0): 
     354        old_node = new_node = None 
     355        old_rev = self.normalize_rev(old_rev) 
     356        new_rev = self.normalize_rev(new_rev) 
     357        if self.has_node(old_path, old_rev): 
     358            old_node = self.get_node(old_path, old_rev) 
     359        else: 
     360            raise TracError, ('The Base for Diff is invalid: path %s' 
     361                              ' doesn\'t exist in revision %s' \ 
     362                              % (old_path, old_rev)) 
     363        if self.has_node(new_path, new_rev): 
     364            new_node = self.get_node(new_path, new_rev) 
     365        else: 
     366            raise TracError, ('The Target for Diff is invalid: path %s' 
     367                              ' doesn\'t exist in revision %s' \ 
     368                              % (new_path, new_rev)) 
     369        if new_node.kind != old_node.kind: 
     370            raise TracError, ('Diff mismatch: Base is a %s (%s in revision %s) ' 
     371                              'and Target is a %s (%s in revision %s).' \ 
     372                              % (old_node.kind, old_path, old_rev, 
     373                                 new_node.kind, new_path, new_rev)) 
     374        subpool = Pool(self.pool) 
     375        if new_node.isdir: 
     376            editor = DiffChangeEditor() 
     377            e_ptr, e_baton = delta.make_editor(editor, subpool()) 
     378            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) 
     379            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) 
     380            def authz_cb(root, path, pool): return 1 
     381            text_deltas = 0 # as this is anyway re-done in Diff.py... 
     382            entry_props = 0 # "... typically used only for working copy updates" 
     383            repos.svn_repos_dir_delta(old_root, 
     384                                      (self.scope + old_path).strip('/'), '', 
     385                                      new_root, 
     386                                      (self.scope + new_path).strip('/'), 
     387                                      e_ptr, e_baton, authz_cb, 
     388                                      text_deltas, 
     389                                      1, # directory 
     390                                      entry_props, 
     391                                      ignore_ancestry, 
     392                                      subpool()) 
     393            for path, kind, change in editor.deltas: 
     394                old_node = new_node = None 
     395                if change != Changeset.ADD: 
     396                    old_node = self.get_node(posixpath.join(old_path, path), 
     397                                             old_rev) 
     398                if change != Changeset.DELETE: 
     399                    new_node = self.get_node(posixpath.join(new_path, path), 
     400                                             new_rev) 
     401                else: 
     402                    kind = _kindmap[fs.check_path(old_root, 
     403                                                  self.scope + old_node.path, 
     404                                                  subpool())] 
     405                yield  (old_node, new_node, kind, change) 
     406        else: 
     407            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) 
     408            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) 
     409            if fs.contents_changed(old_root, self.scope + old_path, 
     410                                   new_root, self.scope + new_path, 
     411                                   subpool()): 
     412                yield (old_node, new_node, Node.FILE, Changeset.EDIT) 
    352413 
     414 
    353415class SubversionNode(Node): 
    354416 
    355417    def __init__(self, path, rev, authz, scope, fs_ptr, pool=None): 
     
    371433                                               self.pool()) 
    372434        self.created_path = fs.node_created_path(self.root, self.scoped_path, 
    373435                                                 self.pool()) 
    374         # 'created_path' differs from 'path' if the last operation is a copy, 
    375         # and furthermore, 'path' might not exist at 'create_rev' 
     436        # Note: 'created_path' differs from 'path' if the last change was a copy, 
     437        #        and furthermore, 'path' might not exist at 'create_rev'. 
     438        #        The only guarantees are: 
     439        #          * this node exists at (path,rev) 
     440        #          * the node existed at (created_path,created_rev) 
     441        # TODO: check node id 
    376442        self.rev = self.created_rev 
    377443         
    378444        Node.__init__(self, path, self.rev, _kindmap[node_type]) 
     
    417483        if newer: 
    418484            yield newer 
    419485 
     486#    def get_previous(self): 
     487#        # FIXME: redo it with fs.node_history 
     488 
    420489    def get_properties(self): 
    421490        props = fs.node_proplist(self.root, self.scoped_path, self.pool()) 
    422491        for name,value in props.items(): 
     
    506575 
    507576    def _get_prop(self, name): 
    508577        return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool()) 
     578 
     579 
     580# 
     581# Delta editor for diffs between arbitrary nodes 
     582# 
     583# Note 1: the 'copyfrom_path' and 'copyfrom_rev' information is not used 
     584#         because 'repos.svn_repos_dir_delta' *doesn't* provide it. 
     585# 
     586# Note 2: the 'dir_baton' is the path of the parent directory 
     587# 
     588 
     589class DiffChangeEditor(delta.Editor):  
     590 
     591    def __init__(self): 
     592        self.deltas = [] 
     593     
     594    # -- svn.delta.Editor callbacks 
     595 
     596    def open_root(self, base_revision, dir_pool): 
     597        return ('/', Changeset.EDIT) 
     598 
     599    def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev, 
     600                      dir_pool): 
     601        self.deltas.append((path, Node.DIRECTORY, Changeset.ADD)) 
     602        return (path, Changeset.ADD) 
     603 
     604    def open_directory(self, path, dir_baton, base_revision, dir_pool): 
     605        return (path, dir_baton[1]) 
     606 
     607    def change_dir_prop(self, dir_baton, name, value, pool): 
     608        path, change = dir_baton 
     609        if change != Changeset.ADD: 
     610            self.deltas.append((path, Node.DIRECTORY, change)) 
     611 
     612    def delete_entry(self, path, revision, dir_baton, pool): 
     613        self.deltas.append((path, None, Changeset.DELETE)) 
     614 
     615    def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision, 
     616                 dir_pool): 
     617        self.deltas.append((path, Node.FILE, Changeset.ADD)) 
     618 
     619    def open_file(self, path, dir_baton, dummy_rev, file_pool): 
     620        self.deltas.append((path, Node.FILE, Changeset.EDIT)) 
     621 
  • trac/versioncontrol/cache.py

     
    125125    def normalize_rev(self, rev): 
    126126        return self.repos.normalize_rev(rev) 
    127127 
     128    def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1): 
     129        return self.repos.get_changes(old_path, old_rev, new_path, new_rev, ignore_ancestry) 
    128130 
     131 
    129132class CachedChangeset(Changeset): 
    130133 
    131134    def __init__(self, rev, db, authz): 
  • trac/versioncontrol/web_ui/diff.py

     
     1# -*- coding: iso-8859-1 -*- 
     2# 
     3# Copyright (C) 2003-2005 Edgewall Software 
     4# Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com> 
     5# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de> 
     6# All rights reserved. 
     7# 
     8# This software is licensed as described in the file COPYING, which 
     9# you should have received as part of this distribution. The terms 
     10# are also available at http://trac.edgewall.com/license.html. 
     11# 
     12# This software consists of voluntary contributions made by many 
     13# individuals. For the exact contribution history, see the revision 
     14# history and logs, available at http://projects.edgewall.com/trac/. 
     15# 
     16# Author: Jonas Borgström <jonas@edgewall.com> 
     17#         Christopher Lenz <cmlenz@gmx.de> 
     18#         Christian Boos <cboos@neuf.fr> 
     19 
     20import time 
     21import re 
     22import posixpath 
     23from urllib import urlencode 
     24 
     25from trac import mimeview, util 
     26from trac.core import * 
     27from trac.perm import IPermissionRequestor 
     28from trac.versioncontrol import Changeset, Node 
     29from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff 
     30from trac.versioncontrol.svn_authz import SubversionAuthorizer 
     31from trac.web import IRequestHandler 
     32from trac.web.chrome import add_link, add_stylesheet 
     33from trac.wiki import wiki_to_html, IWikiSyntaxProvider 
     34 
     35class DiffArgs(dict): 
     36    def __getattr__(self,str): 
     37        return self[str] 
     38     
     39 
     40class ChangesPermission(Component): 
     41    """Simple permission provider for changes related modules.""" 
     42     
     43    implements(IPermissionRequestor) 
     44     
     45    def get_permission_actions(self): 
     46        return ['CHANGESET_VIEW'] 
     47     
     48 
     49class AbstractDiffModule(Component): 
     50    """Provide flexible functionality for showing sets of differences. 
     51 
     52    If the differences shown are coming from a specific changeset, 
     53    then that changeset informations can be shown too. 
     54 
     55    In addition, it is possible to show only a subset of the changeset: 
     56    Only the changes affecting a given path will be shown. 
     57    This is called the ''restricted'' changeset. 
     58 
     59    But the differences can also be computed in a more general way, 
     60    between two arbitrary paths and/or between two arbitrary revisions. 
     61    In that case, there's no changeset information displayed. 
     62    """ 
     63 
     64    abstract = True 
     65 
     66    implements(IRequestHandler) 
     67 
     68    # IRequestHandler methods 
     69 
     70    def match_request(self, req): 
     71        raise NotImplementedError 
     72 
     73    def process_request(self, req): 
     74        """The appropriate mode of operation is inferred from 
     75        the request parameters: 
     76         * If `old` and `new` parameters are given, it will be an 
     77           arbitrary set of differences: `chgset` is False. 
     78           * If `old_path` is given and is different from `path`, 
     79             it's a generalized diff, from `old_path@old` 
     80             to `path@new`: `restricted` is False. 
     81           * Otherwise those are differences between two arbitrary revisions 
     82             of a given path: `restricted` is True. 
     83         * Otherwise, we are dealing with a changeset only: `chgset` is True. 
     84           * If the `path` is not empty or not the root, then only 
     85             the changes affecting that path (i.e. itself, children or 
     86             ancestors) will be considered: `restricted` is True. 
     87           * Otherwise, it's the full changeset: `restricted` is False. 
     88          
     89        In any case, the given path@rev pair must exist. 
     90        """ 
     91        req.perm.assert_permission('CHANGESET_VIEW') 
     92         
     93        # -- retrieve arguments 
     94        path = req.args.get('path') 
     95        rev = req.args.get('rev') 
     96        old = req.args.get('old') 
     97        new = req.args.get('new') 
     98        old_path = req.args.get('old_path') 
     99 
     100        # -- normalize and check for special case 
     101        repos = self.env.get_repository(req.authname) 
     102        path = repos.normalize_path(path) 
     103        rev = repos.normalize_rev(rev) 
     104        if old_path: # Note: normalize_path now returns '/' if given 'None' 
     105            old_path = repos.normalize_path(old_path) 
     106         
     107        authzperm = SubversionAuthorizer(self.env, req.authname) 
     108        authzperm.assert_permission_for_changeset(rev) 
     109 
     110        if old_path == path and old and old == new: # revert to Changeset 
     111            rev = old 
     112            old_path = old = new = None 
     113 
     114        diff_options = get_diff_options(req) 
     115 
     116        # -- setup the `chgset` and `restricted` flags, see docstring above. 
     117        chgset = not old and not new and not old_path 
     118        if chgset: 
     119            restricted = path != '' and path != '/' # (subset or not) 
     120        else: 
     121            restricted = old_path == path # (same path or not) 
     122 
     123        # -- redirect if changing the diff options 
     124        if req.args.has_key('update'): 
     125            if chgset: 
     126                if restricted: 
     127                    req.redirect(self.env.href.diff(path, rev=rev)) 
     128                else: 
     129                    req.redirect(self.env.href.changeset(rev)) 
     130            else: 
     131                req.redirect(self.env.href.diff(path, new=new, 
     132                                                old_path=old_path, old=old)) 
     133 
     134        # -- preparing the diff arguments 
     135        if chgset: 
     136            prev = repos.get_node(path, rev).get_previous() 
     137            if prev: 
     138                prev_path, prev_rev = prev[:2] 
     139            else: 
     140                prev_path, prev_rev = path, repos.previous_rev(rev) 
     141            diff_args = DiffArgs(old_path=prev_path, old_rev=prev_rev, 
     142                                 new_path=path, new_rev=rev) 
     143        else: 
     144            if not new: 
     145                new = repos.youngest_rev 
     146            elif not old: 
     147                old = repos.youngest_rev 
     148            if not old_path: 
     149                old_path = path 
     150            diff_args = DiffArgs(old_path=old_path, old_rev=old, 
     151                                 new_path=path, new_rev=new) 
     152        if chgset: 
     153            chgset = repos.get_changeset(rev) 
     154            req.check_modified(chgset.date, 
     155                               diff_options[0] + ''.join(diff_options[1])) 
     156        else: 
     157            pass # FIXME: what date should we choose for a diff? 
     158 
     159        req.hdf['diff'] = diff_args 
     160 
     161        format = req.args.get('format') 
     162 
     163        if format in ['diff', 'zip']: 
     164            # choosing an appropriate filename 
     165            rpath = path.replace('/','_') 
     166            if chgset: 
     167                if restricted: 
     168                    filename = 'changeset_%s_r%s' % (rpath, rev) 
     169                else: 
     170                    filename = 'changeset_r%s' % rev 
     171            else: 
     172                if restricted: 
     173                    filename = 'diff-%s-from-r%s-to-r%s' \ 
     174                                  % (rpath, old, new) 
     175                elif old_path == '/': # special case for download (#238) 
     176                    filename = '%s-r%s' % (rpath, old) 
     177                else: 
     178                    filename = 'diff-from-%s-r%s-to-%s-r%s' \ 
     179                               % (old_path.replace('/','_'), old, rpath, new) 
     180            if format == 'diff': 
     181                self._render_diff(req, filename, repos, diff_args, 
     182                                  diff_options) 
     183                return 
     184            elif format == 'zip': 
     185                self._render_zip(req, filename, repos, diff_args) 
     186                return 
     187 
     188        # -- HTML format 
     189        self._render_html(req, repos, chgset, restricted, 
     190                          diff_args, diff_options) 
     191        if chgset: 
     192            diff_params = 'rev=%s' % rev 
     193        else: 
     194            diff_params = urlencode({'path': path, 
     195                                     'new': new, 
     196                                     'old_path': old_path, 
     197                                     'old': old}) 
     198        add_link(req, 'alternate', '?format=diff&'+diff_params, 'Unified Diff', 
     199                 'text/plain', 'diff') 
     200        add_link(req, 'alternate', '?format=zip&'+diff_params, 'Zip Archive', 
     201                 'application/zip', 'zip') 
     202        add_stylesheet(req, 'common/css/changeset.css') 
     203        add_stylesheet(req, 'common/css/diff.css') 
     204        add_stylesheet(req, 'common/css/code.css') 
     205        return 'diff.cs', None 
     206 
     207 
     208    # Internal methods 
     209 
     210    def _render_html(self, req, repos, chgset, restricted, diff, diff_options): 
     211        """ 
     212        HTML version 
     213        """ 
     214        req.hdf['diff'] = { 
     215            'chgset': chgset and True, 
     216            'restricted': restricted, 
     217            'href': { 'new_rev': self.env.href.changeset(diff.new_rev), 
     218                      'old_rev': self.env.href.changeset(diff.old_rev), 
     219                      'new_path': self.env.href.browser(diff.new_path, 
     220                                                        rev=diff.new_rev), 
     221                      'old_path': self.env.href.browser(diff.old_path, 
     222                                                        rev=diff.old_rev) 
     223                      } 
     224            } 
     225         
     226        if chgset: # Changeset Mode (possibly restricted on a path) 
     227            path, rev = diff.new_path, diff.new_rev 
     228 
     229            # -- getting the change summary from the Changeset.get_changes method 
     230            def get_changes(): 
     231                old_node = new_node = None 
     232                for npath, kind, change, opath, orev in chgset.get_changes(): 
     233                    if restricted and \ 
     234                           not (npath.startswith(path)      # npath is below 
     235                                or path.startswith(npath)): # npath is above 
     236                        continue 
     237                    if change != Changeset.ADD: 
     238                        old_node = repos.get_node(opath, orev) 
     239                    if change != Changeset.DELETE: 
     240                        new_node = repos.get_node(npath, rev) 
     241                    yield old_node, new_node, kind, change 
     242                     
     243            def _changeset_title(rev): 
     244                if restricted: 
     245                    return 'Changeset %s for %s' % (rev, path) 
     246                else: 
     247                    return 'Changeset %s' % rev 
     248 
     249            title = _changeset_title(rev) 
     250            req.hdf['changeset'] = { 
     251                'revision': chgset.rev, 
     252                'time': util.format_datetime(chgset.date), 
     253                'author': util.escape(chgset.author or 'anonymous'), 
     254                'message': wiki_to_html(chgset.message or '--', self.env, req, 
     255                                        escape_newlines=True) 
     256                } 
     257            oldest_rev = repos.oldest_rev 
     258            if chgset.rev != oldest_rev: 
     259                if restricted: 
     260                    prev = repos.get_node(path, rev).get_previous() 
     261                    if prev: 
     262                        prev_path, prev_rev = prev[:2] 
     263                        prev_href = self.env.href.changeset(prev_rev, prev_path) 
     264                    else: 
     265                        prev_path = prev_rev = None 
     266                else: 
     267                    prev_path = diff.old_path 
     268                    prev_rev = repos.previous_rev(chgset.rev) 
     269                    add_link(req, 'first', self.env.href.changeset(oldest_rev), 
     270                             'Changeset %s' % oldest_rev) 
     271                    prev_href = self.env.href.changeset(prev_rev) 
     272                if prev_rev: 
     273                    add_link(req, 'prev', prev_href, _changeset_title(prev_rev)) 
     274            youngest_rev = repos.youngest_rev 
     275            if str(chgset.rev) != str(youngest_rev): 
     276                if restricted: 
     277                    next_rev = next_href = None 
     278                    # FIXME: find an effective way to find the next rev 
     279                else: 
     280                    next_rev = repos.next_rev(chgset.rev) 
     281                    next_href = self.env.href.changeset(next_rev) 
     282                    add_link(req, 'last', 
     283                             self.env.href.diff(path, rev=youngest_rev), 
     284                             'Changeset %s' % youngest_rev) 
     285                if next_rev: 
     286                    add_link(req, 'next', next_href, _changeset_title(next_rev)) 
     287 
     288        else: # Diff Mode 
     289            # -- getting the change summary from the Repository.get_changes method 
     290            def get_changes(): 
     291                for d in repos.get_changes(**diff): 
     292                    yield d 
     293                     
     294            reverse_href = self.env.href.diff(diff.old_path, 
     295                                              new=diff.old_rev, 
     296                                              old_path=diff.new_path, 
     297                                              old=diff.new_rev) 
     298            req.hdf['diff.reverse_href'] = reverse_href 
     299            title = self.title_for_diff(diff) 
     300        req.hdf['title'] = title 
     301 
     302        def _change_info(old_node, new_node, change): 
     303            info = {'change': change} 
     304            if old_node: 
     305                info['path.old'] = old_node.path 
     306                info['rev.old'] = old_node.rev # this is the created rev. 
     307                old_href = self.env.href.browser(old_node.path, 
     308                                                 rev=diff.old_rev) 
     309                # Reminder: old_node.path may not exist at old_node.rev 
     310                info['browser_href.old'] = old_href 
     311            if new_node: 
     312                info['path.new'] = new_node.path 
     313                info['rev.new'] = new_node.rev # created rev. 
     314                new_href = self.env.href.browser(new_node.path, 
     315                                                 rev=diff.new_rev) 
     316                # (same remark as above) 
     317                info['browser_href.new'] = new_href 
     318            return info 
     319 
     320        hidden_properties = [p.strip() for p 
     321                             in self.config.get('browser', 'hide_properties', 
     322                                                'svk:merge').split(',')] 
     323 
     324        def _prop_changes(old_node, new_node): 
     325            old_props = old_node.get_properties() 
     326            new_props = new_node.get_properties() 
     327            changed_props = {} 
     328            if old_props != new_props: 
     329                for k,v in old_props.items(): 
     330                    if not k in new_props: 
     331                        changed_props[k] = {'old': v} 
     332                    elif v != new_props[k]: 
     333                        changed_props[k] = {'old': v, 'new': new_props[k]} 
     334                for k,v in new_props.items(): 
     335                    if not k in old_props: 
     336                        changed_props[k] = {'new': v} 
     337                for k in hidden_properties: 
     338                    if k in changed_props: 
     339                        del changed_props[k] 
     340            return changed_props 
     341 
     342        def _content_changes(old_node, new_node): 
     343            """ 
     344            Returns the list of differences. 
     345            The list is empty when no differences between comparable files 
     346            are detected, but the return value is None for non-comparable files. 
     347            """ 
     348            default_charset = self.config.get('trac', 'default_charset') 
     349            old_content = old_node.get_content().read()             
     350            if mimeview.is_binary(old_content): 
     351                return None 
     352            charset = mimeview.get_charset(old_node.content_type) 
     353            if not charset: 
     354                charset = mimeview.detect_unicode(old_content) 
     355            old_content = util.to_utf8(old_content, charset or default_charset) 
     356 
     357            new_content = new_node.get_content().read() 
     358            if mimeview.is_binary(new_content): 
     359                return None 
     360            charset = mimeview.get_charset(new_node.content_type) 
     361            if not charset: 
     362                charset = mimeview.detect_unicode(new_content) 
     363            new_content = util.to_utf8(new_content, charset or default_charset) 
     364 
     365            if old_content != new_content: 
     366                context = 3 
     367                options = diff_options[1] 
     368                for option in options: 
     369                    if option.startswith('-U'): 
     370                        context = int(option[2:]) 
     371                        break 
     372                if context < 0: 
     373                    context = None 
     374                tabwidth = int(self.config.get('diff', 'tab_width', 
     375                                               self.config.get('mimeviewer', 
     376                                                               'tab_width'))) 
     377                return hdf_diff(old_content.splitlines(), 
     378                                new_content.splitlines(), 
     379                                context, tabwidth, 
     380                                ignore_blank_lines='-B' in options, 
     381                                ignore_case='-i' in options, 
     382                                ignore_space_changes='-b' in options) 
     383            else: 
     384                return [] 
     385 
     386        idx = 0 
     387        for old_node, new_node, kind, change in get_changes(): 
     388            if change != Changeset.EDIT: 
     389                show_entry = True 
     390            else: 
     391                show_entry = False 
     392                assert old_node and new_node 
     393                props = _prop_changes(old_node, new_node) 
     394                if props: 
     395                    req.hdf['diff.changes.%d.props' % idx] = props 
     396                    show_entry = True 
     397                if kind == Node.FILE: 
     398                    diffs = _content_changes(old_node, new_node) 
     399                    if diffs != []: 
     400                        if diffs: 
     401                            req.hdf['diff.changes.%d.diff' % idx] = diffs 
     402                        # elif None (means: manually compare to (previous)) 
     403                        show_entry = True 
     404            if show_entry: 
     405                info = _change_info(old_node, new_node, change) 
     406                req.hdf['diff.changes.%d' % idx] = info 
     407            idx += 1 # the sequence should be immutable 
     408 
     409    def _render_diff(self, req, filename, repos, diff, diff_options): 
     410        """Raw Unified Diff version""" 
     411        req.send_response(200) 
     412        req.send_header('Content-Type', 'text/plain;charset=utf-8') 
     413        req.send_header('Content-Disposition', 
     414                        'filename=%s.diff' % filename) 
     415        req.end_headers() 
     416 
     417        for old_node, new_node, kind, change in repos.get_deltas(**diff): 
     418            # TODO: Property changes 
     419 
     420            # Content changes 
     421            if kind == Node.DIRECTORY: 
     422                continue 
     423 
     424            default_charset = self.config.get('trac', 'default_charset') 
     425            new_content = old_content = '' 
     426            new_node_info = old_node_info = ('','') 
     427 
     428            if old_node: 
     429                charset = mimeview.get_charset(old_node.content_type) or \ 
     430                          default_charset 
     431                old_content = util.to_utf8(old_node.get_content().read(), 
     432                                           charset) 
     433                old_node_info = (old_node.path, old_node.rev) 
     434                if mimeview.is_binary(old_content): 
     435                    continue 
     436 
     437            if new_node: 
     438                charset = mimeview.get_charset(new_node.content_type) or \ 
     439                          default_charset 
     440                new_content = util.to_utf8(new_node.get_content().read(), 
     441                                           charset) 
     442                new_node_info = (new_node.path, new_node.rev) 
     443                if mimeview.is_binary(new_content): 
     444                    continue 
     445                new_path = new_node.path 
     446            else: 
     447                old_node_path = repos.normalize_path(old_node.path) 
     448                diff_old_path = repos.normalize_path(diff.old_path) 
     449                new_path = posixpath.join(diff.new_path, 
     450                                          old_node_path[len(diff_old_path)+1:]) 
     451 
     452            if old_content != new_content: 
     453                context = 3 
     454                options = diff_options[1] 
     455                for option in options: 
     456                    if option.startswith('-U'): 
     457                        context = int(option[2:]) 
     458                        break 
     459                if not old_node_info[0]: 
     460                    old_node_info = new_node_info # support for 'A'dd changes 
     461                req.write('Index: ' + new_path + util.CRLF) 
     462                req.write('=' * 67 + util.CRLF) 
     463                req.write('--- %s (revision %s)' % old_node_info + 
     464                          util.CRLF) 
     465                req.write('+++ %s (revision %s)' % new_node_info + 
     466                          util.CRLF) 
     467                for line in unified_diff(old_content.splitlines(), 
     468                                         new_content.splitlines(), context, 
     469                                         ignore_blank_lines='-B' in options, 
     470                                         ignore_case='-i' in options, 
     471                                         ignore_space_changes='-b' in options): 
     472                    req.write(line + util.CRLF) 
     473 
     474    def _render_zip(self, req, filename, repos, diff): 
     475        """ZIP archive with all the added and/or modified files.""" 
     476        new_rev = diff.new_rev 
     477        req.send_response(200) 
     478        req.send_header('Content-Type', 'application/zip') 
     479        req.send_header('Content-Disposition', 
     480                        'filename=%s.zip' % filename) 
     481 
     482        try: 
     483            from cStringIO import StringIO 
     484        except ImportError: 
     485            from StringIO import StringIO 
     486        from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED 
     487 
     488        buf = StringIO() 
     489        zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 
     490        for old_node, new_node, kind, change in repos.get_deltas(**diff): 
     491            if kind == Node.FILE and change != Changeset.DELETE: 
     492                assert new_node 
     493                zipinfo = ZipInfo() 
     494                zipinfo.filename = new_node.path 
     495                zipinfo.date_time = time.gmtime(new_node.last_modified)[:6] 
     496                zipinfo.compress_type = ZIP_DEFLATED 
     497                zipfile.writestr(zipinfo, new_node.get_content().read()) 
     498        zipfile.close() 
     499 
     500        buf.seek(0, 2) # be sure to be at the end 
     501        req.send_header("Content-Length", buf.tell()) 
     502        req.end_headers() 
     503         
     504        req.write(buf.getvalue()) 
     505 
     506    def title_for_diff(self, diff): 
     507        if diff.new_path == diff.old_path: # ''diff between 2 revisions'' mode 
     508            return 'Diff r%s:%s for %s' \ 
     509                   % (diff.old_rev or 'latest', diff.new_rev or 'latest', 
     510                      diff.new_path or '/') 
     511        else:                              # ''arbitrary diff'' mode 
     512            return 'Diff from %s@%s to %s@%s' \ 
     513                   % (diff.old_path or '/', diff.old_rev or 'latest', 
     514                      diff.new_path or '/', diff.new_rev or 'latest') 
     515 
     516 
     517 
     518class DiffModule(AbstractDiffModule): 
     519 
     520    implements(IWikiSyntaxProvider) 
     521 
     522    # (reimplemented) IRequestHandler methods 
     523 
     524    def match_request(self, req): 
     525        match = re.match(r'/diff(?:(/.*)|$)', req.path_info) 
     526        if match: 
     527            if match.group(1): 
     528                req.args['path'] = match.group(1) 
     529            return 1 
     530 
     531    # IWikiSyntaxProvider methods 
     532     
     533    def get_wiki_syntax(self): 
     534        return [] 
     535 
     536    def get_link_resolvers(self): 
     537        yield ('diff', self._format_link) 
     538 
     539    def _format_link(self, formatter, ns, params, label): 
     540        def pathrev(path): 
     541            if '@' in path: 
     542                return path.split('@', 1) 
     543            else: 
     544                return (path, None) 
     545        if '//' in params: 
     546            p1, p2 = params.split('//', 1) 
     547            old, new = pathrev(p1), pathrev(p2) 
     548            diff = DiffArgs(old_path=old[0], old_rev=old[1], 
     549                            new_path=new[0], new_rev=new[1]) 
     550        else:  
     551            old_path, old_rev = pathrev(params) 
     552            new_rev = None 
     553            if old_rev and ':' in old_rev: 
     554                old_rev, new_rev = old_rev.split(':', 1) 
     555            diff = DiffArgs(old_path=old_path, old_rev=old_rev, 
     556                            new_path=old_path, new_rev=new_rev) 
     557        title = self.title_for_diff(diff) 
     558        href = formatter.href.diff(diff.new_path, new=diff.new_rev, 
     559                                   old_path=diff.old_path, old=diff.old_rev) 
     560        return '<a class="changeset" title="%s" href="%s">%s</a>' \ 
     561               % (title, href, label) 
     562 
     563 
     564class AnyDiffModule(Component): 
     565 
     566    implements(IRequestHandler) 
     567 
     568    # IRequestHandler methods 
     569 
     570    def match_request(self, req): 
     571        return re.match(r'/anydiff$', req.path_info) 
     572 
     573    def process_request(self, req): 
     574        # -- retrieve arguments 
     575        new_path = req.args.get('new_path') 
     576        new_rev = req.args.get('new_rev') 
     577        old_path = req.args.get('old_path') 
     578        old_rev = req.args.get('old_rev') 
     579 
     580        # -- normalize  
     581        repos = self.env.get_repository(req.authname) 
     582        new_path = repos.normalize_path(new_path) 
     583        new_rev = repos.normalize_rev(new_rev) 
     584        old_path = repos.normalize_path(old_path) 
     585        old_rev = repos.normalize_rev(old_rev) 
     586 
     587        authzperm = SubversionAuthorizer(self.env, req.authname) 
     588        authzperm.assert_permission_for_changeset(new_rev) 
     589        authzperm.assert_permission_for_changeset(old_rev) 
     590         
     591        # -- prepare rendering 
     592        req.hdf['anydiff'] = { 
     593            'new_path': new_path, 
     594            'new_rev': new_rev, 
     595            'old_path': old_path, 
     596            'old_rev': old_rev, 
     597            'diff_href': self.env.href.diff(), 
     598            } 
     599 
     600        return 'anydiff.cs', None 
  • trac/versioncontrol/web_ui/__init__.py

    Property changes on: trac\versioncontrol\web_ui\diff.py
    ___________________________________________________________________
    Name: svn:eol-style
       + native
    
     
    11from trac.versioncontrol.web_ui.browser import * 
    22from trac.versioncontrol.web_ui.changeset import * 
    33from trac.versioncontrol.web_ui.log import * 
     4from trac.versioncontrol.web_ui.diff import * 
  • trac/versioncontrol/web_ui/changeset.py

     
    2222 
    2323from trac import mimeview, util 
    2424from trac.core import * 
    25 from trac.perm import IPermissionRequestor 
    2625from trac.Search import ISearchSource, query_to_sql, shorten_result 
    2726from trac.Timeline import ITimelineEventProvider 
    2827from trac.versioncontrol import Changeset, Node 
    2928from trac.versioncontrol.svn_authz import SubversionAuthorizer 
    30 from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff 
    3129from trac.web import IRequestHandler 
    32 from trac.web.chrome import add_link, add_stylesheet, INavigationContributor 
     30from trac.web.chrome import INavigationContributor 
    3331from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider 
     32from trac.versioncontrol.web_ui.diff import AbstractDiffModule 
    3433 
     34class ChangesetModule(AbstractDiffModule): 
    3535 
    36 class ChangesetModule(Component): 
    37  
    38     implements(INavigationContributor, IPermissionRequestor, IRequestHandler, 
     36    implements(INavigationContributor,  
    3937               ITimelineEventProvider, IWikiSyntaxProvider, ISearchSource) 
    4038 
    4139    # INavigationContributor methods 
     
    4644    def get_navigation_items(self, req): 
    4745        return [] 
    4846 
    49     # IPermissionRequestor methods 
     47    # (reimplemented) IRequestHandler methods 
    5048 
    51     def get_permission_actions(self): 
    52         return ['CHANGESET_VIEW'] 
    53  
    54     # IRequestHandler methods 
    55  
    5649    def match_request(self, req): 
    57         match = re.match(r'/changeset/([0-9]+)$', req.path_info) 
     50        match = re.match(r'/changeset/([0-9]+)(/.*)?$', req.path_info) 
    5851        if match: 
    5952            req.args['rev'] = match.group(1) 
     53            path = match.group(2) 
     54            if path: 
     55                req.args['path'] = path 
    6056            return 1 
    6157 
    62     def process_request(self, req): 
    63         req.perm.assert_permission('CHANGESET_VIEW') 
    64  
    65         rev = req.args.get('rev') 
    66         repos = self.env.get_repository(req.authname) 
    67         authzperm = SubversionAuthorizer(self.env, req.authname) 
    68         authzperm.assert_permission_for_changeset(rev) 
    69  
    70         diff_options = get_diff_options(req) 
    71         if req.args.has_key('update'): 
    72             req.redirect(self.env.href.changeset(rev)) 
    73  
    74         chgset = repos.get_changeset(rev) 
    75         req.check_modified(chgset.date, 
    76                            diff_options[0] + ''.join(diff_options[1])) 
    77  
    78         format = req.args.get('format') 
    79         if format == 'diff': 
    80             self._render_diff(req, repos, chgset, diff_options) 
    81             return 
    82         elif format == 'zip': 
    83             self._render_zip(req, repos, chgset) 
    84             return 
    85  
    86         self._render_html(req, repos, chgset, diff_options) 
    87         add_link(req, 'alternate', '?format=diff', 'Unified Diff', 
    88                  'text/plain', 'diff') 
    89         add_link(req, 'alternate', '?format=zip', 'Zip Archive', 
    90                  'application/zip', 'zip') 
    91         add_stylesheet(req, 'common/css/changeset.css') 
    92         add_stylesheet(req, 'common/css/diff.css') 
    93         add_stylesheet(req, 'common/css/code.css') 
    94         return 'changeset.cs', None 
    95  
    9658    # ITimelineEventProvider methods 
    9759 
    9860    def get_timeline_filters(self, req): 
     
    146108                          message 
    147109                rev = repos.previous_rev(rev) 
    148110 
    149     # Internal methods 
    150111 
    151     def _render_html(self, req, repos, chgset, diff_options): 
    152         """HTML version""" 
    153         req.hdf['title'] = '[%s]' % chgset.rev 
    154         req.hdf['changeset'] = { 
    155             'revision': chgset.rev, 
    156             'time': util.format_datetime(chgset.date), 
    157             'author': util.escape(chgset.author or 'anonymous'), 
    158             'message': wiki_to_html(chgset.message or '--', self.env, req, 
    159                                     escape_newlines=True) 
    160         } 
    161  
    162         oldest_rev = repos.oldest_rev 
    163         if chgset.rev != oldest_rev: 
    164             add_link(req, 'first', self.env.href.changeset(oldest_rev), 
    165                      'Changeset %s' % oldest_rev) 
    166             previous_rev = repos.previous_rev(chgset.rev) 
    167             add_link(req, 'prev', self.env.href.changeset(previous_rev), 
    168                      'Changeset %s' % previous_rev) 
    169         youngest_rev = repos.youngest_rev 
    170         if str(chgset.rev) != str(youngest_rev): 
    171             next_rev = repos.next_rev(chgset.rev) 
    172             add_link(req, 'next', self.env.href.changeset(next_rev), 
    173                      'Changeset %s' % next_rev) 
    174             add_link(req, 'last', self.env.href.changeset(youngest_rev), 
    175                      'Changeset %s' % youngest_rev) 
    176  
    177         edits = [] 
    178         idx = 0 
    179         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    180             info = {'change': change} 
    181             if base_path: 
    182                 info['path.old'] = base_path 
    183                 info['rev.old'] = base_rev 
    184                 info['browser_href.old'] = self.env.href.browser(base_path, 
    185                                                                  rev=base_rev) 
    186             if path: 
    187                 info['path.new'] = path 
    188                 info['rev.new'] = chgset.rev 
    189                 info['browser_href.new'] = self.env.href.browser(path, 
    190                                                                  rev=chgset.rev) 
    191             if change in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE): 
    192                 edits.append((idx, path, kind, base_path, base_rev)) 
    193             req.hdf['changeset.changes.%d' % idx] = info 
    194             idx += 1 
    195  
    196         hidden_properties = [p.strip() for p 
    197                              in self.config.get('browser', 'hide_properties', 
    198                                                 'svk:merge').split(',')] 
    199  
    200         for idx, path, kind, base_path, base_rev in edits: 
    201             old_node = repos.get_node(base_path or path, base_rev) 
    202             new_node = repos.get_node(path, chgset.rev) 
    203  
    204             # Property changes 
    205             old_props = old_node.get_properties() 
    206             new_props = new_node.get_properties() 
    207             changed_props = {} 
    208             if old_props != new_props: 
    209                 for k,v in old_props.items(): 
    210                     if not k in new_props: 
    211                         changed_props[k] = {'old': v} 
    212                     elif v != new_props[k]: 
    213                         changed_props[k] = {'old': v, 'new': new_props[k]} 
    214                 for k,v in new_props.items(): 
    215                     if not k in old_props: 
    216                         changed_props[k] = {'new': v} 
    217                 for k in hidden_properties: 
    218                     if k in changed_props: 
    219                         del changed_props[k] 
    220                 req.hdf['changeset.changes.%d.props' % idx] = changed_props 
    221  
    222             if kind == Node.DIRECTORY: 
    223                 continue 
    224  
    225             # Content changes 
    226             default_charset = self.config.get('trac', 'default_charset') 
    227             old_content = old_node.get_content().read() 
    228             if mimeview.is_binary(old_content): 
    229                 continue 
    230             charset = mimeview.get_charset(old_node.content_type) 
    231             if not charset: 
    232                 charset = mimeview.detect_unicode(old_content) 
    233             old_content = util.to_utf8(old_content, charset or default_charset) 
    234  
    235             new_content = new_node.get_content().read() 
    236             if mimeview.is_binary(new_content): 
    237                 continue 
    238             charset = mimeview.get_charset(new_node.content_type) 
    239             if not charset: 
    240                 charset = mimeview.detect_unicode(new_content) 
    241             new_content = util.to_utf8(new_content, charset or default_charset) 
    242  
    243             if old_content != new_content: 
    244                 context = 3 
    245                 for option in diff_options[1]: 
    246                     if option.startswith('-U'): 
    247                         context = int(option[2:]) 
    248                         break 
    249                 if context < 0: 
    250                     context = None 
    251                 tabwidth = int(self.config.get('diff', 'tab_width', 
    252                                                self.config.get('mimeviewer', 
    253                                                                'tab_width'))) 
    254                 changes = hdf_diff(old_content.splitlines(), 
    255                                    new_content.splitlines(), 
    256                                    context, tabwidth, 
    257                                    ignore_blank_lines='-B' in diff_options[1], 
    258                                    ignore_case='-i' in diff_options[1], 
    259                                    ignore_space_changes='-b' in diff_options[1]) 
    260                 req.hdf['changeset.changes.%d.diff' % idx] = changes 
    261  
    262     def _render_diff(self, req, repos, chgset, diff_options): 
    263         """Raw Unified Diff version""" 
    264         req.send_response(200) 
    265         req.send_header('Content-Type', 'text/plain;charset=utf-8') 
    266         req.send_header('Content-Disposition', 
    267                         'filename=Changeset%s.diff' % req.args.get('rev')) 
    268         req.end_headers() 
    269  
    270         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    271             if change == Changeset.ADD: 
    272                 old_node = None 
    273             else: 
    274                 old_node = repos.get_node(base_path or path, base_rev) 
    275             if change == Changeset.DELETE: 
    276                 new_node = None 
    277             else: 
    278                 new_node = repos.get_node(path, chgset.rev) 
    279  
    280             # TODO: Property changes 
    281  
    282             # Content changes 
    283             if kind == 'dir': 
    284                 continue 
    285  
    286             default_charset = self.config.get('trac', 'default_charset') 
    287             new_content = old_content = '' 
    288             new_node_info = old_node_info = ('','') 
    289  
    290             if old_node: 
    291                 charset = mimeview.get_charset(old_node.content_type) or \ 
    292                           default_charset 
    293                 old_content = util.to_utf8(old_node.get_content().read(), 
    294                                            charset) 
    295                 old_node_info = (old_node.path, old_node.rev) 
    296             if mimeview.is_binary(old_content): 
    297                 continue 
    298  
    299             if new_node: 
    300                 charset = mimeview.get_charset(new_node.content_type) or \ 
    301                           default_charset 
    302                 new_content = util.to_utf8(new_node.get_content().read(), 
    303                                            charset) 
    304                 new_node_info = (new_node.path, new_node.rev) 
    305             if mimeview.is_binary(new_content): 
    306                 continue 
    307  
    308             if old_content != new_content: 
    309                 context = 3 
    310                 for option in diff_options[1]: 
    311                     if option.startswith('-U'): 
    312                         context = int(option[2:]) 
    313                         break 
    314                 req.write('Index: ' + path + util.CRLF) 
    315                 req.write('=' * 67 + util.CRLF) 
    316                 req.write('--- %s (revision %s)' % old_node_info + 
    317                           util.CRLF) 
    318                 req.write('+++ %s (revision %s)' % new_node_info + 
    319                           util.CRLF) 
    320                 for line in unified_diff(old_content.splitlines(), 
    321                                          new_content.splitlines(), context, 
    322                                          ignore_blank_lines='-B' in diff_options[1], 
    323                                          ignore_case='-i' in diff_options[1], 
    324                                          ignore_space_changes='-b' in diff_options[1]): 
    325                     req.write(line + util.CRLF) 
    326  
    327     def _render_zip(self, req, repos, chgset): 
    328         """ZIP archive with all the added and/or modified files.""" 
    329         req.send_response(200) 
    330         req.send_header('Content-Type', 'application/zip') 
    331         req.send_header('Content-Disposition', 
    332                         'filename=Changeset%s.zip' % chgset.rev) 
    333         req.end_headers() 
    334  
    335         try: 
    336             from cStringIO import StringIO 
    337         except ImportError: 
    338             from StringIO import StringIO 
    339         from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED 
    340  
    341         buf = StringIO() 
    342         zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 
    343         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
    344             if kind == Node.FILE and change != Changeset.DELETE: 
    345                 node = repos.get_node(path, chgset.rev) 
    346                 zipinfo = ZipInfo() 
    347                 zipinfo.filename = node.path 
    348                 zipinfo.date_time = time.gmtime(node.last_modified)[:6] 
    349                 zipinfo.compress_type = ZIP_DEFLATED 
    350                 zipfile.writestr(zipinfo, node.get_content().read()) 
    351         zipfile.close() 
    352         req.write(buf.getvalue()) 
    353  
    354112    # IWikiSyntaxProvider methods 
    355113     
    356114    def get_wiki_syntax(self): 
    357         yield (r"!?\[\d+\]|(?:\b|!)r\d+\b(?!:\d)", 
     115        yield (r"!?\[\d+(?:/[^\]]*)?\]|(?:\b|!)r\d+\b(?!:\d)", 
    358116               lambda x, y, z: self._format_link(x, 'changeset', 
    359117                                                 y[0] == 'r' and y[1:] 
    360                                                  or y[1:-1], y)) 
     118                                                 or y[1:-1], y, z)) 
    361119 
    362120    def get_link_resolvers(self): 
    363121        yield ('changeset', self._format_link) 
    364122 
    365     def _format_link(self, formatter, ns, rev, label): 
     123    def _format_link(self, formatter, ns, chgset, label, fullmatch=None): 
     124        sep = chgset.find('/') 
     125        if sep > 0: 
     126            rev, path = chgset[:sep], chgset[sep:] 
     127        else: 
     128            rev, path = chgset, None 
    366129        cursor = formatter.db.cursor() 
    367130        cursor.execute('SELECT message FROM revision WHERE rev=%s', (rev,)) 
    368131        row = cursor.fetchone() 
    369132        if row: 
    370133            return '<a class="changeset" title="%s" href="%s">%s</a>' \ 
    371134                   % (util.escape(util.shorten_line(row[0])), 
    372                       formatter.href.changeset(rev), label) 
     135                      formatter.href.changeset(rev, path), label) 
    373136        else: 
    374             return '<a class="missing changeset" href="%s" rel="nofollow">%s</a>' \ 
    375                    % (formatter.href.changeset(rev), label) 
     137            return '<a class="missing changeset" href="%s"' \ 
     138                   ' rel="nofollow">%s</a>' \ 
     139                   % (formatter.href.changeset(rev, path), label) 
    376140 
    377141    # ISearchProvider methods 
    378142 
  • trac/versioncontrol/web_ui/log.py

     
    6767        stop_rev = req.args.get('stop_rev') 
    6868        verbose = req.args.get('verbose') 
    6969        limit = LOG_LIMIT 
     70        old = req.args.get('old') 
     71        new = req.args.get('new') 
    7072 
     73        repos = self.env.get_repository(req.authname) 
     74        normpath = repos.normalize_path(path) 
     75        rev = str(repos.normalize_rev(rev)) 
     76 
     77        if old and new: 
     78            osep = util.unescape(old).rindex('#') 
     79            nsep = util.unescape(new).rindex('#') 
     80            old_path, old_rev = old[:osep], old[osep+1:] 
     81            new_path, new_rev = new[:nsep], new[nsep+1:] 
     82            req.redirect(self.env.href.diff(new_path, new=new_rev, 
     83                                            old_path=old_path, old=old_rev)) 
     84 
    7185        req.hdf['title'] = path + ' (log)' 
    7286        req.hdf['log'] = { 
    7387            'mode': mode, 
     
    8498        if path_links: 
    8599            add_link(req, 'up', path_links[-1]['href'], 'Parent directory') 
    86100 
    87         repos = self.env.get_repository(req.authname) 
    88         normpath = repos.normalize_path(path) 
    89         rev = str(repos.normalize_rev(rev)) 
    90101 
    91102        # ''Node history'' uses `Node.history()`, 
    92103        # ''Path history'' uses `Repository.get_path_history()` 
  • trac/versioncontrol/web_ui/browser.py

     
    8888 
    8989        repos = self.env.get_repository(req.authname) 
    9090        node = get_existing_node(self.env, repos, path, rev) 
     91        rev = repos.normalize_rev(rev) 
    9192 
    9293        hidden_properties = [p.strip() for p 
    9394                             in self.config.get('browser', 'hide_properties', 
    9495                                                'svk:merge').split(',')] 
     96 
    9597        req.hdf['title'] = path 
    96         req.hdf['browser'] = { 
     98        browser_hdf = { 
    9799            'path': path, 
    98             'revision': rev or repos.youngest_rev, 
     100            'revision': rev, 
    99101            'props': dict([(util.escape(name), util.escape(value)) 
    100102                           for name, value in node.get_properties().items() 
    101                            if not name in hidden_properties]), 
    102             'href': util.escape(self.env.href.browser(path, rev=rev or 
    103                                                       repos.youngest_rev)), 
    104             'log_href': util.escape(self.env.href.log(path, rev=rev or None)) 
    105         } 
     103                           if not name in hidden_properties]) 
     104            } 
     105        browser_hrefs = { 
     106            'href': self.env.href.browser(path,rev=rev), 
     107            'restr_changeset_href': self.env.href.changeset(node.rev, path), 
     108            'anydiff_href': self.env.href.anydiff(), 
     109            'log_href': self.env.href.log(path, rev=rev) 
     110            } 
     111        browser_hdf.update(dict([(key, util.escape(href)) for key, href in 
     112                                 browser_hrefs.items()])) 
     113        req.hdf['browser'] = browser_hdf 
    106114 
    107115        path_links = get_path_links(self.env.href, path, rev) 
    108116        if len(path_links) > 1: 
     
    162170 
    163171        req.hdf['browser.items'] = info 
    164172        req.hdf['browser.changes'] = changes 
    165  
     173        if node.path != '': 
     174            zip_href = self.env.href.diff(node.path, new=rev, old=rev, 
     175                                          old_path='/', # special case (#238) 
     176                                          format='zip') 
     177            add_link(req, 'alternate', zip_href, 'Zip Archive', 
     178                     'application/zip', 'zip') 
     179         
     180         
    166181    def _render_file(self, req, repos, node, rev=None): 
    167182        req.perm.assert_permission('FILE_VIEW') 
    168183 
  • trac/wiki/tests/wiki-tests.txt

     
    5050||Foo||Bar||Baz|| 
    5151<a class="ext-link" href="http://www.edgewall.com/"><span class="icon"></span>http://www.edgewall.com/</a> 
    5252============================== 
    53 #1, [1], r1, {1} 
     53#1, {1} 
     54[1], r1 
     55[1/README.txt] 
    5456------------------------------ 
    5557<p> 
    56 <a class="missing ticket" href="/ticket/1" rel="nofollow">#1</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">[1]</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">r1</a>, <a class="report" href="/report/1">{1}</a> 
     58<a class="missing ticket" href="/ticket/1" rel="nofollow">#1</a>, <a class="report" href="/report/1">{1}</a> 
     59<a class="missing changeset" href="/changeset/1" rel="nofollow">[1]</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">r1</a> 
     60<a class="missing changeset" href="/changeset/1/README.txt" rel="nofollow">[1/README.txt]</a> 
    5761</p> 
    5862------------------------------ 
    5963============================== 
     
    8084------------------------------ 
    8185[1:2], r1:2, [12:23], r12:23 
    8286============================== 
    83 ticket:1, changeset:1, report:1, source:foo/bar 
     87ticket:1, report:1, source:foo/bar 
     88changeset:1, changeset:1/README.txt 
    8489 
    8590Issue [ticket:1], CS[changeset:1], Listing [report:1], File [source:foo/bar] 
    8691------------------------------ 
    8792<p> 
    88 <a class="missing ticket" href="/ticket/1" rel="nofollow">ticket:1</a>, <a class="missing changeset" href="/changeset/1" rel="nofollow">changeset:1</a>, <a class="report" href="/report/1">report:1</a>, <a class="source" href="/browser/foo/bar">source:foo/bar</a> 
     93<a class="missing ticket" href="/ticket/1" rel="nofollow">ticket:1</a>, <a class="report" href="/report/1">report:1</a>, <a class="source" href="/browser/foo/bar">source:foo/bar</a> 
     94<a class="missing changeset" href="/changeset/1" rel="nofollow">changeset:1</a>, <a class="missing changeset" href="/changeset/1/README.txt" rel="nofollow">changeset:1/README.txt</a> 
    8995</p> 
    9096<p> 
    9197Issue <a class="missing ticket" href="/ticket/1" rel="nofollow">1</a>, CS<a class="missing changeset" href="/changeset/1" rel="nofollow">1</a>, Listing <a class="report" href="/report/1">1</a>, File <a class="source" href="/browser/foo/bar">foo/bar</a> 
     
    110116</p> 
    111117------------------------------ 
    112118============================== 
     119diff:trunk//branch 
     120diff:trunk@12//branch@23 
     121diff:trunk@12:23 
     122diff:@12:23 
     123------------------------------ 
     124<p> 
     125<a class="changeset" title="Diff from trunk@latest to branch@latest" href="/diff/branch?old_path=trunk">diff:trunk//branch</a> 
     126<a class="changeset" title="Diff from trunk@12 to branch@23" href="/diff/branch?new=23&old=12&old_path=trunk">diff:trunk@12//branch@23</a> 
     127<a class="changeset" title="Diff r12:23 for trunk" href="/diff/trunk?new=23&old=12&old_path=trunk">diff:trunk@12:23</a> 
     128<a class="changeset" title="Diff r12:23 for /" href="/diff/?new=23&old=12&old_path=">diff:@12:23</a> 
     129</p> 
     130------------------------------ 
     131============================== 
    113132Add-on to changeset:123: 
    114133Some change. 
    115134ticket:1 
  • templates/anydiff.cs

     
     1<?cs include "header.cs"?> 
     2 
     3<div id="ctxtnav" class="nav"> 
     4 <h2>Navigation</h2><?cs 
     5 with:links = chrome.links ?> 
     6  <ul> 
     7  </ul><?cs 
     8 /with ?> 
     9</div> 
     10 
     11<div id="content" class="changeset"> 
     12 <div id="title"> 
     13    <h1>Select Base and Target for Diff:</h1> 
     14 </div> 
     15 
     16 <div id="anydiff"> 
     17  <form action="<?cs var:anydiff.diff_href ?>" method="post"> 
     18   <table> 
     19    <tr> 
     20     <th><label for="old_path">From:</label></th> 
     21     <td> 
     22      <input type="text" id="old_path" name="old_path" value="<?cs 
     23         var:anydiff.old_path ?>" size="44" /> 
     24      <label for="old_rev">at Revision:</label> 
     25      <input type="text" id="old_rev" name="old" value="<?cs 
     26         var:anydiff.old_rev ?>" size="4" /> 
     27     </td> 
     28    </tr> 
     29    <tr> 
     30     <th><label for="new_path">To:</label></th> 
     31     <td> 
     32      <input type="text" id="new_path" name="path" value="<?cs 
     33         var:anydiff.new_path ?>" size="44" /> 
     34      <label for="new_rev">at Revision:</label> 
     35      <input type="text" id="new_rev" name="new" value="<?cs 
     36         var:anydiff.new_rev ?>" size="4" /> 
     37     </td> 
     38    </tr> 
     39   </table> 
     40   <div class="buttons"> 
     41      <input type="submit" value="View changes" /> 
     42   </div> 
     43  </form> 
     44 </div> 
     45</div> 
     46 
     47<?cs include "footer.cs"?> 
  • templates/log.cs

    Property changes on: templates\anydiff.cs
    ___________________________________________________________________
    Name: svn:eol-style
       + native
    
     
    33 
    44<div id="ctxtnav" class="nav"> 
    55 <ul> 
    6   <li class="last"><a href="<?cs 
    7     var:log.browser_href ?>">View Latest Revision</a></li><?cs 
     6  <li class="last"> 
     7   <a href="<?cs var:log.browser_href ?>">View Latest Revision</a> 
     8  </li><?cs 
    89  if:len(chrome.links.prev) ?> 
    910   <li class="first<?cs if:!len(chrome.links.next) ?> last<?cs /if ?>"> 
    1011    &larr; <a href="<?cs var:chrome.links.prev.0.href ?>" title="<?cs 
     
    6162          title="Warning: by updating, you will clear the page history" /> 
    6263  </div> 
    6364 </form> 
     65 
    6466 <div class="diff"> 
    6567  <div id="legend"> 
    6668   <h3>Legend:</h3> 
     
    7476   </dl> 
    7577  </div> 
    7678 </div> 
     79 
     80 <form action="<?cs var:log.href ?>" method="post"> 
     81  <div class="buttons"><input type="submit" value="View changes"  
     82       title="Diff from Old Revision to New Revision (select them below)" /> 
     83 </div> 
    7784 <table id="chglist" class="listing"> 
    7885  <thead> 
    7986   <tr> 
     87    <th>Old</th> 
     88    <th>New</th> 
    8089    <th class="change"></th> 
    8190    <th class="data">Date</th> 
    8291    <th class="rev">Rev</th> 
     
    8796  </thead> 
    8897  <tbody><?cs 
    8998   set:indent = #1 ?><?cs 
     99   set:idx = #0 ?><?cs 
    90100   each:item = log.items ?><?cs 
    91101    if:item.copyfrom_path ?> 
    92102     <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"> 
    93       <td class="copyfrom_path" colspan="6" style="padding-left: <?cs var:indent ?>em"> 
     103      <td class="copyfrom_path" colspan="8" style="padding-left: <?cs var:indent ?>em"> 
    94104       copied from <a href="<?cs var:item.browser_href ?>"?><?cs var:item.copyfrom_path ?></a>: 
    95105      </td> 
    96106     </tr><?cs 
     
    99109      set:indent = #1 ?><?cs 
    100110    /if ?> 
    101111    <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"> 
     112     <td><input type="radio" name="old"  
     113                value="<?cs var:item.path ?>#<?cs var:item.rev ?>" <?cs 
     114          if:idx == #1 ?> checked="checked" <?cs /if ?> /></td> 
     115     <td><input type="radio" name="new"  
     116                value="<?cs var:item.path ?>#<?cs var:item.rev ?>" <?cs 
     117          if:idx == #0 ?> checked="checked" <?cs /if ?> /></td> 
    102118     <td class="change" style="padding-left:<?cs var:indent ?>em"> 
    103119      <a title="View log starting at this revision" href="<?cs var:item.log_href ?>"> 
    104120       <span class="<?cs var:item.change ?>"></span> 
     
    117133     <td class="author"><?cs var:log.changes[item.rev].author ?></td> 
    118134     <td class="summary"><?cs var:log.changes[item.rev].message ?></td> 
    119135    </tr><?cs 
     136    set:idx = idx + 1 ?><?cs 
    120137   /each ?> 
    121138  </tbody> 
    122  </table><?cs 
     139 </table> 
     140 <div class="buttons"><input type="submit" value="View changes"  
     141      title="Diff from Old Revision to New Revision (select them above)" /> 
     142 </div> 
     143 </form><?cs 
    123144 if:len(links.prev) || len(links.next) ?><div id="paging" class="nav"><ul><?cs 
    124145  if:len(links.prev) ?><li class="first<?cs 
    125146   if:!len(links.next) ?> last<?cs /if ?>">&larr; <a href="<?cs 
  • templates/browser.cs

     
    33 
    44<div id="ctxtnav" class="nav"> 
    55 <ul> 
    6   <li class="last"><a href="<?cs var:browser.log_href ?>">Revision Log</a></li> 
     6  <li class="first"><a href="<?cs var:browser.restr_changeset_href ?>"> 
     7   Last Change</a></li> 
     8  <li class="last"><a href="<?cs var:browser.log_href ?>"> 
     9   Revision Log</a></li> 
    710 </ul> 
    811</div> 
    912 
     13 
    1014<div id="content" class="browser"> 
    1115 <h1><?cs call:browser_path_links(browser.path, browser) ?></h1> 
    1216 
    1317 <div id="jumprev"> 
    14   <form action="" method="get"><div> 
    15    <label for="rev">View revision:</label> 
    16    <input type="text" id="rev" name="rev" value="<?cs 
    17      var:browser.revision ?>" size="4" /> 
    18   </div></form> 
     18  <form action="" method="get"> 
     19   <div> 
     20    <label for="rev">View revision:</label> 
     21    <input type="text" id="rev" name="rev" value="<?cs 
     22       var:browser.revision ?>" size="4" /> 
     23   </div> 
     24  </form> 
    1925 </div> 
    2026 
    2127 <?cs if:browser.is_dir ?> 
     
    114120  ?>/TracBrowser">TracBrowser</a> for help on using the browser. 
    115121 </div> 
    116122 
     123  <div id="anydiff"><?cs 
     124   if len(browser.path) > #1 ?> 
     125    <form action="<?cs var:browser.anydiff_href ?>" method="get"> 
     126     <input type="hidden" name="new_path" value="<?cs var:browser.path ?>" /> 
     127     <input type="hidden" name="old_path" value="<?cs var:browser.path ?>" /> 
     128     <input type="hidden" name="new_rev" value="<?cs var:browser.revision ?>" /> 
     129     <input type="hidden" name="old_rev" value="<?cs var:browser.revision ?>" /> 
     130     <div class="buttons"> 
     131      <input type="submit" value="View changes..." title="Prepare an Arbitrary Diff" /> 
     132     </div> 
     133    </form><?cs 
     134   /if ?> 
     135  </div> 
     136 
    117137</div> 
    118138<?cs include:"footer.cs"?> 
  • templates/wiki.cs

     
    154154    var:wiki.page_name ?></a></h1> 
    155155  <?cs if:len(wiki.history) ?><form method="get" action=""> 
    156156   <input type="hidden" name="action" value="diff" /> 
     157   <div class="buttons"> 
     158    <input type="submit" value="View changes" /> 
     159   </div> 
    157160   <table id="wikihist" class="listing" summary="Change history"> 
    158161    <thead><tr> 
    159162     <th class="diff"></th> 
  • templates/diff.cs

     
     1<?cs include "header.cs"?> 
     2<?cs include "macros.cs"?> 
     3 
     4<div id="ctxtnav" class="nav"> 
     5 <h2>Navigation</h2><?cs 
     6 with:links = chrome.links ?> 
     7  <ul><?cs 
     8   if:diff.chgset ?><?cs 
     9    if:len(links.prev) ?> 
     10     <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>"> 
     11     &larr; <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs 
     12        var:links.prev.0.title ?>">Previous <?cs  
     13         if:diff.restricted ?>Change<?cs else ?>Changeset<?cs /if ?></a> 
     14     </li><?cs 
     15    /if ?><?cs 
     16    if:len(links.next) ?> 
     17     <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last"> 
     18      <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs 
     19        var:links.next.0.title ?>">Next <?cs  
     20         if:diff.restricted ?>Change<?cs else ?>Changeset<?cs /if ?></a> &rarr; 
     21     </li><?cs 
     22    /if ?><?cs 
     23   else ?> 
     24    <li class="first"><a href="<?cs var:diff.reverse_href ?>">Reverse Diff</a></li><?cs 
     25   /if ?> 
     26  </ul><?cs 
     27 /with ?> 
     28</div> 
     29 
     30<div id="content" class="changeset"> 
     31 <div id="title"><?cs 
     32  if:diff.chgset ?><?cs 
     33   if:diff.restricted ?> 
     34    <h1>Changeset <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     35      <?cs var:diff.new_rev ?></a>  
     36     for <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     37      <?cs var:diff.new_path ?></a>  
     38    </h1><?cs 
     39   else ?> 
     40    <h1>Changeset <?cs var:diff.new_rev ?></h1><?cs 
     41   /if ?><?cs 
     42  else ?><?cs 
     43    if:diff.restricted ?> 
     44    <h1>Changes in <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     45      <?cs var:diff.new_path ?></a> 
     46     from revision <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>"> 
     47      <?cs var:diff.old_rev ?></a> 
     48     to <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     49      <?cs var:diff.new_rev ?></a> 
     50    </h1><?cs 
     51   else ?> 
     52    <h1>Changes from <a title="Show entry in browser" href="<?cs var:diff.href.old_path ?>"> 
     53      <?cs var:diff.old_path ?></a>  
     54     at revision <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>"> 
     55      <?cs var:diff.old_rev ?></a> 
     56     to <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     57     <?cs var:diff.new_path ?></a>  
     58     at revision <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     59     <?cs var:diff.new_rev ?></a> 
     60    </h1><?cs 
     61   /if ?><?cs 
     62  /if ?> 
     63 </div> 
     64 
     65<?cs each:change = diff.changes ?><?cs 
     66 if:len(change.diff) ?><?cs 
     67  set:has_diffs = 1 ?><?cs 
     68 /if ?><?cs 
     69/each ?><?cs if:has_diffs || diff.options.ignoreblanklines  
     70  || diff.options.ignorecase || diff.options.ignorewhitespace ?> 
     71<form method="post" id="prefs" action=""> 
     72 <div><?cs  
     73  if:!diff.chgset ?> 
     74   <input type="hidden" name="old_path" value="<?cs var:diff.old_path ?>" /> 
     75   <input type="hidden" name="path" value="<?cs var:diff.new_path ?>" /> 
     76   <input type="hidden" name="old" value="<?cs var:diff.old_rev ?>" /> 
     77   <input type="hidden" name="new" value="<?cs var:diff.new_rev ?>" /><?cs 
     78  /if ?> 
     79  <label for="style">View differences</label> 
     80  <select id="style" name="style"> 
     81   <option value="inline"<?cs 
     82     if:diff.style == 'inline' ?> selected="selected"<?cs 
     83     /if ?>>inline</option> 
     84   <option value="sidebyside"<?cs 
     85     if:diff.style == 'sidebyside' ?> selected="selected"<?cs 
     86     /if ?>>side by side</option> 
     87  </select> 
     88  <div class="field"> 
     89   Show <input type="text" name="contextlines" id="contextlines" size="2" 
     90     maxlength="3" value="<?cs var:diff.options.contextlines ?>" /> 
     91   <label for="contextlines">lines around each change</label> 
     92  </div> 
     93  <fieldset id="ignore"> 
     94   <legend>Ignore:</legend> 
     95   <div class="field"> 
     96    <input type="checkbox" id="blanklines" name="ignoreblanklines"<?cs 
     97      if:diff.options.ignoreblanklines ?> checked="checked"<?cs /if ?> /> 
     98    <label for="blanklines">Blank lines</label> 
     99   </div> 
     100   <div class="field"> 
     101    <input type="checkbox" id="case" name="ignorecase"<?cs 
     102      if:diff.options.ignorecase ?> checked="checked"<?cs /if ?> /> 
     103    <label for="case">Case changes</label> 
     104   </div> 
     105   <div class="field"> 
     106    <input type="checkbox" id="whitespace" name="ignorewhitespace"<?cs 
     107      if:diff.options.ignorewhitespace ?> checked="checked"<?cs /if ?> /> 
     108    <label for="whitespace">White space changes</label> 
     109   </div> 
     110  </fieldset> 
     111  <div class="buttons"> 
     112   <input type="submit" name="update" value="Update" /> 
     113  </div> 
     114 </div> 
     115</form><?cs /if ?> 
     116 
     117<?cs def:node_change(item,cl,kind) ?><?cs  
     118  set:ndiffs = len(item.diff) ?><?cs 
     119  set:nprops = len(item.props) ?> 
     120  <div class="<?cs var:cl ?>"></div><?cs  
     121  if:cl == "rem" ?> 
     122   <a title="Show what was removed (rev. <?cs var:item.rev.old ?>)" href="<?cs 
     123     var:item.browser_href.old ?>"><?cs var:item.path.old ?></a><?cs 
     124  else ?> 
     125   <a title="Show entry in browser" href="<?cs 
     126     var:item.browser_href.new ?>"><?cs var:item.path.new ?></a><?cs 
     127  /if ?> 
     128  <span class="comment">(<?cs var:kind ?>)</span><?cs 
     129  if:item.path.old && item.change == 'copy' || item.change == 'move' ?> 
     130   <small><em>(<?cs var:kind ?> from <a href="<?cs 
     131    var:item.browser_href.old ?>" title="Show original file (rev. <?cs 
     132    var:item.rev.old ?>)"><?cs var:item.path.old ?></a>)</em></small><?cs 
     133  /if ?><?cs 
     134  if:$ndiffs + $nprops > #0 ?> 
     135    (<a href="#file<?cs var:name(item) ?>" title="Show differences"><?cs 
     136      if:$ndiffs > #0 ?><?cs var:ndiffs ?>&nbsp;diff<?cs if:$ndiffs > #1 ?>s<?cs /if ?><?cs  
     137      /if ?><?cs 
     138      if:$ndiffs && $nprops ?>, <?cs /if ?><?cs  
     139      if:$nprops > #0 ?><?cs var:nprops ?>&nbsp;prop<?cs if:$nprops > #1 ?>s<?cs /if ?><?cs 
     140      /if ?></a>)<?cs 
     141  elif:cl == "mod" ?> 
     142    (<a href="<?cs var:item.browser_href.old ?>" 
     143        title="Show previous version in browser">previous</a>)<?cs 
     144  /if ?> 
     145<?cs /def ?> 
     146 
     147<dl id="overview"><?cs 
     148 if:diff.chgset ?> 
     149 <dt class="time">Timestamp:</dt> 
     150 <dd class="time"><?cs var:changeset.time ?></dd> 
     151 <dt class="author">Author:</dt> 
     152 <dd class="author"><?cs var:changeset.author ?></dd> 
     153 <dt class="message">Message:</dt> 
     154 <dd class="message" id="searchable"><?cs 
     155  alt:changeset.message ?>&nbsp;<?cs /alt ?></dd><?cs 
     156 /if ?> 
     157 <dt class="files"><?cs  
     158  if:len(diff.changes) > #0 ?> 
     159   Files:<?cs 
     160  else ?> 
     161   (None)<?cs 
     162  /if ?> 
     163 </dt> 
     164 <dd class="files"> 
     165  <ul><?cs each:item = diff.changes ?> 
     166   <li><?cs 
     167    if:item.change == 'add' ?><?cs 
     168     call:node_change(item, 'add', 'added') ?><?cs 
     169    elif:item.change == 'delete' ?><?cs 
     170     call:node_change(item, 'rem', 'deleted') ?><?cs 
     171    elif:item.change == 'copy' ?><?cs 
     172     call:node_change(item, 'cp', 'copied') ?><?cs 
     173    elif:item.change == 'move' ?><?cs 
     174     call:node_change(item, 'mv', 'moved') ?><?cs 
     175    elif:item.change == 'edit' ?><?cs 
     176     call:node_change(item, 'mod', 'modified') ?><?cs 
     177    /if ?> 
     178   </li> 
     179  <?cs /each ?></ul> 
     180 </dd> 
     181</dl> 
     182 
     183<div class="diff"> 
     184 <div id="legend"> 
     185  <h3>Legend:</h3> 
     186  <dl> 
     187   <dt class="unmod"></dt><dd>Unmodified</dd> 
     188   <dt class="add"></dt><dd>Added</dd> 
     189   <dt class="rem"></dt><dd>Removed</dd> 
     190   <dt class="mod"></dt><dd>Modified</dd> 
     191   <dt class="cp"></dt><dd>Copied</dd> 
     192   <dt class="mv"></dt><dd>Moved</dd> 
     193  </dl> 
     194 </div> 
     195 <ul class="entries"><?cs 
     196 each:item = diff.changes ?><?cs 
     197  if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs 
     198   var:name(item) ?>"><h2><a href="<?cs 
     199   var:item.browser_href.new ?>" title="Show new revision <?cs 
     200   var:item.rev.new ?> of this file in browser"><?cs 
     201   var:item.path.new ?></a></h2><?cs 
     202   if:len(item.props) ?><ul class="props"><?cs 
     203    each:prop = item.props ?><li>Property <strong><?cs 
     204     var:name(prop) ?></strong> <?cs 
     205     if:prop.old && prop.new ?>changed from <?cs 
     206     elif:!prop.old ?>set<?cs 
     207     else ?>deleted<?cs 
     208     /if ?><?cs 
     209     if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs 
     210     if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?></li><?cs 
     211    /each ?></ul><?cs 
     212   /if ?><?cs 
     213   if:len(item.diff) ?><table class="<?cs 
     214    var:diff.style ?>" summary="Differences" cellspacing="0"><?cs 
     215    if:diff.style == 'sidebyside' ?> 
     216     <colgroup class="l"><col class="lineno" /><col class="content" /></colgroup> 
     217     <colgroup class="r"><col class="lineno" /><col class="content" /></colgroup> 
     218     <thead><tr> 
     219      <th colspan="2"><a href="<?cs 
     220       var:item.browser_href.old ?>" title="Show old rev. <?cs 
     221       var:item.rev.old ?> of <?cs var:item.path.old ?>">Revision <?cs 
     222       var:item.rev.old ?></a></th> 
     223      <th colspan="2"><a href="<?cs 
     224       var:item.browser_href.new ?>" title="Show new rev. <?cs 
     225       var:item.rev.new ?> of <?cs var:item.path.new ?>">Revision <?cs 
     226       var:item.rev.new ?></a></th> 
     227      </tr> 
     228     </thead><?cs 
     229     each:change = item.diff ?><tbody><?cs 
     230      call:diff_display(change, diff.style) ?></tbody><?cs 
     231      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
     232       <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td> 
     233      </tr></tbody><?cs /if ?><?cs 
     234     /each ?><?cs 
     235    else ?> 
     236     <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup> 
     237     <thead><tr> 
     238      <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs 
     239       var:item.browser_href.old ?>" title="Show old version of <?cs 
     240       var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th> 
     241      <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs 
     242       var:item.browser_href.new ?>" title="Show new version of <?cs 
     243       var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th> 
     244      <th>&nbsp;</th></tr> 
     245     </thead><?cs 
     246     each:change = item.diff ?><?cs 
     247      call:diff_display(change, diff.style) ?><?cs 
     248      if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
     249       <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td> 
     250      </tr></tbody><?cs /if ?><?cs 
     251     /each ?><?cs 
     252    /if ?></table><?cs 
     253   /if ?></li><?cs 
     254  /if ?><?cs 
     255 /each ?></ul> 
     256</div> 
     257 
     258</div> 
     259<?cs include "footer.cs"?> 
  • templates/changeset.cs

    Property changes on: templates\diff.cs
    ___________________________________________________________________
    Name: svn:eol-style
       + native
    
     
    1 <?cs include "header.cs"?> 
    2 <?cs include "macros.cs"?> 
    3  
    4 <div id="ctxtnav" class="nav"> 
    5  <h2>Changeset Navigation</h2><?cs 
    6  with:links = chrome.links ?> 
    7   <ul><?cs 
    8    if:len(links.prev) ?> 
    9     <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>"> 
    10      &larr; <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs 
    11        var:links.prev.0.title ?>">Previous Changeset</a> 
    12     </li><?cs 
    13    /if ?><?cs 
    14    if:len(links.next) ?> 
    15     <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last"> 
    16      <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs 
    17        var:links.next.0.title ?>">Next Changeset</a> &rarr; 
    18     </li><?cs 
    19    /if ?> 
    20   </ul><?cs 
    21  /with ?> 
    22 </div> 
    23  
    24 <div id="content" class="changeset"> 
    25 <h1>Changeset <?cs var:changeset.revision ?></h1> 
    26  
    27 <?cs each:change = changeset.changes ?><?cs 
    28  if:len(change.diff) ?><?cs 
    29   set:has_diffs = 1 ?><?cs 
    30  /if ?><?cs 
    31 /each ?><?cs if:has_diffs || diff.options.ignoreblanklines  
    32   || diff.options.ignorecase || diff.options.ignorewhitespace ?> 
    33 <form method="post" id="prefs" action=""> 
    34  <div> 
    35   <label for="style">View differences</label> 
    36   <select id="style" name="style"> 
    37    <option value="inline"<?cs 
    38      if:diff.style == 'inline' ?> selected="selected"<?cs 
    39      /if ?>>inline</option> 
    40    <option value="sidebyside"<?cs 
    41      if:diff.style == 'sidebyside' ?> selected="selected"<?cs 
    42      /if ?>>side by side</option> 
    43   </select> 
    44   <div class="field"> 
    45    Show <input type="text" name="contextlines" id="contextlines" size="2" 
    46      maxlength="3" value="<?cs var:diff.options.contextlines ?>" /> 
    47    <label for="contextlines">lines around each change</label> 
    48   </div> 
    49   <fieldset id="ignore"> 
    50    <legend>Ignore:</legend> 
    51    <div class="field"> 
    52     <input type="checkbox" id="blanklines" name="ignoreblanklines"<?cs 
    53       if:diff.options.ignoreblanklines ?> checked="checked"<?cs /if ?> /> 
    54     <label for="blanklines">Blank lines</label> 
    55    </div> 
    56    <div class="field"> 
    57     <input type="checkbox" id="case" name="ignorecase"<?cs 
    58       if:diff.options.ignorecase ?> checked="checked"<?cs /if ?> /> 
    59     <label for="case">Case changes</label> 
    60    </div> 
    61    <div class="field"> 
    62     <input type="checkbox" id="whitespace" name="ignorewhitespace"<?cs 
    63       if:diff.options.ignorewhitespace ?> checked="checked"<?cs /if ?> /> 
    64     <label for="whitespace">White space changes</label> 
    65    </div> 
    66   </fieldset> 
    67   <div class="buttons"> 
    68    <input type="submit" name="update" value="Update" /> 
    69   </div> 
    70  </div> 
    71 </form><?cs /if ?> 
    72  
    73 <?cs def:node_change(item,cl,kind) ?><?cs  
    74   set:ndiffs = len(item.diff) ?><?cs 
    75   set:nprops = len(item.props) ?> 
    76   <div class="<?cs var:cl ?>"></div><?cs  
    77   if:cl == "rem" ?> 
    78    <a title="Show what was removed (rev. <?cs var:item.rev.old ?>)" href="<?cs 
    79      var:item.browser_href.old ?>"><?cs var:item.path.old ?></a><?cs 
    80   else ?> 
    81    <a title="Show entry in browser" href="<?cs 
    82      var:item.browser_href.new ?>"><?cs var:item.path.new ?></a><?cs 
    83   /if ?> 
    84   <span class="comment">(<?cs var:kind ?>)</span><?cs 
    85   if:item.path.old && item.change == 'copy' || item.change == 'move' ?> 
    86    <small><em>(<?cs var:kind ?> from <a href="<?cs 
    87     var:item.browser_href.old ?>" title="Show original file (rev. <?cs 
    88     var:item.rev.old ?>)"><?cs var:item.path.old ?></a>)</em></small><?cs 
    89   /if ?><?cs 
    90   if:$ndiffs + $nprops > #0 ?> 
    91     (<a href="#file<?cs var:name(item) ?>" title="Show differences"><?cs 
    92       if:$ndiffs > #0 ?><?cs var:ndiffs ?>&nbsp;diff<?cs if:$ndiffs > #1 ?>s<?cs /if ?><?cs  
    93       /if ?><?cs 
    94       if:$ndiffs && $nprops ?>, <?cs /if ?><?cs  
    95       if:$nprops > #0 ?><?cs var:nprops ?>&nbsp;prop<?cs if:$nprops > #1 ?>s<?cs /if ?><?cs 
    96       /if ?></a>)<?cs 
    97   elif:cl == "mod" ?> 
    98     (<a href="<?cs var:item.browser_href.old ?>" 
    99         title="Show previous version in browser">previous</a>)<?cs 
    100   /if ?> 
    101 <?cs /def ?> 
    102  
    103 <dl id="overview"> 
    104  <dt class="time">Timestamp:</dt> 
    105  <dd class="time"><?cs var:changeset.time ?></dd> 
    106  <dt class="author">Author:</dt> 
    107  <dd class="author"><?cs var:changeset.author ?></dd> 
    108  <dt class="message">Message:</dt> 
    109  <dd class="message" id="searchable"><?cs 
    110   alt:changeset.message ?>&nbsp;<?cs /alt ?></dd> 
    111  <dt class="files">Files:</dt> 
    112  <dd class="files"> 
    113   <ul><?cs each:item = changeset.changes ?> 
    114    <li><?cs 
    115     if:item.change == 'add' ?><?cs 
    116      call:node_change(item, 'add', 'added') ?><?cs 
    117     elif:item.change == 'delete' ?><?cs 
    118      call:node_change(item, 'rem', 'deleted') ?><?cs 
    119     elif:item.change == 'copy' ?><?cs 
    120      call:node_change(item, 'cp', 'copied') ?><?cs 
    121     elif:item.change == 'move' ?><?cs 
    122      call:node_change(item, 'mv', 'moved') ?><?cs 
    123     elif:item.change == 'edit' ?><?cs 
    124      call:node_change(item, 'mod', 'modified') ?><?cs 
    125     /if ?> 
    126    </li> 
    127   <?cs /each ?></ul> 
    128  </dd> 
    129 </dl> 
    130  
    131 <div class="diff"> 
    132  <div id="legend"> 
    133   <h3>Legend:</h3> 
    134   <dl> 
    135    <dt class="unmod"></dt><dd>Unmodified</dd> 
    136    <dt class="add"></dt><dd>Added</dd> 
    137    <dt class="rem"></dt><dd>Removed</dd> 
    138    <dt class="mod"></dt><dd>Modified</dd> 
    139    <dt class="cp"></dt><dd>Copied</dd> 
    140    <dt class="mv"></dt><dd>Moved</dd> 
    141   </dl> 
    142  </div> 
    143  <ul class="entries"><?cs 
    144  each:item = changeset.changes ?><?cs 
    145   if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs 
    146    var:name(item) ?>"><h2><a href="<?cs 
    147    var:item.browser_href.new ?>" title="Show new revision <?cs 
    148    var:item.rev.new ?> of this file in browser"><?cs 
    149    var:item.path.new ?></a></h2><?cs 
    150    if:len(item.props) ?><ul class="props"><?cs 
    151     each:prop = item.props ?><li>Property <strong><?cs 
    152      var:name(prop) ?></strong> <?cs 
    153      if:prop.old && prop.new ?>changed from <?cs 
    154      elif:!prop.old ?>set<?cs 
    155      else ?>deleted<?cs 
    156      /if ?><?cs 
    157      if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs 
    158      if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?></li><?cs 
    159     /each ?></ul><?cs 
    160    /if ?><?cs 
    161    if:len(item.diff) ?><table class="<?cs 
    162     var:diff.style ?>" summary="Differences" cellspacing="0"><?cs 
    163     if:diff.style == 'sidebyside' ?> 
    164      <colgroup class="l"><col class="lineno" /><col class="content" /></colgroup> 
    165      <colgroup class="r"><col class="lineno" /><col class="content" /></colgroup> 
    166      <thead><tr> 
    167       <th colspan="2"><a href="<?cs 
    168        var:item.browser_href.old ?>" title="Show old rev. <?cs 
    169        var:item.rev.old ?> of <?cs var:item.path.old ?>">Revision <?cs 
    170        var:item.rev.old ?></a></th> 
    171       <th colspan="2"><a href="<?cs 
    172        var:item.browser_href.new ?>" title="Show new rev. <?cs 
    173        var:item.rev.new ?> of <?cs var:item.path.new ?>">Revision <?cs 
    174        var:item.rev.new ?></a></th> 
    175       </tr> 
    176      </thead><?cs 
    177      each:change = item.diff ?><tbody><?cs 
    178       call:diff_display(change, diff.style) ?></tbody><?cs 
    179       if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
    180        <th>&hellip;</th><td>&nbsp;</td><th>&hellip;</th><td>&nbsp;</td> 
    181       </tr></tbody><?cs /if ?><?cs 
    182      /each ?><?cs 
    183     else ?> 
    184      <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup> 
    185      <thead><tr> 
    186       <th title="Revision <?cs var:item.rev.old ?>"><a href="<?cs 
    187        var:item.browser_href.old ?>" title="Show old version of <?cs 
    188        var:item.path.old ?>">r<?cs var:item.rev.old ?></a></th> 
    189       <th title="Revision <?cs var:item.rev.new ?>"><a href="<?cs 
    190        var:item.browser_href.new ?>" title="Show new version of <?cs 
    191        var:item.path.new ?>">r<?cs var:item.rev.new ?></a></th> 
    192       <th>&nbsp;</th></tr> 
    193      </thead><?cs 
    194      each:change = item.diff ?><?cs 
    195       call:diff_display(change, diff.style) ?><?cs 
    196       if:name(change) < len(item.diff) - 1 ?><tbody class="skipped"><tr> 
    197        <th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td> 
    198       </tr></tbody><?cs /if ?><?cs 
    199      /each ?><?cs 
    200     /if ?></table><?cs 
    201    /if ?></li><?cs 
    202   /if ?><?cs 
    203  /each ?></ul> 
    204 </div> 
    205  
    206 </div> 
    207 <?cs include "footer.cs"?>