~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: 2007-06-18 05:22:35 UTC
  • mfrom: (1551.15.27 Aaron's mergeable stuff)
  • Revision ID: pqm@pqm.ubuntu.com-20070618052235-mvns8j28szyzscy0
Turn list-weave into list-versionedfile

Show diffs side-by-side

added added

removed removed

Lines of Context:
42
42
 
43
43
In verbose mode we show a summary of what changed in each particular
44
44
revision.  Note that this is the delta for changes in that revision
45
 
relative to its left-most parent, not the delta relative to the last
 
45
relative to its mainline parent, not the delta relative to the last
46
46
logged revision.  So for example if you ask for a verbose log of
47
47
changes touching hello.c you will get a list of those revisions also
48
48
listing other things that were changed in the same revision, but not
49
49
all the changes since the previous revision that touched hello.c.
50
50
"""
51
51
 
52
 
import codecs
53
 
from itertools import (
54
 
    izip,
55
 
    )
 
52
# TODO: option to show delta summaries for merged-in revisions
 
53
 
 
54
from itertools import izip
56
55
import re
57
 
import sys
58
 
from warnings import (
59
 
    warn,
60
 
    )
61
56
 
62
57
from bzrlib import (
63
 
    config,
64
 
    lazy_regex,
65
58
    registry,
66
59
    symbol_versioning,
67
60
    )
68
 
from bzrlib.errors import (
69
 
    BzrCommandError,
70
 
    )
71
 
from bzrlib.osutils import (
72
 
    format_date,
73
 
    get_terminal_encoding,
74
 
    terminal_width,
75
 
    )
76
 
from bzrlib.revision import (
77
 
    NULL_REVISION,
78
 
    )
79
 
from bzrlib.revisionspec import (
80
 
    RevisionInfo,
81
 
    )
 
61
import bzrlib.errors as errors
82
62
from bzrlib.symbol_versioning import (
83
63
    deprecated_method,
 
64
    zero_eleven,
84
65
    zero_seventeen,
85
66
    )
86
67
from bzrlib.trace import mutter
134
115
        revno += 1
135
116
 
136
117
 
 
118
 
137
119
def _enumerate_history(branch):
138
120
    rh = []
139
121
    revno = 1
203
185
             search=None,
204
186
             limit=None):
205
187
    """Worker function for show_log - see show_log."""
 
188
    from bzrlib.osutils import format_date
 
189
    from bzrlib.errors import BzrCheckError
 
190
    
 
191
    from warnings import warn
 
192
 
206
193
    if not isinstance(lf, LogFormatter):
207
194
        warn("not a LogFormatter instance: %r" % lf)
208
195
 
210
197
        mutter('get log for file_id %r', specific_fileid)
211
198
 
212
199
    if search is not None:
 
200
        import re
213
201
        searchRE = re.compile(search, re.IGNORECASE)
214
202
    else:
215
203
        searchRE = None
216
204
 
217
 
    mainline_revs, rev_nos, start_rev_id, end_rev_id = \
218
 
        _get_mainline_revs(branch, start_revision, end_revision)
219
 
    if not mainline_revs:
 
205
    which_revs = _enumerate_history(branch)
 
206
    
 
207
    if start_revision is None:
 
208
        start_revision = 1
 
209
    else:
 
210
        branch.check_real_revno(start_revision)
 
211
    
 
212
    if end_revision is None:
 
213
        end_revision = len(which_revs)
 
214
    else:
 
215
        branch.check_real_revno(end_revision)
 
216
 
 
217
    # list indexes are 0-based; revisions are 1-based
 
218
    cut_revs = which_revs[(start_revision-1):(end_revision)]
 
219
    if not cut_revs:
220
220
        return
221
221
 
222
 
    if direction == 'reverse':
223
 
        start_rev_id, end_rev_id = end_rev_id, start_rev_id
224
 
        
 
222
    # convert the revision history to a dictionary:
 
223
    rev_nos = dict((k, v) for v, k in cut_revs)
 
224
 
 
225
    # override the mainline to look like the revision history.
 
226
    mainline_revs = [revision_id for index, revision_id in cut_revs]
 
227
    if cut_revs[0][0] == 1:
 
228
        mainline_revs.insert(0, None)
 
229
    else:
 
230
        mainline_revs.insert(0, which_revs[start_revision-2][1])
225
231
    legacy_lf = getattr(lf, 'log_revision', None) is None
226
232
    if legacy_lf:
227
233
        # pre-0.17 formatters use show for mainline revisions.
242
248
    else:
243
249
        generate_merge_revisions = getattr(lf, 'supports_merge_revisions', 
244
250
                                           False)
245
 
    generate_single_revision = False
246
 
    if ((not generate_merge_revisions)
247
 
        and ((start_rev_id and (start_rev_id not in rev_nos))
248
 
            or (end_rev_id and (end_rev_id not in rev_nos)))):
249
 
        generate_single_revision = ((start_rev_id == end_rev_id)
250
 
            and getattr(lf, 'supports_single_merge_revision', False))
251
 
        if not generate_single_revision:
252
 
            raise BzrCommandError('Selected log formatter only supports '
253
 
                'mainline revisions.')
254
 
        generate_merge_revisions = generate_single_revision
255
251
    view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
256
252
                          direction, include_merges=generate_merge_revisions)
257
 
    view_revisions = _filter_revision_range(list(view_revs_iter),
258
 
                                            start_rev_id,
259
 
                                            end_rev_id)
260
 
    if view_revisions and generate_single_revision:
261
 
        view_revisions = view_revisions[0:1]
262
253
    if specific_fileid:
263
 
        view_revisions = _filter_revisions_touching_file_id(branch,
 
254
        view_revisions = _get_revisions_touching_file_id(branch,
264
255
                                                         specific_fileid,
265
256
                                                         mainline_revs,
266
 
                                                         view_revisions)
 
257
                                                         view_revs_iter)
 
258
    else:
 
259
        view_revisions = list(view_revs_iter)
267
260
 
268
 
    # rebase merge_depth - unless there are no revisions or 
269
 
    # either the first or last revision have merge_depth = 0.
270
 
    if view_revisions and view_revisions[0][2] and view_revisions[-1][2]:
271
 
        min_depth = min([d for r,n,d in view_revisions])
272
 
        if min_depth != 0:
273
 
            view_revisions = [(r,n,d-min_depth) for r,n,d in view_revisions]
274
 
        
275
261
    rev_tag_dict = {}
276
262
    generate_tags = getattr(lf, 'supports_tags', False)
277
263
    if generate_tags:
283
269
    def iter_revisions():
284
270
        # r = revision, n = revno, d = merge depth
285
271
        revision_ids = [r for r, n, d in view_revisions]
 
272
        zeros = set(r for r, n, d in view_revisions if d == 0)
286
273
        num = 9
287
274
        repository = branch.repository
288
275
        while revision_ids:
289
276
            cur_deltas = {}
290
277
            revisions = repository.get_revisions(revision_ids[:num])
291
278
            if generate_delta:
292
 
                deltas = repository.get_deltas_for_revisions(revisions)
293
 
                cur_deltas = dict(izip((r.revision_id for r in revisions),
294
 
                                       deltas))
 
279
                delta_revisions = [r for r in revisions if
 
280
                                   r.revision_id in zeros]
 
281
                deltas = repository.get_deltas_for_revisions(delta_revisions)
 
282
                cur_deltas = dict(izip((r.revision_id for r in 
 
283
                                        delta_revisions), deltas))
295
284
            for revision in revisions:
 
285
                # The delta value will be None unless
 
286
                # 1. verbose is specified, and
 
287
                # 2. the revision is a mainline revision
296
288
                yield revision, cur_deltas.get(revision.revision_id)
297
289
            revision_ids  = revision_ids[num:]
298
290
            num = min(int(num * 1.5), 200)
332
324
                break
333
325
 
334
326
 
335
 
def _get_mainline_revs(branch, start_revision, end_revision):
336
 
    """Get the mainline revisions from the branch.
337
 
    
338
 
    Generates the list of mainline revisions for the branch.
339
 
    
340
 
    :param  branch: The branch containing the revisions. 
341
 
 
342
 
    :param  start_revision: The first revision to be logged.
343
 
            For backwards compatibility this may be a mainline integer revno,
344
 
            but for merge revision support a RevisionInfo is expected.
345
 
 
346
 
    :param  end_revision: The last revision to be logged.
347
 
            For backwards compatibility this may be a mainline integer revno,
348
 
            but for merge revision support a RevisionInfo is expected.
349
 
 
350
 
    :return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
351
 
    """
352
 
    which_revs = _enumerate_history(branch)
353
 
    if not which_revs:
354
 
        return None, None, None, None
355
 
 
356
 
    # For mainline generation, map start_revision and end_revision to 
357
 
    # mainline revnos. If the revision is not on the mainline choose the 
358
 
    # appropriate extreme of the mainline instead - the extra will be 
359
 
    # filtered later.
360
 
    # Also map the revisions to rev_ids, to be used in the later filtering
361
 
    # stage.
362
 
    start_rev_id = None 
363
 
    if start_revision is None:
364
 
        start_revno = 1
365
 
    else:
366
 
        if isinstance(start_revision,RevisionInfo):
367
 
            start_rev_id = start_revision.rev_id
368
 
            start_revno = start_revision.revno or 1
369
 
        else:
370
 
            branch.check_real_revno(start_revision)
371
 
            start_revno = start_revision
372
 
    
373
 
    end_rev_id = None
374
 
    if end_revision is None:
375
 
        end_revno = len(which_revs)
376
 
    else:
377
 
        if isinstance(end_revision,RevisionInfo):
378
 
            end_rev_id = end_revision.rev_id
379
 
            end_revno = end_revision.revno or len(which_revs)
380
 
        else:
381
 
            branch.check_real_revno(end_revision)
382
 
            end_revno = end_revision
383
 
 
384
 
    if ((start_rev_id == NULL_REVISION)
385
 
        or (end_rev_id == NULL_REVISION)):
386
 
        raise BzrCommandError('Logging revision 0 is invalid.')
387
 
    if start_revno > end_revno:
388
 
        raise BzrCommandError("Start revision must be older than "
389
 
                              "the end revision.")
390
 
 
391
 
    # list indexes are 0-based; revisions are 1-based
392
 
    cut_revs = which_revs[(start_revno-1):(end_revno)]
393
 
    if not cut_revs:
394
 
        return None, None, None, None
395
 
 
396
 
    # convert the revision history to a dictionary:
397
 
    rev_nos = dict((k, v) for v, k in cut_revs)
398
 
 
399
 
    # override the mainline to look like the revision history.
400
 
    mainline_revs = [revision_id for index, revision_id in cut_revs]
401
 
    if cut_revs[0][0] == 1:
402
 
        mainline_revs.insert(0, None)
403
 
    else:
404
 
        mainline_revs.insert(0, which_revs[start_revno-2][1])
405
 
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
406
 
 
407
 
 
408
 
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
409
 
    """Filter view_revisions based on revision ranges.
410
 
 
411
 
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth) 
412
 
            tuples to be filtered.
413
 
 
414
 
    :param start_rev_id: If not NONE specifies the first revision to be logged.
415
 
            If NONE then all revisions up to the end_rev_id are logged.
416
 
 
417
 
    :param end_rev_id: If not NONE specifies the last revision to be logged.
418
 
            If NONE then all revisions up to the end of the log are logged.
419
 
 
420
 
    :return: The filtered view_revisions.
421
 
    """
422
 
    if start_rev_id or end_rev_id: 
423
 
        revision_ids = [r for r, n, d in view_revisions]
424
 
        if start_rev_id:
425
 
            start_index = revision_ids.index(start_rev_id)
426
 
        else:
427
 
            start_index = 0
428
 
        if start_rev_id == end_rev_id:
429
 
            end_index = start_index
430
 
        else:
431
 
            if end_rev_id:
432
 
                end_index = revision_ids.index(end_rev_id)
433
 
            else:
434
 
                end_index = len(view_revisions) - 1
435
 
        # To include the revisions merged into the last revision, 
436
 
        # extend end_rev_id down to, but not including, the next rev
437
 
        # with the same or lesser merge_depth
438
 
        end_merge_depth = view_revisions[end_index][2]
439
 
        try:
440
 
            for index in xrange(end_index+1, len(view_revisions)+1):
441
 
                if view_revisions[index][2] <= end_merge_depth:
442
 
                    end_index = index - 1
443
 
                    break
444
 
        except IndexError:
445
 
            # if the search falls off the end then log to the end as well
446
 
            end_index = len(view_revisions) - 1
447
 
        view_revisions = view_revisions[start_index:end_index+1]
448
 
    return view_revisions
449
 
 
450
 
 
451
 
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
452
 
                                       view_revs_iter):
 
327
def _get_revisions_touching_file_id(branch, file_id, mainline_revisions,
 
328
                                    view_revs_iter):
453
329
    """Return the list of revision ids which touch a given file id.
454
330
 
455
 
    The function filters view_revisions and returns a subset.
456
331
    This includes the revisions which directly change the file id,
457
332
    and the revisions which merge these changes. So if the
458
333
    revision graph is::
591
466
    - supports_delta must be True if this log formatter supports delta.
592
467
        Otherwise the delta attribute may not be populated.
593
468
    - supports_merge_revisions must be True if this log formatter supports 
594
 
        merge revisions.  If not, and if supports_single_merge_revisions is
595
 
        also not True, then only mainline revisions will be passed to the 
596
 
        formatter.
597
 
    - supports_single_merge_revision must be True if this log formatter
598
 
        supports logging only a single merge revision.  This flag is
599
 
        only relevant if supports_merge_revisions is not True.
 
469
        merge revisions.  If not, only revisions mainline revisions (those 
 
470
        with merge_depth == 0) will be passed to the formatter.
600
471
    - supports_tags must be True if this log formatter supports tags.
601
472
        Otherwise the tags attribute may not be populated.
602
473
    """
621
492
        raise NotImplementedError('not implemented in abstract base')
622
493
 
623
494
    def short_committer(self, rev):
624
 
        name, address = config.parse_username(rev.committer)
625
 
        if name:
626
 
            return name
627
 
        return address
628
 
 
629
 
    def short_author(self, rev):
630
 
        name, address = config.parse_username(rev.get_apparent_author())
631
 
        if name:
632
 
            return name
633
 
        return address
 
495
        return re.sub('<.*@.*>', '', rev.committer).strip(' ')
634
496
 
635
497
 
636
498
class LongLogFormatter(LogFormatter):
644
506
        lr = LogRevision(rev, revno, 0, delta, tags)
645
507
        return self.log_revision(lr)
646
508
 
 
509
    @deprecated_method(zero_eleven)
 
510
    def show_merge(self, rev, merge_depth):
 
511
        lr = LogRevision(rev, merge_depth=merge_depth)
 
512
        return self.log_revision(lr)
 
513
 
647
514
    @deprecated_method(zero_seventeen)
648
515
    def show_merge_revno(self, rev, merge_depth, revno, tags=None):
649
516
        """Show a merged revision rev, with merge_depth and a revno."""
652
519
 
653
520
    def log_revision(self, revision):
654
521
        """Log a revision, either merged or not."""
655
 
        indent = '    ' * revision.merge_depth
 
522
        from bzrlib.osutils import format_date
 
523
        indent = '    '*revision.merge_depth
656
524
        to_file = self.to_file
657
 
        to_file.write(indent + '-' * 60 + '\n')
 
525
        print >>to_file,  indent+'-' * 60
658
526
        if revision.revno is not None:
659
 
            to_file.write(indent + 'revno: %s\n' % (revision.revno,))
 
527
            print >>to_file,  indent+'revno:', revision.revno
660
528
        if revision.tags:
661
 
            to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
 
529
            print >>to_file, indent+'tags: %s' % (', '.join(revision.tags))
662
530
        if self.show_ids:
663
 
            to_file.write(indent + 'revision-id:' + revision.rev.revision_id)
664
 
            to_file.write('\n')
 
531
            print >>to_file, indent+'revision-id:', revision.rev.revision_id
665
532
            for parent_id in revision.rev.parent_ids:
666
 
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
667
 
 
668
 
        author = revision.rev.properties.get('author', None)
669
 
        if author is not None:
670
 
            to_file.write(indent + 'author: %s\n' % (author,))
671
 
        to_file.write(indent + 'committer: %s\n' % (revision.rev.committer,))
672
 
 
673
 
        branch_nick = revision.rev.properties.get('branch-nick', None)
674
 
        if branch_nick is not None:
675
 
            to_file.write(indent + 'branch nick: %s\n' % (branch_nick,))
676
 
 
 
533
                print >>to_file, indent+'parent:', parent_id
 
534
        print >>to_file, indent+'committer:', revision.rev.committer
 
535
 
 
536
        try:
 
537
            print >>to_file, indent+'branch nick: %s' % \
 
538
                revision.rev.properties['branch-nick']
 
539
        except KeyError:
 
540
            pass
677
541
        date_str = format_date(revision.rev.timestamp,
678
542
                               revision.rev.timezone or 0,
679
543
                               self.show_timezone)
680
 
        to_file.write(indent + 'timestamp: %s\n' % (date_str,))
 
544
        print >>to_file,  indent+'timestamp: %s' % date_str
681
545
 
682
 
        to_file.write(indent + 'message:\n')
 
546
        print >>to_file,  indent+'message:'
683
547
        if not revision.rev.message:
684
 
            to_file.write(indent + '  (no message)\n')
 
548
            print >>to_file,  indent+'  (no message)'
685
549
        else:
686
550
            message = revision.rev.message.rstrip('\r\n')
687
551
            for l in message.split('\n'):
688
 
                to_file.write(indent + '  %s\n' % (l,))
 
552
                print >>to_file,  indent+'  ' + l
689
553
        if revision.delta is not None:
690
 
            revision.delta.show(to_file, self.show_ids, indent=indent)
 
554
            revision.delta.show(to_file, self.show_ids)
691
555
 
692
556
 
693
557
class ShortLogFormatter(LogFormatter):
694
558
 
695
559
    supports_delta = True
696
 
    supports_single_merge_revision = True
697
560
 
698
561
    @deprecated_method(zero_seventeen)
699
562
    def show(self, revno, rev, delta):
701
564
        return self.log_revision(lr)
702
565
 
703
566
    def log_revision(self, revision):
 
567
        from bzrlib.osutils import format_date
 
568
 
704
569
        to_file = self.to_file
705
570
        date_str = format_date(revision.rev.timestamp,
706
571
                               revision.rev.timezone or 0,
708
573
        is_merge = ''
709
574
        if len(revision.rev.parent_ids) > 1:
710
575
            is_merge = ' [merge]'
711
 
        to_file.write("%5s %s\t%s%s\n" % (revision.revno,
712
 
                self.short_author(revision.rev),
 
576
        print >>to_file, "%5s %s\t%s%s" % (revision.revno,
 
577
                self.short_committer(revision.rev),
713
578
                format_date(revision.rev.timestamp,
714
579
                            revision.rev.timezone or 0,
715
580
                            self.show_timezone, date_fmt="%Y-%m-%d",
716
581
                            show_offset=False),
717
 
                is_merge))
 
582
                is_merge)
718
583
        if self.show_ids:
719
 
            to_file.write('      revision-id:%s\n' % (revision.rev.revision_id,))
 
584
            print >>to_file,  '      revision-id:', revision.rev.revision_id
720
585
        if not revision.rev.message:
721
 
            to_file.write('      (no message)\n')
 
586
            print >>to_file,  '      (no message)'
722
587
        else:
723
588
            message = revision.rev.message.rstrip('\r\n')
724
589
            for l in message.split('\n'):
725
 
                to_file.write('      %s\n' % (l,))
 
590
                print >>to_file,  '      ' + l
726
591
 
727
592
        # TODO: Why not show the modified files in a shorter form as
728
593
        # well? rewrap them single lines of appropriate length
729
594
        if revision.delta is not None:
730
595
            revision.delta.show(to_file, self.show_ids)
731
 
        to_file.write('\n')
 
596
        print >>to_file, ''
732
597
 
733
598
 
734
599
class LineLogFormatter(LogFormatter):
735
600
 
736
 
    supports_single_merge_revision = True
737
 
 
738
601
    def __init__(self, *args, **kwargs):
 
602
        from bzrlib.osutils import terminal_width
739
603
        super(LineLogFormatter, self).__init__(*args, **kwargs)
740
604
        self._max_chars = terminal_width() - 1
741
605
 
745
609
        return str[:max_len-3]+'...'
746
610
 
747
611
    def date_string(self, rev):
 
612
        from bzrlib.osutils import format_date
748
613
        return format_date(rev.timestamp, rev.timezone or 0, 
749
614
                           self.show_timezone, date_fmt="%Y-%m-%d",
750
615
                           show_offset=False)
757
622
 
758
623
    @deprecated_method(zero_seventeen)
759
624
    def show(self, revno, rev, delta):
760
 
        self.to_file.write(self.log_string(revno, rev, terminal_width()-1))
761
 
        self.to_file.write('\n')
 
625
        from bzrlib.osutils import terminal_width
 
626
        print >> self.to_file, self.log_string(revno, rev, terminal_width()-1)
762
627
 
763
628
    def log_revision(self, revision):
764
 
        self.to_file.write(self.log_string(revision.revno, revision.rev,
765
 
                                              self._max_chars))
766
 
        self.to_file.write('\n')
 
629
        print >>self.to_file, self.log_string(revision.revno, revision.rev,
 
630
                                              self._max_chars)
767
631
 
768
632
    def log_string(self, revno, rev, max_chars):
769
633
        """Format log info into one string. Truncate tail of string
777
641
        if revno:
778
642
            # show revno only when is not None
779
643
            out.append("%s:" % revno)
780
 
        out.append(self.truncate(self.short_author(rev), 20))
 
644
        out.append(self.truncate(self.short_committer(rev), 20))
781
645
        out.append(self.date_string(rev))
782
646
        out.append(rev.get_summary())
783
647
        return self.truncate(" ".join(out).rstrip('\n'), max_chars)
824
688
    name -- Name of the formatter to construct; currently 'long', 'short' and
825
689
        'line' are supported.
826
690
    """
 
691
    from bzrlib.errors import BzrCommandError
827
692
    try:
828
693
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
829
694
    except KeyError:
836
701
    lf.show(revno, rev, delta)
837
702
 
838
703
 
839
 
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
840
 
                           log_format='long'):
 
704
def show_changed_revisions(branch, old_rh, new_rh, to_file=None, log_format='long'):
841
705
    """Show the change in revision history comparing the old revision history to the new one.
842
706
 
843
707
    :param branch: The branch where the revisions exist
846
710
    :param to_file: A file to write the results to. If None, stdout will be used
847
711
    """
848
712
    if to_file is None:
849
 
        to_file = codecs.getwriter(get_terminal_encoding())(sys.stdout,
850
 
            errors='replace')
 
713
        import sys
 
714
        import codecs
 
715
        import bzrlib
 
716
        to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
851
717
    lf = log_formatter(log_format,
852
718
                       show_ids=False,
853
719
                       to_file=to_file,
884
750
        show_log(branch,
885
751
                 lf,
886
752
                 None,
887
 
                 verbose=False,
 
753
                 verbose=True,
888
754
                 direction='forward',
889
755
                 start_revision=base_idx+1,
890
756
                 end_revision=len(new_rh),