/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Andrew Bennetts
  • Date: 2008-09-08 12:59:00 UTC
  • mfrom: (3695 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3756.
  • Revision ID: andrew.bennetts@canonical.com-20080908125900-8ywtsr7jqyyatjz0
Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
61
61
 
62
62
from bzrlib.lazy_import import lazy_import
63
63
lazy_import(globals(), """
 
64
 
64
65
from bzrlib import (
 
66
    config,
65
67
    errors,
66
 
    revision,
 
68
    repository as _mod_repository,
 
69
    revision as _mod_revision,
67
70
    revisionspec,
 
71
    trace,
68
72
    tsort,
69
73
    )
70
74
""")
71
75
 
72
76
from bzrlib import (
73
 
    config,
74
77
    registry,
75
78
    )
76
79
from bzrlib.osutils import (
78
81
    get_terminal_encoding,
79
82
    terminal_width,
80
83
    )
81
 
from bzrlib.repository import _strip_NULL_ghosts
82
 
from bzrlib.trace import mutter
83
84
 
84
85
 
85
86
def find_touching_revisions(branch, file_id):
200
201
        warn("not a LogFormatter instance: %r" % lf)
201
202
 
202
203
    if specific_fileid:
203
 
        mutter('get log for file_id %r', specific_fileid)
 
204
        trace.mutter('get log for file_id %r', specific_fileid)
204
205
    generate_merge_revisions = getattr(lf, 'supports_merge_revisions', False)
205
206
    allow_single_merge_revision = getattr(lf,
206
207
        'supports_single_merge_revision', False)
209
210
                                              specific_fileid,
210
211
                                              generate_merge_revisions,
211
212
                                              allow_single_merge_revision)
212
 
    if search is not None:
213
 
        searchRE = re.compile(search, re.IGNORECASE)
214
 
    else:
215
 
        searchRE = None
216
 
 
217
213
    rev_tag_dict = {}
218
214
    generate_tags = getattr(lf, 'supports_tags', False)
219
215
    if generate_tags:
224
220
 
225
221
    # now we just print all the revisions
226
222
    log_count = 0
227
 
    for (rev_id, revno, merge_depth), rev, delta in _iter_revisions(
228
 
        branch.repository, view_revisions, generate_delta):
229
 
        if searchRE:
230
 
            if not searchRE.search(rev.message):
231
 
                continue
232
 
 
233
 
        lr = LogRevision(rev, revno, merge_depth, delta,
234
 
                         rev_tag_dict.get(rev_id))
235
 
        lf.log_revision(lr)
236
 
        if limit:
237
 
            log_count += 1
238
 
            if log_count >= limit:
239
 
                break
 
223
    revision_iterator = make_log_rev_iterator(branch, view_revisions,
 
224
        generate_delta, search)
 
225
    for revs in revision_iterator:
 
226
        for (rev_id, revno, merge_depth), rev, delta in revs:
 
227
            lr = LogRevision(rev, revno, merge_depth, delta,
 
228
                             rev_tag_dict.get(rev_id))
 
229
            lf.log_revision(lr)
 
230
            if limit:
 
231
                log_count += 1
 
232
                if log_count >= limit:
 
233
                    return
240
234
 
241
235
 
242
236
def calculate_view_revisions(branch, start_revision, end_revision, direction,
261
255
        generate_single_revision = ((start_rev_id == end_rev_id)
262
256
            and allow_single_merge_revision)
263
257
        if not generate_single_revision:
264
 
            raise errors.BzrCommandError('Selected log formatter only supports '
265
 
                'mainline revisions.')
 
258
            raise errors.BzrCommandError('Selected log formatter only supports'
 
259
                ' mainline revisions.')
266
260
        generate_merge_revisions = generate_single_revision
267
261
    view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
268
262
                          direction, include_merges=generate_merge_revisions)
294
288
        yield revision_id, str(start_revno - num), 0
295
289
 
296
290
 
297
 
def _iter_revisions(repository, view_revisions, generate_delta):
 
291
def make_log_rev_iterator(branch, view_revisions, generate_delta, search):
 
292
    """Create a revision iterator for log.
 
293
 
 
294
    :param branch: The branch being logged.
 
295
    :param view_revisions: The revisions being viewed.
 
296
    :param generate_delta: Whether to generate a delta for each revision.
 
297
    :param search: A user text search string.
 
298
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
 
299
        delta).
 
300
    """
 
301
    # Convert view_revisions into (view, None, None) groups to fit with
 
302
    # the standard interface here.
 
303
    if type(view_revisions) == list:
 
304
        # A single batch conversion is faster than many incremental ones.
 
305
        # As we have all the data, do a batch conversion.
 
306
        nones = [None] * len(view_revisions)
 
307
        log_rev_iterator = iter([zip(view_revisions, nones, nones)])
 
308
    else:
 
309
        def _convert():
 
310
            for view in view_revisions:
 
311
                yield (view, None, None)
 
312
        log_rev_iterator = iter([_convert()])
 
313
    for adapter in log_adapters:
 
314
        log_rev_iterator = adapter(branch, generate_delta, search,
 
315
            log_rev_iterator)
 
316
    return log_rev_iterator
 
317
 
 
318
 
 
319
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
 
320
    """Create a filtered iterator of log_rev_iterator matching on a regex.
 
321
 
 
322
    :param branch: The branch being logged.
 
323
    :param generate_delta: Whether to generate a delta for each revision.
 
324
    :param search: A user text search string.
 
325
    :param log_rev_iterator: An input iterator containing all revisions that
 
326
        could be displayed, in lists.
 
327
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
 
328
        delta).
 
329
    """
 
330
    if search is None:
 
331
        return log_rev_iterator
 
332
    # Compile the search now to get early errors.
 
333
    searchRE = re.compile(search, re.IGNORECASE)
 
334
    return _filter_message_re(searchRE, log_rev_iterator)
 
335
 
 
336
 
 
337
def _filter_message_re(searchRE, log_rev_iterator):
 
338
    for revs in log_rev_iterator:
 
339
        new_revs = []
 
340
        for (rev_id, revno, merge_depth), rev, delta in revs:
 
341
            if searchRE.search(rev.message):
 
342
                new_revs.append(((rev_id, revno, merge_depth), rev, delta))
 
343
        yield new_revs
 
344
 
 
345
 
 
346
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator):
 
347
    """Add revision deltas to a log iterator if needed.
 
348
 
 
349
    :param branch: The branch being logged.
 
350
    :param generate_delta: Whether to generate a delta for each revision.
 
351
    :param search: A user text search string.
 
352
    :param log_rev_iterator: An input iterator containing all revisions that
 
353
        could be displayed, in lists.
 
354
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
 
355
        delta).
 
356
    """
 
357
    if not generate_delta:
 
358
        return log_rev_iterator
 
359
    return _generate_deltas(branch.repository, log_rev_iterator)
 
360
 
 
361
 
 
362
def _generate_deltas(repository, log_rev_iterator):
 
363
    """Create deltas for each batch of revisions in log_rev_iterator."""
 
364
    for revs in log_rev_iterator:
 
365
        revisions = [rev[1] for rev in revs]
 
366
        deltas = repository.get_deltas_for_revisions(revisions)
 
367
        revs = [(rev[0], rev[1], delta) for rev, delta in izip(revs, deltas)]
 
368
        yield revs
 
369
 
 
370
 
 
371
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
 
372
    """Extract revision objects from the repository
 
373
 
 
374
    :param branch: The branch being logged.
 
375
    :param generate_delta: Whether to generate a delta for each revision.
 
376
    :param search: A user text search string.
 
377
    :param log_rev_iterator: An input iterator containing all revisions that
 
378
        could be displayed, in lists.
 
379
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
 
380
        delta).
 
381
    """
 
382
    repository = branch.repository
 
383
    for revs in log_rev_iterator:
 
384
        # r = revision_id, n = revno, d = merge depth
 
385
        revision_ids = [view[0] for view, _, _ in revs]
 
386
        revisions = repository.get_revisions(revision_ids)
 
387
        revs = [(rev[0], revision, rev[2]) for rev, revision in
 
388
            izip(revs, revisions)]
 
389
        yield revs
 
390
 
 
391
 
 
392
def _make_batch_filter(branch, generate_delta, search, log_rev_iterator):
 
393
    """Group up a single large batch into smaller ones.
 
394
 
 
395
    :param branch: The branch being logged.
 
396
    :param generate_delta: Whether to generate a delta for each revision.
 
397
    :param search: A user text search string.
 
398
    :param log_rev_iterator: An input iterator containing all revisions that
 
399
        could be displayed, in lists.
 
400
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev, delta).
 
401
    """
 
402
    repository = branch.repository
298
403
    num = 9
299
 
    view_revisions = iter(view_revisions)
300
 
    while True:
301
 
        cur_view_revisions = [d for x, d in zip(range(num), view_revisions)]
302
 
        if len(cur_view_revisions) == 0:
303
 
            break
304
 
        cur_deltas = {}
305
 
        # r = revision, n = revno, d = merge depth
306
 
        revision_ids = [r for (r, n, d) in cur_view_revisions]
307
 
        revisions = repository.get_revisions(revision_ids)
308
 
        if generate_delta:
309
 
            deltas = repository.get_deltas_for_revisions(revisions)
310
 
            cur_deltas = dict(izip((r.revision_id for r in revisions),
311
 
                                   deltas))
312
 
        for view_data, revision in izip(cur_view_revisions, revisions):
313
 
            yield view_data, revision, cur_deltas.get(revision.revision_id)
314
 
        num = min(int(num * 1.5), 200)
 
404
    for batch in log_rev_iterator:
 
405
        batch = iter(batch)
 
406
        while True:
 
407
            step = [detail for _, detail in zip(range(num), batch)]
 
408
            if len(step) == 0:
 
409
                break
 
410
            yield step
 
411
            num = min(int(num * 1.5), 200)
315
412
 
316
413
 
317
414
def _get_mainline_revs(branch, start_revision, end_revision):
331
428
 
332
429
    :return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
333
430
    """
334
 
    which_revs = _enumerate_history(branch)
335
 
    if not which_revs:
 
431
    branch_revno, branch_last_revision = branch.last_revision_info()
 
432
    if branch_revno == 0:
336
433
        return None, None, None, None
337
434
 
338
435
    # For mainline generation, map start_revision and end_revision to 
354
451
    
355
452
    end_rev_id = None
356
453
    if end_revision is None:
357
 
        end_revno = len(which_revs)
 
454
        end_revno = branch_revno
358
455
    else:
359
456
        if isinstance(end_revision, revisionspec.RevisionInfo):
360
457
            end_rev_id = end_revision.rev_id
361
 
            end_revno = end_revision.revno or len(which_revs)
 
458
            end_revno = end_revision.revno or branch_revno
362
459
        else:
363
460
            branch.check_real_revno(end_revision)
364
461
            end_revno = end_revision
365
462
 
366
 
    if ((start_rev_id == revision.NULL_REVISION)
367
 
        or (end_rev_id == revision.NULL_REVISION)):
 
463
    if ((start_rev_id == _mod_revision.NULL_REVISION)
 
464
        or (end_rev_id == _mod_revision.NULL_REVISION)):
368
465
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
369
466
    if start_revno > end_revno:
370
467
        raise errors.BzrCommandError("Start revision must be older than "
371
468
                                     "the end revision.")
372
469
 
373
 
    # list indexes are 0-based; revisions are 1-based
374
 
    cut_revs = which_revs[(start_revno-1):(end_revno)]
375
 
    if not cut_revs:
 
470
    if end_revno < start_revno:
376
471
        return None, None, None, None
 
472
    cur_revno = branch_revno
 
473
    rev_nos = {}
 
474
    mainline_revs = []
 
475
    for revision_id in branch.repository.iter_reverse_revision_history(
 
476
                        branch_last_revision):
 
477
        if cur_revno < start_revno:
 
478
            # We have gone far enough, but we always add 1 more revision
 
479
            rev_nos[revision_id] = cur_revno
 
480
            mainline_revs.append(revision_id)
 
481
            break
 
482
        if cur_revno <= end_revno:
 
483
            rev_nos[revision_id] = cur_revno
 
484
            mainline_revs.append(revision_id)
 
485
        cur_revno -= 1
 
486
    else:
 
487
        # We walked off the edge of all revisions, so we add a 'None' marker
 
488
        mainline_revs.append(None)
377
489
 
378
 
    # convert the revision history to a dictionary:
379
 
    rev_nos = dict((k, v) for v, k in cut_revs)
 
490
    mainline_revs.reverse()
380
491
 
381
492
    # override the mainline to look like the revision history.
382
 
    mainline_revs = [revision_id for index, revision_id in cut_revs]
383
 
    if cut_revs[0][0] == 1:
384
 
        mainline_revs.insert(0, None)
385
 
    else:
386
 
        mainline_revs.insert(0, which_revs[start_revno-2][1])
387
493
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
388
494
 
389
495
 
451
557
    :return: A list of (revision_id, dotted_revno, merge_depth) tuples.
452
558
    """
453
559
    # find all the revisions that change the specific file
454
 
    file_weave = branch.repository.weave_store.get_weave(file_id,
455
 
                branch.repository.get_transaction())
456
 
    weave_modifed_revisions = set(file_weave.versions())
457
560
    # build the ancestry of each revision in the graph
458
561
    # - only listing the ancestors that change the specific file.
459
562
    graph = branch.repository.get_graph()
460
563
    # This asks for all mainline revisions, which means we only have to spider
461
564
    # sideways, rather than depth history. That said, its still size-of-history
462
565
    # and should be addressed.
 
566
    # mainline_revisions always includes an extra revision at the beginning, so
 
567
    # don't request it.
463
568
    parent_map = dict(((key, value) for key, value in
464
 
        graph.iter_ancestry(mainline_revisions) if value is not None))
 
569
        graph.iter_ancestry(mainline_revisions[1:]) if value is not None))
465
570
    sorted_rev_list = tsort.topo_sort(parent_map.items())
 
571
    text_keys = [(file_id, rev_id) for rev_id in sorted_rev_list]
 
572
    modified_text_versions = branch.repository.texts.get_parent_map(text_keys)
466
573
    ancestry = {}
467
574
    for rev in sorted_rev_list:
 
575
        text_key = (file_id, rev)
468
576
        parents = parent_map[rev]
469
 
        if rev not in weave_modifed_revisions and len(parents) == 1:
 
577
        if text_key not in modified_text_versions and len(parents) == 1:
470
578
            # We will not be adding anything new, so just use a reference to
471
579
            # the parent ancestry.
472
580
            rev_ancestry = ancestry[parents[0]]
473
581
        else:
474
582
            rev_ancestry = set()
475
 
            if rev in weave_modifed_revisions:
 
583
            if text_key in modified_text_versions:
476
584
                rev_ancestry.add(rev)
477
585
            for parent in parents:
 
586
                if parent not in ancestry:
 
587
                    # parent is a Ghost, which won't be present in
 
588
                    # sorted_rev_list, but we may access it later, so create an
 
589
                    # empty node for it
 
590
                    ancestry[parent] = set()
478
591
                rev_ancestry = rev_ancestry.union(ancestry[parent])
479
592
        ancestry[rev] = rev_ancestry
480
593
 
491
604
    # filter from the view the revisions that did not change or merge 
492
605
    # the specific file
493
606
    return [(r, n, d) for r, n, d in view_revs_iter
494
 
            if r in weave_modifed_revisions or is_merging_rev(r)]
 
607
            if (file_id, r) in modified_text_versions or is_merging_rev(r)]
495
608
 
496
609
 
497
610
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
511
624
    # This asks for all mainline revisions, which means we only have to spider
512
625
    # sideways, rather than depth history. That said, its still size-of-history
513
626
    # and should be addressed.
 
627
    # mainline_revisions always includes an extra revision at the beginning, so
 
628
    # don't request it.
514
629
    parent_map = dict(((key, value) for key, value in
515
 
        graph.iter_ancestry(mainline_revs) if value is not None))
 
630
        graph.iter_ancestry(mainline_revs[1:]) if value is not None))
516
631
    # filter out ghosts; merge_sort errors on ghosts.
517
 
    rev_graph = _strip_NULL_ghosts(parent_map)
 
632
    rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
518
633
    merge_sorted_revisions = tsort.merge_sort(
519
634
        rev_graph,
520
635
        mainline_revs[-1],
543
658
        if val[2] == _depth:
544
659
            zd_revisions.append([val])
545
660
        else:
546
 
            assert val[2] > _depth
547
661
            zd_revisions[-1].append(val)
548
662
    for revisions in zd_revisions:
549
663
        if len(revisions) > 1:
594
708
        only relevant if supports_merge_revisions is not True.
595
709
    - supports_tags must be True if this log formatter supports tags.
596
710
        Otherwise the tags attribute may not be populated.
 
711
 
 
712
    Plugins can register functions to show custom revision properties using
 
713
    the properties_handler_registry. The registered function
 
714
    must respect the following interface description:
 
715
        def my_show_properties(properties_dict):
 
716
            # code that returns a dict {'name':'value'} of the properties 
 
717
            # to be shown
597
718
    """
598
719
 
599
720
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
623
744
            return name
624
745
        return address
625
746
 
 
747
    def show_properties(self, revision, indent):
 
748
        """Displays the custom properties returned by each registered handler.
 
749
        
 
750
        If a registered handler raises an error it is propagated.
 
751
        """
 
752
        for key, handler in properties_handler_registry.iteritems():
 
753
            for key, value in handler(revision).items():
 
754
                self.to_file.write(indent + key + ': ' + value + '\n')
 
755
 
626
756
 
627
757
class LongLogFormatter(LogFormatter):
628
758
 
644
774
            to_file.write('\n')
645
775
            for parent_id in revision.rev.parent_ids:
646
776
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
 
777
        self.show_properties(revision.rev, indent)
647
778
 
648
779
        author = revision.rev.properties.get('author', None)
649
780
        if author is not None:
677
808
 
678
809
    def log_revision(self, revision):
679
810
        to_file = self.to_file
680
 
        date_str = format_date(revision.rev.timestamp,
681
 
                               revision.rev.timezone or 0,
682
 
                               self.show_timezone)
683
811
        is_merge = ''
684
812
        if len(revision.rev.parent_ids) > 1:
685
813
            is_merge = ' [merge]'
860
988
                 end_revision=len(new_rh),
861
989
                 search=None)
862
990
 
 
991
 
 
992
properties_handler_registry = registry.Registry()
 
993
 
 
994
# adapters which revision ids to log are filtered. When log is called, the
 
995
# log_rev_iterator is adapted through each of these factory methods.
 
996
# Plugins are welcome to mutate this list in any way they like - as long
 
997
# as the overall behaviour is preserved. At this point there is no extensible
 
998
# mechanism for getting parameters to each factory method, and until there is
 
999
# this won't be considered a stable api.
 
1000
log_adapters = [
 
1001
    # core log logic
 
1002
    _make_batch_filter,
 
1003
    # read revision objects
 
1004
    _make_revision_objects,
 
1005
    # filter on log messages
 
1006
    _make_search_filter,
 
1007
    # generate deltas for things we will show
 
1008
    _make_delta_filter
 
1009
    ]