93
114
out_chunk_list = []
94
115
for chunk in chunk_list:
97
117
delete_list, insert_list = [], []
98
118
for line in chunk.diff:
99
# Add <wbr/> every X characters so we can wrap properly
100
wrap_line = re.findall(r'.{%d}|.+$' % 78, line.line)
101
wrap_lines = [util.html_clean(_line) for _line in wrap_line]
102
wrapped_line = wrap_char.join(wrap_lines)
104
119
if line.type == 'context':
105
120
if len(delete_list) or len(insert_list):
106
_process_side_by_side_buffers(line_list, delete_list,
121
_process_side_by_side_buffers(line_list, delete_list, insert_list)
108
122
delete_list, insert_list = [], []
109
line_list.append(util.Container(old_lineno=line.old_lineno,
110
new_lineno=line.new_lineno,
111
old_line=wrapped_line,
112
new_line=wrapped_line,
123
line_list.append(util.Container(old_lineno=line.old_lineno, new_lineno=line.new_lineno,
124
old_line=line.line, new_line=line.line,
125
old_type=line.type, new_type=line.type))
115
126
elif line.type == 'delete':
116
delete_list.append((line.old_lineno, wrapped_line, line.type))
127
delete_list.append((line.old_lineno, line.line, line.type))
117
128
elif line.type == 'insert':
118
insert_list.append((line.new_lineno, wrapped_line, line.type))
129
insert_list.append((line.new_lineno, line.line, line.type))
119
130
if len(delete_list) or len(insert_list):
120
131
_process_side_by_side_buffers(line_list, delete_list, insert_list)
121
132
out_chunk_list.append(util.Container(diff=line_list))
186
180
class History (object):
187
"""Decorate a branch to provide information for rendering.
189
History objects are expected to be short lived -- when serving a request
190
for a particular branch, open it, read-lock it, wrap a History object
191
around it, serve the request, throw the History object away, unlock the
192
branch and throw it away.
194
:ivar _file_change_cache: xx
197
def __init__(self, branch, whole_history_data_cache):
198
assert branch.is_locked(), (
199
"Can only construct a History object with a read-locked branch.")
200
self._file_change_cache = None
183
self._change_cache = None
185
self._lock = threading.RLock()
188
def from_branch(cls, branch, name=None):
201
191
self._branch = branch
202
self._inventory_cache = {}
203
self.log = logging.getLogger('loggerhead.%s' % (branch.nick,))
205
self.last_revid = branch.last_revision()
207
whole_history_data = whole_history_data_cache.get(self.last_revid)
208
if whole_history_data is None:
209
whole_history_data = compute_whole_history_data(branch)
210
whole_history_data_cache[self.last_revid] = whole_history_data
212
(self._revision_graph, self._full_history, self._revision_info,
213
self._revno_revid, self._merge_sort, self._where_merged
214
) = whole_history_data
217
def use_file_cache(self, cache):
218
self._file_change_cache = cache
221
def has_revisions(self):
222
return not bzrlib.revision.is_null(self.last_revid)
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)
224
272
def get_config(self):
225
273
return self._branch.get_config()
276
def get_revision(self, revid):
277
return self._branch.repository.get_revision(revid)
227
279
def get_revno(self, revid):
228
280
if revid not in self._revision_info:
231
283
seq, revid, merge_depth, revno_str, end_of_merge = self._revision_info[revid]
234
def get_revids_from(self, revid_list, start_revid):
236
Yield the mainline (wrt start_revid) revisions that merged each
239
if revid_list is None:
240
revid_list = self._full_history
241
revid_set = set(revid_list)
243
def introduced_revisions(revid):
245
seq, revid, md, revno, end_of_merge = self._revision_info[revid]
247
while i < len(self._merge_sort) and self._merge_sort[i][2] > md:
248
r.add(self._merge_sort[i][1])
252
if bzrlib.revision.is_null(revid):
254
if introduced_revisions(revid) & revid_set:
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):
313
if not self._revision_graph.has_key(revid):
256
315
parents = self._revision_graph[revid]
257
316
if len(parents) == 0:
259
318
revid = parents[0]
261
321
def get_short_revision_history_by_fileid(self, file_id):
322
# wow. is this really the only way we can get this list? by
323
# man-handling the weave store directly? :-0
262
324
# FIXME: would be awesome if we could get, for a folder, the list of
263
# revisions where items within that folder changed.i
265
# FIXME: Workaround for bzr versions prior to 1.6b3.
266
# Remove me eventually pretty please :)
267
w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
268
w_revids = w.versions()
269
revids = [r for r in self._full_history if r in w_revids]
270
except AttributeError:
271
possible_keys = [(file_id, revid) for revid in self._full_history]
272
existing_keys = self._branch.repository.texts.get_parent_map(possible_keys)
273
revids = [revid for _, revid in existing_keys.iterkeys()]
325
# 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]
276
332
def get_revision_history_since(self, revid_list, date):
277
333
# if a user asks for revisions starting at 01-sep, they mean inclusive,
278
334
# so start at midnight on 02-sep.
285
341
revid_list.reverse()
287
343
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]
289
371
def get_search_revid_list(self, query, revid_list):
291
373
given a "quick-search" query, try a few obvious possible meanings:
293
375
- revision id or # ("128.1.3")
294
376
- date (US style "mm/dd/yy", earth style "dd-mm-yy", or iso style "yyyy-mm-dd")
295
377
- comment text as a fallback
340
425
if revid is None:
342
427
if revid == 'head:':
343
return self.last_revid
345
if self.revno_re.match(revid):
346
revid = self._revno_revid[revid]
348
raise bzrlib.errors.NoSuchRevision(self._branch.nick, revid)
428
return self._last_revid
429
if self.revno_re.match(revid):
430
revid = self._revno_revid[revid]
351
434
def get_file_view(self, revid, file_id):
353
Given a revid and optional path, return a (revlist, revid) for
354
navigation through the current scope: from the revid (or the latest
355
revision) back to the original revision.
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.
357
440
If file_id is None, the entire revision history is the list scope.
441
If revid is None, the latest revision is used.
359
443
if revid is None:
360
revid = self.last_revid
444
revid = self._last_revid
361
445
if file_id is not None:
362
# since revid is 'start_revid', possibly should start the path
363
# tracing from revid... FIXME
446
# since revid is 'start_revid', possibly should start the path tracing from revid... FIXME
447
inv = self._branch.repository.get_revision_inventory(revid)
364
448
revlist = list(self.get_short_revision_history_by_fileid(file_id))
365
449
revlist = list(self.get_revids_from(revlist, revid))
367
451
revlist = list(self.get_revids_from(None, revid))
454
return revlist, revid
370
457
def get_view(self, revid, start_revid, file_id, query=None):
372
459
use the URL parameters (revid, start_revid, file_id, and query) to
373
460
determine the revision list we're viewing (start_revid, file_id, query)
374
461
and where we are in it (revid).
376
- if a query is given, we're viewing query results.
377
- if a file_id is given, we're viewing revisions for a specific
379
- if a start_revid is given, we're viewing the branch from a
380
specific revision up the tree.
382
these may be combined to view revisions for a specific file, from
383
a specific revision, with a specific search query.
385
returns a new (revid, start_revid, revid_list) where:
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:
387
472
- revid: current position within the view
388
473
- start_revid: starting revision of this view
389
474
- revid_list: list of revision ids for this view
391
476
file_id and query are never changed so aren't returned, but they may
392
477
contain vital context for future url navigation.
394
if start_revid is None:
395
start_revid = self.last_revid
397
479
if query is None:
398
revid_list = self.get_file_view(start_revid, file_id)
480
revid_list, start_revid = self.get_file_view(start_revid, file_id)
399
481
if revid is None:
400
482
revid = start_revid
401
483
if revid not in revid_list:
402
484
# if the given revid is not in the revlist, use a revlist that
403
485
# starts at the given revid.
404
revid_list = self.get_file_view(revid, file_id)
486
revid_list, start_revid = self.get_file_view(revid, file_id)
406
487
return revid, start_revid, revid_list
408
489
# potentially limit the search
409
if file_id is not None:
410
revid_list = self.get_file_view(start_revid, file_id)
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)
412
493
revid_list = None
413
revid_list = search.search_revisions(self._branch, query)
414
if revid_list and len(revid_list) > 0:
495
revid_list = self.get_search_revid_list(query, revid_list)
496
if len(revid_list) > 0:
415
497
if revid not in revid_list:
416
498
revid = revid_list[0]
417
499
return revid, start_revid, revid_list
419
# XXX: This should return a message saying that the search could
420
# not be completed due to either missing the plugin or missing a
422
502
return None, None, []
424
505
def get_inventory(self, revid):
425
if revid not in self._inventory_cache:
426
self._inventory_cache[revid] = (
427
self._branch.repository.get_revision_inventory(revid))
428
return self._inventory_cache[revid]
506
return self._branch.repository.get_revision_inventory(revid)
430
509
def get_path(self, revid, file_id):
431
510
if (file_id is None) or (file_id == ''):
433
path = self.get_inventory(revid).id2path(file_id)
512
path = self._branch.repository.get_revision_inventory(revid).id2path(file_id)
434
513
if (len(path) > 0) and not path.startswith('/'):
435
514
path = '/' + path
438
518
def get_file_id(self, revid, path):
439
519
if (len(path) > 0) and not path.startswith('/'):
440
520
path = '/' + path
441
return self.get_inventory(revid).path2id(path)
521
return self._branch.repository.get_revision_inventory(revid).path2id(path)
523
def get_where_merged(self, revid):
525
return self._where_merged[revid]
443
529
def get_merge_point_list(self, revid):
445
531
Return the list of revids that have merged this node.
447
if '.' not in self.get_revno(revid):
533
if revid in self._history:
452
children = self._where_merged.get(revid, [])
538
children = self.get_where_merged(revid)
454
540
for child in children:
455
541
child_parents = self._revision_graph[child]
512
598
p.branch_nick = p_change_dict[p.revid].branch_nick
514
600
p.branch_nick = '(missing)'
516
def get_changes(self, revid_list):
517
"""Return a list of changes objects for the given revids.
519
Revisions not present and NULL_REVISION will be ignored.
521
changes = self.get_changes_uncached(revid_list)
522
if len(changes) == 0:
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)
525
611
# some data needs to be recalculated each time, because it may
526
612
# change as new revisions are added.
527
for change in changes:
528
merge_revids = self.simplify_merge_point_list(self.get_merge_point_list(change.revid))
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))
529
617
change.merge_points = [util.Container(revid=r, revno=self.get_revno(r)) for r in merge_revids]
530
if len(change.parents) > 0:
531
change.parents = [util.Container(revid=r,
532
revno=self.get_revno(r)) for r in change.parents]
533
change.revno = self.get_revno(change.revid)
536
for change in changes:
537
change.parity = parity
542
def get_changes_uncached(self, revid_list):
543
# FIXME: deprecated method in getting a null revision
544
revid_list = filter(lambda revid: not bzrlib.revision.is_null(revid),
546
parent_map = self._branch.repository.get_graph().get_parent_map(revid_list)
547
# We need to return the answer in the same order as the input,
549
present_revids = [revid for revid in revid_list
550
if revid in parent_map]
551
rev_list = self._branch.repository.get_revisions(present_revids)
553
return [self._change_from_revision(rev) for rev in rev_list]
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!')
555
632
def _get_deltas_for_revisions_with_trees(self, revisions):
556
"""Produce a list of revision deltas.
633
"""Produce a generator of revision deltas.
558
635
Note that the input is a sequence of REVISIONS, not revision_ids.
559
636
Trees will be held in memory until the generator exits.
560
637
Each delta is relative to the revision's lefthand predecessor.
561
(This is copied from bzrlib.)
563
639
required_trees = set()
564
640
for revision in revisions:
565
required_trees.add(revision.revid)
566
required_trees.update([p.revid for p in revision.parents[:1]])
567
trees = dict((t.get_revision_id(), t) for
641
required_trees.add(revision.revision_id)
642
required_trees.update(revision.parent_ids[:1])
643
trees = dict((t.get_revision_id(), t) for
568
644
t in self._branch.repository.revision_trees(required_trees))
570
for revision in revisions:
571
if not revision.parents:
572
old_tree = self._branch.repository.revision_tree(
573
bzrlib.revision.NULL_REVISION)
575
old_tree = trees[revision.parents[0].revid]
576
tree = trees[revision.revid]
577
ret.append(tree.changes_from(old_tree))
580
def _change_from_revision(self, revision):
582
Given a bzrlib Revision, return a processed "change" for use in
646
self._branch.repository.lock_read()
648
for revision in revisions:
649
if not revision.parent_ids:
650
old_tree = self._branch.repository.revision_tree(None)
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)))
657
self._branch.repository.unlock()
659
def entry_from_revision(self, revision):
585
660
commit_time = datetime.datetime.fromtimestamp(revision.timestamp)
587
662
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]
589
669
message, short_message = clean_message(revision.message)
592
672
'revid': revision.revision_id,
673
'revno': self.get_revno(revision.revision_id),
593
674
'date': commit_time,
594
'author': revision.get_apparent_author(),
675
'author': revision.committer,
595
676
'branch_nick': revision.properties.get('branch-nick', None),
596
677
'short_comment': short_message,
597
678
'comment': revision.message,
598
679
'comment_clean': [util.html_clean(s) for s in message],
599
'parents': revision.parent_ids,
601
682
return util.Container(entry)
603
def get_file_changes_uncached(self, entries):
604
delta_list = self._get_deltas_for_revisions_with_trees(entries)
606
return [self.parse_delta(delta) for delta in delta_list]
608
def get_file_changes(self, entries):
609
if self._file_change_cache is None:
610
return self.get_file_changes_uncached(entries)
612
return self._file_change_cache.get_file_changes(entries)
614
def add_changes(self, entries):
615
changes_list = self.get_file_changes(entries)
617
for entry, changes in zip(entries, changes_list):
618
entry.changes = changes
620
def get_change_with_diff(self, revid, compare_revid=None):
621
change = self.get_changes([revid])[0]
623
if compare_revid is None:
625
compare_revid = change.parents[0].revid
627
compare_revid = 'null:'
629
rev_tree1 = self._branch.repository.revision_tree(compare_revid)
630
rev_tree2 = self._branch.repository.revision_tree(revid)
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)
631
713
delta = rev_tree2.changes_from(rev_tree1)
633
change.changes = self.parse_delta(delta)
634
change.changes.modified = self._parse_diffs(rev_tree1, rev_tree2, delta)
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)
638
723
def get_file(self, file_id, revid):
639
724
"returns (path, filename, data)"
640
725
inv = self.get_inventory(revid)
754
if C{get_diffs} is false, the C{chunks} will be omitted.
668
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
670
process.append((old_path, new_path, fid, kind))
671
for path, fid, kind, text_modified, meta_modified in delta.modified:
672
process.append((path, path, fid, kind))
674
for old_path, new_path, fid, kind in process:
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))
675
809
old_lines = old_tree.get_file_lines(fid)
676
810
new_lines = new_tree.get_file_lines(fid)
677
811
buffer = StringIO()
678
if old_lines != new_lines:
680
bzrlib.diff.internal_diff(old_path, old_lines,
681
new_path, new_lines, buffer)
682
except bzrlib.errors.BinaryFile:
685
diff = buffer.getvalue()
813
bzrlib.diff.internal_diff(old_path, old_lines,
814
new_path, new_lines, buffer)
815
except bzrlib.errors.BinaryFile:
688
out.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid, chunks=self._process_diff(diff), raw_diff=diff))
692
def _process_diff(self, diff):
693
# doesn't really need to be a method; could be static.
696
for line in diff.splitlines():
699
if line.startswith('+++ ') or line.startswith('--- '):
701
if line.startswith('@@ '):
703
if chunk is not None:
705
chunk = util.Container()
707
lines = [int(x.split(',')[0][1:]) for x in line.split(' ')[1:3]]
708
old_lineno = lines[0]
709
new_lineno = lines[1]
710
elif line.startswith(' '):
711
chunk.diff.append(util.Container(old_lineno=old_lineno,
712
new_lineno=new_lineno,
717
elif line.startswith('+'):
718
chunk.diff.append(util.Container(old_lineno=None,
719
new_lineno=new_lineno,
720
type='insert', line=line[1:]))
722
elif line.startswith('-'):
723
chunk.diff.append(util.Container(old_lineno=old_lineno,
725
type='delete', line=line[1:]))
728
chunk.diff.append(util.Container(old_lineno=None,
732
if chunk is not None:
736
def parse_delta(self, delta):
738
Return a nested data structure containing the changes in a delta::
740
added: list((filename, file_id)),
741
renamed: list((old_filename, new_filename, file_id)),
742
deleted: list((filename, file_id)),
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))
753
821
for path, fid, kind in delta.added:
754
822
added.append((rich_filename(path, kind), fid))
756
824
for path, fid, kind, text_modified, meta_modified in delta.modified:
757
modified.append(util.Container(filename=rich_filename(path, kind), file_id=fid))
759
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
760
renamed.append((rich_filename(old_path, kind), rich_filename(new_path, kind), fid))
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))
761
829
if meta_modified or text_modified:
762
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
830
handle_modify(oldpath, newpath, fid, kind)
764
832
for path, fid, kind in delta.removed:
765
833
removed.append((rich_filename(path, kind), fid))
767
835
return util.Container(added=added, renamed=renamed, removed=removed, modified=modified)
772
840
for change in changes:
773
841
for m in change.changes.modified:
774
842
m.sbs_chunks = _make_side_by_side(m.chunks)
776
def get_filelist(self, inv, file_id, sort_type=None):
845
def get_filelist(self, inv, path, sort_type=None):
778
847
return the list of all files (and their attributes) within a given
782
dir_ie = inv[file_id]
783
path = inv.id2path(file_id)
850
while path.endswith('/'):
852
if path.startswith('/'):
855
entries = inv.entries()
788
for filename, entry in dir_ie.children.iteritems():
789
revid_set.add(entry.revision)
792
for change in self.get_changes(list(revid_set)):
793
change_dict[change.revid] = change
795
for filename, entry in dir_ie.children.iteritems():
858
for filepath, entry in entries:
859
if posixpath.dirname(filepath) != path:
861
filename = posixpath.basename(filepath)
862
rich_filename = filename
796
863
pathname = filename
797
864
if entry.kind == 'directory':
800
867
revid = entry.revision
868
revision = self._branch.repository.get_revision(revid)
802
file = util.Container(
803
filename=filename, executable=entry.executable, kind=entry.kind,
804
pathname=pathname, file_id=entry.file_id, size=entry.text_size,
805
revid=revid, change=change_dict[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)
806
875
file_list.append(file)
808
if sort_type == 'filename' or sort_type is None:
809
file_list.sort(key=lambda x: x.filename.lower()) # case-insensitive
877
if sort_type == 'filename':
878
file_list.sort(key=lambda x: x.filename)
810
879
elif sort_type == 'size':
811
880
file_list.sort(key=lambda x: x.size)
812
881
elif sort_type == 'date':
813
882
file_list.sort(key=lambda x: x.change.date)
815
# Always sort by kind to get directories first
816
file_list.sort(key=lambda x: x.kind != 'directory')
819
885
for file in file_list:
820
886
file.parity = parity
826
892
_BADCHARS_RE = re.compile(ur'[\x00-\x08\x0b\x0e-\x1f]')
828
895
def annotate_file(self, file_id, revid):
833
900
file_revid = self.get_inventory(revid)[file_id].revision
835
tree = self._branch.repository.revision_tree(file_revid)
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())
836
908
revid_set = set()
838
for line_revid, text in tree.annotate_iter(file_id):
909
for line_revid, text in w.annotate_iter(file_revid):
839
910
revid_set.add(line_revid)
840
911
if self._BADCHARS_RE.match(text):
841
912
# bail out; this isn't displayable text
842
913
yield util.Container(parity=0, lineno=1, status='same',
843
text='(This is a binary file.)',
914
text='<i>' + util.html_clean('(This is a binary file.)') + '</i>',
844
915
change=util.Container())
846
change_cache = dict([(c.revid, c) \
847
for c in self.get_changes(list(revid_set))])
917
change_cache = dict([(c.revid, c) for c in self.get_changes(list(revid_set))])
849
919
last_line_revid = None
850
for line_revid, text in tree.annotate_iter(file_id):
920
for line_revid, text in w.annotate_iter(file_revid):
851
921
if line_revid == last_line_revid:
852
922
# remember which lines have a new revno and which don't