Ticket #199: diff_module_beta1.diff
| File diff_module_beta1.diff, 38.5 KB (added by cboos, 7 years ago) |
|---|
-
htdocs/css/browser.css
45 45 #dirlist td.name a, #dirlist td.rev a { border-bottom: none; display: block } 46 46 #dirlist td.change * { font-size: 9px } 47 47 48 /* Log */ 49 tr.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 48 74 /* Styles for the revision log table 49 75 (extends the styles for "table.listing") */ 50 76 #chglist { margin-top: 0 } -
trac/db_default.py
458 458 459 459 default_components = ('trac.About', 'trac.attachment', 'trac.Browser', 460 460 'trac.Changeset', 'trac.Search', 'trac.Settings', 461 'trac.Diff', 461 462 'trac.ticket.query', 'trac.ticket.report', 462 463 'trac.Roadmap', 463 464 'trac.ticket.web_ui', 'trac.Timeline', 'trac.wiki.web_ui', -
trac/versioncontrol/svn_fs.py
27 27 import os.path 28 28 import time 29 29 import weakref 30 import posixpath 30 31 31 32 from svn import fs, repos, core, delta 32 33 … … 175 176 def __del__(self): 176 177 self.close() 177 178 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 178 184 def normalize_path(self, path): 179 return path == '/' and path or path .strip('/')185 return path == '/' and path or path and path.strip('/') or '' 180 186 181 187 def normalize_rev(self, rev): 182 188 try: … … 267 273 rev = self.normalize_rev(rev) 268 274 expect_deletion = False 269 275 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): 273 277 if expect_deletion: 274 278 # it was missing, now it's there again: rev+1 must be a delete 275 279 yield path, rev+1, Changeset.DELETE … … 294 298 expect_deletion = True 295 299 rev = self.previous_rev(rev) 296 300 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) 297 359 360 298 361 class SubversionNode(Node): 299 362 300 363 pool = property(fget=lambda self: self._pool(), … … 450 513 451 514 def _get_prop(self, name): 452 515 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 527 class 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
88 88 def get_node(self, path, rev=None): 89 89 return self.repos.get_node(path, rev) 90 90 91 def has_node(self, path, rev): 92 return self.repos.has_node(path, rev) 93 91 94 def get_oldest_rev(self): 92 95 return self.repos.oldest_rev 93 96 … … 112 115 def normalize_rev(self, rev): 113 116 return self.repos.normalize_rev(rev) 114 117 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) 115 120 121 116 122 class CachedChangeset(Changeset): 117 123 118 124 def __init__(self, rev, db, authz): -
trac/versioncontrol/main.py
42 42 """ 43 43 raise NotImplementedError 44 44 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 45 51 def get_node(self, path, rev=None): 46 52 """ 47 53 Retrieve a Node (directory or file) from the repository at the … … 114 120 'None' is a valid revision value and represents the youngest revision. 115 121 """ 116 122 return NotImplementedError 117 118 123 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 119 132 class Node(object): 120 133 """ 121 134 Represents a directory or file in the repository. -
trac/Diff.py
24 24 from __future__ import generators 25 25 import time 26 26 import re 27 import posixpath 27 28 28 29 from trac import mimeview, perm, util 29 30 from trac.core import * 30 from trac.Timeline import ITimelineEventProvider31 31 from trac.versioncontrol import Changeset, Node 32 32 from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff 33 33 from trac.web.chrome import add_link, add_stylesheet … … 35 35 from trac.wiki import wiki_to_html, wiki_to_oneliner 36 36 37 37 38 class ChangesetModule(Component): 38 class Diff(dict): 39 def __getattr__(self,str): 40 return self[str] 41 39 42 40 implements(IRequestHandler, ITimelineEventProvider) 43 class DiffModule(Component): 41 44 45 implements(IRequestHandler) 46 42 47 # IRequestHandler methods 43 48 44 49 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) 46 51 if match: 47 req.args[' rev'] = match.group(1)52 req.args['path'] = match.group(1) 48 53 return 1 49 54 50 55 def process_request(self, req): 51 56 req.perm.assert_permission(perm.CHANGESET_VIEW) 52 57 53 rev = req.args.get('rev')58 path = req.args.get('path') 54 59 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 55 69 56 70 diff_options = get_diff_options(req) 57 71 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)) 59 76 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 63 94 95 print diff 96 97 # TODO: 98 # req.check_modified(chgset.date, 99 # diff_options[0] + ''.join(diff_options[1])) 100 64 101 format = req.args.get('format') 65 102 if format == 'diff': 66 self._render_diff(req, repos, chgset, diff_options)103 self._render_diff(req, repos, diff, chgset, diff_options) 67 104 return 68 105 elif format == 'zip': 69 self._render_zip(req, repos, chgset)106 self._render_zip(req, repos, diff, chgset) 70 107 return 71 108 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', 74 111 'text/plain', 'diff') 75 add_link(req, 'alternate', '?format=zip ', 'Zip Archive',112 add_link(req, 'alternate', '?format=zip&'+diffargs, 'Zip Archive', 76 113 'application/zip', 'zip') 77 114 add_stylesheet(req, 'changeset.css') 78 115 add_stylesheet(req, 'diff.css') 79 return ' changeset.cs', None116 return 'diff.cs', None 80 117 81 # ITimelineEventProvider methods82 118 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' # Kludge90 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_rev95 while rev:96 chgset = repos.get_changeset(rev)97 if chgset.date < start:98 return99 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 break114 files.append('<span class="%s">%s</span>'115 % (chg[2], util.escape(chg[0])))116 message = '<span class="changes">' + ', '.join(files) +\117 '</span>: ' + message118 yield 'changeset', href, title, chgset.date, chgset.author,\119 message120 rev = repos.previous_rev(rev)121 122 119 # Internal methods 123 120 124 def _render_html(self, req, repos, chgset, diff_options):121 def _render_html(self, req, repos, diff, chgset, diff_options): 125 122 """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 } 134 139 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) 149 161 150 162 edits = [] 151 163 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) 153 169 info = {'change': change} 154 if base_path:155 info['path.old'] = base_path156 info['rev.old'] = base_rev157 info['browser_href.old'] = self.env.href.browser( base_path,158 rev= base_rev)159 if path:160 info['path.new'] = path161 info['rev.new'] = chgset.rev162 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) 164 180 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] = info181 edits.append((idx, old_path, new_path, kind)) 182 req.hdf['diff.changes.%d' % idx] = info 167 183 idx += 1 168 169 for idx, path, kind, base_path, base_revin 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 173 189 # Property changes 174 190 old_props = old_node.get_properties() 175 191 new_props = new_node.get_properties() … … 183 199 for k,v in new_props.items(): 184 200 if not k in old_props: 185 201 changed_props[k] = {'new': v} 186 req.hdf[' changeset.changes.%d.props' % idx] = changed_props202 req.hdf['diff.changes.%d.props' % idx] = changed_props 187 203 188 204 if kind == Node.DIRECTORY: 189 205 continue 190 206 191 207 # Content changes 192 208 default_charset = self.config.get('trac', 'default_charset') 193 old_content = old_node.get_content().read() 209 old_content = old_node.get_content().read() 194 210 if mimeview.is_binary(old_content): 195 211 continue 196 212 charset = mimeview.get_charset(old_node.content_type) or \ … … 217 233 ignore_blank_lines='-B' in diff_options[1], 218 234 ignore_case='-i' in diff_options[1], 219 235 ignore_space_changes='-b' in diff_options[1]) 220 req.hdf[' changeset.changes.%d.diff' % idx] = changes236 req.hdf['diff.changes.%d.diff' % idx] = changes 221 237 222 def _render_diff(self, req, repos, chgset, diff_options): 238 239 def _render_diff(self, req, repos, diff, chgset, diff_options): 223 240 """Raw Unified Diff version""" 224 241 req.send_response(200) 225 242 req.send_header('Content-Type', 'text/plain;charset=utf-8') … … 227 244 'filename=Changeset%s.diff' % req.args.get('rev')) 228 245 req.end_headers() 229 246 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) 231 253 if change == Changeset.ADD: 232 254 old_node = None 233 255 else: 234 old_node = repos.get_node( base_path or path, base_rev)256 old_node = repos.get_node(old_path, old_rev) 235 257 if change == Changeset.DELETE: 236 258 new_node = None 237 259 else: 238 new_node = repos.get_node( path, chgset.rev)260 new_node = repos.get_node(new_path, new_rev) 239 261 240 262 # TODO: Property changes 241 263 … … 271 293 if option[:2] == '-U': 272 294 context = int(option[2:]) 273 295 break 274 req.write('Index: ' + path + util.CRLF)296 req.write('Index: ' + new_path + util.CRLF) 275 297 req.write('=' * 67 + util.CRLF) 276 298 req.write('--- %s (revision %s)' % old_node_info + 277 299 util.CRLF) … … 284 306 ignore_space_changes='-b' in diff_options[1]): 285 307 req.write(line + util.CRLF) 286 308 287 def _render_zip(self, req, repos, chgset):309 def _render_zip(self, req, repos, diff, chgset): 288 310 """ZIP archive with all the added and/or modified files.""" 311 new_rev = diff.new_rev 289 312 req.send_response(200) 290 313 req.send_header('Content-Type', 'application/zip') 291 314 req.send_header('Content-Disposition', 292 'filename=Changeset%s.zip' % chgset.rev)315 'filename=Changeset%s.zip' % new_rev) 293 316 req.end_headers() 294 317 295 318 try: … … 300 323 301 324 buf = StringIO() 302 325 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): 304 327 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) 306 329 zipinfo = ZipInfo() 307 330 zipinfo.filename = node.path 308 331 zipinfo.date_time = time.gmtime(node.last_modified)[:6] -
trac/Browser.py
75 75 }) 76 76 return links 77 77 78 def _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 78 85 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 79 96 class BrowserModule(Component): 80 97 81 98 implements(INavigationContributor, IRequestHandler) … … 113 130 114 131 req.hdf['title'] = path 115 132 req.hdf['browser'] = { 116 'path': path,117 'revision': rev or repos.youngest_rev,133 'path': node.path, 134 'revision': node.rev, 118 135 'props': dict([(util.escape(name), util.escape(value)) 119 136 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) 122 140 } 123 141 142 _anydiff_support(self.env, req, node) 143 124 144 path_links = _get_path_links(self.env.href, path, rev) 125 145 if len(path_links) > 1: 126 146 add_link(req, 'up', path_links[-2]['href'], 'Parent directory') … … 268 288 stop_rev = req.args.get('stop_rev') 269 289 verbose = req.args.get('verbose') 270 290 limit = int(req.args.get('limit') or 100) 291 old = req.args.get('old') 292 new = req.args.get('new') 271 293 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 272 300 req.hdf['title'] = path + ' (log)' 273 301 req.hdf['log'] = { 274 302 'path': path, … … 276 304 'verbose': verbose, 277 305 'stop_rev': stop_rev, 278 306 '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 280 311 } 281 312 282 313 path_links = _get_path_links(self.env.href, path, rev) … … 284 315 if path_links: 285 316 add_link(req, 'up', path_links[-1]['href'], 'Parent directory') 286 317 287 repos = self.env.get_repository(req.authname)288 normpath = repos.normalize_path(path)289 rev = str(repos.normalize_rev(rev))290 291 318 # 'node' or 'path' history: use get_node()/get_history() or get_path_history() 292 319 if mode != 'path_history': 293 320 try: 294 321 node = repos.get_node(path, rev) 322 _anydiff_support(self.env, req, node) 295 323 except TracError: 296 324 node = None 297 325 if not node: -
templates/anydiff.cs
1 <?cs 2 def: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
1 1 <?cs include "header.cs"?> 2 2 <?cs include "macros.cs"?> 3 <?cs include "anydiff.cs"?> 3 4 4 5 <div id="ctxtnav" class="nav"> 5 6 <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 8 10 if:len(chrome.links.prev) ?> 9 11 <li class="first<?cs if:!len(chrome.links.next) ?> last<?cs /if ?>"> 10 12 ← <a href="<?cs var:chrome.links.prev.0.href ?>" title="<?cs … … 61 63 title="Warning: by updating, you will clear the page history" /> 62 64 </div> 63 65 </form> 66 67 <?cs call:anydiff(log.path, log.rev, log.log_href) ?> 68 64 69 <div class="diff"> 65 70 <div id="legend"> 66 71 <h3>Legend:</h3> … … 74 79 </dl> 75 80 </div> 76 81 </div> 82 77 83 <table id="chglist" class="listing"> 84 <form action="<?cs var:log.diff_href ?>" method="get"> 78 85 <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> 79 92 <tr> 93 <th>Old</th> 94 <th>New</th> 80 95 <th class="change"></th> 81 96 <th class="data">Date</th> 82 97 <th class="rev">Rev</th> … … 87 102 </thead> 88 103 <tbody><?cs 89 104 set:indent = #1 ?><?cs 105 set:idx = #0 ?><?cs 90 106 each:item = log.items ?><?cs 91 107 if:item.copyfrom_path ?> 92 108 <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>"> … … 99 115 set:indent = #1 ?><?cs 100 116 /if ?> 101 117 <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> 102 122 <td class="change" style="padding-left:<?cs var:indent ?>em"> 103 123 <a title="View log starting at this revision" href="<?cs var:item.log_href ?>"> 104 124 <div class="<?cs var:item.change ?>"></div> … … 115 135 <td class="author"><?cs var:log.changes[item.rev].author ?></td> 116 136 <td class="summary"><?cs var:log.changes[item.rev].message ?></td> 117 137 </tr><?cs 138 set:idx = idx + 1 ?><?cs 118 139 /each ?> 119 140 </tbody> 141 </form> 120 142 </table><?cs 121 143 if:len(links.prev) || len(links.next) ?><div id="paging" class="nav"><ul><?cs 122 144 if:len(links.prev) ?><li class="first<?cs -
templates/browser.cs
1 1 <?cs include "header.cs"?> 2 2 <?cs include "macros.cs"?> 3 <?cs include "anydiff.cs"?> 3 4 4 5 <div id="ctxtnav" class="nav"> 5 6 <ul> 7 <li class="first"><a href="<?cs var:browser.diff_href ?>">Diff to previous</a></li> 6 8 <li class="last"><a href="<?cs var:browser.log_href ?>">Revision Log</a></li> 7 9 </ul> 8 10 </div> … … 114 116 ?>/TracBrowser">TracBrowser</a> for help on using the browser. 115 117 </div> 116 118 119 <?cs call:anydiff(browser.path, browser.revision, browser.href) ?> 120 117 121 </div> 118 122 <?cs include:"footer.cs"?> -
templates/diff.cs
2 2 <?cs include "macros.cs"?> 3 3 4 4 <div id="ctxtnav" class="nav"> 5 <h2> ChangesetNavigation</h2><?cs5 <h2>Diff Navigation</h2><?cs 6 6 with:links = chrome.links ?> 7 7 <ul><?cs 8 8 if:len(links.prev) ?> 9 9 <li class="first<?cs if:!len(links.next) ?> last<?cs /if ?>"> 10 10 <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> 12 12 </li><?cs 13 13 /if ?><?cs 14 14 if:len(links.next) ?> 15 15 <li class="<?cs if:len(links.prev) ?>first <?cs /if ?>last"> 16 16 <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> 18 18 </li><?cs 19 19 /if ?> 20 20 </ul><?cs … … 22 22 </div> 23 23 24 24 <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> 26 49 27 <?cs each:change = changeset.changes ?><?cs50 <?cs each:change = diff.changes ?><?cs 28 51 if:len(change.diff) ?><?cs 29 52 set:has_diffs = 1 ?><?cs 30 53 /if ?><?cs … … 32 55 || diff.options.ignorecase || diff.options.ignorewhitespace ?> 33 56 <form method="post" id="prefs" action=""> 34 57 <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 ?>" /> 35 61 <label for="style">View differences</label> 36 62 <select id="style" name="style"> 37 63 <option value="inline"<?cs … … 100 126 /if ?> 101 127 <?cs /def ?> 102 128 103 <dl id="overview"> 129 <dl id="overview"><?cs 130 if:len(changeset) > #0 ?> 104 131 <dt class="time">Timestamp:</dt> 105 132 <dd class="time"><?cs var:changeset.time ?></dd> 106 133 <dt class="author">Author:</dt> 107 134 <dd class="author"><?cs var:changeset.author ?></dd> 108 135 <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 ?> 110 138 <dt class="files">Files:</dt> 111 139 <dd class="files"> 112 <ul><?cs each:item = changeset.changes ?>140 <ul><?cs each:item = diff.changes ?> 113 141 <li><?cs 114 142 if:item.change == 'add' ?><?cs 115 143 call:node_change(item, 'add', 'added') ?><?cs … … 140 168 </dl> 141 169 </div> 142 170 <ul class="entries"><?cs 143 each:item = changeset.changes ?><?cs171 each:item = diff.changes ?><?cs 144 172 if:len(item.diff) || len(item.props) ?><li class="entry" id="file<?cs 145 173 var:name(item) ?>"><h2><a href="<?cs 146 174 var:item.browser_href.new ?>" title="Show new revision <?cs
