180
174
class History (object):
183
self._change_cache = None
185
self._lock = threading.RLock()
188
def from_branch(cls, branch, name=None):
175
"""Decorate a branch to provide information for rendering.
177
History objects are expected to be short lived -- when serving a request
178
for a particular branch, open it, read-lock it, wrap a History object
179
around it, serve the request, throw the History object away, unlock the
180
branch and throw it away.
182
:ivar _file_change_cache: xx
185
def __init__(self, branch, whole_history_data_cache):
186
assert branch.is_locked(), (
187
"Can only construct a History object with a read-locked branch.")
188
self._file_change_cache = None
191
189
self._branch = branch
192
self._history = branch.revision_history()
193
self._last_revid = self._history[-1]
194
self._revision_graph = branch.repository.get_revision_graph(self._last_revid)
197
name = self._branch.nick
199
self.log = logging.getLogger('loggerhead.%s' % (name,))
201
self._full_history = []
202
self._revision_info = {}
203
self._revno_revid = {}
204
self._merge_sort = bzrlib.tsort.merge_sort(self._revision_graph, self._last_revid, generate_revno=True)
206
for (seq, revid, merge_depth, revno, end_of_merge) in self._merge_sort:
207
self._full_history.append(revid)
208
revno_str = '.'.join(str(n) for n in revno)
209
self._revno_revid[revno_str] = revid
210
self._revision_info[revid] = (seq, revid, merge_depth, revno_str, end_of_merge)
215
self._where_merged = {}
216
for revid in self._revision_graph.keys():
217
if not revid in self._full_history:
219
for parent in self._revision_graph[revid]:
220
self._where_merged.setdefault(parent, set()).add(revid)
222
self.log.info('built revision graph cache: %r secs' % (time.time() - z,))
226
def from_folder(cls, path, name=None):
227
b = bzrlib.branch.Branch.open(path)
228
return cls.from_branch(b, name)
231
def out_of_date(self):
232
if self._branch.revision_history()[-1] != self._last_revid:
236
def use_cache(self, cache):
237
self._change_cache = cache
239
def use_search_index(self, index):
244
# called when a new history object needs to be created, because the
245
# branch history has changed. we need to immediately close and stop
246
# using our caches, because a new history object will be created to
247
# replace us, using the same cache files.
248
# (may also be called during server shutdown.)
249
if self._change_cache is not None:
250
self._change_cache.close()
251
self._change_cache = None
252
if self._index is not None:
256
def flush_cache(self):
257
if self._change_cache is None:
259
self._change_cache.flush()
261
def check_rebuild(self):
262
if self._change_cache is not None:
263
self._change_cache.check_rebuild()
264
if self._index is not None:
265
self._index.check_rebuild()
267
last_revid = property(lambda self: self._last_revid, None, None)
269
count = property(lambda self: self._count, None, None)
190
self.log = logging.getLogger('loggerhead.%s' % (branch.nick,))
192
self.last_revid = branch.last_revision()
194
whole_history_data = whole_history_data_cache.get(self.last_revid)
195
if whole_history_data is None:
196
whole_history_data = compute_whole_history_data(branch)
197
whole_history_data_cache[self.last_revid] = whole_history_data
199
(self._revision_graph, self._full_history, self._revision_info,
200
self._revno_revid, self._merge_sort, self._where_merged
201
) = whole_history_data
203
def use_file_cache(self, cache):
204
self._file_change_cache = cache
207
def has_revisions(self):
208
return not bzrlib.revision.is_null(self.last_revid)
272
210
def get_config(self):
273
211
return self._branch.get_config()
276
def get_revision(self, revid):
277
return self._branch.repository.get_revision(revid)
279
213
def get_revno(self, revid):
280
214
if revid not in self._revision_info:
283
217
seq, revid, merge_depth, revno_str, end_of_merge = self._revision_info[revid]
286
def get_sequence(self, revid):
287
seq, revid, merge_depth, revno_str, end_of_merge = self._revision_info[revid]
290
def get_revision_history(self):
291
return self._full_history
293
def get_revid_sequence(self, revid_list, revid):
295
given a list of revision ids, return the sequence # of this revid in
304
def get_revids_from(self, revid_list, revid):
306
given a list of revision ids, yield revisions in graph order,
307
starting from revid. the list can be None if you just want to travel
308
across all revisions.
311
if (revid_list is None) or (revid in revid_list):
220
def get_revids_from(self, revid_list, start_revid):
222
Yield the mainline (wrt start_revid) revisions that merged each
225
if revid_list is None:
226
revid_list = self._full_history
227
revid_set = set(revid_list)
229
def introduced_revisions(revid):
231
seq, revid, md, revno, end_of_merge = self._revision_info[revid]
233
while i < len(self._merge_sort) and self._merge_sort[i][2] > md:
234
r.add(self._merge_sort[i][1])
238
if bzrlib.revision.is_null(revid):
240
if introduced_revisions(revid) & revid_set:
313
if not self._revision_graph.has_key(revid):
315
242
parents = self._revision_graph[revid]
316
243
if len(parents) == 0:
318
245
revid = parents[0]
321
247
def get_short_revision_history_by_fileid(self, file_id):
322
248
# wow. is this really the only way we can get this list? by
323
249
# man-handling the weave store directly? :-0
324
250
# FIXME: would be awesome if we could get, for a folder, the list of
325
251
# revisions where items within that folder changed.
326
w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
327
w_revids = w.versions()
328
revids = [r for r in self._full_history if r in w_revids]
252
possible_keys = [(file_id, revid) for revid in self._full_history]
253
existing_keys = self._branch.repository.texts.get_parent_map(possible_keys)
254
return [revid for _, revid in existing_keys.iterkeys()]
332
256
def get_revision_history_since(self, revid_list, date):
333
257
# if a user asks for revisions starting at 01-sep, they mean inclusive,
334
258
# so start at midnight on 02-sep.
341
265
revid_list.reverse()
343
267
return revid_list[index:]
346
def get_revision_history_matching(self, revid_list, text):
347
self.log.debug('searching %d revisions for %r', len(revid_list), text)
349
# this is going to be painfully slow. :(
352
for revid in revid_list:
353
change = self.get_changes([ revid ])[0]
354
if text in change.comment.lower():
356
self.log.debug('searched %d revisions for %r in %r secs', len(revid_list), text, time.time() - z)
359
def get_revision_history_matching_indexed(self, revid_list, text):
360
self.log.debug('searching %d revisions for %r', len(revid_list), text)
362
if self._index is None:
363
return self.get_revision_history_matching(revid_list, text)
364
out = self._index.find(text, revid_list)
365
self.log.debug('searched %d revisions for %r in %r secs: %d results', len(revid_list), text, time.time() - z, len(out))
366
# put them in some coherent order :)
367
out = [r for r in self._full_history if r in out]
371
269
def get_search_revid_list(self, query, revid_list):
373
271
given a "quick-search" query, try a few obvious possible meanings:
375
273
- revision id or # ("128.1.3")
376
274
- date (US style "mm/dd/yy", earth style "dd-mm-yy", or iso style "yyyy-mm-dd")
377
275
- comment text as a fallback
425
320
if revid is None:
427
322
if revid == 'head:':
428
return self._last_revid
323
return self.last_revid
429
324
if self.revno_re.match(revid):
430
325
revid = self._revno_revid[revid]
434
328
def get_file_view(self, revid, file_id):
436
Given an optional revid and optional path, return a (revlist, revid)
437
for navigation through the current scope: from the revid (or the
438
latest revision) back to the original revision.
330
Given a revid and optional path, return a (revlist, revid) for
331
navigation through the current scope: from the revid (or the latest
332
revision) back to the original revision.
440
334
If file_id is None, the entire revision history is the list scope.
441
If revid is None, the latest revision is used.
443
336
if revid is None:
444
revid = self._last_revid
337
revid = self.last_revid
445
338
if file_id is not None:
446
# since revid is 'start_revid', possibly should start the path tracing from revid... FIXME
447
inv = self._branch.repository.get_revision_inventory(revid)
339
# since revid is 'start_revid', possibly should start the path
340
# tracing from revid... FIXME
448
341
revlist = list(self.get_short_revision_history_by_fileid(file_id))
449
342
revlist = list(self.get_revids_from(revlist, revid))
451
344
revlist = list(self.get_revids_from(None, revid))
454
return revlist, revid
457
347
def get_view(self, revid, start_revid, file_id, query=None):
459
349
use the URL parameters (revid, start_revid, file_id, and query) to
460
350
determine the revision list we're viewing (start_revid, file_id, query)
461
351
and where we are in it (revid).
463
if a query is given, we're viewing query results.
464
if a file_id is given, we're viewing revisions for a specific file.
465
if a start_revid is given, we're viewing the branch from a
466
specific revision up the tree.
467
(these may be combined to view revisions for a specific file, from
468
a specific revision, with a specific search query.)
470
returns a new (revid, start_revid, revid_list, scan_list) where:
353
- if a query is given, we're viewing query results.
354
- if a file_id is given, we're viewing revisions for a specific
356
- if a start_revid is given, we're viewing the branch from a
357
specific revision up the tree.
359
these may be combined to view revisions for a specific file, from
360
a specific revision, with a specific search query.
362
returns a new (revid, start_revid, revid_list) where:
472
364
- revid: current position within the view
473
365
- start_revid: starting revision of this view
474
366
- revid_list: list of revision ids for this view
476
368
file_id and query are never changed so aren't returned, but they may
477
369
contain vital context for future url navigation.
371
if start_revid is None:
372
start_revid = self.last_revid
479
374
if query is None:
480
revid_list, start_revid = self.get_file_view(start_revid, file_id)
375
revid_list = self.get_file_view(start_revid, file_id)
481
376
if revid is None:
482
377
revid = start_revid
483
378
if revid not in revid_list:
484
379
# if the given revid is not in the revlist, use a revlist that
485
380
# starts at the given revid.
486
revid_list, start_revid = self.get_file_view(revid, file_id)
381
revid_list = self.get_file_view(revid, file_id)
487
383
return revid, start_revid, revid_list
489
385
# potentially limit the search
490
if (start_revid is not None) or (file_id is not None):
491
revid_list, start_revid = self.get_file_view(start_revid, file_id)
386
if file_id is not None:
387
revid_list = self.get_file_view(start_revid, file_id)
493
389
revid_list = None
495
revid_list = self.get_search_revid_list(query, revid_list)
496
if len(revid_list) > 0:
390
revid_list = search.search_revisions(self._branch, query)
391
if revid_list and len(revid_list) > 0:
497
392
if revid not in revid_list:
498
393
revid = revid_list[0]
499
394
return revid, start_revid, revid_list
396
# XXX: This should return a message saying that the search could
397
# not be completed due to either missing the plugin or missing a
502
399
return None, None, []
505
401
def get_inventory(self, revid):
506
402
return self._branch.repository.get_revision_inventory(revid)
509
404
def get_path(self, revid, file_id):
510
405
if (file_id is None) or (file_id == ''):
598
486
p.branch_nick = p_change_dict[p.revid].branch_nick
600
488
p.branch_nick = '(missing)'
603
def get_changes(self, revid_list, get_diffs=False):
604
if self._change_cache is None:
605
changes = self.get_changes_uncached(revid_list, get_diffs)
607
changes = self._change_cache.get_changes(revid_list, get_diffs)
490
def get_changes(self, revid_list):
491
"""Return a list of changes objects for the given revids.
493
Revisions not present and NULL_REVISION will be ignored.
495
changes = self.get_changes_uncached(revid_list)
496
if len(changes) == 0:
611
499
# some data needs to be recalculated each time, because it may
612
500
# change as new revisions are added.
613
for i in xrange(len(revid_list)):
614
revid = revid_list[i]
616
merge_revids = self.simplify_merge_point_list(self.get_merge_point_list(revid))
501
for change in changes:
502
merge_revids = self.simplify_merge_point_list(self.get_merge_point_list(change.revid))
617
503
change.merge_points = [util.Container(revid=r, revno=self.get_revno(r)) for r in merge_revids]
504
if len(change.parents) > 0:
505
change.parents = [util.Container(revid=r,
506
revno=self.get_revno(r)) for r in change.parents]
507
change.revno = self.get_revno(change.revid)
510
for change in changes:
511
change.parity = parity
621
# alright, let's profile this sucka.
622
def _get_changes_profiled(self, revid_list, get_diffs=False):
623
from loggerhead.lsprof import profile
625
ret, stats = profile(self.get_changes_uncached, revid_list, get_diffs)
628
cPickle.dump(stats, open('lsprof.stats', 'w'), 2)
629
self.log.info('lsprof complete!')
516
def get_changes_uncached(self, revid_list):
517
# FIXME: deprecated method in getting a null revision
518
revid_list = filter(lambda revid: not bzrlib.revision.is_null(revid),
520
parent_map = self._branch.repository.get_graph().get_parent_map(revid_list)
521
# We need to return the answer in the same order as the input,
523
present_revids = [revid for revid in revid_list
524
if revid in parent_map]
525
rev_list = self._branch.repository.get_revisions(present_revids)
527
return [self._change_from_revision(rev) for rev in rev_list]
632
529
def _get_deltas_for_revisions_with_trees(self, revisions):
633
"""Produce a generator of revision deltas.
530
"""Produce a list of revision deltas.
635
532
Note that the input is a sequence of REVISIONS, not revision_ids.
636
533
Trees will be held in memory until the generator exits.
637
534
Each delta is relative to the revision's lefthand predecessor.
535
(This is copied from bzrlib.)
639
537
required_trees = set()
640
538
for revision in revisions:
641
required_trees.add(revision.revision_id)
642
required_trees.update(revision.parent_ids[:1])
643
trees = dict((t.get_revision_id(), t) for
539
required_trees.add(revision.revid)
540
required_trees.update([p.revid for p in revision.parents[:1]])
541
trees = dict((t.get_revision_id(), t) for
644
542
t in self._branch.repository.revision_trees(required_trees))
646
544
self._branch.repository.lock_read()
648
546
for revision in revisions:
649
if not revision.parent_ids:
650
old_tree = self._branch.repository.revision_tree(None)
547
if not revision.parents:
548
old_tree = self._branch.repository.revision_tree(
549
bzrlib.revision.NULL_REVISION)
652
old_tree = trees[revision.parent_ids[0]]
653
tree = trees[revision.revision_id]
654
ret.append((tree, old_tree, tree.changes_from(old_tree)))
551
old_tree = trees[revision.parents[0].revid]
552
tree = trees[revision.revid]
553
ret.append(tree.changes_from(old_tree))
657
556
self._branch.repository.unlock()
659
def entry_from_revision(self, revision):
558
def _change_from_revision(self, revision):
560
Given a bzrlib Revision, return a processed "change" for use in
660
563
commit_time = datetime.datetime.fromtimestamp(revision.timestamp)
662
565
parents = [util.Container(revid=r, revno=self.get_revno(r)) for r in revision.parent_ids]
664
if len(parents) == 0:
667
left_parent = revision.parent_ids[0]
669
567
message, short_message = clean_message(revision.message)
672
570
'revid': revision.revision_id,
673
'revno': self.get_revno(revision.revision_id),
674
571
'date': commit_time,
675
'author': revision.committer,
572
'author': revision.get_apparent_author(),
676
573
'branch_nick': revision.properties.get('branch-nick', None),
677
574
'short_comment': short_message,
678
575
'comment': revision.message,
679
576
'comment_clean': [util.html_clean(s) for s in message],
577
'parents': revision.parent_ids,
682
579
return util.Container(entry)
685
@with_bzrlib_read_lock
686
def get_changes_uncached(self, revid_list, get_diffs=False):
690
rev_list = self._branch.repository.get_revisions(revid_list)
692
except (KeyError, bzrlib.errors.NoSuchRevision), e:
693
# this sometimes happens with arch-converted branches.
694
# i don't know why. :(
695
self.log.debug('No such revision (skipping): %s', e)
696
revid_list.remove(e.revision)
698
delta_list = self._get_deltas_for_revisions_with_trees(rev_list)
699
combined_list = zip(rev_list, delta_list)
702
for rev, (new_tree, old_tree, delta) in combined_list:
703
entry = self.entry_from_revision(rev)
704
entry.changes = self.parse_delta(delta, get_diffs, old_tree, new_tree)
705
entries.append(entry)
709
@with_bzrlib_read_lock
710
def _get_diff(self, revid1, revid2):
711
rev_tree1 = self._branch.repository.revision_tree(revid1)
712
rev_tree2 = self._branch.repository.revision_tree(revid2)
581
def get_file_changes_uncached(self, entries):
582
delta_list = self._get_deltas_for_revisions_with_trees(entries)
584
return [self.parse_delta(delta) for delta in delta_list]
586
def get_file_changes(self, entries):
587
if self._file_change_cache is None:
588
return self.get_file_changes_uncached(entries)
590
return self._file_change_cache.get_file_changes(entries)
592
def add_changes(self, entries):
593
changes_list = self.get_file_changes(entries)
595
for entry, changes in zip(entries, changes_list):
596
entry.changes = changes
598
def get_change_with_diff(self, revid, compare_revid=None):
599
change = self.get_changes([revid])[0]
601
if compare_revid is None:
603
compare_revid = change.parents[0].revid
605
compare_revid = 'null:'
607
rev_tree1 = self._branch.repository.revision_tree(compare_revid)
608
rev_tree2 = self._branch.repository.revision_tree(revid)
713
609
delta = rev_tree2.changes_from(rev_tree1)
714
return rev_tree1, rev_tree2, delta
716
def get_diff(self, revid1, revid2):
717
rev_tree1, rev_tree2, delta = self._get_diff(revid1, revid2)
718
entry = self.get_changes([ revid2 ], False)[0]
719
entry.changes = self.parse_delta(delta, True, rev_tree1, rev_tree2)
611
change.changes = self.parse_delta(delta)
612
change.changes.modified = self._parse_diffs(rev_tree1, rev_tree2, delta)
723
616
def get_file(self, file_id, revid):
724
617
"returns (path, filename, data)"
725
618
inv = self.get_inventory(revid)
754
if C{get_diffs} is false, the C{chunks} will be omitted.
646
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
648
process.append((old_path, new_path, fid, kind))
649
for path, fid, kind, text_modified, meta_modified in delta.modified:
650
process.append((path, path, fid, kind))
652
for old_path, new_path, fid, kind in process:
653
old_lines = old_tree.get_file_lines(fid)
654
new_lines = new_tree.get_file_lines(fid)
656
if old_lines != new_lines:
658
bzrlib.diff.internal_diff(old_path, old_lines,
659
new_path, new_lines, buffer)
660
except bzrlib.errors.BinaryFile:
663
diff = buffer.getvalue()
666
out.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid, chunks=self._process_diff(diff), raw_diff=diff))
670
def _process_diff(self, diff):
671
# doesn't really need to be a method; could be static.
674
for line in diff.splitlines():
677
if line.startswith('+++ ') or line.startswith('--- '):
679
if line.startswith('@@ '):
681
if chunk is not None:
683
chunk = util.Container()
685
lines = [int(x.split(',')[0][1:]) for x in line.split(' ')[1:3]]
686
old_lineno = lines[0]
687
new_lineno = lines[1]
688
elif line.startswith(' '):
689
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
690
type='context', line=util.fixed_width(line[1:])))
693
elif line.startswith('+'):
694
chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
695
type='insert', line=util.fixed_width(line[1:])))
697
elif line.startswith('-'):
698
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
699
type='delete', line=util.fixed_width(line[1:])))
702
chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
703
type='unknown', line=util.fixed_width(repr(line))))
704
if chunk is not None:
708
def parse_delta(self, delta):
710
Return a nested data structure containing the changes in a delta::
712
added: list((filename, file_id)),
713
renamed: list((old_filename, new_filename, file_id)),
714
deleted: list((filename, file_id)),
761
def rich_filename(path, kind):
762
if kind == 'directory':
764
if kind == 'symlink':
768
def process_diff(diff):
771
for line in diff.splitlines():
774
if line.startswith('+++ ') or line.startswith('--- '):
776
if line.startswith('@@ '):
778
if chunk is not None:
780
chunk = util.Container()
782
lines = [int(x.split(',')[0][1:]) for x in line.split(' ')[1:3]]
783
old_lineno = lines[0]
784
new_lineno = lines[1]
785
elif line.startswith(' '):
786
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
787
type='context', line=util.html_clean(line[1:])))
790
elif line.startswith('+'):
791
chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
792
type='insert', line=util.html_clean(line[1:])))
794
elif line.startswith('-'):
795
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
796
type='delete', line=util.html_clean(line[1:])))
799
chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
800
type='unknown', line=util.html_clean(repr(line))))
801
if chunk is not None:
805
def handle_modify(old_path, new_path, fid, kind):
807
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
809
old_lines = old_tree.get_file_lines(fid)
810
new_lines = new_tree.get_file_lines(fid)
813
bzrlib.diff.internal_diff(old_path, old_lines,
814
new_path, new_lines, buffer)
815
except bzrlib.errors.BinaryFile:
818
diff = buffer.getvalue()
819
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid, chunks=process_diff(diff), raw_diff=diff))
821
725
for path, fid, kind in delta.added:
822
726
added.append((rich_filename(path, kind), fid))
824
728
for path, fid, kind, text_modified, meta_modified in delta.modified:
825
handle_modify(path, path, fid, kind)
827
for oldpath, newpath, fid, kind, text_modified, meta_modified in delta.renamed:
828
renamed.append((rich_filename(oldpath, kind), rich_filename(newpath, kind), fid))
729
modified.append(util.Container(filename=rich_filename(path, kind), file_id=fid))
731
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
732
renamed.append((rich_filename(old_path, kind), rich_filename(new_path, kind), fid))
829
733
if meta_modified or text_modified:
830
handle_modify(oldpath, newpath, fid, kind)
734
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
832
736
for path, fid, kind in delta.removed:
833
737
removed.append((rich_filename(path, kind), fid))
835
739
return util.Container(added=added, renamed=renamed, removed=removed, modified=modified)
840
744
for change in changes:
841
745
for m in change.changes.modified:
842
746
m.sbs_chunks = _make_side_by_side(m.chunks)
845
def get_filelist(self, inv, path, sort_type=None):
748
def get_filelist(self, inv, file_id, sort_type=None):
847
750
return the list of all files (and their attributes) within a given
850
while path.endswith('/'):
852
if path.startswith('/'):
855
entries = inv.entries()
754
dir_ie = inv[file_id]
755
path = inv.id2path(file_id)
858
for filepath, entry in entries:
859
if posixpath.dirname(filepath) != path:
861
filename = posixpath.basename(filepath)
862
rich_filename = filename
760
for filename, entry in dir_ie.children.iteritems():
761
revid_set.add(entry.revision)
764
for change in self.get_changes(list(revid_set)):
765
change_dict[change.revid] = change
767
for filename, entry in dir_ie.children.iteritems():
863
768
pathname = filename
864
769
if entry.kind == 'directory':
867
772
revid = entry.revision
868
revision = self._branch.repository.get_revision(revid)
870
change = util.Container(date=datetime.datetime.fromtimestamp(revision.timestamp),
871
revno=self.get_revno(revid))
873
file = util.Container(filename=filename, rich_filename=rich_filename, executable=entry.executable, kind=entry.kind,
874
pathname=pathname, file_id=entry.file_id, size=entry.text_size, revid=revid, change=change)
774
file = util.Container(
775
filename=filename, executable=entry.executable, kind=entry.kind,
776
pathname=pathname, file_id=entry.file_id, size=entry.text_size,
777
revid=revid, change=change_dict[revid])
875
778
file_list.append(file)
877
if sort_type == 'filename':
878
file_list.sort(key=lambda x: x.filename)
780
if sort_type == 'filename' or sort_type is None:
781
file_list.sort(key=lambda x: x.filename.lower()) # case-insensitive
879
782
elif sort_type == 'size':
880
783
file_list.sort(key=lambda x: x.size)
881
784
elif sort_type == 'date':
882
785
file_list.sort(key=lambda x: x.change.date)
787
# Always sort by kind to get directories first
788
file_list.sort(key=lambda x: x.kind != 'directory')
885
791
for file in file_list:
886
792
file.parity = parity
892
798
_BADCHARS_RE = re.compile(ur'[\x00-\x08\x0b\x0e-\x1f]')
895
800
def annotate_file(self, file_id, revid):
900
805
file_revid = self.get_inventory(revid)[file_id].revision
903
# because we cache revision metadata ourselves, it's actually much
904
# faster to call 'annotate_iter' on the weave directly than it is to
905
# ask bzrlib to annotate for us.
906
w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
807
tree = self._branch.repository.revision_tree(file_revid)
908
808
revid_set = set()
909
for line_revid, text in w.annotate_iter(file_revid):
810
for line_revid, text in tree.annotate_iter(file_id):
910
811
revid_set.add(line_revid)
911
812
if self._BADCHARS_RE.match(text):
912
813
# bail out; this isn't displayable text
913
814
yield util.Container(parity=0, lineno=1, status='same',
914
text='<i>' + util.html_clean('(This is a binary file.)') + '</i>',
815
text='(This is a binary file.)',
915
816
change=util.Container())
917
change_cache = dict([(c.revid, c) for c in self.get_changes(list(revid_set))])
818
change_cache = dict([(c.revid, c) \
819
for c in self.get_changes(list(revid_set))])
919
821
last_line_revid = None
920
for line_revid, text in w.annotate_iter(file_revid):
822
for line_revid, text in tree.annotate_iter(file_id):
921
823
if line_revid == last_line_revid:
922
824
# remember which lines have a new revno and which don't