~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/controllers/revision_ui.py

ok i should've known those API calls wouldn't be consistent.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
#
19
19
 
20
 
import re
21
 
from StringIO import StringIO
22
 
 
23
 
import bzrlib.diff
24
 
 
25
 
from paste.httpexceptions import HTTPServerError
 
20
import datetime
 
21
import logging
 
22
import os
 
23
import textwrap
 
24
import time
 
25
 
 
26
import turbogears
 
27
from cherrypy import InternalError, session
26
28
 
27
29
from loggerhead import util
28
 
from loggerhead.controllers import TemplatedBranchView
29
 
from loggerhead.history import rich_filename
30
 
 
31
 
 
32
 
DEFAULT_LINE_COUNT_LIMIT = 3000
33
 
 
34
 
def _process_side_by_side_buffers(line_list, delete_list, insert_list):
35
 
    while len(delete_list) < len(insert_list):
36
 
        delete_list.append((None, '', 'context'))
37
 
    while len(insert_list) < len(delete_list):
38
 
        insert_list.append((None, '', 'context'))
39
 
    while len(delete_list) > 0:
40
 
        d = delete_list.pop(0)
41
 
        i = insert_list.pop(0)
42
 
        line_list.append(util.Container(old_lineno=d[0], new_lineno=i[0],
43
 
                                        old_line=d[1], new_line=i[1],
44
 
                                        old_type=d[2], new_type=i[2]))
45
 
 
46
 
 
47
 
def _make_side_by_side(chunk_list):
48
 
    """
49
 
    turn a normal unified-style diff (post-processed by parse_delta) into a
50
 
    side-by-side diff structure.  the new structure is::
51
 
 
52
 
        chunks: list(
53
 
            diff: list(
54
 
                old_lineno: int,
55
 
                new_lineno: int,
56
 
                old_line: str,
57
 
                new_line: str,
58
 
                type: str('context' or 'changed'),
59
 
            )
60
 
        )
61
 
    """
62
 
    out_chunk_list = []
63
 
    for chunk in chunk_list:
64
 
        line_list = []
65
 
        wrap_char = '<wbr/>'
66
 
        delete_list, insert_list = [], []
67
 
        for line in chunk.diff:
68
 
            # Add <wbr/> every X characters so we can wrap properly
69
 
            wrap_line = re.findall(r'.{%d}|.+$' % 78, line.line)
70
 
            wrap_lines = [util.html_clean(_line) for _line in wrap_line]
71
 
            wrapped_line = wrap_char.join(wrap_lines)
72
 
 
73
 
            if line.type == 'context':
74
 
                if len(delete_list) or len(insert_list):
75
 
                    _process_side_by_side_buffers(line_list, delete_list,
76
 
                                                  insert_list)
77
 
                    delete_list, insert_list = [], []
78
 
                line_list.append(util.Container(old_lineno=line.old_lineno,
79
 
                                                new_lineno=line.new_lineno,
80
 
                                                old_line=wrapped_line,
81
 
                                                new_line=wrapped_line,
82
 
                                                old_type=line.type,
83
 
                                                new_type=line.type))
84
 
            elif line.type == 'delete':
85
 
                delete_list.append((line.old_lineno, wrapped_line, line.type))
86
 
            elif line.type == 'insert':
87
 
                insert_list.append((line.new_lineno, wrapped_line, line.type))
88
 
        if len(delete_list) or len(insert_list):
89
 
            _process_side_by_side_buffers(line_list, delete_list, insert_list)
90
 
        out_chunk_list.append(util.Container(diff=line_list))
91
 
    return out_chunk_list
92
 
 
93
 
class RevisionUI(TemplatedBranchView):
94
 
 
95
 
    template_path = 'loggerhead.templates.revision'
96
 
 
97
 
    def _process_diff(self, diff):
98
 
        # doesn't really need to be a method; could be static.
99
 
        chunks = []
100
 
        chunk = None
101
 
        for line in diff.splitlines():
102
 
            if len(line) == 0:
103
 
                continue
104
 
            if line.startswith('+++ ') or line.startswith('--- '):
105
 
                continue
106
 
            if line.startswith('@@ '):
107
 
                # new chunk
108
 
                if chunk is not None:
109
 
                    chunks.append(chunk)
110
 
                chunk = util.Container()
111
 
                chunk.diff = []
112
 
                split_lines = line.split(' ')[1:3]
113
 
                lines = [int(x.split(',')[0][1:]) for x in split_lines]
114
 
                old_lineno = lines[0]
115
 
                new_lineno = lines[1]
116
 
            elif line.startswith(' '):
117
 
                chunk.diff.append(util.Container(old_lineno=old_lineno,
118
 
                                                 new_lineno=new_lineno,
119
 
                                                 type='context',
120
 
                                                 line=line[1:]))
121
 
                old_lineno += 1
122
 
                new_lineno += 1
123
 
            elif line.startswith('+'):
124
 
                chunk.diff.append(util.Container(old_lineno=None,
125
 
                                                 new_lineno=new_lineno,
126
 
                                                 type='insert', line=line[1:]))
127
 
                new_lineno += 1
128
 
            elif line.startswith('-'):
129
 
                chunk.diff.append(util.Container(old_lineno=old_lineno,
130
 
                                                 new_lineno=None,
131
 
                                                 type='delete', line=line[1:]))
132
 
                old_lineno += 1
133
 
            else:
134
 
                chunk.diff.append(util.Container(old_lineno=None,
135
 
                                                 new_lineno=None,
136
 
                                                 type='unknown',
137
 
                                                 line=repr(line)))
138
 
        if chunk is not None:
139
 
            chunks.append(chunk)
140
 
        return chunks
141
 
 
142
 
    def _parse_diffs(self, old_tree, new_tree, delta):
143
 
        """
144
 
        Return a list of processed diffs, in the format::
145
 
 
146
 
            list(
147
 
                filename: str,
148
 
                file_id: str,
149
 
                chunks: list(
150
 
                    diff: list(
151
 
                        old_lineno: int,
152
 
                        new_lineno: int,
153
 
                        type: str('context', 'delete', or 'insert'),
154
 
                        line: str,
155
 
                    ),
156
 
                ),
157
 
            )
158
 
        """
159
 
        process = []
160
 
        out = []
161
 
 
162
 
        for old_path, new_path, fid, \
163
 
            kind, text_modified, meta_modified in delta.renamed:
164
 
            if text_modified:
165
 
                process.append((old_path, new_path, fid, kind))
166
 
        for path, fid, kind, text_modified, meta_modified in delta.modified:
167
 
            process.append((path, path, fid, kind))
168
 
 
169
 
        for old_path, new_path, fid, kind in process:
170
 
            old_lines = old_tree.get_file_lines(fid)
171
 
            new_lines = new_tree.get_file_lines(fid)
172
 
            buffer = StringIO()
173
 
            if old_lines != new_lines:
174
 
                try:
175
 
                    bzrlib.diff.internal_diff(old_path, old_lines,
176
 
                                              new_path, new_lines, buffer)
177
 
                except bzrlib.errors.BinaryFile:
178
 
                    diff = ''
179
 
                else:
180
 
                    diff = buffer.getvalue()
181
 
            else:
182
 
                diff = ''
183
 
            out.append(util.Container(
184
 
                          filename=rich_filename(new_path, kind),
185
 
                          file_id=fid,
186
 
                          chunks=self._process_diff(diff),
187
 
                          raw_diff=diff))
188
 
 
189
 
        return out
190
 
 
191
 
    def get_change_with_diff(self, revid, compare_revid):
192
 
        h = self._history
193
 
        change = h.get_changes([revid])[0]
194
 
 
195
 
        if compare_revid is None:
196
 
            if change.parents:
197
 
                compare_revid = change.parents[0].revid
198
 
            else:
199
 
                compare_revid = 'null:'
200
 
 
201
 
        rev_tree1 = h._branch.repository.revision_tree(compare_revid)
202
 
        rev_tree2 = h._branch.repository.revision_tree(revid)
203
 
        delta = rev_tree2.changes_from(rev_tree1)
204
 
 
205
 
        change.changes = h.parse_delta(delta)
206
 
        change.changes.modified = self._parse_diffs(rev_tree1,
207
 
                                                    rev_tree2,
208
 
                                                    delta)
209
 
 
210
 
        return change
211
 
 
212
 
    @staticmethod
213
 
    def add_side_by_side(changes):
214
 
        # FIXME: this is a rotten API.
215
 
        for change in changes:
216
 
            for m in change.changes.modified:
217
 
                m.sbs_chunks = _make_side_by_side(m.chunks)
218
 
 
219
 
    def get_values(self, path, kwargs, headers):
220
 
        h = self._history
221
 
        revid = self.get_revid()
222
 
 
223
 
        filter_file_id = kwargs.get('filter_file_id', None)
224
 
        start_revid = h.fix_revid(kwargs.get('start_revid', None))
225
 
        query = kwargs.get('q', None)
226
 
        remember = h.fix_revid(kwargs.get('remember', None))
227
 
        compare_revid = h.fix_revid(kwargs.get('compare_revid', None))
228
 
 
 
30
 
 
31
 
 
32
class RevisionUI (object):
 
33
 
 
34
    def __init__(self, branch):
 
35
        # BranchView object
 
36
        self._branch = branch
 
37
        self.log = branch.log
 
38
    
 
39
#    @util.lsprof
 
40
    @util.strip_whitespace
 
41
    @turbogears.expose(html='loggerhead.templates.revision')
 
42
    def default(self, *args, **kw):
 
43
        z = time.time()
 
44
        h = self._branch.get_history()
 
45
        util.set_context(kw)
 
46
        
 
47
        if len(args) > 0:
 
48
            revid = h.fix_revid(args[0])
 
49
        else:
 
50
            revid = None
 
51
        
 
52
        file_id = kw.get('file_id', None)
 
53
        start_revid = h.fix_revid(kw.get('start_revid', None))
 
54
        query = kw.get('q', None)
 
55
        remember = kw.get('remember', None)
 
56
        compare_revid = kw.get('compare_revid', None)
 
57
        
229
58
        try:
230
 
            revid, start_revid, revid_list = h.get_view(revid,
231
 
                                                        start_revid,
232
 
                                                        filter_file_id,
233
 
                                                        query)
 
59
            revid, start_revid, revid_list = h.get_view(revid, start_revid, file_id, query)
234
60
        except:
235
61
            self.log.exception('Exception fetching changes')
236
 
            raise HTTPServerError('Could not fetch changes')
237
 
 
238
 
        navigation = util.Container(
239
 
            revid_list=revid_list, revid=revid, start_revid=start_revid,
240
 
            filter_file_id=filter_file_id, pagesize=1,
241
 
            scan_url='/revision', branch=self._branch, feed=True, history=h)
 
62
            raise InternalError('Could not fetch changes')
 
63
        
 
64
        navigation = util.Container(revid_list=revid_list, revid=revid, start_revid=start_revid, file_id=file_id,
 
65
                                    pagesize=1, scan_url='/revision', branch=self._branch, feed=True)
242
66
        if query is not None:
243
67
            navigation.query = query
244
 
        util.fill_in_navigation(navigation)
 
68
        util.fill_in_navigation(h, navigation)
245
69
 
246
 
        change = self.get_change_with_diff(revid, compare_revid)
 
70
        if compare_revid is not None:
 
71
            change = h.get_diff(compare_revid, revid)
 
72
        else:
 
73
            change = h.get_changes([ revid ], get_diffs=True)[0]
247
74
        # add parent & merge-point branch-nick info, in case it's useful
248
 
        h.get_branch_nicks([change])
249
 
 
250
 
        line_count_limit = DEFAULT_LINE_COUNT_LIMIT
251
 
        line_count = 0
252
 
        for file in change.changes.modified:
253
 
            for chunk in file.chunks:
254
 
                line_count += len(chunk.diff)
 
75
        h.get_branch_nicks([ change ])
255
76
 
256
77
        # let's make side-by-side diff be the default
257
 
        # FIXME: not currently in use. Should be
258
 
        side_by_side = not kwargs.get('unified', False)
 
78
        side_by_side = not kw.get('unified', False)
259
79
        if side_by_side:
260
 
            self.add_side_by_side([change])
261
 
 
262
 
        # Directory Breadcrumbs
263
 
        directory_breadcrumbs = (
264
 
            util.directory_breadcrumbs(
265
 
                self._branch.friendly_name,
266
 
                self._branch.is_root,
267
 
                'changes'))
268
 
 
269
 
        return {
 
80
            h.add_side_by_side([ change ])
 
81
        
 
82
        vals = {
270
83
            'branch': self._branch,
271
84
            'revid': revid,
272
85
            'change': change,
273
86
            'start_revid': start_revid,
274
 
            'filter_file_id': filter_file_id,
 
87
            'file_id': file_id,
275
88
            'util': util,
276
89
            'history': h,
277
90
            'navigation': navigation,
279
92
            'remember': remember,
280
93
            'compare_revid': compare_revid,
281
94
            'side_by_side': side_by_side,
282
 
            'url': self._branch.context_url,
283
 
            'line_count': line_count,
284
 
            'line_count_limit': line_count_limit,
285
 
            'show_plain_diffs': line_count > line_count_limit,
286
 
            'directory_breadcrumbs': directory_breadcrumbs,
287
95
        }
 
96
        h.flush_cache()
 
97
        self.log.info('/revision: %r seconds' % (time.time() - z,))
 
98
        return vals