| | 1 | # -*- coding: iso8859-1 -*- |
| | 2 | # |
| | 3 | # Copyright (C) 2003, 2004 Edgewall Software |
| | 4 | # Copyright (C) 2003, 2004 Jonas Borgstr�jonas@edgewall.com> |
| | 5 | # |
| | 6 | # Trac is free software; you can redistribute it and/or |
| | 7 | # modify it under the terms of the GNU General Public License as |
| | 8 | # published by the Free Software Foundation; either version 2 of the |
| | 9 | # License, or (at your option) any later version. |
| | 10 | # |
| | 11 | # Trac is distributed in the hope that it will be useful, |
| | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| | 14 | # General Public License for more details. |
| | 15 | # |
| | 16 | # You should have received a copy of the GNU General Public License |
| | 17 | # along with this program; if not, write to the Free Software |
| | 18 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| | 19 | # |
| | 20 | # Author: Jonas Borgstr�jonas@edgewall.com> |
| | 21 | |
| | 22 | import time |
| | 23 | |
| | 24 | import util |
| | 25 | import Diff |
| | 26 | import perm |
| | 27 | import Module |
| | 28 | from WikiFormatter import wiki_to_html |
| | 29 | |
| | 30 | import svn.delta |
| | 31 | import svn |
| | 32 | |
| | 33 | class HtmlDiffEditor (svn.delta.Editor): |
| | 34 | """ |
| | 35 | generates a htmlized unified diff of the changes for a given changeset. |
| | 36 | the output is written to stdout. |
| | 37 | """ |
| | 38 | def __init__(self, old_root, new_root, rev, req, args, env, authzperm): |
| | 39 | self.old_root = old_root |
| | 40 | self.new_root = new_root |
| | 41 | self.rev = rev |
| | 42 | self.req = req |
| | 43 | self.args = args |
| | 44 | self.env = env |
| | 45 | self.authzperm = authzperm |
| | 46 | self.fileno = 0 |
| | 47 | |
| | 48 | def print_diff (self, old_path, new_path, pool): |
| | 49 | if not old_path or not new_path: |
| | 50 | return |
| | 51 | |
| | 52 | old_rev = svn.fs.node_created_rev(self.old_root, old_path, pool) |
| | 53 | new_rev = svn.fs.node_created_rev(self.new_root, new_path, pool) |
| | 54 | |
| | 55 | options = Diff.get_options(self.env, self.req, self.args, 1) |
| | 56 | |
| | 57 | # Make sure we have permission to view this diff |
| | 58 | if not self.authzperm.has_permission(new_path): |
| | 59 | return |
| | 60 | |
| | 61 | # Try to figure out the charset used. We assume that both the old |
| | 62 | # and the new version uses the same charset, not always the case |
| | 63 | # but that's all we can do... |
| | 64 | mime_type = svn.fs.node_prop (self.new_root, new_path, |
| | 65 | svn.util.SVN_PROP_MIME_TYPE, |
| | 66 | pool) |
| | 67 | # We don't have to guess if the charset is specified in the |
| | 68 | # svn:mime-type property |
| | 69 | ctpos = mime_type and mime_type.find('charset=') or -1 |
| | 70 | if ctpos >= 0: |
| | 71 | charset = mime_type[ctpos + 8:] |
| | 72 | self.env.log.debug("Charset %s selected" % charset) |
| | 73 | else: |
| | 74 | charset = self.env.get_config('trac', 'default_charset', 'iso-8859-15') |
| | 75 | |
| | 76 | # Start up the diff process |
| | 77 | differ = svn.fs.FileDiff(self.old_root, old_path, self.new_root, |
| | 78 | new_path, pool, options) |
| | 79 | differ.get_files() |
| | 80 | pobj = differ.get_pipe() |
| | 81 | prefix = 'changeset.diff.files.%d' % (self.fileno) |
| | 82 | self.req.hdf.setValue(prefix + '.browser_href.old', |
| | 83 | self.env.href.file(old_path, old_rev)) |
| | 84 | self.req.hdf.setValue(prefix + '.browser_href.new', |
| | 85 | self.env.href.file(new_path, new_rev)) |
| | 86 | tabwidth = int(self.env.get_config('diff', 'tab_width', '8')) |
| | 87 | builder = Diff.HDFBuilder(self.req.hdf, prefix, tabwidth) |
| | 88 | self.fileno += 1 |
| | 89 | builder.writeline('header %s %s | %s %s redaeh' % (old_path, old_rev, |
| | 90 | new_path, new_rev)) |
| | 91 | while 1: |
| | 92 | line = pobj.readline() |
| | 93 | if not line: |
| | 94 | break |
| | 95 | builder.writeline(util.to_utf8(line, charset)) |
| | 96 | builder.close() |
| | 97 | |
| | 98 | def add_file(self, path, parent_baton, copyfrom_path, |
| | 99 | copyfrom_revision, file_pool): |
| | 100 | return [None, path, file_pool] |
| | 101 | |
| | 102 | def open_file(self, path, parent_baton, base_revision, file_pool): |
| | 103 | return [path, path, file_pool] |
| | 104 | |
| | 105 | def apply_textdelta(self, file_baton, base_checksum): |
| | 106 | self.print_diff (*file_baton) |
| | 107 | |
| | 108 | |
| | 109 | class UnifiedDiffEditor(HtmlDiffEditor): |
| | 110 | """ |
| | 111 | generates a unified diff of the changes for a given changeset. |
| | 112 | the output is written to stdout. |
| | 113 | """ |
| | 114 | |
| | 115 | def __init__(self, old_root, new_root, rev, req, args, env, authzperm): |
| | 116 | HtmlDiffEditor.__init__(self, old_root, new_root, rev, req, args, env, authzperm) |
| | 117 | self.output = req |
| | 118 | |
| | 119 | def print_diff (self, old_path, new_path, pool): |
| | 120 | options = ['-u'] |
| | 121 | options.append('-L') |
| | 122 | options.append("%s\t(revision %d)" % (old_path, self.rev-1)) |
| | 123 | options.append('-L') |
| | 124 | options.append("%s\t(revision %d)" % (new_path, self.rev)) |
| | 125 | |
| | 126 | differ = svn.fs.FileDiff(self.old_root, old_path, |
| | 127 | self.new_root, new_path, pool, options) |
| | 128 | differ.get_files() |
| | 129 | pobj = differ.get_pipe() |
| | 130 | line = pobj.readline() |
| | 131 | while line: |
| | 132 | self.output.write(line) |
| | 133 | line = pobj.readline() |
| | 134 | |
| | 135 | |
| | 136 | class Anydiff (Module.Module): |
| | 137 | template_name = 'anydiff.cs' |
| | 138 | perm = None |
| | 139 | fs_ptr = None |
| | 140 | pool = None |
| | 141 | |
| | 142 | def get_changeset_info (self, rev): |
| | 143 | cursor = self.db.cursor () |
| | 144 | cursor.execute ('SELECT time, author, message FROM revision ' + |
| | 145 | 'WHERE rev=%d', rev) |
| | 146 | row = cursor.fetchone() |
| | 147 | if not row: |
| | 148 | raise util.TracError('Anydiff %d does not exist.' % rev, |
| | 149 | 'Invalid Changset') |
| | 150 | return row |
| | 151 | |
| | 152 | def get_change_info (self, rev): |
| | 153 | cursor = self.db.cursor () |
| | 154 | cursor.execute ('SELECT name, change FROM node_change ' + |
| | 155 | 'WHERE rev=%d', rev) |
| | 156 | info = [] |
| | 157 | while 1: |
| | 158 | row = cursor.fetchone() |
| | 159 | if not row: |
| | 160 | break |
| | 161 | info.append({'name': row['name'], |
| | 162 | 'change': row['change'], |
| | 163 | 'browser_href': self.env.href.browser(row['name'], rev), |
| | 164 | 'log_href': self.env.href.log(row['name'])}) |
| | 165 | return info |
| | 166 | |
| | 167 | def render (self): |
| | 168 | self.perm.assert_permission (perm.CHANGESET_VIEW) |
| | 169 | |
| | 170 | self.add_link('alternate', '?format=diff', 'Unified Diff', |
| | 171 | 'text/plain', 'diff') |
| | 172 | |
| | 173 | if self.args.has_key('rev'): |
| | 174 | self.rev = int(self.args.get('rev')) |
| | 175 | else: |
| | 176 | self.rev = svn.fs.youngest_rev(self.fs_ptr, self.pool) |
| | 177 | |
| | 178 | change_info = self.get_change_info (self.rev) |
| | 179 | changeset_info = self.get_changeset_info (self.rev) |
| | 180 | |
| | 181 | self.req.hdf.setValue('changeset.time', |
| | 182 | time.asctime (time.localtime(int(changeset_info['time'])))) |
| | 183 | author = changeset_info['author'] or 'anonymous' |
| | 184 | self.req.hdf.setValue('changeset.author', util.escape(author)) |
| | 185 | self.req.hdf.setValue('changeset.message', |
| | 186 | wiki_to_html(util.wiki_escape_newline(changeset_info['message']), |
| | 187 | self.req.hdf, self.env, self.db)) |
| | 188 | self.req.hdf.setValue('changeset.revision', str(self.rev)) |
| | 189 | util.add_dictlist_to_hdf(change_info, self.req.hdf, 'changeset.changes') |
| | 190 | self.req.hdf.setValue('anydiff.href', |
| | 191 | self.env.href.anydiff(self.args.get('path', '/'), self.args.get('a'), self.args.get('b'))) |
| | 192 | self.req.hdf.setValue('title', '[%d] (changeset)' % self.rev) |
| | 193 | |
| | 194 | def render_diffs(self, editor_class=HtmlDiffEditor): |
| | 195 | """ |
| | 196 | generates a unified diff of the changes for a given changeset. |
| | 197 | the output is written to stdout. |
| | 198 | """ |
| | 199 | try: |
| | 200 | old_root = svn.fs.revision_root(self.fs_ptr, int(self.args.get('a')), self.pool) |
| | 201 | new_root = svn.fs.revision_root(self.fs_ptr, int(self.args.get('b')), self.pool) |
| | 202 | except svn.core.SubversionException: |
| | 203 | raise util.TracError('Invalid revision number: %d' % int(self.rev)) |
| | 204 | |
| | 205 | editor = editor_class(old_root, new_root, int(self.rev), self.req, |
| | 206 | self.args, self.env, self.authzperm) |
| | 207 | e_ptr, e_baton = svn.delta.make_editor(editor, self.pool) |
| | 208 | |
| | 209 | def authz_cb(root, path, pool): return 1 |
| | 210 | path = self.args.get('path', '/')[1:] |
| | 211 | |
| | 212 | svn.repos.svn_repos_dir_delta(old_root, "", path, |
| | 213 | new_root, path, e_ptr, e_baton, authz_cb, |
| | 214 | 0, 1, 0, 1, self.pool) |
| | 215 | def display(self): |
| | 216 | """Pretty HTML view of the changeset""" |
| | 217 | self.render_diffs() |
| | 218 | Module.Module.display(self) |
| | 219 | |
| | 220 | def display_hdf(self): |
| | 221 | self.render_diffs() |
| | 222 | Module.Module.display_hdf(self) |
| | 223 | |
| | 224 | def display_diff (self): |
| | 225 | """Raw Unified Diff version""" |
| | 226 | self.req.send_response(200) |
| | 227 | self.req.send_header('Content-Type', 'text/plain;charset=utf-8') |
| | 228 | self.req.end_headers() |
| | 229 | self.render_diffs(UnifiedDiffEditor) |