Edgewall Software

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

File trac-diff-for-0.9.patch, 91.6 KB (added by cboos, 3 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�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�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