Ticket #295: patch_for_295-1107.diff
| File patch_for_295-1107.diff, 35.3 KB (added by cboos@…, 7 years ago) |
|---|
-
htdocs/css/diff.css
20 20 /* Colors for change types */ 21 21 #overview .mod, .diff #legend .mod { background: #fd8 } 22 22 #overview .rem, .diff #legend .rem { background: #f88 } 23 #overview .add, .diff #legend .add { background: #dfd } 23 #overview .add, .diff #legend .add { background: #bfb } 24 #overview .cp, .diff #legend .cp { background: #88f } 25 #overview .mv, .diff #legend .mv { background: #ccc } 24 26 25 27 /* Legend for diff colors */ 26 28 .diff #legend { … … 46 48 margin: 0; 47 49 margin-right: .5em; 48 50 } 49 51 .diff #legend dt.unmod div.mod { 52 border: 0; 53 margin: 0; 54 float: right; 55 width: .4em; height: .8em; 56 } 50 57 /* Styles for the list of diffs */ 51 58 .diff ul.entries { clear: both; margin: 0; padding: 0 } 52 59 .diff li.entry { -
htdocs/css/changeset.css
12 12 overflow: hidden; 13 13 width: .8em; height: .8em; 14 14 } 15 #overview div.add div, #overview div.cp div, #overview div.mv div { 16 border: 0; 17 margin: 0; 18 float: right; 19 width: .35em; 20 } 21 15 22 #overview .message { padding: 1em 0 1px } 16 23 #overview dd.message p, #overview dd.message ul, #overview dd.message ol { 17 24 margin-bottom: 1em; -
trac/sync.py
21 21 22 22 from svn import fs, util, delta, repos, core 23 23 24 import posixpath 25 24 26 def sync(db, repos, fs_ptr, pool): 25 27 """ 26 updatesthe revision and node_change tables to be in sync with28 Update the revision and node_change tables to be in sync with 27 29 the repository. 28 30 """ 29 31 … … 59 61 core.svn_pool_destroy(subpool) 60 62 db.commit() 61 63 62 def insert_change (pool, fs_ptr, rev, cursor):63 64 65 def insert_change(pool, fs_ptr, rev, cursor): 66 """ 67 Save node changes for revision 'rev'. 68 69 Analyse the difference tree at revision 'rev', as given by 70 'repos.svn_repos_replay' (which offers usable 'copyfrom_' information). 71 72 The results are cached in the 'node_change' table, as follows: 73 74 || rev || action || name || 75 || --- || ------ || --------------------------------------------- || 76 || || 'A' || added path || 77 || || 'D' || removed path || 78 || || 'M' || modified path || 79 || || 'C' || original rev, original path // copied path || 80 || || 'R' || original rev, original path // renamed path || 81 || || 'd' || original rev, original path // deleted path || 82 83 The 'ADM' operations are direct operations. 84 The 'CR' operation can be direct operations. 85 The 'CRdm' may happen after a 'CR' operation on a parent path. 86 """ 87 64 88 class ChangeEditor(delta.Editor): 65 def __init__(self, rev, old_root,new_root, cursor):89 def __init__(self, rev, new_root, cursor): 66 90 self.rev = rev 67 91 self.cursor = cursor 68 self.old_root = old_root69 92 self.new_root = new_root 70 self.dir_has_prop_change = 0 93 self.additions = [] # List of tuples 94 # (file/dir,new_path,old_path,old_rev,action) 95 self.deletions = {} # Used to detect rename and copy operations. 96 self.skip_dir_prop_change = 0 97 98 def _norm(self, path): 99 if path and path[0] == '/': 100 path = path[1:] 101 return path 71 102 72 def delete_entry(self, path, revision, parent_baton, pool): 73 self.cursor.execute('INSERT INTO node_change (rev, name, change) ' 74 'VALUES (%s, %s, \'D\')', self.rev, path) 103 # -- svn.delta.Editor callbacks 104 105 # A directory_baton is a tuple (old_path,old_rev,new_path). 106 # This information is used to keep track of the original path 107 # after a copy or a rename operation on the parent. 75 108 76 def add_directory(self, path, parent_baton, 77 copyfrom_path, copyfrom_revision, dir_pool): 78 self.cursor.execute('INSERT INTO node_change (rev, name, change) ' 79 'VALUES (%s, %s, \'A\')', self.rev, path) 109 # -- -- directory 110 111 def open_root(self, base_revision, dir_pool): 112 return (None, None, '/') 113 # Note: '/' is needed for proper handling of prop changes at root 114 # (like SVK does for the svm:mirrors property). 80 115 81 def open_directory(self, path, parent_baton, base_revision, dir_pool): 82 self.dir_has_prop_change = 0 83 return [path, path, dir_pool] 116 def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev, 117 dir_pool): 118 old_path, old_rev = dir_baton[:2] 119 if copyfrom_path: # copied or renamed directory 120 old_path = self._norm(copyfrom_path) 121 old_rev = copyfrom_rev 122 action = 'C' 123 elif old_path: # already on a branch, expand the original path 124 old_path = posixpath.join(old_path, posixpath.split(path)[1]) 125 action = 'A' 126 else: 127 self._save_change(core.svn_node_file, 'A', path) 128 action = None 84 129 130 if action: 131 self.additions.append( (core.svn_node_dir, self._norm(path), 132 old_path, old_rev, action) ) 133 134 # don't create an additional 'M' entry for this directory 135 # in case there's also a dir property change 136 self.skip_dir_prop_change = 1 137 138 return (old_path, old_rev, path) 139 140 def open_directory(self, path, dir_baton, base_revision, dir_pool): 141 old_path, old_rev = dir_baton[:2] 142 if old_path: # already on a branch, expand the original path 143 old_path = posixpath.join(old_path, posixpath.split(path)[1]) 144 self.skip_dir_prop_change = 0 145 return (old_path, old_rev, path) 146 85 147 def change_dir_prop(self, dir_baton, name, value, pool): 86 if not dir_baton or self.dir_has_prop_change:148 if self.skip_dir_prop_change: 87 149 return 88 self.cursor.execute('INSERT INTO node_change (rev, name, change) ' 89 'VALUES (%s, %s, \'M\')', self.rev, dir_baton[1]) 90 self.dir_has_prop_change = 1 150 old_path, old_rev, path = dir_baton 151 if old_path: # already on a branch 152 self._save_change(core.svn_node_dir, 'm', path, old_path, old_rev) 153 else: 154 self._save_change(core.svn_node_dir, 'M', path) 91 155 156 self.skip_dir_prop_change = 1 157 92 158 def close_directory(self, dir_baton): 93 if not dir_baton: 94 return 95 self.dir_has_prop_change = 0 159 self.skip_dir_prop_change = 0 96 160 97 def add_file(self, path, parent_baton, 98 copyfrom_path, copyfrom_revision, file_pool): 99 self.cursor.execute('INSERT INTO node_change (rev, name, change) ' 100 'VALUES (%s, %s, \'A\')',self.rev, path) 161 def delete_entry(self, path, revision, dir_baton, pool): 162 """ 163 This is a removed path. It corresponds to one of the 164 following actions: 'R'ename, 'D'elete, or 'd'elete on a branch. 165 """ 166 old_path, old_rev = dir_baton[:2] 167 if old_path: # already on a branch, expand the original path 168 old_path = posixpath.join(old_path, posixpath.split(path)[1]) 169 path_info = (old_path, old_rev) 170 else: 171 path_info = core.svn_node_unknown 172 self.deletions[self._norm(path)] = path_info 101 173 102 def open_file(self, path, parent_baton, base_revision, file_pool): 174 # -- -- file 175 176 def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision, 177 dir_pool): 178 old_path, old_rev = dir_baton[:2] 179 if copyfrom_path: # copied or renamed file 180 old_path = self._norm(copyfrom_path) 181 old_rev = copyfrom_revision 182 action = 'C' 183 elif old_path: # already on a branch, resolve old_rev later 184 old_path = posixpath.join(old_path, posixpath.split(path)[1]) 185 old_rev = -1 186 action = 'A' 187 else: 188 return self._save_change(core.svn_node_file, 'A', self._norm(path)) 189 190 self.additions.append( (core.svn_node_file, self._norm(path), 191 old_path, old_rev, action) ) 192 193 def open_file(self, path, dir_baton, dummy_rev, file_pool): 194 old_path, old_rev = dir_baton[:2] 195 if old_path: # already on a branch (at b_rev) 196 old_path = posixpath.join(old_path, posixpath.split(path)[1]) 197 # then this is a copy from a file for which f_rev > b_rev 198 self.additions.append( (core.svn_node_file, self._norm(path), 199 old_path, -1, 'C') ) 200 else: # no branch, it must be a modification 201 self._save_change(core.svn_node_file, 'M', self._norm(path)) 202 203 def _save_change(self, node_type, action, path, old_path=None, old_rev=None): 204 # Note: node_type is ignored for now 205 if old_path and old_rev: 206 path = "%s // %d, %s" % ( path, old_rev, old_path ) 103 207 self.cursor.execute('INSERT INTO node_change (rev, name, change) ' 104 'VALUES (%s, %s, \'M\')',self.rev, path)208 'VALUES (%s, %s, %s)', self.rev, path, action) 105 209 210 def finalize(self): 211 """ 212 The rename detection is deferred until the end of edition, 213 as 'delete' and 'add' notifications can happen in any order. 214 """ 215 for node_type, path, old_path, old_rev, action in self.additions: 216 if self.deletions.has_key(old_path): # normal rename 217 self.deletions.pop(old_path) 218 action = 'R' 219 elif self.deletions.has_key(path): # copy+modification in a branch 220 action = 'C' 221 # FIXME: actually, this could be a 'R' if the parent branch 222 # is a 'R'. This can be fixed if the parent branch 223 # is recorded in addition to the old_path. 224 self.deletions.pop(path) 225 if action == 'A': # add on a branch 226 old_path = old_rev = None 227 self._save_change(node_type, action, path, old_path, old_rev) 228 for path, path_info in self.deletions.items(): 229 if path_info == core.svn_node_unknown: # simple deletion 230 self._save_change(core.svn_node_unknown, 'D', path) 231 else: # delete on a branch 232 self._save_change(core.svn_node_unknown, 'd', path, *path_info) 106 233 107 old_root = fs.revision_root(fs_ptr, rev - 1, pool) 234 108 235 new_root = fs.revision_root(fs_ptr, rev, pool) 109 110 editor = ChangeEditor(rev, old_root,new_root, cursor)236 237 editor = ChangeEditor(rev, new_root, cursor) 111 238 e_ptr, e_baton = delta.make_editor(editor, pool) 112 239 113 def authz_cb(root, path, pool): return 1 114 repos.svn_repos_dir_delta(old_root, '', '', 115 new_root, '', e_ptr, e_baton, authz_cb, 116 0, 1, 0, 1, pool) 240 repos.svn_repos_replay(new_root, e_ptr, e_baton, pool) 241 242 editor.finalize() # Editor's close_edit not called... 243 244 -
trac/Changeset.py
23 23 import sys 24 24 import time 25 25 import util 26 import re 27 import posixpath 26 28 from StringIO import StringIO 27 29 from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED 28 30 29 31 import svn 30 32 import svn.delta 31 33 import svn.fs 34 import svn.core 32 35 33 36 import Diff 34 37 import perm … … 41 44 Base class for diff renderers. 42 45 """ 43 46 44 def __init__(self, old_root, new_root, rev, req, args, env): 47 def __init__(self, old_root, new_root, rev, req, args, env, path_info): 48 self.path_info = path_info 45 49 self.old_root = old_root 46 50 self.new_root = new_root 47 51 self.rev = rev … … 49 53 self.args = args 50 54 self.env = env 51 55 52 def open_directory(self, path, parent_baton, base_revision, dir_pool): 53 return [path, path, dir_pool] 56 # svn.delta.Editor callbacks: 57 # This editor will be driven by a 'repos.svn_repos_dir_delta' call. 58 # With this driver, The 'copyfrom_path' will always be 'None'. 59 # We can't use it. 60 # This is why we merge the path_info data (obtained during a 61 # 'repos.svn_repos_replay' call) back into this process. 62 63 def _retrieve_old_path(self, parent_baton, path, pool): 64 old_path = parent_baton[0] 65 self.prefix = None 66 if self.path_info.has_key(path): # retrieve 'copyfrom_path' info 67 seq, old_path = self.path_info[path][:2] 68 self.prefix = 'changeset.changes.%d' % seq 69 elif old_path: # already on a branch, expand the original path 70 old_path = posixpath.join(old_path, posixpath.split(path)[1]) 71 else: 72 old_path = path 73 return (old_path, path, pool) 54 74 75 def open_root(self, base_revision, dir_pool): 76 return self._retrieve_old_path((None, None, None), '/', dir_pool) 77 78 def open_directory(self, path, dir_baton, base_revision, dir_pool): 79 return self._retrieve_old_path(dir_baton, path, dir_pool) 80 55 81 def open_file(self, path, parent_baton, base_revision, file_pool): 56 return [path, path, file_pool]82 return self._retrieve_old_path(parent_baton, path, file_pool) 57 83 58 84 59 85 class HtmlDiffEditor(BaseDiffEditor): … … 62 88 the output is written to stdout. 63 89 """ 64 90 65 def __init__(self, old_root, new_root, rev, req, args, env): 66 BaseDiffEditor.__init__(self, old_root, new_root, rev, req, args, env) 67 self.prev_path = None 68 self.fileno = -1 91 def __init__(self, old_root, new_root, rev, req, args, env, change_info): 92 BaseDiffEditor.__init__(self, old_root, new_root, rev, req, args, 93 env, change_info) 69 94 self.prefix = None 70 95 71 def _check_next(self, old_path, new_path, pool):72 if self.prev_path == (old_path or new_path):73 return74 75 self.fileno += 176 self.prev_path = old_path or new_path77 78 self.prefix = 'changeset.changes.%d' % (self.fileno)79 if old_path:80 old_rev = svn.fs.node_created_rev(self.old_root, old_path, pool)81 self.req.hdf.setValue('%s.rev.old' % self.prefix, str(old_rev))82 self.req.hdf.setValue('%s.browser_href.old' % self.prefix,83 self.env.href.browser(old_path, old_rev))84 if new_path:85 new_rev = svn.fs.node_created_rev(self.new_root, new_path, pool)86 self.req.hdf.setValue('%s.rev.new' % self.prefix, str(new_rev))87 self.req.hdf.setValue('%s.browser_href.new' % self.prefix,88 self.env.href.browser(new_path, new_rev))89 90 96 def add_directory(self, path, parent_baton, copyfrom_path, 91 97 copyfrom_revision, dir_pool): 92 self._check_next(None, path, dir_pool)98 return self._retrieve_old_path(parent_baton, path, dir_pool) 93 99 100 def add_file(self, path, parent_baton, copyfrom_path, copyfrom_revision, 101 file_pool): 102 return self._retrieve_old_path(parent_baton, path, file_pool) 103 94 104 def delete_entry(self, path, revision, parent_baton, pool): 95 self._check_next(path, None, pool) 105 old_path = self._retrieve_old_path(parent_baton, path, pool)[0] 106 return old_path, None, pool 96 107 97 def change_dir_prop(self, dir_baton, name, value, dir_pool):98 if not dir_baton:99 return100 (old_path, new_path, pool) = dir_baton101 self._check_next(old_path, new_path, dir_pool)102 108 103 prefix = '%s.props.%s' % (self.prefix, name) 104 if old_path: 105 old_value = svn.fs.node_prop(self.old_root, old_path, name, dir_pool) 106 if old_value: 107 self.req.hdf.setValue(prefix + '.old', old_value) 108 if value: 109 self.req.hdf.setValue(prefix + '.new', value) 109 # -- changes: 110 110 111 def add_file(self, path, parent_baton, copyfrom_path, copyfrom_revision, 112 file_pool): 113 self._check_next(None, path, file_pool) 111 def _old_root(self, new_path, pool): 112 if not new_path: 113 return 114 old_rev = self.path_info[new_path][2] 115 if not old_rev: 116 return 117 elif old_rev == self.rev - 1: 118 return self.old_root 119 else: 120 return svn.fs.revision_root(svn.fs.root_fs(self.old_root), 121 old_rev, pool) 122 123 # -- -- textual changes: 114 124 115 125 def apply_textdelta(self, file_baton, base_checksum): 116 if not file_baton: 126 old_path, new_path, pool = file_baton 127 if not self.prefix or not (old_path and new_path): 117 128 return 118 (old_path, new_path, pool) = file_baton 119 self._check_next(old_path, new_path, pool) 120 129 old_root = self._old_root(new_path, pool) 130 if not old_root: 131 return 132 121 133 # Try to figure out the charset used. We assume that both the old 122 134 # and the new version uses the same charset, not always the case 123 135 # but that's all we can do... … … 128 140 ctpos = mime_type and mime_type.find('charset=') or -1 129 141 if ctpos >= 0: 130 142 charset = mime_type[ctpos + 8:] 131 self.log.debug("Charset %s selected" % charset)132 143 else: 133 144 charset = self.env.get_config('trac', 'default_charset', 134 145 'iso-8859-15') 135 146 136 147 # Start up the diff process 137 148 options = Diff.get_options(self.env, self.req, self.args, 1) 138 differ = svn.fs.FileDiff( self.old_root, old_path, self.new_root,139 new_path, pool, options)149 differ = svn.fs.FileDiff(old_root, old_path, 150 self.new_root, new_path, pool, options) 140 151 differ.get_files() 141 152 pobj = differ.get_pipe() 142 153 … … 157 168 os.waitpid(-1, 0) 158 169 except OSError: pass 159 170 171 # -- -- property changes: 172 173 def change_dir_prop(self, dir_baton, name, value, dir_pool): 174 self._change_prop(dir_baton, name, value, dir_pool) 175 160 176 def change_file_prop(self, file_baton, name, value, file_pool): 161 if not file_baton: 177 self._change_prop(file_baton, name, value, file_pool) 178 179 def _change_prop(self, baton, name, value, pool): 180 if not self.prefix: 162 181 return 163 (old_path, new_path, pool) = file_baton 164 self._check_next(old_path, new_path, file_pool) 182 old_path, new_path, pool = baton 165 183 166 184 prefix = '%s.props.%s' % (self.prefix, name) 167 185 if old_path: 168 old_value = svn.fs.node_prop(self.old_root, old_path, name, file_pool) 169 if old_value: 170 self.req.hdf.setValue(prefix + '.old', old_value) 186 old_root = self._old_root(new_path, pool) 187 if old_root: 188 old_value = svn.fs.node_prop(old_root, old_path, name, pool) 189 if old_value: 190 if value == old_value: 191 return # spurious change prop after a copy 192 self.req.hdf.setValue(prefix + '.old', util.escape(old_value)) 171 193 if value: 172 self.req.hdf.setValue(prefix + '.new', value)194 self.req.hdf.setValue(prefix + '.new', util.escape(value)) 173 195 174 196 175 197 class UnifiedDiffEditor(BaseDiffEditor): … … 178 200 the output is written to stdout. 179 201 """ 180 202 203 def add_file(self, path, parent_baton, copyfrom_path, copyfrom_revision, 204 file_pool): 205 return (None, path, file_pool) 206 207 def delete_entry(self, path, revision, parent_baton, pool): 208 if svn.fs.check_path(self.old_root, path, pool) == svn.core.svn_node_file: 209 self.apply_textdelta((path, None, pool),None) 210 181 211 def apply_textdelta(self, file_baton, base_checksum): 182 212 if not file_baton: 183 213 return … … 193 223 differ.get_files() 194 224 pobj = differ.get_pipe() 195 225 line = pobj.readline() 226 # rewrite 'None' as appropriate ('A' and 'D' support) 227 fix_second_line = 0 228 if line[:6] != 'Files ' and line[:7] != 'Binary ': 229 if old_path == None: # 'A' 230 line = '--- %s %s' % (new_path, line[9:]) 231 elif new_path == None: # 'D' 232 fix_second_line = 1 196 233 while line: 197 234 self.req.write(line) 198 235 line = pobj.readline() 236 if fix_second_line: # 'D' 237 line = '--- %s %s' % (old_path, line[9:]) 238 fix_second_line = 0 199 239 pobj.close() 200 240 if sys.platform[:3] != "win" and sys.platform != "os2emx": 201 241 try: … … 208 248 Generates a ZIP archive containing the modified and added files. 209 249 """ 210 250 211 def __init__(self, old_root, new_root, rev, req, args, env): 212 BaseDiffEditor.__init__(self, old_root, new_root, rev, req, args, env) 251 def __init__(self, old_root, new_root, rev, req, args, env, path_info): 252 BaseDiffEditor.__init__(self, old_root, new_root, rev, req, args, 253 env, path_info) 213 254 self.buffer = StringIO() 214 255 self.zip = ZipFile(self.buffer, 'w', ZIP_DEFLATED) 215 256 … … 256 297 row = cursor.fetchone() 257 298 if not row: 258 299 raise util.TracError('Changeset %d does not exist.' % rev, 259 'Invalid Changset')300 'Invalid Changset') 260 301 return row 261 302 262 def get_change_info (self, rev):303 def get_change_info(self, rev): 263 304 cursor = self.db.cursor () 264 305 cursor.execute ('SELECT name, change FROM node_change ' + 265 306 'WHERE rev=%d', rev) … … 268 309 row = cursor.fetchone() 269 310 if not row: 270 311 break 271 info.append({'name': row['name'], 272 'change': row['change'], 273 'log_href': self.env.href.log(row['name'])}) 274 return info 312 change = row['change'] 313 name = row['name'] 314 if change in 'CRdm': # 'C'opy, 'R'eplace or 'd'elete on a branch 315 # the name column contains the encoded ''path_info'' 316 # (see _save_change method in sync.py). 317 m = re.match('(.*) // (-?\d+), (.*)', name) 318 if change == 'd': 319 new_path = None 320 else: 321 new_path = m.group(1) 322 old_rev = int(m.group(2)) 323 if old_rev < 0: 324 old_rev = None 325 old_path = m.group(3) 326 elif change == 'D': # 'D'elete 327 new_path = None 328 old_path = name 329 old_rev = None 330 elif change == 'A': # 'A'dd 331 new_path = name 332 old_path = old_rev = None 333 else: # 'M'odify 334 new_path = old_path = name 335 old_rev = None 336 if old_path and not old_rev: # 'D' and 'M' 337 history = svn.fs.node_history(self.old_root, old_path, self.pool) 338 history = svn.fs.history_prev(history, 0, self.pool) # what an API... 339 old_rev = svn.fs.history_location(history, self.pool)[1] 340 # Note: 'node_created_rev' doesn't work reliably 341 key = (new_path or old_path) 342 info.append((key, change, new_path, old_path, old_rev)) 275 343 344 info.sort(lambda x,y: cmp(x[0],y[0])) 345 self.path_info = {} 346 # path_info is a mapping of paths to sequence number and additional info 347 # 'new_path' to '(seq, copyfrom_path, copyfrom_rev)', 348 # 'old_path' to '(seq)' 349 sinfo = [] 350 seq = 0 351 for _, change, new_path, old_path, old_rev in info: 352 cinfo = { 'name.new': new_path, 353 'name.old': old_path, 354 'log_href': new_path or old_path } 355 if new_path: 356 self.path_info[new_path] = (seq, old_path, old_rev) 357 cinfo['rev.new'] = str(rev) 358 cinfo['browser_href.new'] = self.env.href.browser(new_path, rev) 359 if old_path: 360 cinfo['rev.old'] = str(old_rev) 361 cinfo['browser_href.old'] = self.env.href.browser(old_path, old_rev) 362 if change in 'CRm': 363 cinfo['copyfrom_path'] = old_path 364 cinfo['change'] = change.upper() 365 cinfo['seq'] = seq 366 sinfo.append(cinfo) 367 seq += 1 368 return sinfo 369 276 370 def render(self): 277 371 self.perm.assert_permission (perm.CHANGESET_VIEW) 278 372 … … 291 385 if self.args.has_key('update'): 292 386 self.req.redirect(self.env.href.changeset(self.rev)) 293 387 294 change_info = self.get_change_info (self.rev) 295 changeset_info = self.get_changeset_info (self.rev) 388 try: 389 self.old_root = svn.fs.revision_root(self.fs_ptr, int(self.rev) - 1, self.pool) 390 self.new_root = svn.fs.revision_root(self.fs_ptr, int(self.rev), self.pool) 391 except svn.core.SubversionException: 392 raise util.TracError('Invalid revision number: %d' % int(self.rev)) 296 393 394 cinfo = self.get_change_info(self.rev) 395 changeset_info = self.get_changeset_info(self.rev) 396 297 397 self.req.hdf.setValue('title', '[%d] (changeset)' % self.rev) 298 398 self.req.hdf.setValue('changeset.time', 299 399 time.asctime(time.localtime(int(changeset_info['time'])))) … … 304 404 changeset_info['message']), 305 405 self.req.hdf, self.env, self.db)) 306 406 self.req.hdf.setValue('changeset.revision', str(self.rev)) 307 util.add_to_hdf(c hange_info, self.req.hdf, 'changeset.changes')407 util.add_to_hdf(cinfo, self.req.hdf, 'changeset.changes') 308 408 309 409 self.req.hdf.setValue('changeset.href', 310 410 self.env.href.changeset(self.rev)) … … 320 420 321 421 def render_diffs(self, editor_class=HtmlDiffEditor): 322 422 """ 323 generatesa unified diff of the changes for a given changeset.324 the output is written to stdout.423 Generate a unified diff of the changes for a given changeset. 424 The output is written to stdout. 325 425 """ 326 try: 327 old_root = svn.fs.revision_root(self.fs_ptr, int(self.rev) - 1, self.pool) 328 new_root = svn.fs.revision_root(self.fs_ptr, int(self.rev), self.pool) 329 except svn.core.SubversionException: 330 raise util.TracError('Invalid revision number: %d' % int(self.rev)) 331 332 editor = editor_class(old_root, new_root, int(self.rev), self.req, 333 self.args, self.env) 426 editor = editor_class(self.old_root, self.new_root, int(self.rev), self.req, 427 self.args, self.env, self.path_info) 334 428 e_ptr, e_baton = svn.delta.make_editor(editor, self.pool) 335 429 336 430 def authz_cb(root, path, pool): 337 431 return self.authzperm.has_permission(path) and 1 or 0 338 svn.repos.svn_repos_dir_delta( old_root, '', '',339 new_root, '', e_ptr, e_baton, authz_cb,432 svn.repos.svn_repos_dir_delta(self.old_root, '', '', 433 self.new_root, '', e_ptr, e_baton, authz_cb, 340 434 0, 1, 0, 1, self.pool) 341 435 342 436 def display(self): -
templates/changeset.cs
2 2 <?cs include "header.cs"?> 3 3 <?cs include "macros.cs"?> 4 4 5 5 6 <div id="ctxtnav" class="nav"> 6 7 <h2>Changeset Navigation</h2> 7 8 <ul><?cs … … 27 28 if:len(change.diff) ?><?cs 28 29 set:has_diffs = 1 ?><?cs 29 30 /if ?><?cs 30 /each ?><?cs if:has_diffs ?> 31 /each ?><?cs if:has_diffs || diff.options.ignoreblanklines 32 || diff.options.ignorecase || diff.options.ignorewhitespace ?> 31 33 <form method="post" id="prefs" action=""> 32 34 <div> 33 35 <label for="style">View differences</label> … … 68 70 </div> 69 71 </form><?cs /if ?> 70 72 73 74 <?cs def:node_change(item,cl,kind) ?><?cs 75 set:ndiffs = len(item.diff) ?><?cs 76 set:nprops = len(item.props) ?><?cs 77 if:$ndiffs + $nprops > #0 && cl != "mod" ?> 78 <div class="<?cs var:cl ?>"><div class="mod"></div></div><?cs 79 else ?> 80 <div class="<?cs var:cl ?>"></div><?cs 81 /if ?><?cs 82 if:cl == "rem" ?> 83 <a title="Show what was removed (rev. <?cs var:item.rev.old ?>)" 84 href="<?cs var:item.browser_href.old ?>"><?cs var:item.name.old ?></a><?cs 85 else ?> 86 <a title="Show entry in browser" 87 href="<?cs var:item.browser_href.new ?>"><?cs var:item.name.new ?></a><?cs 88 /if ?> 89 <span class="comment">(<?cs var:kind ?>)</span><?cs 90 if:item.copyfrom_path ?> 91 <small><em>(<?cs var:kind ?> from <a 92 href="<?cs var:item.browser_href.old ?>" 93 title="Show original file (rev. <?cs var:item.rev.old ?>)" 94 ><?cs var:item.copyfrom_path ?></a>)</em></small><?cs 95 /if ?><?cs 96 if:$ndiffs + $nprops > #0 ?> 97 (<a href="#file<?cs var:name(item) ?>" title="Show differences"><?cs 98 if:$ndiffs > #0 ?><?cs var:ndiffs ?> diff<?cs if:$ndiffs > #1 ?>s<?cs /if ?><?cs 99 /if ?><?cs 100 if:$ndiffs && $nprops ?>, <?cs /if ?><?cs 101 if:$nprops > #0 ?><?cs var:nprops ?> prop<?cs if:$nprops > #1 ?>s<?cs /if ?><?cs 102 /if ?></a>)<?cs 103 elif:cl == "mod" ?> 104 (<a href="<?cs var:item.browser_href.old ?>" 105 title="Show previous version in browser">previous</a>)<?cs 106 /if ?> 107 <?cs /def ?> 108 71 109 <dl id="overview"> 72 110 <dt class="time">Timestamp:</dt> 73 111 <dd class="time"><?cs var:changeset.time ?></dd> … … 80 118 <ul><?cs each:item = changeset.changes ?> 81 119 <li> 82 120 <?cs if:item.change == "A" ?> 83 <div class="add"></div> 84 <a href="<?cs var:item.browser_href.new ?>" title="Show file in browser"><?cs 85 var:item.name ?></a> <span class="comment">(added)</span> 121 <?cs call:node_change(item,"add","added") ?> 122 <?cs elif:item.change == "D" ?> 123 <?cs call:node_change(item,"rem","deleted") ?> 124 <?cs elif:item.change == "C" ?> 125 <?cs call:node_change(item,"cp","copied") ?> 126 <?cs elif:item.change == "R" ?> 127 <?cs call:node_change(item,"mv","renamed") ?> 86 128 <?cs elif:item.change == "M" ?> 87 <div class="mod"></div> 88 <a href="<?cs var:item.browser_href.new ?>" title="Show file in browser"><?cs 89 var:item.name ?></a> <span class="comment">(modified)</span><?cs 90 if:len(item.diff) || len(item.props) ?> 91 (<a href="#file<?cs var:name(item) ?>" title="Show differences">diff</a>)<?cs 92 /if ?> 93 <?cs elif:item.change == "D" ?> 94 <div class="rem"></div> 95 <?cs var:item.name ?> <span class="comment">(deleted)</span> 129 <?cs call:node_change(item,"mod","modified") ?> 96 130 <?cs /if ?> 97 131 </li> 98 132 <?cs /each ?></ul> … … 108 142 <dt class="rem"></dt><dd>Removed</dd> 109 143 <dt class="mod"></dt><dd>Modified</dd> 110 144 </dl> 145 <dl> 146 <dt class="cp"></dt><dd>Copied</dd> 147 <dt class="mv"></dt><dd>Renamed</dd> 148 <dt class="unmod"><div class="mod"></div></dt><dd><em>(... and modified)</em></dd> 149 </dl> 111 150 </div> 112 151 <ul class="entries"> 113 152 <?cs each:item = changeset.changes ?> 114 153 <?cs if:len(item.diff) || len(item.props) ?> 115 154 <li class="entry" id="file<?cs var:name(item) ?>"> 116 <h2><a href="<?cs 117 var:item.browser_href.new ?>" title="Show version <?cs 118 var:item.rev.new ?> of this file in browser"><?cs 119 var:item.name ?></a></h2><?cs 155 <h2><a href="<?cs var:item.browser_href.new ?>" 156 title="Show new revision <?cs var:item.rev.new ?> of this file in browser"><?cs 157 var:item.name.new ?></a></h2><?cs 120 158 if:len(item.props) ?> 121 159 <ul class="props"><?cs each:prop = item.props ?><li> 122 160 Property <strong><?cs var:name(prop) ?></strong> <?cs … … 124 162 elif:!prop.old ?>set<?cs 125 163 else ?>deleted<?cs 126 164 /if ?><?cs 127 if:prop.old && prop.new ?><em>< ?cs var:prop.old ?></em><?cs /if ?><?cs128 if:prop.new ?> to <em>< ?cs var:prop.new ?></em><?cs /if ?>165 if:prop.old && prop.new ?><em><tt><?cs var:prop.old ?></tt></em><?cs /if ?><?cs 166 if:prop.new ?> to <em><tt><?cs var:prop.new ?></tt></em><?cs /if ?> 129 167 </li><?cs /each ?></ul><?cs 130 168 /if ?><?cs 131 169 if:len(item.diff) ?> … … 137 175 <col class="lineno" /><col class="content" /> 138 176 </colgroup> 139 177 <thead><tr> 140 <th colspan="2"><a href="<?cs var:item.browser_href.old ?>">Revision <?cs 141 var:item.rev.old ?></a></th> 142 <th colspan="2"><a href="<?cs var:item.browser_href.new ?>">Revision <?cs 143 var:item.rev.new ?></a></th> 178 <th colspan="2"> 179 <a href="<?cs var:item.browser_href.old ?>" 180 title="Show old rev. <?cs var:item.rev.old ?> of <?cs var:item.name.old ?>"> 181 Revision <?cs var:item.rev.old ?></a></th> 182 <th colspan="2"> 183 <a href="<?cs var:item.browser_href.new ?>" 184 title="Show new rev. <?cs var:item.rev.old ?> of <?cs var:item.name.new ?>"> 185 Revision <?cs var:item.rev.new ?></a></th> 144 186 </tr></thead> 145 187 <?cs each:change = item.diff ?> 146 188 <tbody> … … 160 202 <col class="content" /> 161 203 </colgroup> 162 204 <thead><tr> 163 <th title="Revision <?cs var:item.rev.old ?>"> <a href="<?cs164 var:item.browser_href.old ?>" title="Show revision <?cs165 var:item.rev.old ?> of this file in browser">r<?cs166 var:item.rev.old ?></a></th>167 <th title="Revision <?cs var:item.rev.new ?>"> <a href="<?cs168 var:item.browser_href.new ?>" title="Show revision <?cs169 var:item.rev.new ?> of this file in browser">r<?cs170 var:item.rev.new ?></a></th>205 <th title="Revision <?cs var:item.rev.old ?>"> 206 <a href="<?cs var:item.browser_href.old ?>" 207 title="Show old rev. <?cs var:item.rev.old ?> of <?cs var:item.name.old ?>"> 208 r<?cs var:item.rev.old ?></a></th> 209 <th title="Revision <?cs var:item.rev.new ?>"> 210 <a href="<?cs var:item.browser_href.new ?>" 211 title="Show new rev. <?cs var:item.rev.new ?> of <?cs var:item.name.new ?>"> 212 r<?cs var:item.rev.new ?></a></th> 171 213 <th> </th> 172 214 </tr></thead> 173 215 <?cs each:change = item.diff ?>
