108
91
out_chunk_list = []
109
92
for chunk in chunk_list:
111
95
delete_list, insert_list = [], []
112
96
for line in chunk.diff:
97
# Add <wbr/> every X characters so we can wrap properly
98
wrap_line = re.findall(r'.{%d}|.+$' % 78, line.line)
99
wrap_lines = [util.html_clean(_line) for _line in wrap_line]
100
wrapped_line = wrap_char.join(wrap_lines)
113
102
if line.type == 'context':
114
103
if len(delete_list) or len(insert_list):
115
_process_side_by_side_buffers(line_list, delete_list, insert_list)
104
_process_side_by_side_buffers(line_list, delete_list,
116
106
delete_list, insert_list = [], []
117
line_list.append(util.Container(old_lineno=line.old_lineno, new_lineno=line.new_lineno,
118
old_line=line.line, new_line=line.line,
119
old_type=line.type, new_type=line.type))
107
line_list.append(util.Container(old_lineno=line.old_lineno,
108
new_lineno=line.new_lineno,
109
old_line=wrapped_line,
110
new_line=wrapped_line,
120
113
elif line.type == 'delete':
121
delete_list.append((line.old_lineno, line.line, line.type))
114
delete_list.append((line.old_lineno, wrapped_line, line.type))
122
115
elif line.type == 'insert':
123
insert_list.append((line.new_lineno, line.line, line.type))
116
insert_list.append((line.new_lineno, wrapped_line, line.type))
124
117
if len(delete_list) or len(insert_list):
125
118
_process_side_by_side_buffers(line_list, delete_list, insert_list)
126
119
out_chunk_list.append(util.Container(diff=line_list))
191
184
class History (object):
194
self._change_cache = None
185
"""Decorate a branch to provide information for rendering.
187
History objects are expected to be short lived -- when serving a request
188
for a particular branch, open it, read-lock it, wrap a History object
189
around it, serve the request, throw the History object away, unlock the
190
branch and throw it away.
192
:ivar _file_change_cache: xx
195
def __init__(self, branch, whole_history_data_cache):
196
assert branch.is_locked(), (
197
"Can only construct a History object with a read-locked branch.")
195
198
self._file_change_cache = None
197
self._lock = threading.RLock()
200
def from_branch(cls, branch, name=None):
203
199
self._branch = branch
204
self._last_revid = self._branch.last_revision()
207
name = self._branch.nick
209
self.log = logging.getLogger('loggerhead.%s' % (name,))
211
graph = branch.repository.get_graph()
212
parent_map = dict(((key, value) for key, value in
213
graph.iter_ancestry([self._last_revid]) if value is not None))
215
self._revision_graph = self._strip_NULL_ghosts(parent_map)
216
self._full_history = []
217
self._revision_info = {}
218
self._revno_revid = {}
219
if bzrlib.revision.is_null(self._last_revid):
220
self._merge_sort = []
222
self._merge_sort = bzrlib.tsort.merge_sort(
223
self._revision_graph, self._last_revid, generate_revno=True)
225
for (seq, revid, merge_depth, revno, end_of_merge) in self._merge_sort:
226
self._full_history.append(revid)
227
revno_str = '.'.join(str(n) for n in revno)
228
self._revno_revid[revno_str] = revid
229
self._revision_info[revid] = (
230
seq, revid, merge_depth, revno_str, end_of_merge)
233
self._where_merged = {}
235
for revid in self._revision_graph.keys():
236
if self._revision_info[revid][2] == 0:
238
for parent in self._revision_graph[revid]:
239
self._where_merged.setdefault(parent, set()).add(revid)
241
self.log.info('built revision graph cache: %r secs' % (time.time() - z,))
245
def _strip_NULL_ghosts(revision_graph):
247
Copied over from bzrlib meant as a temporary workaround deprecated
251
# Filter ghosts, and null:
252
if bzrlib.revision.NULL_REVISION in revision_graph:
253
del revision_graph[bzrlib.revision.NULL_REVISION]
254
for key, parents in revision_graph.items():
255
revision_graph[key] = tuple(parent for parent in parents if parent
257
return revision_graph
260
def from_folder(cls, path, name=None):
261
b = bzrlib.branch.Branch.open(path)
264
return cls.from_branch(b, name)
269
def out_of_date(self):
270
# the branch may have been upgraded on disk, in which case we're stale.
271
newly_opened = bzrlib.branch.Branch.open(self._branch.base)
272
if self._branch.__class__ is not \
273
newly_opened.__class__:
275
if self._branch.repository.__class__ is not \
276
newly_opened.repository.__class__:
278
return self._branch.last_revision() != self._last_revid
280
def use_cache(self, cache):
281
self._change_cache = cache
200
self.log = logging.getLogger('loggerhead.%s' % (branch.nick,))
202
self.last_revid = branch.last_revision()
204
whole_history_data = whole_history_data_cache.get(self.last_revid)
205
if whole_history_data is None:
206
whole_history_data = compute_whole_history_data(branch)
207
whole_history_data_cache[self.last_revid] = whole_history_data
209
(self._revision_graph, self._full_history, self._revision_info,
210
self._revno_revid, self._merge_sort, self._where_merged
211
) = whole_history_data
283
213
def use_file_cache(self, cache):
284
214
self._file_change_cache = cache
286
def use_search_index(self, index):
290
217
def has_revisions(self):
291
218
return not bzrlib.revision.is_null(self.last_revid)
295
# called when a new history object needs to be created, because the
296
# branch history has changed. we need to immediately close and stop
297
# using our caches, because a new history object will be created to
298
# replace us, using the same cache files.
299
# (may also be called during server shutdown.)
300
if self._change_cache is not None:
301
self._change_cache.close()
302
self._change_cache = None
303
if self._index is not None:
307
def flush_cache(self):
308
if self._change_cache is None:
310
self._change_cache.flush()
312
def check_rebuild(self):
313
if self._change_cache is not None:
314
self._change_cache.check_rebuild()
315
#if self._index is not None:
316
# self._index.check_rebuild()
318
last_revid = property(lambda self: self._last_revid, None, None)
321
220
def get_config(self):
322
221
return self._branch.get_config()
325
223
def get_revno(self, revid):
326
224
if revid not in self._revision_info:
360
255
revid = parents[0]
363
257
def get_short_revision_history_by_fileid(self, file_id):
364
258
# wow. is this really the only way we can get this list? by
365
259
# man-handling the weave store directly? :-0
366
260
# FIXME: would be awesome if we could get, for a folder, the list of
367
261
# revisions where items within that folder changed.
368
w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
369
w_revids = w.versions()
370
revids = [r for r in self._full_history if r in w_revids]
262
possible_keys = [(file_id, revid) for revid in self._full_history]
263
existing_keys = self._branch.repository.texts.get_parent_map(possible_keys)
264
return [revid for _, revid in existing_keys.iterkeys()]
374
266
def get_revision_history_since(self, revid_list, date):
375
267
# if a user asks for revisions starting at 01-sep, they mean inclusive,
376
268
# so start at midnight on 02-sep.
385
277
return revid_list[index:]
388
def get_revision_history_matching(self, revid_list, text):
389
self.log.debug('searching %d revisions for %r', len(revid_list), text)
391
# this is going to be painfully slow. :(
394
for revid in revid_list:
395
change = self.get_changes([ revid ])[0]
396
if text in change.comment.lower():
398
self.log.debug('searched %d revisions for %r in %r secs', len(revid_list), text, time.time() - z)
401
def get_revision_history_matching_indexed(self, revid_list, text):
402
self.log.debug('searching %d revisions for %r', len(revid_list), text)
404
if self._index is None:
405
return self.get_revision_history_matching(revid_list, text)
406
out = self._index.find(text, revid_list)
407
self.log.debug('searched %d revisions for %r in %r secs: %d results', len(revid_list), text, time.time() - z, len(out))
408
# put them in some coherent order :)
409
out = [r for r in self._full_history if r in out]
413
279
def get_search_revid_list(self, query, revid_list):
415
281
given a "quick-search" query, try a few obvious possible meanings:
677
# alright, let's profile this sucka. (FIXME remove this eventually...)
678
def _get_changes_profiled(self, revid_list):
679
from loggerhead.lsprof import profile
681
ret, stats = profile(self.get_changes_uncached, revid_list)
684
cPickle.dump(stats, open('lsprof.stats', 'w'), 2)
685
self.log.info('lsprof complete!')
689
@with_bzrlib_read_lock
690
526
def get_changes_uncached(self, revid_list):
691
527
# FIXME: deprecated method in getting a null revision
692
528
revid_list = filter(lambda revid: not bzrlib.revision.is_null(revid),
694
repo = self._branch.repository
695
parent_map = repo.get_graph().get_parent_map(revid_list)
530
parent_map = self._branch.repository.get_graph().get_parent_map(revid_list)
696
531
# We need to return the answer in the same order as the input,
697
532
# less any ghosts.
698
533
present_revids = [revid for revid in revid_list
699
534
if revid in parent_map]
700
rev_list = repo.get_revisions(present_revids)
535
rev_list = self._branch.repository.get_revisions(present_revids)
702
537
return [self._change_from_revision(rev) for rev in rev_list]
864
696
old_lineno = lines[0]
865
697
new_lineno = lines[1]
866
698
elif line.startswith(' '):
867
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
868
type='context', line=util.fixed_width(line[1:])))
699
chunk.diff.append(util.Container(old_lineno=old_lineno,
700
new_lineno=new_lineno,
871
705
elif line.startswith('+'):
872
chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
873
type='insert', line=util.fixed_width(line[1:])))
706
chunk.diff.append(util.Container(old_lineno=None,
707
new_lineno=new_lineno,
708
type='insert', line=line[1:]))
875
710
elif line.startswith('-'):
876
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
877
type='delete', line=util.fixed_width(line[1:])))
711
chunk.diff.append(util.Container(old_lineno=old_lineno,
713
type='delete', line=line[1:]))
880
chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
881
type='unknown', line=util.fixed_width(repr(line))))
716
chunk.diff.append(util.Container(old_lineno=None,
882
720
if chunk is not None:
883
721
chunks.append(chunk)