413
426
@with_branch_lock
414
427
def get_file_view(self, revid, file_id):
416
Given an optional revid and optional path, return a (revlist, revid)
417
for navigation through the current scope: from the revid (or the
418
latest revision) back to the original revision.
429
Given a revid and optional path, return a (revlist, revid) for
430
navigation through the current scope: from the revid (or the latest
431
revision) back to the original revision.
420
433
If file_id is None, the entire revision history is the list scope.
421
If revid is None, the latest revision is used.
423
435
if revid is None:
424
436
revid = self._last_revid
425
437
if file_id is not None:
426
# since revid is 'start_revid', possibly should start the path tracing from revid... FIXME
427
inv = self._branch.repository.get_revision_inventory(revid)
438
# since revid is 'start_revid', possibly should start the path
439
# tracing from revid... FIXME
428
440
revlist = list(self.get_short_revision_history_by_fileid(file_id))
429
441
revlist = list(self.get_revids_from(revlist, revid))
431
443
revlist = list(self.get_revids_from(None, revid))
434
return revlist, revid
436
446
@with_branch_lock
437
447
def get_view(self, revid, start_revid, file_id, query=None):
561
576
p_changes = self.get_changes(list(fetch_set))
562
577
p_change_dict = dict([(c.revid, c) for c in p_changes])
563
578
for change in changes:
579
# arch-converted branches may not have merged branch info :(
564
580
for p in change.parents:
565
p.branch_nick = p_change_dict[p.revid].branch_nick
581
if p.revid in p_change_dict:
582
p.branch_nick = p_change_dict[p.revid].branch_nick
584
p.branch_nick = '(missing)'
566
585
for p in change.merge_points:
567
p.branch_nick = p_change_dict[p.revid].branch_nick
586
if p.revid in p_change_dict:
587
p.branch_nick = p_change_dict[p.revid].branch_nick
589
p.branch_nick = '(missing)'
569
591
@with_branch_lock
570
592
def get_changes(self, revid_list, get_diffs=False):
571
593
if self._change_cache is None:
572
changes = self.get_changes_uncached(revid_list, get_diffs)
594
changes = self.get_changes_uncached(revid_list)
574
changes = self._change_cache.get_changes(revid_list, get_diffs)
596
changes = self._change_cache.get_changes(revid_list)
597
if len(changes) == 0:
578
600
# some data needs to be recalculated each time, because it may
579
601
# change as new revisions are added.
580
for i in xrange(len(revid_list)):
581
revid = revid_list[i]
583
merge_revids = self.simplify_merge_point_list(self.get_merge_point_list(revid))
602
for change in changes:
603
merge_revids = self.simplify_merge_point_list(self.get_merge_point_list(change.revid))
584
604
change.merge_points = [util.Container(revid=r, revno=self.get_revno(r)) for r in merge_revids]
605
change.revno = self.get_revno(change.revid)
607
# this may be time-consuming, but it only happens on the revision page, and only for one revision at a time.
608
if len(change.parents) > 0:
609
parent_revid = change.parents[0].revid
612
change.changes.modified = self._parse_diffs(parent_revid, change.revid)
615
for change in changes:
616
change.parity = parity
624
657
self._branch.repository.unlock()
659
def entry_from_revision(self, revision):
660
commit_time = datetime.datetime.fromtimestamp(revision.timestamp)
662
parents = [util.Container(revid=r, revno=self.get_revno(r)) for r in revision.parent_ids]
664
message, short_message = clean_message(revision.message)
667
'revid': revision.revision_id,
669
'author': revision.committer,
670
'branch_nick': revision.properties.get('branch-nick', None),
671
'short_comment': short_message,
672
'comment': revision.message,
673
'comment_clean': [util.html_clean(s) for s in message],
676
return util.Container(entry)
626
678
@with_branch_lock
627
679
@with_bzrlib_read_lock
628
def get_changes_uncached(self, revid_list, get_diffs=False):
630
rev_list = self._branch.repository.get_revisions(revid_list)
631
except (KeyError, bzrlib.errors.NoSuchRevision):
680
def get_changes_uncached(self, revid_list):
684
rev_list = self._branch.repository.get_revisions(revid_list)
686
except (KeyError, bzrlib.errors.NoSuchRevision), e:
687
# this sometimes happens with arch-converted branches.
688
# i don't know why. :(
689
self.log.debug('No such revision (skipping): %s', e)
690
revid_list.remove(e.revision)
634
694
delta_list = self._get_deltas_for_revisions_with_trees(rev_list)
635
695
combined_list = zip(rev_list, delta_list)
638
698
for rev, (new_tree, old_tree, delta) in combined_list:
639
commit_time = datetime.datetime.fromtimestamp(rev.timestamp)
641
parents = [util.Container(revid=r, revno=self.get_revno(r)) for r in rev.parent_ids]
643
if len(parents) == 0:
646
left_parent = rev.parent_ids[0]
648
message = rev.message.splitlines()
649
if len(message) == 1:
650
# robey-style 1-line long message
651
message = textwrap.wrap(message[0])
653
# make short form of commit message
654
short_message = message[0]
655
if len(short_message) > 60:
656
short_message = short_message[:60] + '...'
659
'revid': rev.revision_id,
660
'revno': self.get_revno(rev.revision_id),
662
'author': rev.committer,
663
'branch_nick': rev.properties.get('branch-nick', None),
664
'short_comment': short_message,
665
'comment': rev.message,
666
'comment_clean': [util.html_clean(s) for s in message],
668
'changes': self.parse_delta(delta, get_diffs, old_tree, new_tree),
670
entries.append(util.Container(entry))
699
entry = self.entry_from_revision(rev)
700
entry.changes = self.parse_delta(delta, old_tree, new_tree)
701
entries.append(entry)
705
@with_bzrlib_read_lock
706
def _get_diff(self, revid1, revid2):
707
rev_tree1 = self._branch.repository.revision_tree(revid1)
708
rev_tree2 = self._branch.repository.revision_tree(revid2)
709
delta = rev_tree2.changes_from(rev_tree1)
710
return rev_tree1, rev_tree2, delta
712
def get_diff(self, revid1, revid2):
713
rev_tree1, rev_tree2, delta = self._get_diff(revid1, revid2)
714
entry = self.get_changes([ revid2 ], False)[0]
715
entry.changes = self.parse_delta(delta, True, rev_tree1, rev_tree2)
674
718
@with_branch_lock
675
719
def get_file(self, file_id, revid):
676
"returns (filename, data)"
677
inv_entry = self.get_inventory(revid)[file_id]
720
"returns (path, filename, data)"
721
inv = self.get_inventory(revid)
722
inv_entry = inv[file_id]
678
723
rev_tree = self._branch.repository.revision_tree(inv_entry.revision)
679
return inv_entry.name, rev_tree.get_file_text(file_id)
724
path = inv.id2path(file_id)
725
if not path.startswith('/'):
727
return path, inv_entry.name, rev_tree.get_file_text(file_id)
682
def parse_delta(self, delta, get_diffs=True, old_tree=None, new_tree=None):
729
def _parse_diffs(self, revid1, revid2):
684
Return a nested data structure containing the changes in a delta::
731
Return a list of processed diffs, in the format::
686
added: list((filename, file_id)),
687
renamed: list((old_filename, new_filename, file_id)),
688
deleted: list((filename, file_id)),
702
if C{get_diffs} is false, the C{chunks} will be omitted.
746
old_tree, new_tree, delta = self._get_diff(revid1, revid2)
750
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
752
process.append((old_path, new_path, fid, kind))
753
for path, fid, kind, text_modified, meta_modified in delta.modified:
754
process.append((path, path, fid, kind))
756
for old_path, new_path, fid, kind in process:
757
old_lines = old_tree.get_file_lines(fid)
758
new_lines = new_tree.get_file_lines(fid)
760
if old_lines != new_lines:
762
bzrlib.diff.internal_diff(old_path, old_lines,
763
new_path, new_lines, buffer)
764
except bzrlib.errors.BinaryFile:
767
diff = buffer.getvalue()
770
out.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid, chunks=self._process_diff(diff)))
774
def _process_diff(self, diff):
775
# doesn't really need to be a method; could be static.
778
for line in diff.splitlines():
781
if line.startswith('+++ ') or line.startswith('--- '):
783
if line.startswith('@@ '):
785
if chunk is not None:
787
chunk = util.Container()
789
lines = [int(x.split(',')[0][1:]) for x in line.split(' ')[1:3]]
790
old_lineno = lines[0]
791
new_lineno = lines[1]
792
elif line.startswith(' '):
793
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
794
type='context', line=util.fixed_width(line[1:])))
797
elif line.startswith('+'):
798
chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
799
type='insert', line=util.fixed_width(line[1:])))
801
elif line.startswith('-'):
802
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
803
type='delete', line=util.fixed_width(line[1:])))
806
chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
807
type='unknown', line=util.fixed_width(repr(line))))
808
if chunk is not None:
813
def parse_delta(self, delta, get_diffs=True, old_tree=None, new_tree=None):
815
Return a nested data structure containing the changes in a delta::
817
added: list((filename, file_id)),
818
renamed: list((old_filename, new_filename, file_id)),
819
deleted: list((filename, file_id)),
709
def rich_filename(path, kind):
710
if kind == 'directory':
712
if kind == 'symlink':
716
def process_diff(diff):
719
for line in diff.splitlines():
722
if line.startswith('+++ ') or line.startswith('--- '):
724
if line.startswith('@@ '):
726
if chunk is not None:
728
chunk = util.Container()
730
lines = [int(x.split(',')[0][1:]) for x in line.split(' ')[1:3]]
731
old_lineno = lines[0]
732
new_lineno = lines[1]
733
elif line.startswith(' '):
734
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
735
type='context', line=util.html_clean(line[1:])))
738
elif line.startswith('+'):
739
chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
740
type='insert', line=util.html_clean(line[1:])))
742
elif line.startswith('-'):
743
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
744
type='delete', line=util.html_clean(line[1:])))
747
chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
748
type='unknown', line=util.html_clean(repr(line))))
749
if chunk is not None:
753
def handle_modify(old_path, new_path, fid, kind):
755
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
757
old_lines = old_tree.get_file_lines(fid)
758
new_lines = new_tree.get_file_lines(fid)
760
bzrlib.diff.internal_diff(old_path, old_lines, new_path, new_lines, buffer)
761
diff = buffer.getvalue()
762
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid, chunks=process_diff(diff), raw_diff=diff))
764
830
for path, fid, kind in delta.added:
765
831
added.append((rich_filename(path, kind), fid))
767
833
for path, fid, kind, text_modified, meta_modified in delta.modified:
768
handle_modify(path, path, fid, kind)
834
modified.append(util.Container(filename=rich_filename(path, kind), file_id=fid))
770
for oldpath, newpath, fid, kind, text_modified, meta_modified in delta.renamed:
771
renamed.append((rich_filename(oldpath, kind), rich_filename(newpath, kind), fid))
836
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
837
renamed.append((rich_filename(old_path, kind), rich_filename(new_path, kind), fid))
772
838
if meta_modified or text_modified:
773
handle_modify(oldpath, newpath, fid, kind)
839
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
775
841
for path, fid, kind in delta.removed:
776
842
removed.append((rich_filename(path, kind), fid))
785
851
m.sbs_chunks = _make_side_by_side(m.chunks)
787
853
@with_branch_lock
788
def get_filelist(self, inv, path, sort_type=None):
854
def get_filelist(self, inv, file_id, sort_type=None):
790
856
return the list of all files (and their attributes) within a given
793
while path.endswith('/'):
795
if path.startswith('/'):
798
entries = inv.entries()
801
for filepath, entry in entries:
802
fetch_set.add(entry.revision)
803
change_dict = dict([(c.revid, c) for c in self.get_changes(list(fetch_set))])
860
dir_ie = inv[file_id]
861
path = inv.id2path(file_id)
806
for filepath, entry in entries:
807
if posixpath.dirname(filepath) != path:
809
filename = posixpath.basename(filepath)
810
rich_filename = filename
864
for filename, entry in dir_ie.children.iteritems():
811
865
pathname = filename
812
866
if entry.kind == 'directory':
816
869
revid = entry.revision
817
change = change_dict[revid]
819
file = util.Container(filename=filename, rich_filename=rich_filename, executable=entry.executable, kind=entry.kind,
820
pathname=pathname, file_id=entry.file_id, size=entry.text_size, revid=revid, change=change)
870
if self._change_cache:
871
timestamp = self.get_changes([revid])[0].date
873
revision = self._branch.repository.get_revision(revid)
874
timestamp = datetime.datetime.fromtimestamp(revision.timestamp)
876
change = util.Container(date=timestamp,
877
revno=self.get_revno(revid))
879
file = util.Container(
880
filename=filename, executable=entry.executable, kind=entry.kind,
881
pathname=pathname, file_id=entry.file_id, size=entry.text_size,
882
revid=revid, change=change)
821
883
file_list.append(file)
823
if sort_type == 'filename':
885
if sort_type == 'filename' or sort_type is None:
824
886
file_list.sort(key=lambda x: x.filename)
825
887
elif sort_type == 'size':
826
888
file_list.sort(key=lambda x: x.size)
827
889
elif sort_type == 'date':
828
890
file_list.sort(key=lambda x: x.change.date)
831
893
for file in file_list:
832
894
file.parity = parity
875
937
trunc_revno = change.revno
876
938
if len(trunc_revno) > 10:
877
939
trunc_revno = trunc_revno[:9] + '...'
879
941
yield util.Container(parity=parity, lineno=lineno, status=status,
880
change=change, text=util.html_clean(text))
942
change=change, text=util.fixed_width(text))
883
945
self.log.debug('annotate: %r secs' % (time.time() - z,))
885
947
@with_branch_lock
886
948
@with_bzrlib_read_lock
887
def get_bundle(self, revid):
888
parents = self._revision_graph[revid]
890
parent_revid = parents[0]
949
def get_bundle(self, revid, compare_revid=None):
950
if compare_revid is None:
951
parents = self._revision_graph[revid]
953
compare_revid = parents[0]
894
bzrlib.bundle.serializer.write_bundle(self._branch.repository, revid, parent_revid, s)
957
bzrlib.bundle.serializer.write_bundle(self._branch.repository, revid, compare_revid, s)
895
958
return s.getvalue()