~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-01-27 20:24:43 UTC
  • mfrom: (3960.2.1 1.12-progress-warnings)
  • Revision ID: pqm@pqm.ubuntu.com-20090127202443-ty2bu1hh91dumasz
(jam) Avoid getting a UserWarning by not creating an unused progress
        bar.

Show diffs side-by-side

added added

removed removed

Lines of Context:
50
50
"""
51
51
 
52
52
import codecs
53
 
from cStringIO import StringIO
54
53
from itertools import (
55
54
    izip,
56
55
    )
65
64
 
66
65
from bzrlib import (
67
66
    config,
68
 
    diff,
69
67
    errors,
70
68
    repository as _mod_repository,
71
69
    revision as _mod_revision,
146
144
             start_revision=None,
147
145
             end_revision=None,
148
146
             search=None,
149
 
             limit=None,
150
 
             show_diff=False):
 
147
             limit=None):
151
148
    """Write out human-readable log of commits to this branch.
152
149
 
153
150
    :param lf: The LogFormatter object showing the output.
169
166
 
170
167
    :param limit: If set, shows only 'limit' revisions, all revisions are shown
171
168
        if None or 0.
172
 
 
173
 
    :param show_diff: If True, output a diff after each revision.
174
169
    """
175
170
    branch.lock_read()
176
171
    try:
178
173
            lf.begin_log()
179
174
 
180
175
        _show_log(branch, lf, specific_fileid, verbose, direction,
181
 
                  start_revision, end_revision, search, limit, show_diff)
 
176
                  start_revision, end_revision, search, limit)
182
177
 
183
178
        if getattr(lf, 'end_log', None):
184
179
            lf.end_log()
194
189
             start_revision=None,
195
190
             end_revision=None,
196
191
             search=None,
197
 
             limit=None,
198
 
             show_diff=False):
 
192
             limit=None):
199
193
    """Worker function for show_log - see show_log."""
200
194
    if not isinstance(lf, LogFormatter):
201
195
        warn("not a LogFormatter instance: %r" % lf)
202
196
 
203
197
    if specific_fileid:
204
198
        trace.mutter('get log for file_id %r', specific_fileid)
205
 
    levels_to_display = lf.get_levels()
206
 
    generate_merge_revisions = levels_to_display != 1
207
 
    allow_single_merge_revision = True
208
 
    if not getattr(lf, 'supports_merge_revisions', False):
209
 
        allow_single_merge_revision = getattr(lf,
210
 
            'supports_single_merge_revision', False)
 
199
    generate_merge_revisions = getattr(lf, 'supports_merge_revisions', False)
 
200
    allow_single_merge_revision = getattr(lf,
 
201
        'supports_single_merge_revision', False)
211
202
    view_revisions = calculate_view_revisions(branch, start_revision,
212
203
                                              end_revision, direction,
213
204
                                              specific_fileid,
220
211
            rev_tag_dict = branch.tags.get_reverse_tag_dict()
221
212
 
222
213
    generate_delta = verbose and getattr(lf, 'supports_delta', False)
223
 
    generate_diff = show_diff and getattr(lf, 'supports_diff', False)
224
214
 
225
215
    # now we just print all the revisions
226
 
    repo = branch.repository
227
216
    log_count = 0
228
217
    revision_iterator = make_log_rev_iterator(branch, view_revisions,
229
218
        generate_delta, search)
230
219
    for revs in revision_iterator:
231
220
        for (rev_id, revno, merge_depth), rev, delta in revs:
232
 
            # Note: 0 levels means show everything; merge_depth counts from 0
233
 
            if levels_to_display != 0 and merge_depth >= levels_to_display:
234
 
                continue
235
 
            if generate_diff:
236
 
                diff = _format_diff(repo, rev, rev_id, specific_fileid)
237
 
            else:
238
 
                diff = None
239
221
            lr = LogRevision(rev, revno, merge_depth, delta,
240
 
                             rev_tag_dict.get(rev_id), diff)
 
222
                             rev_tag_dict.get(rev_id))
241
223
            lf.log_revision(lr)
242
224
            if limit:
243
225
                log_count += 1
245
227
                    return
246
228
 
247
229
 
248
 
def _format_diff(repo, rev, rev_id, specific_fileid):
249
 
    if len(rev.parent_ids) == 0:
250
 
        ancestor_id = _mod_revision.NULL_REVISION
251
 
    else:
252
 
        ancestor_id = rev.parent_ids[0]
253
 
    tree_1 = repo.revision_tree(ancestor_id)
254
 
    tree_2 = repo.revision_tree(rev_id)
255
 
    if specific_fileid:
256
 
        specific_files = [tree_2.id2path(specific_fileid)]
257
 
    else:
258
 
        specific_files = None
259
 
    s = StringIO()
260
 
    diff.show_diff_trees(tree_1, tree_2, s, specific_files, old_label='',
261
 
        new_label='')
262
 
    return s.getvalue()
263
 
 
264
 
 
265
230
def calculate_view_revisions(branch, start_revision, end_revision, direction,
266
231
                             specific_fileid, generate_merge_revisions,
267
232
                             allow_single_merge_revision):
729
694
    """
730
695
 
731
696
    def __init__(self, rev=None, revno=None, merge_depth=0, delta=None,
732
 
                 tags=None, diff=None):
 
697
                 tags=None):
733
698
        self.rev = rev
734
699
        self.revno = revno
735
700
        self.merge_depth = merge_depth
736
701
        self.delta = delta
737
702
        self.tags = tags
738
 
        self.diff = diff
739
703
 
740
704
 
741
705
class LogFormatter(object):
758
722
        merge revisions.  If not, and if supports_single_merge_revisions is
759
723
        also not True, then only mainline revisions will be passed to the 
760
724
        formatter.
761
 
 
762
 
    - preferred_levels is the number of levels this formatter defaults to.
763
 
        The default value is zero meaning display all levels.
764
 
        This value is only relevant if supports_merge_revisions is True.
765
 
 
766
725
    - supports_single_merge_revision must be True if this log formatter
767
726
        supports logging only a single merge revision.  This flag is
768
727
        only relevant if supports_merge_revisions is not True.
769
 
 
770
728
    - supports_tags must be True if this log formatter supports tags.
771
729
        Otherwise the tags attribute may not be populated.
772
730
 
773
 
    - supports_diff must be True if this log formatter supports diffs.
774
 
        Otherwise the diff attribute may not be populated.
775
 
 
776
731
    Plugins can register functions to show custom revision properties using
777
732
    the properties_handler_registry. The registered function
778
733
    must respect the following interface description:
780
735
            # code that returns a dict {'name':'value'} of the properties 
781
736
            # to be shown
782
737
    """
783
 
    preferred_levels = 0
784
738
 
785
739
    def __init__(self, to_file, show_ids=False, show_timezone='original',
786
 
                 delta_format=None, levels=None):
787
 
        """Create a LogFormatter.
788
 
 
789
 
        :param to_file: the file to output to
790
 
        :param show_ids: if True, revision-ids are to be displayed
791
 
        :param show_timezone: the timezone to use
792
 
        :param delta_format: the level of delta information to display
793
 
          or None to leave it u to the formatter to decide
794
 
        :param levels: the number of levels to display; None or -1 to
795
 
          let the log formatter decide.
796
 
        """
 
740
                 delta_format=None):
797
741
        self.to_file = to_file
798
742
        self.show_ids = show_ids
799
743
        self.show_timezone = show_timezone
801
745
            # Ensures backward compatibility
802
746
            delta_format = 2 # long format
803
747
        self.delta_format = delta_format
804
 
        self.levels = levels
805
 
 
806
 
    def get_levels(self):
807
 
        """Get the number of levels to display or 0 for all."""
808
 
        if getattr(self, 'supports_merge_revisions', False):
809
 
            if self.levels is None or self.levels == -1:
810
 
                return self.preferred_levels
811
 
            else:
812
 
                return self.levels
813
 
        return 1
814
 
 
815
 
    def log_revision(self, revision):
816
 
        """Log a revision.
817
 
 
818
 
        :param  revision:   The LogRevision to be logged.
819
 
        """
820
 
        raise NotImplementedError('not implemented in abstract base')
 
748
 
 
749
# TODO: uncomment this block after show() has been removed.
 
750
# Until then defining log_revision would prevent _show_log calling show() 
 
751
# in legacy formatters.
 
752
#    def log_revision(self, revision):
 
753
#        """Log a revision.
 
754
#
 
755
#        :param  revision:   The LogRevision to be logged.
 
756
#        """
 
757
#        raise NotImplementedError('not implemented in abstract base')
821
758
 
822
759
    def short_committer(self, rev):
823
760
        name, address = config.parse_username(rev.committer)
840
777
            for key, value in handler(revision).items():
841
778
                self.to_file.write(indent + key + ': ' + value + '\n')
842
779
 
843
 
    def show_diff(self, to_file, diff, indent):
844
 
        for l in diff.rstrip().split('\n'):
845
 
            to_file.write(indent + '%s\n' % (l,))
846
 
 
847
780
 
848
781
class LongLogFormatter(LogFormatter):
849
782
 
850
783
    supports_merge_revisions = True
851
784
    supports_delta = True
852
785
    supports_tags = True
853
 
    supports_diff = True
854
786
 
855
787
    def log_revision(self, revision):
856
788
        """Log a revision, either merged or not."""
893
825
            # We don't respect delta_format for compatibility
894
826
            revision.delta.show(to_file, self.show_ids, indent=indent,
895
827
                                short_status=False)
896
 
        if revision.diff is not None:
897
 
            to_file.write(indent + 'diff:\n')
898
 
            # Note: we explicitly don't indent the diff (relative to the
899
 
            # revision information) so that the output can be fed to patch -p0
900
 
            self.show_diff(to_file, revision.diff, indent)
901
828
 
902
829
 
903
830
class ShortLogFormatter(LogFormatter):
904
831
 
905
 
    supports_merge_revisions = True
906
 
    preferred_levels = 1
907
832
    supports_delta = True
908
833
    supports_tags = True
909
 
    supports_diff = True
910
 
 
911
 
    def __init__(self, *args, **kwargs):
912
 
        super(ShortLogFormatter, self).__init__(*args, **kwargs)
913
 
        self.revno_width_by_depth = {}
 
834
    supports_single_merge_revision = True
914
835
 
915
836
    def log_revision(self, revision):
916
 
        # We need two indents: one per depth and one for the information
917
 
        # relative to that indent. Most mainline revnos are 5 chars or
918
 
        # less while dotted revnos are typically 11 chars or less. Once
919
 
        # calculated, we need to remember the offset for a given depth
920
 
        # as we might be starting from a dotted revno in the first column
921
 
        # and we want subsequent mainline revisions to line up.
922
 
        depth = revision.merge_depth
923
 
        indent = '    ' * depth
924
 
        revno_width = self.revno_width_by_depth.get(depth)
925
 
        if revno_width is None:
926
 
            if revision.revno.find('.') == -1:
927
 
                # mainline revno, e.g. 12345
928
 
                revno_width = 5
929
 
            else:
930
 
                # dotted revno, e.g. 12345.10.55
931
 
                revno_width = 11
932
 
            self.revno_width_by_depth[depth] = revno_width
933
 
        offset = ' ' * (revno_width + 1)
934
 
 
935
837
        to_file = self.to_file
936
838
        is_merge = ''
937
839
        if len(revision.rev.parent_ids) > 1:
939
841
        tags = ''
940
842
        if revision.tags:
941
843
            tags = ' {%s}' % (', '.join(revision.tags))
942
 
        to_file.write(indent + "%*s %s\t%s%s%s\n" % (revno_width,
943
 
                revision.revno, self.short_author(revision.rev),
 
844
 
 
845
        to_file.write("%5s %s\t%s%s%s\n" % (revision.revno,
 
846
                self.short_author(revision.rev),
944
847
                format_date(revision.rev.timestamp,
945
848
                            revision.rev.timezone or 0,
946
849
                            self.show_timezone, date_fmt="%Y-%m-%d",
947
850
                            show_offset=False),
948
851
                tags, is_merge))
949
852
        if self.show_ids:
950
 
            to_file.write(indent + offset + 'revision-id:%s\n'
 
853
            to_file.write('      revision-id:%s\n'
951
854
                          % (revision.rev.revision_id,))
952
855
        if not revision.rev.message:
953
 
            to_file.write(indent + offset + '(no message)\n')
 
856
            to_file.write('      (no message)\n')
954
857
        else:
955
858
            message = revision.rev.message.rstrip('\r\n')
956
859
            for l in message.split('\n'):
957
 
                to_file.write(indent + offset + '%s\n' % (l,))
 
860
                to_file.write('      %s\n' % (l,))
958
861
 
959
862
        if revision.delta is not None:
960
 
            revision.delta.show(to_file, self.show_ids, indent=indent + offset,
 
863
            revision.delta.show(to_file, self.show_ids,
961
864
                                short_status=self.delta_format==1)
962
 
        if revision.diff is not None:
963
 
            self.show_diff(to_file, revision.diff, '      ')
964
865
        to_file.write('\n')
965
866
 
966
867
 
967
868
class LineLogFormatter(LogFormatter):
968
869
 
969
 
    supports_merge_revisions = True
970
 
    preferred_levels = 1
971
870
    supports_tags = True
 
871
    supports_single_merge_revision = True
972
872
 
973
873
    def __init__(self, *args, **kwargs):
974
874
        super(LineLogFormatter, self).__init__(*args, **kwargs)
991
891
            return rev.message
992
892
 
993
893
    def log_revision(self, revision):
994
 
        indent = '  ' * revision.merge_depth
995
894
        self.to_file.write(self.log_string(revision.revno, revision.rev,
996
 
            self._max_chars, revision.tags, indent))
 
895
            self._max_chars, revision.tags))
997
896
        self.to_file.write('\n')
998
897
 
999
 
    def log_string(self, revno, rev, max_chars, tags=None, prefix=''):
 
898
    def log_string(self, revno, rev, max_chars, tags=None):
1000
899
        """Format log info into one string. Truncate tail of string
1001
900
        :param  revno:      revision number or None.
1002
901
                            Revision numbers counts from 1.
1003
902
        :param  rev:        revision object
1004
903
        :param  max_chars:  maximum length of resulting string
1005
904
        :param  tags:       list of tags or None
1006
 
        :param  prefix:     string to prefix each line
1007
905
        :return:            formatted truncated string
1008
906
        """
1009
907
        out = []
1016
914
            tag_str = '{%s}' % (', '.join(tags))
1017
915
            out.append(tag_str)
1018
916
        out.append(rev.get_summary())
1019
 
        return self.truncate(prefix + " ".join(out).rstrip('\n'), max_chars)
 
917
        return self.truncate(" ".join(out).rstrip('\n'), max_chars)
1020
918
 
1021
919
 
1022
920
def line_log(rev, max_chars):
1222
1120
        lf.log_revision(lr)
1223
1121
 
1224
1122
 
1225
 
def _get_fileid_to_log(revision, tree, b, fp):
1226
 
    """Find the file-id to log for a file path in a revision range.
1227
 
 
1228
 
    :param revision: the revision range as parsed on the command line
1229
 
    :param tree: the working tree, if any
1230
 
    :param b: the branch
1231
 
    :param fp: file path
1232
 
    """
1233
 
    if revision is None:
1234
 
        if tree is None:
1235
 
            tree = b.basis_tree()
1236
 
        file_id = tree.path2id(fp)
1237
 
        if file_id is None:
1238
 
            # go back to when time began
1239
 
            try:
1240
 
                rev1 = b.get_rev_id(1)
1241
 
            except errors.NoSuchRevision:
1242
 
                # No history at all
1243
 
                file_id = None
1244
 
            else:
1245
 
                tree = b.repository.revision_tree(rev1)
1246
 
                file_id = tree.path2id(fp)
1247
 
 
1248
 
    elif len(revision) == 1:
1249
 
        # One revision given - file must exist in it
1250
 
        tree = revision[0].as_tree(b)
1251
 
        file_id = tree.path2id(fp)
1252
 
 
1253
 
    elif len(revision) == 2:
1254
 
        # Revision range given. Get the file-id from the end tree.
1255
 
        # If that fails, try the start tree.
1256
 
        rev_id = revision[1].as_revision_id(b)
1257
 
        if rev_id is None:
1258
 
            tree = b.basis_tree()
1259
 
        else:
1260
 
            tree = revision[1].as_tree(b)
1261
 
        file_id = tree.path2id(fp)
1262
 
        if file_id is None:
1263
 
            rev_id = revision[0].as_revision_id(b)
1264
 
            if rev_id is None:
1265
 
                rev1 = b.get_rev_id(1)
1266
 
                tree = b.repository.revision_tree(rev1)
1267
 
            else:
1268
 
                tree = revision[0].as_tree(b)
1269
 
            file_id = tree.path2id(fp)
1270
 
    else:
1271
 
        raise errors.BzrCommandError(
1272
 
            'bzr log --revision takes one or two values.')
1273
 
    return file_id
1274
 
 
1275
 
 
1276
1123
properties_handler_registry = registry.Registry()
1277
1124
properties_handler_registry.register_lazy("foreign",
1278
1125
                                          "bzrlib.foreign",