~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/history.py

  • Committer: Michael Hudson
  • Date: 2007-07-04 11:44:22 UTC
  • mfrom: (128.1.39 testing)
  • Revision ID: michael.hudson@canonical.com-20070704114422-77n0yamvzpd3ddhi
merge in a lot of changes from my development branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
153
153
        
154
154
    # make short form of commit message
155
155
    short_message = message[0]
156
 
    if len(short_message) > 60:
157
 
        short_message = short_message[:60] + '...'
 
156
    if len(short_message) > 80:
 
157
        short_message = short_message[:80] + '...'
158
158
    
159
159
    return message, short_message
160
160
 
161
161
 
 
162
def rich_filename(path, kind):
 
163
    if kind == 'directory':
 
164
        path += '/'
 
165
    if kind == 'symlink':
 
166
        path += '@'
 
167
    return path
 
168
        
 
169
 
 
170
 
162
171
# from bzrlib
163
172
class _RevListToTimestamps(object):
164
173
    """This takes a list of revisions, and allows you to bisect by date"""
228
237
 
229
238
    @with_branch_lock
230
239
    def out_of_date(self):
 
240
        # the branch may have been upgraded on disk, in which case we're stale.
231
241
        if self._branch.__class__ is not \
232
242
               bzrlib.branch.Branch.open(self._branch.base).__class__:
233
 
            return False
 
243
            return True
234
244
        return self._branch.last_revision() != self._last_revid
235
245
 
236
246
    def use_cache(self, cache):
416
426
    @with_branch_lock
417
427
    def get_file_view(self, revid, file_id):
418
428
        """
419
 
        Given an optional revid and optional path, return a (revlist, revid)
420
 
        for navigation through the current scope: from the revid (or the
421
 
        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.
422
432
        
423
433
        If file_id is None, the entire revision history is the list scope.
424
 
        If revid is None, the latest revision is used.
425
434
        """
426
435
        if revid is None:
427
436
            revid = self._last_revid
428
437
        if file_id is not None:
429
 
            # since revid is 'start_revid', possibly should start the path tracing from revid... FIXME
 
438
            # since revid is 'start_revid', possibly should start the path
 
439
            # tracing from revid... FIXME
430
440
            revlist = list(self.get_short_revision_history_by_fileid(file_id))
431
441
            revlist = list(self.get_revids_from(revlist, revid))
432
442
        else:
433
443
            revlist = list(self.get_revids_from(None, revid))
434
 
        if revid is None:
435
 
            revid = revlist[0]
436
 
        return revlist, revid
 
444
        return revlist
437
445
    
438
446
    @with_branch_lock
439
447
    def get_view(self, revid, start_revid, file_id, query=None):
458
466
        file_id and query are never changed so aren't returned, but they may
459
467
        contain vital context for future url navigation.
460
468
        """
 
469
        if start_revid is None:
 
470
            start_revid = self._last_revid
 
471
 
461
472
        if query is None:
462
 
            revid_list, start_revid = self.get_file_view(start_revid, file_id)
 
473
            revid_list = self.get_file_view(start_revid, file_id)
463
474
            if revid is None:
464
475
                revid = start_revid
465
476
            if revid not in revid_list:
466
477
                # if the given revid is not in the revlist, use a revlist that
467
478
                # starts at the given revid.
468
 
                revid_list, start_revid = self.get_file_view(revid, file_id)
 
479
                revid_list= self.get_file_view(revid, file_id)
 
480
                start_revid = revid
469
481
            return revid, start_revid, revid_list
470
482
        
471
483
        # potentially limit the search
472
 
        if (start_revid is not None) or (file_id is not None):
473
 
            revid_list, start_revid = self.get_file_view(start_revid, file_id)
 
484
        if file_id is not None:
 
485
            revid_list = self.get_file_view(start_revid, file_id)
474
486
        else:
475
487
            revid_list = None
476
488
 
579
591
    @with_branch_lock
580
592
    def get_changes(self, revid_list, get_diffs=False):
581
593
        if self._change_cache is None:
582
 
            changes = self.get_changes_uncached(revid_list, get_diffs)
 
594
            changes = self.get_changes_uncached(revid_list)
583
595
        else:
584
 
            changes = self._change_cache.get_changes(revid_list, get_diffs)
 
596
            changes = self._change_cache.get_changes(revid_list)
585
597
        if len(changes) == 0:
586
598
            return changes
587
599
        
588
600
        # some data needs to be recalculated each time, because it may
589
601
        # change as new revisions are added.
590
 
        for i in xrange(len(revid_list)):
591
 
            revid = revid_list[i]
592
 
            change = changes[i]
593
 
            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))
594
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)
 
606
            if get_diffs:
 
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
 
610
                else:
 
611
                    parent_revid = None
 
612
                change.changes.modified = self._parse_diffs(parent_revid, change.revid)
 
613
 
 
614
        parity = 0
 
615
        for change in changes:
 
616
            change.parity = parity
 
617
            parity ^= 1
595
618
        
596
619
        return changes
597
620
 
638
661
        
639
662
        parents = [util.Container(revid=r, revno=self.get_revno(r)) for r in revision.parent_ids]
640
663
 
641
 
        if len(parents) == 0:
642
 
            left_parent = None
643
 
        else:
644
 
            left_parent = revision.parent_ids[0]
645
 
        
646
664
        message, short_message = clean_message(revision.message)
647
665
 
648
666
        entry = {
649
667
            'revid': revision.revision_id,
650
 
            'revno': self.get_revno(revision.revision_id),
651
668
            'date': commit_time,
652
669
            'author': revision.committer,
653
670
            'branch_nick': revision.properties.get('branch-nick', None),
660
677
 
661
678
    @with_branch_lock
662
679
    @with_bzrlib_read_lock
663
 
    def get_changes_uncached(self, revid_list, get_diffs=False):
 
680
    def get_changes_uncached(self, revid_list):
664
681
        done = False
665
682
        while not done:
666
683
            try:
680
697
        entries = []
681
698
        for rev, (new_tree, old_tree, delta) in combined_list:
682
699
            entry = self.entry_from_revision(rev)
683
 
            entry.changes = self.parse_delta(delta, get_diffs, old_tree, new_tree)
 
700
            entry.changes = self.parse_delta(delta, old_tree, new_tree)
684
701
            entries.append(entry)
685
702
        
686
703
        return entries
709
726
            path = '/' + path
710
727
        return path, inv_entry.name, rev_tree.get_file_text(file_id)
711
728
    
712
 
    @with_branch_lock
713
 
    def parse_delta(self, delta, get_diffs=True, old_tree=None, new_tree=None):
 
729
    def _parse_diffs(self, revid1, revid2):
714
730
        """
715
 
        Return a nested data structure containing the changes in a delta::
 
731
        Return a list of processed diffs, in the format::
716
732
        
717
 
            added: list((filename, file_id)),
718
 
            renamed: list((old_filename, new_filename, file_id)),
719
 
            deleted: list((filename, file_id)),
720
 
            modified: list(
 
733
            list(
721
734
                filename: str,
722
735
                file_id: str,
723
736
                chunks: list(
729
742
                    ),
730
743
                ),
731
744
            )
732
 
        
733
 
        if C{get_diffs} is false, the C{chunks} will be omitted.
 
745
        """
 
746
        old_tree, new_tree, delta = self._get_diff(revid1, revid2)
 
747
        process = []
 
748
        out = []
 
749
        
 
750
        for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
 
751
            if text_modified:
 
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))
 
755
        
 
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)
 
759
            buffer = StringIO()
 
760
            if old_lines != new_lines:
 
761
                try:
 
762
                    bzrlib.diff.internal_diff(old_path, old_lines,
 
763
                                              new_path, new_lines, buffer)
 
764
                except bzrlib.errors.BinaryFile:
 
765
                    diff = ''
 
766
                else:
 
767
                    diff = buffer.getvalue()
 
768
            else:
 
769
                diff = ''
 
770
            out.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid, chunks=self._process_diff(diff)))
 
771
        
 
772
        return out
 
773
 
 
774
    def _process_diff(self, diff):
 
775
        # doesn't really need to be a method; could be static.
 
776
        chunks = []
 
777
        chunk = None
 
778
        for line in diff.splitlines():
 
779
            if len(line) == 0:
 
780
                continue
 
781
            if line.startswith('+++ ') or line.startswith('--- '):
 
782
                continue
 
783
            if line.startswith('@@ '):
 
784
                # new chunk
 
785
                if chunk is not None:
 
786
                    chunks.append(chunk)
 
787
                chunk = util.Container()
 
788
                chunk.diff = []
 
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:])))
 
795
                old_lineno += 1
 
796
                new_lineno += 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:])))
 
800
                new_lineno += 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:])))
 
804
                old_lineno += 1
 
805
            else:
 
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:
 
809
            chunks.append(chunk)
 
810
        return chunks
 
811
                
 
812
    @with_branch_lock
 
813
    def parse_delta(self, delta, get_diffs=True, old_tree=None, new_tree=None):
 
814
        """
 
815
        Return a nested data structure containing the changes in a delta::
 
816
        
 
817
            added: list((filename, file_id)),
 
818
            renamed: list((old_filename, new_filename, file_id)),
 
819
            deleted: list((filename, file_id)),
 
820
            modified: list(
 
821
                filename: str,
 
822
                file_id: str,
 
823
            )
734
824
        """
735
825
        added = []
736
826
        modified = []
737
827
        renamed = []
738
828
        removed = []
739
829
        
740
 
        def rich_filename(path, kind):
741
 
            if kind == 'directory':
742
 
                path += '/'
743
 
            if kind == 'symlink':
744
 
                path += '@'
745
 
            return path
746
 
        
747
 
        def process_diff(diff):
748
 
            chunks = []
749
 
            chunk = None
750
 
            for line in diff.splitlines():
751
 
                if len(line) == 0:
752
 
                    continue
753
 
                if line.startswith('+++ ') or line.startswith('--- '):
754
 
                    continue
755
 
                if line.startswith('@@ '):
756
 
                    # new chunk
757
 
                    if chunk is not None:
758
 
                        chunks.append(chunk)
759
 
                    chunk = util.Container()
760
 
                    chunk.diff = []
761
 
                    lines = [int(x.split(',')[0][1:]) for x in line.split(' ')[1:3]]
762
 
                    old_lineno = lines[0]
763
 
                    new_lineno = lines[1]
764
 
                elif line.startswith(' '):
765
 
                    chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
766
 
                                                     type='context', line=util.html_clean(line[1:])))
767
 
                    old_lineno += 1
768
 
                    new_lineno += 1
769
 
                elif line.startswith('+'):
770
 
                    chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
771
 
                                                     type='insert', line=util.html_clean(line[1:])))
772
 
                    new_lineno += 1
773
 
                elif line.startswith('-'):
774
 
                    chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
775
 
                                                     type='delete', line=util.html_clean(line[1:])))
776
 
                    old_lineno += 1
777
 
                else:
778
 
                    chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
779
 
                                                     type='unknown', line=util.html_clean(repr(line))))
780
 
            if chunk is not None:
781
 
                chunks.append(chunk)
782
 
            return chunks
783
 
                    
784
 
        def handle_modify(old_path, new_path, fid, kind):
785
 
            if not get_diffs:
786
 
                modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
787
 
                return
788
 
            old_lines = old_tree.get_file_lines(fid)
789
 
            new_lines = new_tree.get_file_lines(fid)
790
 
            buffer = StringIO()
791
 
            try:
792
 
                bzrlib.diff.internal_diff(old_path, old_lines,
793
 
                                          new_path, new_lines, buffer)
794
 
            except bzrlib.errors.BinaryFile:
795
 
                diff = ''
796
 
            else:
797
 
                diff = buffer.getvalue()
798
 
            modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid, chunks=process_diff(diff), raw_diff=diff))
799
 
 
800
830
        for path, fid, kind in delta.added:
801
831
            added.append((rich_filename(path, kind), fid))
802
832
        
803
833
        for path, fid, kind, text_modified, meta_modified in delta.modified:
804
 
            handle_modify(path, path, fid, kind)
 
834
            modified.append(util.Container(filename=rich_filename(path, kind), file_id=fid))
805
835
        
806
 
        for oldpath, newpath, fid, kind, text_modified, meta_modified in delta.renamed:
807
 
            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))
808
838
            if meta_modified or text_modified:
809
 
                handle_modify(oldpath, newpath, fid, kind)
 
839
                modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
810
840
        
811
841
        for path, fid, kind in delta.removed:
812
842
            removed.append((rich_filename(path, kind), fid))
821
851
                m.sbs_chunks = _make_side_by_side(m.chunks)
822
852
    
823
853
    @with_branch_lock
824
 
    def get_filelist(self, inv, path, sort_type=None):
 
854
    def get_filelist(self, inv, file_id, sort_type=None):
825
855
        """
826
856
        return the list of all files (and their attributes) within a given
827
857
        path subtree.
828
858
        """
829
 
        while path.endswith('/'):
830
 
            path = path[:-1]
831
 
        if path.startswith('/'):
832
 
            path = path[1:]
833
 
        
834
 
        entries = inv.entries()
835
 
        
 
859
 
 
860
        dir_ie = inv[file_id]
 
861
        path = inv.id2path(file_id)
836
862
        file_list = []
837
 
        for filepath, entry in entries:
838
 
            if posixpath.dirname(filepath) != path:
839
 
                continue
840
 
            filename = posixpath.basename(filepath)
 
863
 
 
864
        for filename, entry in dir_ie.children.iteritems():
841
865
            pathname = filename
842
866
            if entry.kind == 'directory':
843
867
                pathname += '/'
844
868
 
845
869
            revid = entry.revision
846
 
            revision = self._branch.repository.get_revision(revid)
 
870
            if self._change_cache:
 
871
                timestamp = self.get_changes([revid])[0].date
 
872
            else:
 
873
                revision = self._branch.repository.get_revision(revid)
 
874
                timestamp = datetime.datetime.fromtimestamp(revision.timestamp)
847
875
 
848
 
            change = util.Container(date=datetime.datetime.fromtimestamp(revision.timestamp),
 
876
            change = util.Container(date=timestamp,
849
877
                                    revno=self.get_revno(revid))
850
 
            
851
 
            file = util.Container(filename=filename, executable=entry.executable, kind=entry.kind,
852
 
                                  pathname=pathname, file_id=entry.file_id, size=entry.text_size, revid=revid, change=change)
 
878
 
 
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)
853
883
            file_list.append(file)
854
 
        
855
 
        if sort_type == 'filename':
 
884
 
 
885
        if sort_type == 'filename' or sort_type is None:
856
886
            file_list.sort(key=lambda x: x.filename)
857
887
        elif sort_type == 'size':
858
888
            file_list.sort(key=lambda x: x.size)
859
889
        elif sort_type == 'date':
860
890
            file_list.sort(key=lambda x: x.change.date)
861
 
        
 
891
 
862
892
        parity = 0
863
893
        for file in file_list:
864
894
            file.parity = parity
889
919
            if self._BADCHARS_RE.match(text):
890
920
                # bail out; this isn't displayable text
891
921
                yield util.Container(parity=0, lineno=1, status='same',
892
 
                                     text='<i>' + util.html_clean('(This is a binary file.)') + '</i>',
 
922
                                     text='(This is a binary file.)',
893
923
                                     change=util.Container())
894
924
                return
895
925
        change_cache = dict([(c.revid, c) for c in self.get_changes(list(revid_set))])
907
937
                trunc_revno = change.revno
908
938
                if len(trunc_revno) > 10:
909
939
                    trunc_revno = trunc_revno[:9] + '...'
910
 
                
 
940
 
911
941
            yield util.Container(parity=parity, lineno=lineno, status=status,
912
 
                                 change=change, text=util.html_clean(text))
 
942
                                 change=change, text=util.fixed_width(text))
913
943
            lineno += 1
914
944
        
915
945
        self.log.debug('annotate: %r secs' % (time.time() - z,))