17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from StringIO import StringIO
25
from paste.httpexceptions import HTTPServerError
27
from cherrypy import InternalError, session
27
29
from loggerhead import util
28
from loggerhead.controllers import TemplatedBranchView
29
from loggerhead.history import rich_filename
32
DEFAULT_LINE_COUNT_LIMIT = 3000
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]))
47
def _make_side_by_side(chunk_list):
49
turn a normal unified-style diff (post-processed by parse_delta) into a
50
side-by-side diff structure. the new structure is::
58
type: str('context' or 'changed'),
63
for chunk in chunk_list:
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)
73
if line.type == 'context':
74
if len(delete_list) or len(insert_list):
75
_process_side_by_side_buffers(line_list, delete_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,
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))
93
class RevisionUI(TemplatedBranchView):
95
template_path = 'loggerhead.templates.revision'
97
def _process_diff(self, diff):
98
# doesn't really need to be a method; could be static.
101
for line in diff.splitlines():
104
if line.startswith('+++ ') or line.startswith('--- '):
106
if line.startswith('@@ '):
108
if chunk is not None:
110
chunk = util.Container()
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,
123
elif line.startswith('+'):
124
chunk.diff.append(util.Container(old_lineno=None,
125
new_lineno=new_lineno,
126
type='insert', line=line[1:]))
128
elif line.startswith('-'):
129
chunk.diff.append(util.Container(old_lineno=old_lineno,
131
type='delete', line=line[1:]))
134
chunk.diff.append(util.Container(old_lineno=None,
138
if chunk is not None:
142
def _parse_diffs(self, old_tree, new_tree, delta):
144
Return a list of processed diffs, in the format::
153
type: str('context', 'delete', or 'insert'),
162
for old_path, new_path, fid, \
163
kind, text_modified, meta_modified in delta.renamed:
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))
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)
173
if old_lines != new_lines:
175
bzrlib.diff.internal_diff(old_path, old_lines,
176
new_path, new_lines, buffer)
177
except bzrlib.errors.BinaryFile:
180
diff = buffer.getvalue()
183
out.append(util.Container(
184
filename=rich_filename(new_path, kind),
186
chunks=self._process_diff(diff),
191
def get_change_with_diff(self, revid, compare_revid):
193
change = h.get_changes([revid])[0]
195
if compare_revid is None:
197
compare_revid = change.parents[0].revid
199
compare_revid = 'null:'
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)
205
change.changes = h.parse_delta(delta)
206
change.changes.modified = self._parse_diffs(rev_tree1,
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)
219
def get_values(self, path, kwargs, headers):
221
revid = self.get_revid()
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))
32
class RevisionUI (object):
34
def __init__(self, branch):
40
@util.strip_whitespace
41
@turbogears.expose(html='loggerhead.templates.revision')
42
def default(self, *args, **kw):
44
h = self._branch.get_history()
48
revid = h.fix_revid(args[0])
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)
230
revid, start_revid, revid_list = h.get_view(revid,
59
revid, start_revid, revid_list = h.get_view(revid, start_revid, file_id, query)
235
61
self.log.exception('Exception fetching changes')
236
raise HTTPServerError('Could not fetch changes')
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')
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)
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)
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])
250
line_count_limit = DEFAULT_LINE_COUNT_LIMIT
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 ])
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)
260
self.add_side_by_side([change])
262
# Directory Breadcrumbs
263
directory_breadcrumbs = (
264
util.directory_breadcrumbs(
265
self._branch.friendly_name,
266
self._branch.is_root,
80
h.add_side_by_side([ change ])
270
83
'branch': self._branch,
273
86
'start_revid': start_revid,
274
'filter_file_id': filter_file_id,
277
90
'navigation': navigation,