Edgewall Software

Ticket #199: diff_module_beta1.diff

File diff_module_beta1.diff, 38.5 KB (added by cboos, 7 years ago)

Things begin to look good (patch against [1756])

  • htdocs/css/browser.css

     
    4545#dirlist td.name a, #dirlist td.rev a { border-bottom: none; display: block } 
    4646#dirlist td.change * { font-size: 9px } 
    4747 
     48/* Log */ 
     49tr.diff input {  
     50 padding: 0 1em 0 1em; 
     51 margin: 0;  
     52} 
     53 
     54#anydiff { 
     55 background: #f7f7f0; 
     56 border: 1px outset #998; 
     57 margin: 0 1em 1em; 
     58 padding: .8em; 
     59 float: left; 
     60} 
     61#anydiff em { 
     62 background: #fbfbfb; 
     63} 
     64#anydiff form, #anydiff div { 
     65 vertical-align: top; 
     66 display: inline; 
     67 margin-right: 0; 
     68 margin-left: 0; 
     69} 
     70#anydiff input {  
     71 vertical-align: baseline; 
     72} 
     73 
    4874/* Styles for the revision log table 
    4975   (extends the styles for "table.listing") */ 
    5076#chglist { margin-top: 0 } 
  • trac/db_default.py

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

     
    2727import os.path 
    2828import time 
    2929import weakref 
     30import posixpath 
    3031 
    3132from svn import fs, repos, core, delta 
    3233 
     
    175176    def __del__(self): 
    176177        self.close() 
    177178 
     179    def has_node(self, path, rev): 
     180        rev_root = fs.revision_root(self.fs_ptr, rev, self.pool) 
     181        node_type = fs.check_path(rev_root, path, self.pool) 
     182        return node_type in _kindmap 
     183 
    178184    def normalize_path(self, path): 
    179         return path == '/' and path or path.strip('/') 
     185        return path == '/' and path or path and path.strip('/') or '' 
    180186 
    181187    def normalize_rev(self, rev): 
    182188        try: 
     
    267273        rev = self.normalize_rev(rev) 
    268274        expect_deletion = False 
    269275        while rev: 
    270             rev_root = fs.revision_root(self.fs_ptr, rev, self.pool) 
    271             node_type = fs.check_path(rev_root, path, self.pool) 
    272             if node_type in _kindmap: # then path exists at that rev 
     276            if self.has_node(path, rev): 
    273277                if expect_deletion: 
    274278                    # it was missing, now it's there again: rev+1 must be a delete 
    275279                    yield path, rev+1, Changeset.DELETE 
     
    294298                expect_deletion = True 
    295299                rev = self.previous_rev(rev) 
    296300 
     301    def get_diffs(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1): 
     302        old_node = new_node = None 
     303        old_rev = self.normalize_rev(old_rev) 
     304        new_rev = self.normalize_rev(new_rev) 
     305        if self.has_node(old_path, old_rev): 
     306            old_node = self.get_node(old_path, old_rev) 
     307            old_path = old_node.created_path 
     308            old_rev = old_node.created_rev 
     309        if self.has_node(new_path, new_rev): 
     310            new_node = self.get_node(new_path, new_rev) 
     311            new_path = new_node.created_path 
     312            new_rev = new_node.created_rev 
     313        if not old_node and not new_node: 
     314            raise TracError, ('None of the diff arguments are valid: ' 
     315                              'neither %s in revision %s nor %s in revision %s exist ' 
     316                              'in the repository' % (old_path, old_rev, 
     317                                                     new_path, new_rev)) 
     318        elif old_node and new_node: 
     319            if new_node.kind != old_node.kind: 
     320                raise TracError, ('Diff mismatch: Trying to diff ' 
     321                                  'a %s (%s in revision %s) ' 
     322                                  'with a %s (%s in revision %s).' \ 
     323                                  % (old_node.kind, old_path, old_rev, 
     324                                     new_node.kind, new_path, new_rev)) 
     325        if new_node: 
     326            isdir = new_node.isdir 
     327        else: 
     328            isdir = old_node.isdir 
     329        if isdir: 
     330            editor = DiffChangeEditor() 
     331            e_ptr, e_baton = delta.make_editor(editor, self.pool) 
     332            old_root = fs.revision_root(self.fs_ptr, old_rev, self.pool) 
     333            new_root = fs.revision_root(self.fs_ptr, new_rev, self.pool) 
     334            if isdir: 
     335                old_dir, old_entry = old_path, '' 
     336            def authz_cb(root, path, pool): return 1 
     337            text_deltas = 0 # as this is currently re-done in Diff.py... 
     338            entry_props = 0 # ("... typically used only for working copy updates") 
     339            print 'svn_repos_dir_delta: ', old_dir, old_entry, ' -vs.- ', new_path 
     340            repos.svn_repos_dir_delta(old_root, old_path, '', 
     341                                      new_root, new_path, 
     342                                      e_ptr, e_baton, authz_cb, 
     343                                      text_deltas, 
     344                                      isdir and 1 or 0, 
     345                                      entry_props, 
     346                                      ignore_ancestry, 
     347                                      self.pool) 
     348            for d in editor.deltas: 
     349                yield (posixpath.join(old_path,d[0]), posixpath.join(new_path,d[0]), 
     350                       d[1], d[2]) 
     351        else: 
     352            if new_node and old_node: 
     353                change = Changeset.EDIT 
     354            elif new_node: 
     355                change = Changeset.ADD 
     356            elif old_node: 
     357                change = Changeset.DELETE 
     358            yield (old_path, new_path, Node.FILE, change) 
    297359 
     360 
    298361class SubversionNode(Node): 
    299362 
    300363    pool = property(fget=lambda self: self._pool(), 
     
    450513 
    451514    def _get_prop(self, name): 
    452515        return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool) 
     516 
     517 
     518# 
     519# Delta editor for diffs between arbitrary nodes (recycling my old code for #295 :) ) 
     520# 
     521# Note 1: the 'copyfrom_path' and 'copyfrom_rev' information is not used 
     522#         because 'repos.svn_repos_dir_delta' *doesn't* provide it. 
     523# 
     524# Note 2: the 'dir_baton' is the path of the parent directory 
     525# 
     526 
     527class DiffChangeEditor(delta.Editor):  
     528 
     529    def __init__(self): 
     530        self.deltas = [] 
     531        self.skip_dir_prop_change = 0 
     532 
     533    def _norm(self, path): 
     534        """Path are normalized to __not__ have a leading slash""" 
     535        return path == '/' and path or path and path.strip('/') or '' 
     536     
     537    # -- svn.delta.Editor callbacks 
     538 
     539    def open_root(self, base_revision, dir_pool): 
     540        return '/' 
     541 
     542    def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev, dir_pool): 
     543        self.deltas.append((path, Node.DIRECTORY, Changeset.ADD)) 
     544        # don't create an additional 'Changeset.EDIT' entry for this directory 
     545        # in case there's also a dir property change: 
     546        self.skip_dir_prop_change = 1  
     547        return path 
     548 
     549    def open_directory(self, path, dir_baton, base_revision, dir_pool): 
     550        self.deltas.append((path, Node.DIRECTORY, Changeset.EDIT)) 
     551        self.skip_dir_prop_change = 0 
     552        return path 
     553 
     554    def change_dir_prop(self, dir_baton, name, value, pool): 
     555        if self.skip_dir_prop_change: 
     556            return 
     557        self.deltas.append((dir_baton, Node.DIRECTORY, Changeset.EDIT)) 
     558        self.skip_dir_prop_change = 1 
     559 
     560    def close_directory(self, dir_baton): 
     561        self.skip_dir_prop_change = 0 
     562 
     563    def delete_entry(self, path, revision, dir_baton, pool): 
     564        self.deltas.append((path, Node.FILE, Changeset.DELETE)) # should be Node.UNKNOWN 
     565 
     566    def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision, dir_pool): 
     567        self.deltas.append((self._norm(path), Node.FILE, Changeset.ADD)) 
     568 
     569    def open_file(self, path, dir_baton, dummy_rev, file_pool): 
     570        self.deltas.append((self._norm(path), Node.FILE, Changeset.EDIT)) 
     571 
  • trac/versioncontrol/cache.py

     
    8888    def get_node(self, path, rev=None): 
    8989        return self.repos.get_node(path, rev) 
    9090 
     91    def has_node(self, path, rev): 
     92        return self.repos.has_node(path, rev) 
     93 
    9194    def get_oldest_rev(self): 
    9295        return self.repos.oldest_rev 
    9396 
     
    112115    def normalize_rev(self, rev): 
    113116        return self.repos.normalize_rev(rev) 
    114117 
     118    def get_diffs(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1): 
     119        return self.repos.get_diffs(old_path, old_rev, new_path, new_rev, ignore_ancestry) 
    115120 
     121 
    116122class CachedChangeset(Changeset): 
    117123 
    118124    def __init__(self, rev, db, authz): 
  • trac/versioncontrol/main.py

     
    4242        """ 
    4343        raise NotImplementedError 
    4444 
     45    def has_node(self, path, rev): 
     46        """ 
     47        Tell if there's a node at the specified (path,rev) combination. 
     48        """ 
     49        raise NotImplementedError 
     50     
    4551    def get_node(self, path, rev=None): 
    4652        """ 
    4753        Retrieve a Node (directory or file) from the repository at the 
     
    114120        'None' is a valid revision value and represents the youngest revision. 
    115121        """ 
    116122        return NotImplementedError 
    117          
    118123 
     124    def get_diffs(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=1): 
     125        """ 
     126        Generator that yields (old_path, new_path, kind, change) tuples 
     127        for each node change between the two arbitrary (path,rev) pairs. 
     128        """ 
     129        raise NotImplementedError 
     130 
     131 
    119132class Node(object): 
    120133    """ 
    121134    Represents a directory or file in the repository. 
  • trac/Diff.py

     
    2424from __future__ import generators 
    2525import time 
    2626import re 
     27import posixpath 
    2728 
    2829from trac import mimeview, perm, util 
    2930from trac.core import * 
    30 from trac.Timeline import ITimelineEventProvider 
    3131from trac.versioncontrol import Changeset, Node 
    3232from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff 
    3333from trac.web.chrome import add_link, add_stylesheet 
     
    3535from trac.wiki import wiki_to_html, wiki_to_oneliner 
    3636 
    3737 
    38 class ChangesetModule(Component): 
     38class Diff(dict): 
     39    def __getattr__(self,str): 
     40        return self[str] 
     41     
    3942 
    40     implements(IRequestHandler, ITimelineEventProvider) 
     43class DiffModule(Component): 
    4144 
     45    implements(IRequestHandler) 
     46 
    4247    # IRequestHandler methods 
    4348 
    4449    def match_request(self, req): 
    45         match = re.match(r'/changeset/([0-9]+)$', req.path_info) 
     50        match = re.match(r'/diff(?:(/.*)|$)', req.path_info) 
    4651        if match: 
    47             req.args['rev'] = match.group(1) 
     52            req.args['path'] = match.group(1) 
    4853            return 1 
    4954 
    5055    def process_request(self, req): 
    5156        req.perm.assert_permission(perm.CHANGESET_VIEW) 
    5257 
    53         rev = req.args.get('rev') 
     58        path = req.args.get('path') 
    5459        repos = self.env.get_repository(req.authname) 
     60        path = repos.normalize_path(path) 
     61        rev = req.args.get('rev', repos.youngest_rev) # 'path history' mode 
     62        old = req.args.get('old')                     # 'arbitrary diff' mode 
     63        new = req.args.get('new') 
     64        old_path = req.args.get('old_path', path) 
     65        if old_path == path and old == new: # force 'path history' mode 
     66            print "force 'path history' mode" 
     67            rev = old 
     68            old_path = old = new = None 
    5569 
    5670        diff_options = get_diff_options(req) 
    5771        if req.args.has_key('update'): 
    58             req.redirect(self.env.href.changeset(rev)) 
     72            if old or new: 
     73                req.redirect(self.env.href.diff(path, new=new, old_path=old_path, old=old)) 
     74            else: 
     75                req.redirect(self.env.href.diff(path, rev=rev)) 
    5976 
    60         chgset = repos.get_changeset(rev) 
    61         req.check_modified(chgset.date, 
    62                            diff_options[0] + ''.join(diff_options[1])) 
     77        if old or new: 
     78            chgset = None 
     79            if not new: 
     80                new = repos.next_rev(old) # FIXME: must lookup the next entry in node history 
     81            elif not old: 
     82                old = repos.previous_rev(new) 
     83            if not old_path: 
     84                old_path = path 
     85            diff = Diff(old_path=old_path, old_rev=old, 
     86                        new_path=path, new_rev=new) 
     87            diffargs = 'new=%s&old_path=%s&old=%s' \ 
     88                       % (new, old_path, old) 
     89        else: 
     90            chgset = repos.get_changeset(rev) 
     91            diff = Diff(old_path=path, old_rev=repos.previous_rev(rev), 
     92                        new_path=path, new_rev=rev) 
     93            diffargs = 'rev=%s' % rev 
    6394 
     95        print diff 
     96         
     97        # TODO: 
     98#         req.check_modified(chgset.date, 
     99#                            diff_options[0] + ''.join(diff_options[1])) 
     100 
    64101        format = req.args.get('format') 
    65102        if format == 'diff': 
    66             self._render_diff(req, repos, chgset, diff_options) 
     103            self._render_diff(req, repos, diff, chgset, diff_options) 
    67104            return 
    68105        elif format == 'zip': 
    69             self._render_zip(req, repos, chgset) 
     106            self._render_zip(req, repos, diff, chgset) 
    70107            return 
    71108 
    72         self._render_html(req, repos, chgset, diff_options) 
    73         add_link(req, 'alternate', '?format=diff', 'Unified Diff', 
     109        self._render_html(req, repos, diff, chgset, diff_options) 
     110        add_link(req, 'alternate', '?format=diff&'+diffargs, 'Unified Diff', 
    74111                 'text/plain', 'diff') 
    75         add_link(req, 'alternate', '?format=zip', 'Zip Archive', 
     112        add_link(req, 'alternate', '?format=zip&'+diffargs, 'Zip Archive', 
    76113                 'application/zip', 'zip') 
    77114        add_stylesheet(req, 'changeset.css') 
    78115        add_stylesheet(req, 'diff.css') 
    79         return 'changeset.cs', None 
     116        return 'diff.cs', None 
    80117 
    81     # ITimelineEventProvider methods 
    82118 
    83     def get_timeline_filters(self, req): 
    84         if req.perm.has_permission(perm.CHANGESET_VIEW): 
    85             yield ('changeset', 'Repository checkins') 
    86  
    87     def get_timeline_events(self, req, start, stop, filters): 
    88         if 'changeset' in filters: 
    89             absurls = req.args.get('format') == 'rss' # Kludge 
    90             show_files = int(self.config.get('timeline', 
    91                                              'changeset_show_files')) 
    92             db = self.env.get_db_cnx() 
    93             repos = self.env.get_repository() 
    94             rev = repos.youngest_rev 
    95             while rev: 
    96                 chgset = repos.get_changeset(rev) 
    97                 if chgset.date < start: 
    98                     return 
    99                 if chgset.date < stop: 
    100                     if absurls: 
    101                         href = self.env.abs_href.changeset(chgset.rev) 
    102                     else: 
    103                         href = self.env.href.changeset(chgset.rev) 
    104                     title = 'Changeset <em>[%s]</em> by %s' % ( 
    105                             util.escape(chgset.rev), util.escape(chgset.author)) 
    106                     message = wiki_to_oneliner(util.shorten_line(chgset.message or '--'), 
    107                                                self.env, db, absurls=absurls) 
    108                     if show_files: 
    109                         files = [] 
    110                         for chg in chgset.get_changes(): 
    111                             if show_files > 0 and len(files) >= show_files: 
    112                                 files.append('...') 
    113                                 break 
    114                             files.append('<span class="%s">%s</span>' 
    115                                          % (chg[2], util.escape(chg[0]))) 
    116                         message = '<span class="changes">' + ', '.join(files) +\ 
    117                                   '</span>: ' + message 
    118                     yield 'changeset', href, title, chgset.date, chgset.author,\ 
    119                           message 
    120                 rev = repos.previous_rev(rev) 
    121  
    122119    # Internal methods 
    123120 
    124     def _render_html(self, req, repos, chgset, diff_options): 
     121    def _render_html(self, req, repos, diff, chgset, diff_options): 
    125122        """HTML version""" 
    126         req.hdf['title'] = '[%s]' % chgset.rev 
    127         req.hdf['changeset'] = { 
    128             'revision': chgset.rev, 
    129             'time': time.strftime('%c', time.localtime(chgset.date)), 
    130             'author': util.escape(chgset.author or 'anonymous'), 
    131             'message': wiki_to_html(chgset.message or '--', self.env, req, 
    132                                     escape_newlines=True) 
    133         } 
     123        req.hdf['diff'] = diff 
     124        req.hdf['diff.href'] = { 
     125            'new_rev': self.env.href.changeset(diff.new_rev), 
     126            'old_rev': self.env.href.changeset(diff.old_rev), 
     127            'new_path': self.env.href.browser(diff.new_path, rev=diff.new_rev), 
     128            'old_path': self.env.href.browser(diff.old_path, rev=diff.old_rev) 
     129            } 
     130        if chgset: # 'path history' mode 
     131            req.hdf['title'] = 'Changes for %s at Revision %s' % (diff.new_path, chgset.rev) 
     132            req.hdf['changeset'] = { 
     133                'revision': chgset.rev, 
     134                'time': time.strftime('%c', time.localtime(chgset.date)), 
     135                'author': util.escape(chgset.author or 'anonymous'), 
     136                'message': wiki_to_html(chgset.message or '--', self.env, req, 
     137                                        escape_newlines=True) 
     138                } 
    134139 
    135         oldest_rev = repos.oldest_rev 
    136         if chgset.rev != oldest_rev: 
    137             add_link(req, 'first', self.env.href.changeset(oldest_rev), 
    138                      'Changeset %s' % oldest_rev) 
    139             previous_rev = repos.previous_rev(chgset.rev) 
    140             add_link(req, 'prev', self.env.href.changeset(previous_rev), 
    141                      'Changeset %s' % previous_rev) 
    142         youngest_rev = repos.youngest_rev 
    143         if str(chgset.rev) != str(youngest_rev): 
    144             next_rev = repos.next_rev(chgset.rev) 
    145             add_link(req, 'next', self.env.href.changeset(next_rev), 
    146                      'Changeset %s' % next_rev) 
    147             add_link(req, 'last', self.env.href.changeset(youngest_rev), 
    148                      'Changeset %s' % youngest_rev) 
     140            oldest_rev = repos.oldest_rev 
     141            if chgset.rev != oldest_rev: 
     142                add_link(req, 'first', self.env.href.diff(diff.old_path, rev=oldest_rev), 
     143                         'Changeset %s' % oldest_rev) # FIXME (use the history) 
     144                previous_rev = repos.previous_rev(chgset.rev) 
     145                add_link(req, 'prev', self.env.href.diff(diff.old_path, rev=previous_rev), 
     146                         'Changeset %s' % previous_rev) 
     147            youngest_rev = repos.youngest_rev 
     148            if str(chgset.rev) != str(youngest_rev): 
     149                next_rev = repos.next_rev(chgset.rev) 
     150                add_link(req, 'next', self.env.href.diff(diff.new_path, rev=next_rev), 
     151                         'Changeset %s' % next_rev) 
     152                add_link(req, 'last', self.env.href.diff(diff.new_path, rev=youngest_rev), 
     153                         'Changeset %s' % youngest_rev) 
     154        elif diff.new_path == diff.old_path: # 'diff between 2 revisions' mode 
     155            req.hdf['title'] = 'Diff for %s between Revisions %s and %s' \ 
     156                               % (diff.new_path, diff.old_rev, diff.new_rev) 
     157        else:                           # 'arbitrary diff' mode 
     158            req.hdf['title'] = 'Diff between %s at Revision %s and %s at Revision %s' \ 
     159                               % (diff.old_path, diff.old_rev, 
     160                                  diff.new_path, diff.new_rev) 
    149161 
    150162        edits = [] 
    151163        idx = 0 
    152         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
     164        old_rev = diff.old_rev 
     165        new_rev = diff.new_rev 
     166        for old_path, new_path, kind, change in repos.get_diffs(**diff): 
     167            print 'delta %d: %s %s delta from %s@%s to %s@%s' \ 
     168                  % (idx, change, kind, old_path, old_rev, new_path, new_rev) 
    153169            info = {'change': change} 
    154             if base_path: 
    155                 info['path.old'] = base_path 
    156                 info['rev.old'] = base_rev 
    157                 info['browser_href.old'] = self.env.href.browser(base_path, 
    158                                                                  rev=base_rev) 
    159             if path: 
    160                 info['path.new'] = path 
    161                 info['rev.new'] = chgset.rev 
    162                 info['browser_href.new'] = self.env.href.browser(path, 
    163                                                                  rev=chgset.rev) 
     170            if old_path: 
     171                info['path.old'] = old_path 
     172                info['rev.old'] = old_rev 
     173                info['browser_href.old'] = self.env.href.browser(old_path, 
     174                                                                 rev=old_rev) 
     175            if new_path: 
     176                info['path.new'] = new_path 
     177                info['rev.new'] = new_rev 
     178                info['browser_href.new'] = self.env.href.browser(new_path, 
     179                                                                 rev=new_rev) 
    164180            if change in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE): 
    165                 edits.append((idx, path, kind, base_path, base_rev)) 
    166             req.hdf['changeset.changes.%d' % idx] = info 
     181                edits.append((idx, old_path, new_path, kind)) 
     182            req.hdf['diff.changes.%d' % idx] = info 
    167183            idx += 1 
    168  
    169         for idx, path, kind, base_path, base_rev in edits: 
    170             old_node = repos.get_node(base_path or path, base_rev) 
    171             new_node = repos.get_node(path, chgset.rev) 
    172  
     184         
     185        for idx, old_path, new_path, kind in edits: 
     186            old_node = repos.get_node(old_path, old_rev) 
     187            new_node = repos.get_node(new_path, new_rev) 
     188             
    173189            # Property changes 
    174190            old_props = old_node.get_properties() 
    175191            new_props = new_node.get_properties() 
     
    183199                for k,v in new_props.items(): 
    184200                    if not k in old_props: 
    185201                        changed_props[k] = {'new': v} 
    186                 req.hdf['changeset.changes.%d.props' % idx] = changed_props 
     202                req.hdf['diff.changes.%d.props' % idx] = changed_props 
    187203 
    188204            if kind == Node.DIRECTORY: 
    189205                continue 
    190206 
    191207            # Content changes 
    192208            default_charset = self.config.get('trac', 'default_charset') 
    193             old_content = old_node.get_content().read() 
     209            old_content = old_node.get_content().read()             
    194210            if mimeview.is_binary(old_content): 
    195211                continue 
    196212            charset = mimeview.get_charset(old_node.content_type) or \ 
     
    217233                                   ignore_blank_lines='-B' in diff_options[1], 
    218234                                   ignore_case='-i' in diff_options[1], 
    219235                                   ignore_space_changes='-b' in diff_options[1]) 
    220                 req.hdf['changeset.changes.%d.diff' % idx] = changes 
     236                req.hdf['diff.changes.%d.diff' % idx] = changes 
    221237 
    222     def _render_diff(self, req, repos, chgset, diff_options): 
     238 
     239    def _render_diff(self, req, repos, diff, chgset, diff_options): 
    223240        """Raw Unified Diff version""" 
    224241        req.send_response(200) 
    225242        req.send_header('Content-Type', 'text/plain;charset=utf-8') 
     
    227244                        'filename=Changeset%s.diff' % req.args.get('rev')) 
    228245        req.end_headers() 
    229246 
    230         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
     247        old_rev = diff.old_rev 
     248        new_rev = diff.new_rev 
     249        print diff 
     250        for old_path, new_path, kind, change in repos.get_diffs(**diff): 
     251            print '.diff delta : %s %s delta from %s@%s to %s@%s' \ 
     252                  % ( change, kind, old_path, old_rev, new_path, new_rev) 
    231253            if change == Changeset.ADD: 
    232254                old_node = None 
    233255            else: 
    234                 old_node = repos.get_node(base_path or path, base_rev) 
     256                old_node = repos.get_node(old_path, old_rev) 
    235257            if change == Changeset.DELETE: 
    236258                new_node = None 
    237259            else: 
    238                 new_node = repos.get_node(path, chgset.rev) 
     260                new_node = repos.get_node(new_path, new_rev) 
    239261 
    240262            # TODO: Property changes 
    241263 
     
    271293                    if option[:2] == '-U': 
    272294                        context = int(option[2:]) 
    273295                        break 
    274                 req.write('Index: ' + path + util.CRLF) 
     296                req.write('Index: ' + new_path + util.CRLF) 
    275297                req.write('=' * 67 + util.CRLF) 
    276298                req.write('--- %s (revision %s)' % old_node_info + 
    277299                          util.CRLF) 
     
    284306                                         ignore_space_changes='-b' in diff_options[1]): 
    285307                    req.write(line + util.CRLF) 
    286308 
    287     def _render_zip(self, req, repos, chgset): 
     309    def _render_zip(self, req, repos, diff, chgset): 
    288310        """ZIP archive with all the added and/or modified files.""" 
     311        new_rev = diff.new_rev 
    289312        req.send_response(200) 
    290313        req.send_header('Content-Type', 'application/zip') 
    291314        req.send_header('Content-Disposition', 
    292                         'filename=Changeset%s.zip' % chgset.rev) 
     315                        'filename=Changeset%s.zip' % new_rev) 
    293316        req.end_headers() 
    294317 
    295318        try: 
     
    300323 
    301324        buf = StringIO() 
    302325        zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) 
    303         for path, kind, change, base_path, base_rev in chgset.get_changes(): 
     326        for old_path, new_path, kind, change in repos.get_diffs(**diff): 
    304327            if kind == Node.FILE and change != Changeset.DELETE: 
    305                 node = repos.get_node(path, chgset.rev) 
     328                node = repos.get_node(new_path, new_rev) 
    306329                zipinfo = ZipInfo() 
    307330                zipinfo.filename = node.path 
    308331                zipinfo.date_time = time.gmtime(node.last_modified)[:6] 
  • trac/Browser.py

     
    7575        }) 
    7676    return links 
    7777 
     78def _anydiff_support(env, req, node): 
     79    path, rev = node.created_path, node.created_rev # Kludge: needs to be in Node interface 
     80    select_for_diff = req.args.get('diff') 
     81    req.hdf['diff.anydiff_href'] = env.href.diff(path) 
     82    if select_for_diff == "1": 
     83        req.session['diff_base_path'] = path 
     84        req.session['diff_base_rev'] = rev 
    7885 
     86    if req.session.has_key('diff_base_path'): 
     87        if select_for_diff == "0": 
     88            del req.session['diff_base_path'] 
     89            del req.session['diff_base_rev'] 
     90        else: 
     91            req.hdf['session'] = { 
     92                'diff_base_path': req.session['diff_base_path'], 
     93                'diff_base_rev': req.session['diff_base_rev'] 
     94                } 
     95 
    7996class BrowserModule(Component): 
    8097 
    8198    implements(INavigationContributor, IRequestHandler) 
     
    113130 
    114131        req.hdf['title'] = path 
    115132        req.hdf['browser'] = { 
    116             'path': path, 
    117             'revision': rev or repos.youngest_rev, 
     133            'path': node.path, 
     134            'revision': node.rev, 
    118135            'props': dict([(util.escape(name), util.escape(value)) 
    119136                           for name, value in node.get_properties().items()]), 
    120             'href': self.env.href.browser(path,rev=rev or repos.youngest_rev), 
    121             'log_href': self.env.href.log(path) 
     137            'href': self.env.href.browser(node.path,rev=node.rev), 
     138            'diff_href': self.env.href.diff(node.path,rev=node.rev), 
     139            'log_href': self.env.href.log(node.path) 
    122140        } 
    123141 
     142        _anydiff_support(self.env, req, node) 
     143 
    124144        path_links = _get_path_links(self.env.href, path, rev) 
    125145        if len(path_links) > 1: 
    126146            add_link(req, 'up', path_links[-2]['href'], 'Parent directory') 
     
    268288        stop_rev = req.args.get('stop_rev') 
    269289        verbose = req.args.get('verbose') 
    270290        limit = int(req.args.get('limit') or 100) 
     291        old = req.args.get('old') 
     292        new = req.args.get('new') 
    271293 
     294        repos = self.env.get_repository(req.authname) 
     295        normpath = repos.normalize_path(path) 
     296        rev = str(repos.normalize_rev(rev)) 
     297        old = old or str(repos.previous_rev(rev)) 
     298        new = new or rev 
     299 
    272300        req.hdf['title'] = path + ' (log)' 
    273301        req.hdf['log'] = { 
    274302            'path': path, 
     
    276304            'verbose': verbose, 
    277305            'stop_rev': stop_rev, 
    278306            'browser_href': self.env.href.browser(path, rev=rev), 
    279             'log_href': self.env.href.log(path, rev=rev) 
     307            'log_href': self.env.href.log(path, rev=rev), 
     308            'diff_href': self.env.href.diff(path, old=old, new=new), 
     309            'old': old, 
     310            'new': new 
    280311        } 
    281312 
    282313        path_links = _get_path_links(self.env.href, path, rev) 
     
    284315        if path_links: 
    285316            add_link(req, 'up', path_links[-1]['href'], 'Parent directory') 
    286317 
    287         repos = self.env.get_repository(req.authname) 
    288         normpath = repos.normalize_path(path) 
    289         rev = str(repos.normalize_rev(rev)) 
    290  
    291318        # 'node' or 'path' history: use get_node()/get_history() or get_path_history() 
    292319        if mode != 'path_history': 
    293320            try: 
    294321                node = repos.get_node(path, rev) 
     322                _anydiff_support(self.env, req, node) 
    295323            except TracError: 
    296324                node = None 
    297325            if not node: 
  • templates/anydiff.cs

     
     1<?cs  
     2def:anydiff(new_path,new_rev,module_href) ?> 
     3 <div id="anydiff"><?cs 
     4  if:session.diff_base_path == new_path && session.diff_base_rev == new_rev ?> 
     5   <em>This Path/Revision is the Base for Diff</em><?cs 
     6  else ?><?cs 
     7   if:session.diff_base_path ?> 
     8     <form action="<?cs var:diff.anydiff_href ?>" method="get"> 
     9      <input type="hidden" name="old_path" value="<?cs var:session.diff_base_path ?>" /> 
     10      <input type="hidden" name="old" value="<?cs var:session.diff_base_rev ?>" /> 
     11      <input type="hidden" name="new" value="<?cs var:new_rev ?>" /> 
     12      <div class="buttons"> 
     13       <input type="submit" value="Diff"  
     14              title="Diff the current Path/Revision against the selected Base" /> 
     15      </div> 
     16     </form> 
     17    against: <em><?cs var:session.diff_base_path ?></em>  
     18     in revision <em><?cs var:session.diff_base_rev ?></em> 
     19    <form action="<?cs var:module_href ?>" method="get"> 
     20     <input type="hidden" name="diff" value="0" /> 
     21     <div class="buttons"> 
     22      <input type="submit" value="Clear"  
     23       title="Clear the Base for Diff" /> 
     24     </div> 
     25    </form><?cs 
     26   /if ?> 
     27   <form action="<?cs var:module_href ?>" method="get"> 
     28    <input type="hidden" name="diff" value="1" /> 
     29    <div class="buttons"> 
     30     <input type="submit"  
     31       value="Set<?cs if:!session.diff_base_path ?>  Base for Diff<?cs /if ?>" 
     32      title="Select the current Path/Revision as the new Base for Diff" /> 
     33    </div> 
     34   </form><?cs 
     35  /if ?> 
     36 </div><?cs 
     37/def ?> 
  • templates/log.cs

     
    11<?cs include "header.cs"?> 
    22<?cs include "macros.cs"?> 
     3<?cs include "anydiff.cs"?> 
    34 
    45<div id="ctxtnav" class="nav"> 
    56 <ul> 
    6   <li class="last"><a href="<?cs 
    7     var:log.browser_href ?>">View Latest Revision</a></li><?cs 
     7  <li class="last"> 
     8   <a href="<?cs var:log.browser_href ?>">View Latest Revision</a> 
     9  </li><?cs 
    810  if:len(chrome.links.prev) ?> 
    911   <li class="first<?cs if:!len(chrome.links.next) ?> last<?cs /if ?>"> 
    1012    &larr; <a href="<?cs var:chrome.links.prev.0.href ?>" title="<?cs 
     
    6163          title="Warning: by updating, you will clear the page history" /> 
    6264  </div> 
    6365 </form> 
     66 
     67<?cs call:anydiff(log.path, log.rev, log.log_href) ?> 
     68 
    6469 <div class="diff"> 
    6570  <div id="legend"> 
    6671   <h3>Legend:</h3> 
     
    7479   </dl> 
    7580  </div> 
    7681 </div> 
     82 
    7783 <table id="chglist" class="listing"> 
     84  <form action="<?cs var:log.diff_href ?>" method="get"> 
    7885  <thead> 
     86   <tr class="diff"> 
     87    <th colspan="2"> 
     88     <div class="buttons"><input type="submit" value="Diff"  
     89       title="Diff from Old Revision to New Revision (select them below)" /></div> 
     90    </th> 
     91   </tr> 
    7992   <tr> 
     93    <th>Old</th> 
     94    <th>New</th> 
    8095    <th class="change"></th> 
    8196    <th class="data">Date</th> 
    8297    <th class="rev">Rev</th> 
     
    87102  </thead> 
    88103  <tbody><?cs 
    89104   set:indent = #1 ?><?cs 
     105   set:idx = #0 ?><?cs 
    90106   each:item = log.items ?><?cs 
    91107    if:item.copyfrom_path ?> 
    92108     <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"> 
     
    99115      set:indent = #1 ?><?cs 
    100116    /if ?> 
    101117    <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"> 
     118     <td><input type="radio" name="old" value="<?cs var:item.rev ?>" <?cs 
     119          if:idx == #1 ?> checked="checked" <?cs /if ?> /></td> 
     120     <td><input type="radio" name="new" value="<?cs var:item.rev ?>" <?cs 
     121          if:idx == #0 ?> checked="checked" <?cs /if ?> /></td> 
    102122     <td class="change" style="padding-left:<?cs var:indent ?>em"> 
    103123      <a title="View log starting at this revision" href="<?cs var:item.log_href ?>"> 
    104124       <div class="<?cs var:item.change ?>"></div> 
     
    115135     <td class="author"><?cs var:log.changes[item.rev].author ?></td> 
    116136     <td class="summary"><?cs var:log.changes[item.rev].message ?></td> 
    117137    </tr><?cs 
     138    set:idx = idx + 1 ?><?cs 
    118139   /each ?> 
    119140  </tbody> 
     141  </form> 
    120142 </table><?cs 
    121143 if:len(links.prev) || len(links.next) ?><div id="paging" class="nav"><ul><?cs 
    122144  if:len(links.prev) ?><li class="first<?cs 
  • templates/browser.cs

     
    11<?cs include "header.cs"?> 
    22<?cs include "macros.cs"?> 
     3<?cs include "anydiff.cs"?> 
    34 
    45<div id="ctxtnav" class="nav"> 
    56 <ul> 
     7  <li class="first"><a href="<?cs var:browser.diff_href ?>">Diff to previous</a></li> 
    68  <li class="last"><a href="<?cs var:browser.log_href ?>">Revision Log</a></li> 
    79 </ul> 
    810</div> 
     
    114116  ?>/TracBrowser">TracBrowser</a> for help on using the browser. 
    115117 </div> 
    116118 
     119 <?cs call:anydiff(browser.path, browser.revision, browser.href) ?> 
     120 
    117121</div> 
    118122<?cs include:"footer.cs"?> 
  • templates/diff.cs

     
    22<?cs include "macros.cs"?> 
    33 
    44<div id="ctxtnav" class="nav"> 
    5  <h2>Changeset Navigation</h2><?cs 
     5 <h2>Diff Navigation</h2><?cs 
    66 with:links = chrome.links ?> 
    77  <ul><?cs 
    88   if:len(links.prev) ?> 
    99    <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>"> 
    1010     <a class="prev" href="<?cs var:links.prev.0.href ?>" title="<?cs 
    11        var:links.prev.0.title ?>">Previous Changeset</a> 
     11       var:links.prev.0.title ?>">Previous Diff</a> 
    1212    </li><?cs 
    1313   /if ?><?cs 
    1414   if:len(links.next) ?> 
    1515    <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last"> 
    1616     <a class="next" href="<?cs var:links.next.0.href ?>" title="<?cs 
    17        var:links.next.0.title ?>">Next Changeset</a> 
     17       var:links.next.0.title ?>">Next Diff</a> 
    1818    </li><?cs 
    1919   /if ?> 
    2020  </ul><?cs 
     
    2222</div> 
    2323 
    2424<div id="content" class="changeset"> 
    25 <h1>Changeset <?cs var:changeset.revision ?></h1> 
     25<h1><?cs 
     26 if:len(changeset) > #0 ?> 
     27  Changes for <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     28   <?cs var:diff.new_path ?></a>  
     29  in Revision <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     30   <?cs var:diff.new_rev ?></a><?cs 
     31 elif:diff.new_path == diff.old_path ?> 
     32  Differences for <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     33   <?cs var:diff.new_path ?></a>  
     34  between Revisions <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>"> 
     35   <?cs var:diff.old_rev ?></a> 
     36  and <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     37   <?cs var:diff.new_rev ?></a><?cs 
     38 else ?> 
     39  Differences between <a title="Show entry in browser" href="<?cs var:diff.href.old_path ?>"> 
     40   <?cs var:diff.old_path ?></a>  
     41  at Revision <a title="Show full changeset" href="<?cs var:diff.href.old_rev ?>"> 
     42   <?cs var:diff.old_rev ?></a> 
     43  and <a title="Show entry in browser" href="<?cs var:diff.href.new_path ?>"> 
     44   <?cs var:diff.new_path ?></a>  
     45  at Revision <a title="Show full changeset" href="<?cs var:diff.href.new_rev ?>"> 
     46   <?cs var:diff.new_rev ?></a><?cs 
     47 /if ?> 
     48</h1> 
    2649 
    27 <?cs each:change = changeset.changes ?><?cs 
     50<?cs each:change = diff.changes ?><?cs 
    2851 if:len(change.diff) ?><?cs 
    2952  set:has_diffs = 1 ?><?cs 
    3053 /if ?><?cs 
     
    3255  || diff.options.ignorecase || diff.options.ignorewhitespace ?> 
    3356<form method="post" id="prefs" action=""> 
    3457 <div> 
     58  <input type="hidden" name="old_path" value="<?cs var:diff.old_path ?>" /> 
     59  <input type="hidden" name="old" value="<?cs var:diff.old_rev ?>" /> 
     60  <input type="hidden" name="new" value="<?cs var:diff.new_rev ?>" /> 
    3561  <label for="style">View differences</label> 
    3662  <select id="style" name="style"> 
    3763   <option value="inline"<?cs 
     
    100126  /if ?> 
    101127<?cs /def ?> 
    102128 
    103 <dl id="overview"> 
     129<dl id="overview"><?cs 
     130 if:len(changeset) > #0 ?> 
    104131 <dt class="time">Timestamp:</dt> 
    105132 <dd class="time"><?cs var:changeset.time ?></dd> 
    106133 <dt class="author">Author:</dt> 
    107134 <dd class="author"><?cs var:changeset.author ?></dd> 
    108135 <dt class="message">Message:</dt> 
    109  <dd class="message" id="searchable"><?cs var:changeset.message ?></dd> 
     136 <dd class="message" id="searchable"><?cs var:changeset.message ?></dd><?cs 
     137 /if ?> 
    110138 <dt class="files">Files:</dt> 
    111139 <dd class="files"> 
    112   <ul><?cs each:item = changeset.changes ?> 
     140  <ul><?cs each:item = diff.changes ?> 
    113141   <li><?cs 
    114142    if:item.change == 'add' ?><?cs 
    115143     call:node_change(item, 'add', 'added') ?><?cs 
     
    140168  </dl> 
    141169 </div> 
    142170 <ul class="entries"><?cs 
    143  each:item = changeset.changes ?><?cs 
     171 each:item = diff.changes ?><?cs 
    144172  if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs 
    145173   var:name(item) ?>"><h2><a href="<?cs 
    146174   var:item.browser_href.new ?>" title="Show new revision <?cs