~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Ian Clatworthy
  • Date: 2008-03-27 07:51:10 UTC
  • mto: (3311.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3312.
  • Revision ID: ian.clatworthy@canonical.com-20080327075110-afgd7x03ybju06ez
Reduce evangelism in the User Guide

Show diffs side-by-side

added added

removed removed

Lines of Context:
59
59
    warn,
60
60
    )
61
61
 
62
 
from bzrlib.lazy_import import lazy_import
63
 
lazy_import(globals(), """
64
 
 
65
62
from bzrlib import (
66
63
    config,
67
 
    errors,
68
 
    repository as _mod_repository,
69
 
    revision as _mod_revision,
70
 
    revisionspec,
71
 
    trace,
72
 
    tsort,
73
 
    )
74
 
""")
75
 
 
76
 
from bzrlib import (
 
64
    lazy_regex,
77
65
    registry,
78
66
    )
 
67
from bzrlib.errors import (
 
68
    BzrCommandError,
 
69
    )
79
70
from bzrlib.osutils import (
80
71
    format_date,
81
72
    get_terminal_encoding,
82
73
    terminal_width,
83
74
    )
 
75
from bzrlib.revision import (
 
76
    NULL_REVISION,
 
77
    )
 
78
from bzrlib.revisionspec import (
 
79
    RevisionInfo,
 
80
    )
 
81
from bzrlib.trace import mutter
 
82
from bzrlib.tsort import (
 
83
    merge_sort,
 
84
    topo_sort,
 
85
    )
84
86
 
85
87
 
86
88
def find_touching_revisions(branch, file_id):
201
203
        warn("not a LogFormatter instance: %r" % lf)
202
204
 
203
205
    if specific_fileid:
204
 
        trace.mutter('get log for file_id %r', specific_fileid)
 
206
        mutter('get log for file_id %r', specific_fileid)
205
207
    generate_merge_revisions = getattr(lf, 'supports_merge_revisions', False)
206
208
    allow_single_merge_revision = getattr(lf,
207
209
        'supports_single_merge_revision', False)
210
212
                                              specific_fileid,
211
213
                                              generate_merge_revisions,
212
214
                                              allow_single_merge_revision)
 
215
    if search is not None:
 
216
        searchRE = re.compile(search, re.IGNORECASE)
 
217
    else:
 
218
        searchRE = None
 
219
 
213
220
    rev_tag_dict = {}
214
221
    generate_tags = getattr(lf, 'supports_tags', False)
215
222
    if generate_tags:
220
227
 
221
228
    # now we just print all the revisions
222
229
    log_count = 0
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
 
230
    for (rev_id, revno, merge_depth), rev, delta in _iter_revisions(
 
231
        branch.repository, view_revisions, generate_delta):
 
232
        if searchRE:
 
233
            if not searchRE.search(rev.message):
 
234
                continue
 
235
 
 
236
        lr = LogRevision(rev, revno, merge_depth, delta,
 
237
                         rev_tag_dict.get(rev_id))
 
238
        lf.log_revision(lr)
 
239
        if limit:
 
240
            log_count += 1
 
241
            if log_count >= limit:
 
242
                break
234
243
 
235
244
 
236
245
def calculate_view_revisions(branch, start_revision, end_revision, direction,
237
246
                             specific_fileid, generate_merge_revisions,
238
247
                             allow_single_merge_revision):
239
 
    if (    not generate_merge_revisions
240
 
        and start_revision is end_revision is None
241
 
        and direction == 'reverse'
242
 
        and specific_fileid is None):
 
248
    if (not generate_merge_revisions and start_revision is end_revision is
 
249
        None and direction == 'reverse' and specific_fileid is None):
243
250
        return _linear_view_revisions(branch)
244
251
 
245
 
    mainline_revs, rev_nos, start_rev_id, end_rev_id = _get_mainline_revs(
246
 
        branch, start_revision, end_revision)
 
252
    mainline_revs, rev_nos, start_rev_id, end_rev_id = \
 
253
        _get_mainline_revs(branch, start_revision, end_revision)
247
254
    if not mainline_revs:
248
255
        return []
249
256
 
 
257
    if direction == 'reverse':
 
258
        start_rev_id, end_rev_id = end_rev_id, start_rev_id
 
259
 
250
260
    generate_single_revision = False
251
261
    if ((not generate_merge_revisions)
252
262
        and ((start_rev_id and (start_rev_id not in rev_nos))
254
264
        generate_single_revision = ((start_rev_id == end_rev_id)
255
265
            and allow_single_merge_revision)
256
266
        if not generate_single_revision:
257
 
            raise errors.BzrCommandError('Selected log formatter only supports'
258
 
                ' mainline revisions.')
 
267
            raise BzrCommandError('Selected log formatter only supports '
 
268
                'mainline revisions.')
259
269
        generate_merge_revisions = generate_single_revision
260
270
    view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
261
271
                          direction, include_merges=generate_merge_revisions)
262
 
 
263
 
    if direction == 'reverse':
264
 
        start_rev_id, end_rev_id = end_rev_id, start_rev_id
265
272
    view_revisions = _filter_revision_range(list(view_revs_iter),
266
273
                                            start_rev_id,
267
274
                                            end_rev_id)
269
276
        view_revisions = view_revisions[0:1]
270
277
    if specific_fileid:
271
278
        view_revisions = _filter_revisions_touching_file_id(branch,
272
 
                                                            specific_fileid,
273
 
                                                            view_revisions)
 
279
                                                         specific_fileid,
 
280
                                                         mainline_revs,
 
281
                                                         view_revisions)
274
282
 
275
283
    # rebase merge_depth - unless there are no revisions or 
276
284
    # either the first or last revision have merge_depth = 0.
289
297
        yield revision_id, str(start_revno - num), 0
290
298
 
291
299
 
292
 
def make_log_rev_iterator(branch, view_revisions, generate_delta, search):
293
 
    """Create a revision iterator for log.
294
 
 
295
 
    :param branch: The branch being logged.
296
 
    :param view_revisions: The revisions being viewed.
297
 
    :param generate_delta: Whether to generate a delta for each revision.
298
 
    :param search: A user text search string.
299
 
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
300
 
        delta).
301
 
    """
302
 
    # Convert view_revisions into (view, None, None) groups to fit with
303
 
    # the standard interface here.
304
 
    if type(view_revisions) == list:
305
 
        # A single batch conversion is faster than many incremental ones.
306
 
        # As we have all the data, do a batch conversion.
307
 
        nones = [None] * len(view_revisions)
308
 
        log_rev_iterator = iter([zip(view_revisions, nones, nones)])
309
 
    else:
310
 
        def _convert():
311
 
            for view in view_revisions:
312
 
                yield (view, None, None)
313
 
        log_rev_iterator = iter([_convert()])
314
 
    for adapter in log_adapters:
315
 
        log_rev_iterator = adapter(branch, generate_delta, search,
316
 
            log_rev_iterator)
317
 
    return log_rev_iterator
318
 
 
319
 
 
320
 
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
321
 
    """Create a filtered iterator of log_rev_iterator matching on a regex.
322
 
 
323
 
    :param branch: The branch being logged.
324
 
    :param generate_delta: Whether to generate a delta for each revision.
325
 
    :param search: A user text search string.
326
 
    :param log_rev_iterator: An input iterator containing all revisions that
327
 
        could be displayed, in lists.
328
 
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
329
 
        delta).
330
 
    """
331
 
    if search is None:
332
 
        return log_rev_iterator
333
 
    # Compile the search now to get early errors.
334
 
    searchRE = re.compile(search, re.IGNORECASE)
335
 
    return _filter_message_re(searchRE, log_rev_iterator)
336
 
 
337
 
 
338
 
def _filter_message_re(searchRE, log_rev_iterator):
339
 
    for revs in log_rev_iterator:
340
 
        new_revs = []
341
 
        for (rev_id, revno, merge_depth), rev, delta in revs:
342
 
            if searchRE.search(rev.message):
343
 
                new_revs.append(((rev_id, revno, merge_depth), rev, delta))
344
 
        yield new_revs
345
 
 
346
 
 
347
 
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator):
348
 
    """Add revision deltas to a log iterator if needed.
349
 
 
350
 
    :param branch: The branch being logged.
351
 
    :param generate_delta: Whether to generate a delta for each revision.
352
 
    :param search: A user text search string.
353
 
    :param log_rev_iterator: An input iterator containing all revisions that
354
 
        could be displayed, in lists.
355
 
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
356
 
        delta).
357
 
    """
358
 
    if not generate_delta:
359
 
        return log_rev_iterator
360
 
    return _generate_deltas(branch.repository, log_rev_iterator)
361
 
 
362
 
 
363
 
def _generate_deltas(repository, log_rev_iterator):
364
 
    """Create deltas for each batch of revisions in log_rev_iterator."""
365
 
    for revs in log_rev_iterator:
366
 
        revisions = [rev[1] for rev in revs]
367
 
        deltas = repository.get_deltas_for_revisions(revisions)
368
 
        revs = [(rev[0], rev[1], delta) for rev, delta in izip(revs, deltas)]
369
 
        yield revs
370
 
 
371
 
 
372
 
def _make_revision_objects(branch, generate_delta, search, log_rev_iterator):
373
 
    """Extract revision objects from the repository
374
 
 
375
 
    :param branch: The branch being logged.
376
 
    :param generate_delta: Whether to generate a delta for each revision.
377
 
    :param search: A user text search string.
378
 
    :param log_rev_iterator: An input iterator containing all revisions that
379
 
        could be displayed, in lists.
380
 
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
381
 
        delta).
382
 
    """
383
 
    repository = branch.repository
384
 
    for revs in log_rev_iterator:
385
 
        # r = revision_id, n = revno, d = merge depth
386
 
        revision_ids = [view[0] for view, _, _ in revs]
 
300
def _iter_revisions(repository, view_revisions, generate_delta):
 
301
    num = 9
 
302
    view_revisions = iter(view_revisions)
 
303
    while True:
 
304
        cur_view_revisions = [d for x, d in zip(range(num), view_revisions)]
 
305
        if len(cur_view_revisions) == 0:
 
306
            break
 
307
        cur_deltas = {}
 
308
        # r = revision, n = revno, d = merge depth
 
309
        revision_ids = [r for (r, n, d) in cur_view_revisions]
387
310
        revisions = repository.get_revisions(revision_ids)
388
 
        revs = [(rev[0], revision, rev[2]) for rev, revision in
389
 
            izip(revs, revisions)]
390
 
        yield revs
391
 
 
392
 
 
393
 
def _make_batch_filter(branch, generate_delta, search, log_rev_iterator):
394
 
    """Group up a single large batch into smaller ones.
395
 
 
396
 
    :param branch: The branch being logged.
397
 
    :param generate_delta: Whether to generate a delta for each revision.
398
 
    :param search: A user text search string.
399
 
    :param log_rev_iterator: An input iterator containing all revisions that
400
 
        could be displayed, in lists.
401
 
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev, delta).
402
 
    """
403
 
    repository = branch.repository
404
 
    num = 9
405
 
    for batch in log_rev_iterator:
406
 
        batch = iter(batch)
407
 
        while True:
408
 
            step = [detail for _, detail in zip(range(num), batch)]
409
 
            if len(step) == 0:
410
 
                break
411
 
            yield step
412
 
            num = min(int(num * 1.5), 200)
 
311
        if generate_delta:
 
312
            deltas = repository.get_deltas_for_revisions(revisions)
 
313
            cur_deltas = dict(izip((r.revision_id for r in revisions),
 
314
                                   deltas))
 
315
        for view_data, revision in izip(cur_view_revisions, revisions):
 
316
            yield view_data, revision, cur_deltas.get(revision.revision_id)
 
317
        num = min(int(num * 1.5), 200)
413
318
 
414
319
 
415
320
def _get_mainline_revs(branch, start_revision, end_revision):
429
334
 
430
335
    :return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
431
336
    """
432
 
    branch_revno, branch_last_revision = branch.last_revision_info()
433
 
    if branch_revno == 0:
 
337
    which_revs = _enumerate_history(branch)
 
338
    if not which_revs:
434
339
        return None, None, None, None
435
340
 
436
341
    # For mainline generation, map start_revision and end_revision to 
439
344
    # filtered later.
440
345
    # Also map the revisions to rev_ids, to be used in the later filtering
441
346
    # stage.
442
 
    start_rev_id = None
 
347
    start_rev_id = None 
443
348
    if start_revision is None:
444
349
        start_revno = 1
445
350
    else:
446
 
        if isinstance(start_revision, revisionspec.RevisionInfo):
 
351
        if isinstance(start_revision,RevisionInfo):
447
352
            start_rev_id = start_revision.rev_id
448
353
            start_revno = start_revision.revno or 1
449
354
        else:
450
355
            branch.check_real_revno(start_revision)
451
356
            start_revno = start_revision
452
 
 
 
357
    
453
358
    end_rev_id = None
454
359
    if end_revision is None:
455
 
        end_revno = branch_revno
 
360
        end_revno = len(which_revs)
456
361
    else:
457
 
        if isinstance(end_revision, revisionspec.RevisionInfo):
 
362
        if isinstance(end_revision,RevisionInfo):
458
363
            end_rev_id = end_revision.rev_id
459
 
            end_revno = end_revision.revno or branch_revno
 
364
            end_revno = end_revision.revno or len(which_revs)
460
365
        else:
461
366
            branch.check_real_revno(end_revision)
462
367
            end_revno = end_revision
463
368
 
464
 
    if ((start_rev_id == _mod_revision.NULL_REVISION)
465
 
        or (end_rev_id == _mod_revision.NULL_REVISION)):
466
 
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
 
369
    if ((start_rev_id == NULL_REVISION)
 
370
        or (end_rev_id == NULL_REVISION)):
 
371
        raise BzrCommandError('Logging revision 0 is invalid.')
467
372
    if start_revno > end_revno:
468
 
        raise errors.BzrCommandError("Start revision must be older than "
469
 
                                     "the end revision.")
 
373
        raise BzrCommandError("Start revision must be older than "
 
374
                              "the end revision.")
470
375
 
471
 
    if end_revno < start_revno:
 
376
    # list indexes are 0-based; revisions are 1-based
 
377
    cut_revs = which_revs[(start_revno-1):(end_revno)]
 
378
    if not cut_revs:
472
379
        return None, None, None, None
473
 
    cur_revno = branch_revno
474
 
    rev_nos = {}
475
 
    mainline_revs = []
476
 
    for revision_id in branch.repository.iter_reverse_revision_history(
477
 
                        branch_last_revision):
478
 
        if cur_revno < start_revno:
479
 
            # We have gone far enough, but we always add 1 more revision
480
 
            rev_nos[revision_id] = cur_revno
481
 
            mainline_revs.append(revision_id)
482
 
            break
483
 
        if cur_revno <= end_revno:
484
 
            rev_nos[revision_id] = cur_revno
485
 
            mainline_revs.append(revision_id)
486
 
        cur_revno -= 1
487
 
    else:
488
 
        # We walked off the edge of all revisions, so we add a 'None' marker
489
 
        mainline_revs.append(None)
490
380
 
491
 
    mainline_revs.reverse()
 
381
    # convert the revision history to a dictionary:
 
382
    rev_nos = dict((k, v) for v, k in cut_revs)
492
383
 
493
384
    # override the mainline to look like the revision history.
 
385
    mainline_revs = [revision_id for index, revision_id in cut_revs]
 
386
    if cut_revs[0][0] == 1:
 
387
        mainline_revs.insert(0, None)
 
388
    else:
 
389
        mainline_revs.insert(0, which_revs[start_revno-2][1])
494
390
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
495
391
 
496
392
 
508
404
 
509
405
    :return: The filtered view_revisions.
510
406
    """
511
 
    if start_rev_id or end_rev_id:
 
407
    if start_rev_id or end_rev_id: 
512
408
        revision_ids = [r for r, n, d in view_revisions]
513
409
        if start_rev_id:
514
410
            start_index = revision_ids.index(start_rev_id)
537
433
    return view_revisions
538
434
 
539
435
 
540
 
def _filter_revisions_touching_file_id(branch, file_id, view_revisions):
541
 
    r"""Return the list of revision ids which touch a given file id.
 
436
def _filter_revisions_touching_file_id(branch, file_id, mainline_revisions,
 
437
                                       view_revs_iter):
 
438
    """Return the list of revision ids which touch a given file id.
542
439
 
543
440
    The function filters view_revisions and returns a subset.
544
441
    This includes the revisions which directly change the file id,
545
442
    and the revisions which merge these changes. So if the
546
443
    revision graph is::
547
 
        A-.
548
 
        |\ \
549
 
        B C E
550
 
        |/ /
551
 
        D |
552
 
        |\|
553
 
        | F
 
444
        A
 
445
        |\
 
446
        B C
554
447
        |/
555
 
        G
556
 
 
557
 
    And 'C' changes a file, then both C and D will be returned. F will not be
558
 
    returned even though it brings the changes to C into the branch starting
559
 
    with E. (Note that if we were using F as the tip instead of G, then we
560
 
    would see C, D, F.)
561
 
 
562
 
    This will also be restricted based on a subset of the mainline.
563
 
 
564
 
    :param branch: The branch where we can get text revision information.
565
 
 
566
 
    :param file_id: Filter out revisions that do not touch file_id.
567
 
 
568
 
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
569
 
        tuples. This is the list of revisions which will be filtered. It is
570
 
        assumed that view_revisions is in merge_sort order (i.e. newest
571
 
        revision first ).
 
448
        D
 
449
 
 
450
    And 'C' changes a file, then both C and D will be returned.
 
451
 
 
452
    This will also can be restricted based on a subset of the mainline.
572
453
 
573
454
    :return: A list of (revision_id, dotted_revno, merge_depth) tuples.
574
455
    """
575
 
    # Lookup all possible text keys to determine which ones actually modified
576
 
    # the file.
577
 
    text_keys = [(file_id, rev_id) for rev_id, revno, depth in view_revisions]
578
 
    # Looking up keys in batches of 1000 can cut the time in half, as well as
579
 
    # memory consumption. GraphIndex *does* like to look for a few keys in
580
 
    # parallel, it just doesn't like looking for *lots* of keys in parallel.
581
 
    # TODO: This code needs to be re-evaluated periodically as we tune the
582
 
    #       indexing layer. We might consider passing in hints as to the known
583
 
    #       access pattern (sparse/clustered, high success rate/low success
584
 
    #       rate). This particular access is clustered with a low success rate.
585
 
    get_parent_map = branch.repository.texts.get_parent_map
586
 
    modified_text_revisions = set()
587
 
    chunk_size = 1000
588
 
    for start in xrange(0, len(text_keys), chunk_size):
589
 
        next_keys = text_keys[start:start + chunk_size]
590
 
        # Only keep the revision_id portion of the key
591
 
        modified_text_revisions.update(
592
 
            [k[1] for k in get_parent_map(next_keys)])
593
 
    del text_keys, next_keys
594
 
 
595
 
    result = []
596
 
    # Track what revisions will merge the current revision, replace entries
597
 
    # with 'None' when they have been added to result
598
 
    current_merge_stack = [None]
599
 
    for info in view_revisions:
600
 
        rev_id, revno, depth = info
601
 
        if depth == len(current_merge_stack):
602
 
            current_merge_stack.append(info)
 
456
    # find all the revisions that change the specific file
 
457
    file_weave = branch.repository.weave_store.get_weave(file_id,
 
458
                branch.repository.get_transaction())
 
459
    weave_modifed_revisions = set(file_weave.versions())
 
460
    # build the ancestry of each revision in the graph
 
461
    # - only listing the ancestors that change the specific file.
 
462
    rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
 
463
    sorted_rev_list = topo_sort(rev_graph)
 
464
    ancestry = {}
 
465
    for rev in sorted_rev_list:
 
466
        parents = rev_graph[rev]
 
467
        if rev not in weave_modifed_revisions and len(parents) == 1:
 
468
            # We will not be adding anything new, so just use a reference to
 
469
            # the parent ancestry.
 
470
            rev_ancestry = ancestry[parents[0]]
603
471
        else:
604
 
            del current_merge_stack[depth + 1:]
605
 
            current_merge_stack[-1] = info
606
 
 
607
 
        if rev_id in modified_text_revisions:
608
 
            # This needs to be logged, along with the extra revisions
609
 
            for idx in xrange(len(current_merge_stack)):
610
 
                node = current_merge_stack[idx]
611
 
                if node is not None:
612
 
                    result.append(node)
613
 
                    current_merge_stack[idx] = None
614
 
    return result
 
472
            rev_ancestry = set()
 
473
            if rev in weave_modifed_revisions:
 
474
                rev_ancestry.add(rev)
 
475
            for parent in parents:
 
476
                rev_ancestry = rev_ancestry.union(ancestry[parent])
 
477
        ancestry[rev] = rev_ancestry
 
478
 
 
479
    def is_merging_rev(r):
 
480
        parents = rev_graph[r]
 
481
        if len(parents) > 1:
 
482
            leftparent = parents[0]
 
483
            for rightparent in parents[1:]:
 
484
                if not ancestry[leftparent].issuperset(
 
485
                        ancestry[rightparent]):
 
486
                    return True
 
487
        return False
 
488
 
 
489
    # filter from the view the revisions that did not change or merge 
 
490
    # the specific file
 
491
    return [(r, n, d) for r, n, d in view_revs_iter
 
492
            if r in weave_modifed_revisions or is_merging_rev(r)]
615
493
 
616
494
 
617
495
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
627
505
        for revision_id in revision_ids:
628
506
            yield revision_id, str(rev_nos[revision_id]), 0
629
507
        return
630
 
    graph = branch.repository.get_graph()
631
 
    # This asks for all mainline revisions, which means we only have to spider
632
 
    # sideways, rather than depth history. That said, its still size-of-history
633
 
    # and should be addressed.
634
 
    # mainline_revisions always includes an extra revision at the beginning, so
635
 
    # don't request it.
636
 
    parent_map = dict(((key, value) for key, value in
637
 
        graph.iter_ancestry(mainline_revs[1:]) if value is not None))
638
 
    # filter out ghosts; merge_sort errors on ghosts.
639
 
    rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
640
 
    merge_sorted_revisions = tsort.merge_sort(
641
 
        rev_graph,
 
508
    merge_sorted_revisions = merge_sort(
 
509
        branch.repository.get_revision_graph(mainline_revs[-1]),
642
510
        mainline_revs[-1],
643
511
        mainline_revs,
644
512
        generate_revno=True)
660
528
    revision of that depth.  There may be no topological justification for this,
661
529
    but it looks much nicer.
662
530
    """
663
 
    # Add a fake revision at start so that we can always attach sub revisions
664
 
    merge_sorted_revisions = [(None, None, _depth)] + merge_sorted_revisions
665
531
    zd_revisions = []
666
532
    for val in merge_sorted_revisions:
667
533
        if val[2] == _depth:
668
 
            # Each revision at the current depth becomes a chunk grouping all
669
 
            # higher depth revisions.
670
534
            zd_revisions.append([val])
671
535
        else:
 
536
            assert val[2] > _depth
672
537
            zd_revisions[-1].append(val)
673
538
    for revisions in zd_revisions:
674
539
        if len(revisions) > 1:
675
 
            # We have higher depth revisions, let reverse them locally
676
540
            revisions[1:] = reverse_by_depth(revisions[1:], _depth + 1)
677
541
    zd_revisions.reverse()
678
542
    result = []
679
543
    for chunk in zd_revisions:
680
544
        result.extend(chunk)
681
 
    if _depth == 0:
682
 
        # Top level call, get rid of the fake revisions that have been added
683
 
        result = [r for r in result if r[0] is not None and r[1] is not None]
684
545
    return result
685
546
 
686
547
 
723
584
        only relevant if supports_merge_revisions is not True.
724
585
    - supports_tags must be True if this log formatter supports tags.
725
586
        Otherwise the tags attribute may not be populated.
726
 
 
727
 
    Plugins can register functions to show custom revision properties using
728
 
    the properties_handler_registry. The registered function
729
 
    must respect the following interface description:
730
 
        def my_show_properties(properties_dict):
731
 
            # code that returns a dict {'name':'value'} of the properties 
732
 
            # to be shown
733
587
    """
734
588
 
735
589
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
759
613
            return name
760
614
        return address
761
615
 
762
 
    def show_properties(self, revision, indent):
763
 
        """Displays the custom properties returned by each registered handler.
764
 
        
765
 
        If a registered handler raises an error it is propagated.
766
 
        """
767
 
        for key, handler in properties_handler_registry.iteritems():
768
 
            for key, value in handler(revision).items():
769
 
                self.to_file.write(indent + key + ': ' + value + '\n')
770
 
 
771
616
 
772
617
class LongLogFormatter(LogFormatter):
773
618
 
789
634
            to_file.write('\n')
790
635
            for parent_id in revision.rev.parent_ids:
791
636
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
792
 
        self.show_properties(revision.rev, indent)
793
637
 
794
638
        author = revision.rev.properties.get('author', None)
795
639
        if author is not None:
823
667
 
824
668
    def log_revision(self, revision):
825
669
        to_file = self.to_file
 
670
        date_str = format_date(revision.rev.timestamp,
 
671
                               revision.rev.timezone or 0,
 
672
                               self.show_timezone)
826
673
        is_merge = ''
827
674
        if len(revision.rev.parent_ids) > 1:
828
675
            is_merge = ' [merge]'
863
710
        return str[:max_len-3]+'...'
864
711
 
865
712
    def date_string(self, rev):
866
 
        return format_date(rev.timestamp, rev.timezone or 0,
 
713
        return format_date(rev.timestamp, rev.timezone or 0, 
867
714
                           self.show_timezone, date_fmt="%Y-%m-%d",
868
715
                           show_offset=False)
869
716
 
880
727
 
881
728
    def log_string(self, revno, rev, max_chars):
882
729
        """Format log info into one string. Truncate tail of string
883
 
        :param  revno:      revision number or None.
 
730
        :param  revno:      revision number (int) or None.
884
731
                            Revision numbers counts from 1.
885
732
        :param  rev:        revision info object
886
733
        :param  max_chars:  maximum length of resulting string
940
787
    try:
941
788
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
942
789
    except KeyError:
943
 
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
 
790
        raise BzrCommandError("unknown log formatter: %r" % name)
944
791
 
945
792
 
946
793
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1003
850
                 end_revision=len(new_rh),
1004
851
                 search=None)
1005
852
 
1006
 
 
1007
 
properties_handler_registry = registry.Registry()
1008
 
properties_handler_registry.register_lazy("foreign",
1009
 
                                          "bzrlib.foreign",
1010
 
                                          "show_foreign_properties")
1011
 
 
1012
 
 
1013
 
# adapters which revision ids to log are filtered. When log is called, the
1014
 
# log_rev_iterator is adapted through each of these factory methods.
1015
 
# Plugins are welcome to mutate this list in any way they like - as long
1016
 
# as the overall behaviour is preserved. At this point there is no extensible
1017
 
# mechanism for getting parameters to each factory method, and until there is
1018
 
# this won't be considered a stable api.
1019
 
log_adapters = [
1020
 
    # core log logic
1021
 
    _make_batch_filter,
1022
 
    # read revision objects
1023
 
    _make_revision_objects,
1024
 
    # filter on log messages
1025
 
    _make_search_filter,
1026
 
    # generate deltas for things we will show
1027
 
    _make_delta_filter
1028
 
    ]