~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-11-04 18:51:39 UTC
  • mfrom: (2961.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20071104185139-kaio3sneodg2kp71
Authentication ring implementation (read-only)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
from itertools import izip
56
53
import re
57
 
import sys
58
 
from warnings import (
59
 
    warn,
60
 
    )
61
 
 
62
 
from bzrlib.lazy_import import lazy_import
63
 
lazy_import(globals(), """
64
 
 
65
 
from bzrlib import (
66
 
    config,
67
 
    errors,
68
 
    repository as _mod_repository,
69
 
    revision as _mod_revision,
70
 
    revisionspec,
71
 
    trace,
72
 
    tsort,
73
 
    )
74
 
""")
75
54
 
76
55
from bzrlib import (
77
56
    registry,
78
 
    )
79
 
from bzrlib.osutils import (
80
 
    format_date,
81
 
    get_terminal_encoding,
82
 
    terminal_width,
 
57
    symbol_versioning,
 
58
    )
 
59
import bzrlib.errors as errors
 
60
from bzrlib.revisionspec import(
 
61
    RevisionInfo
 
62
    )
 
63
from bzrlib.symbol_versioning import (
 
64
    deprecated_method,
 
65
    zero_seventeen,
 
66
    )
 
67
from bzrlib.trace import mutter
 
68
from bzrlib.tsort import (
 
69
    merge_sort,
 
70
    topo_sort,
83
71
    )
84
72
 
85
73
 
186
174
    finally:
187
175
        branch.unlock()
188
176
 
189
 
 
190
177
def _show_log(branch,
191
178
             lf,
192
179
             specific_fileid=None,
197
184
             search=None,
198
185
             limit=None):
199
186
    """Worker function for show_log - see show_log."""
 
187
    from bzrlib.osutils import format_date
 
188
    from bzrlib.errors import BzrCheckError
 
189
    
 
190
    from warnings import warn
 
191
 
200
192
    if not isinstance(lf, LogFormatter):
201
193
        warn("not a LogFormatter instance: %r" % lf)
202
194
 
203
195
    if specific_fileid:
204
 
        trace.mutter('get log for file_id %r', specific_fileid)
205
 
    generate_merge_revisions = getattr(lf, 'supports_merge_revisions', False)
206
 
    allow_single_merge_revision = getattr(lf,
207
 
        'supports_single_merge_revision', False)
208
 
    view_revisions = calculate_view_revisions(branch, start_revision,
209
 
                                              end_revision, direction,
210
 
                                              specific_fileid,
211
 
                                              generate_merge_revisions,
212
 
                                              allow_single_merge_revision)
213
 
    rev_tag_dict = {}
214
 
    generate_tags = getattr(lf, 'supports_tags', False)
215
 
    if generate_tags:
216
 
        if branch.supports_tags():
217
 
            rev_tag_dict = branch.tags.get_reverse_tag_dict()
218
 
 
219
 
    generate_delta = verbose and getattr(lf, 'supports_delta', False)
220
 
 
221
 
    # now we just print all the revisions
222
 
    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
234
 
 
235
 
 
236
 
def calculate_view_revisions(branch, start_revision, end_revision, direction,
237
 
                             specific_fileid, generate_merge_revisions,
238
 
                             allow_single_merge_revision):
239
 
    if (not generate_merge_revisions and start_revision is end_revision is
240
 
        None and direction == 'reverse' and specific_fileid is None):
241
 
        return _linear_view_revisions(branch)
 
196
        mutter('get log for file_id %r', specific_fileid)
 
197
 
 
198
    if search is not None:
 
199
        import re
 
200
        searchRE = re.compile(search, re.IGNORECASE)
 
201
    else:
 
202
        searchRE = None
242
203
 
243
204
    mainline_revs, rev_nos, start_rev_id, end_rev_id = \
244
205
        _get_mainline_revs(branch, start_revision, end_revision)
245
206
    if not mainline_revs:
246
 
        return []
 
207
        return
247
208
 
248
209
    if direction == 'reverse':
249
210
        start_rev_id, end_rev_id = end_rev_id, start_rev_id
250
 
 
251
 
    generate_single_revision = False
252
 
    if ((not generate_merge_revisions)
253
 
        and ((start_rev_id and (start_rev_id not in rev_nos))
254
 
            or (end_rev_id and (end_rev_id not in rev_nos)))):
255
 
        generate_single_revision = ((start_rev_id == end_rev_id)
256
 
            and allow_single_merge_revision)
257
 
        if not generate_single_revision:
258
 
            raise errors.BzrCommandError('Selected log formatter only supports'
259
 
                ' mainline revisions.')
260
 
        generate_merge_revisions = generate_single_revision
 
211
        
 
212
    legacy_lf = getattr(lf, 'log_revision', None) is None
 
213
    if legacy_lf:
 
214
        # pre-0.17 formatters use show for mainline revisions.
 
215
        # how should we show merged revisions ?
 
216
        #   pre-0.11 api: show_merge
 
217
        #   0.11-0.16 api: show_merge_revno
 
218
        show_merge_revno = getattr(lf, 'show_merge_revno', None)
 
219
        show_merge = getattr(lf, 'show_merge', None)
 
220
        if show_merge is None and show_merge_revno is None:
 
221
            # no merged-revno support
 
222
            generate_merge_revisions = False
 
223
        else:
 
224
            generate_merge_revisions = True
 
225
        # tell developers to update their code
 
226
        symbol_versioning.warn('LogFormatters should provide log_revision '
 
227
            'instead of show and show_merge_revno since bzr 0.17.',
 
228
            DeprecationWarning, stacklevel=3)
 
229
    else:
 
230
        generate_merge_revisions = getattr(lf, 'supports_merge_revisions', 
 
231
                                           False)
261
232
    view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
262
233
                          direction, include_merges=generate_merge_revisions)
263
234
    view_revisions = _filter_revision_range(list(view_revs_iter),
264
235
                                            start_rev_id,
265
236
                                            end_rev_id)
266
 
    if view_revisions and generate_single_revision:
267
 
        view_revisions = view_revisions[0:1]
268
237
    if specific_fileid:
269
238
        view_revisions = _filter_revisions_touching_file_id(branch,
270
239
                                                         specific_fileid,
277
246
        min_depth = min([d for r,n,d in view_revisions])
278
247
        if min_depth != 0:
279
248
            view_revisions = [(r,n,d-min_depth) for r,n,d in view_revisions]
280
 
    return view_revisions
281
 
 
282
 
 
283
 
def _linear_view_revisions(branch):
284
 
    start_revno, start_revision_id = branch.last_revision_info()
285
 
    repo = branch.repository
286
 
    revision_ids = repo.iter_reverse_revision_history(start_revision_id)
287
 
    for num, revision_id in enumerate(revision_ids):
288
 
        yield revision_id, str(start_revno - num), 0
289
 
 
290
 
 
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
403
 
    num = 9
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:
 
249
        
 
250
    rev_tag_dict = {}
 
251
    generate_tags = getattr(lf, 'supports_tags', False)
 
252
    if generate_tags:
 
253
        if branch.supports_tags():
 
254
            rev_tag_dict = branch.tags.get_reverse_tag_dict()
 
255
 
 
256
    generate_delta = verbose and getattr(lf, 'supports_delta', False)
 
257
 
 
258
    def iter_revisions():
 
259
        # r = revision, n = revno, d = merge depth
 
260
        revision_ids = [r for r, n, d in view_revisions]
 
261
        num = 9
 
262
        repository = branch.repository
 
263
        while revision_ids:
 
264
            cur_deltas = {}
 
265
            revisions = repository.get_revisions(revision_ids[:num])
 
266
            if generate_delta:
 
267
                deltas = repository.get_deltas_for_revisions(revisions)
 
268
                cur_deltas = dict(izip((r.revision_id for r in revisions),
 
269
                                       deltas))
 
270
            for revision in revisions:
 
271
                yield revision, cur_deltas.get(revision.revision_id)
 
272
            revision_ids  = revision_ids[num:]
 
273
            num = min(int(num * 1.5), 200)
 
274
 
 
275
    # now we just print all the revisions
 
276
    log_count = 0
 
277
    for ((rev_id, revno, merge_depth), (rev, delta)) in \
 
278
         izip(view_revisions, iter_revisions()):
 
279
 
 
280
        if searchRE:
 
281
            if not searchRE.search(rev.message):
 
282
                continue
 
283
 
 
284
        if not legacy_lf:
 
285
            lr = LogRevision(rev, revno, merge_depth, delta,
 
286
                             rev_tag_dict.get(rev_id))
 
287
            lf.log_revision(lr)
 
288
        else:
 
289
            # support for legacy (pre-0.17) LogFormatters
 
290
            if merge_depth == 0:
 
291
                if generate_tags:
 
292
                    lf.show(revno, rev, delta, rev_tag_dict.get(rev_id))
 
293
                else:
 
294
                    lf.show(revno, rev, delta)
 
295
            else:
 
296
                if show_merge_revno is None:
 
297
                    lf.show_merge(rev, merge_depth)
 
298
                else:
 
299
                    if generate_tags:
 
300
                        lf.show_merge_revno(rev, merge_depth, revno,
 
301
                                            rev_tag_dict.get(rev_id))
 
302
                    else:
 
303
                        lf.show_merge_revno(rev, merge_depth, revno)
 
304
        if limit:
 
305
            log_count += 1
 
306
            if log_count >= limit:
409
307
                break
410
 
            yield step
411
 
            num = min(int(num * 1.5), 200)
412
308
 
413
309
 
414
310
def _get_mainline_revs(branch, start_revision, end_revision):
428
324
 
429
325
    :return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
430
326
    """
431
 
    branch_revno, branch_last_revision = branch.last_revision_info()
432
 
    if branch_revno == 0:
 
327
    which_revs = _enumerate_history(branch)
 
328
    if not which_revs:
433
329
        return None, None, None, None
434
330
 
435
331
    # For mainline generation, map start_revision and end_revision to 
442
338
    if start_revision is None:
443
339
        start_revno = 1
444
340
    else:
445
 
        if isinstance(start_revision, revisionspec.RevisionInfo):
 
341
        if isinstance(start_revision,RevisionInfo):
446
342
            start_rev_id = start_revision.rev_id
447
343
            start_revno = start_revision.revno or 1
448
344
        else:
451
347
    
452
348
    end_rev_id = None
453
349
    if end_revision is None:
454
 
        end_revno = branch_revno
 
350
        end_revno = len(which_revs)
455
351
    else:
456
 
        if isinstance(end_revision, revisionspec.RevisionInfo):
 
352
        if isinstance(end_revision,RevisionInfo):
457
353
            end_rev_id = end_revision.rev_id
458
 
            end_revno = end_revision.revno or branch_revno
 
354
            end_revno = end_revision.revno or len(which_revs)
459
355
        else:
460
356
            branch.check_real_revno(end_revision)
461
357
            end_revno = end_revision
462
358
 
463
 
    if ((start_rev_id == _mod_revision.NULL_REVISION)
464
 
        or (end_rev_id == _mod_revision.NULL_REVISION)):
465
 
        raise errors.BzrCommandError('Logging revision 0 is invalid.')
466
359
    if start_revno > end_revno:
467
 
        raise errors.BzrCommandError("Start revision must be older than "
468
 
                                     "the end revision.")
 
360
        from bzrlib.errors import BzrCommandError
 
361
        raise BzrCommandError("Start revision must be older than "
 
362
                              "the end revision.")
469
363
 
470
 
    if end_revno < start_revno:
 
364
    # list indexes are 0-based; revisions are 1-based
 
365
    cut_revs = which_revs[(start_revno-1):(end_revno)]
 
366
    if not cut_revs:
471
367
        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)
489
368
 
490
 
    mainline_revs.reverse()
 
369
    # convert the revision history to a dictionary:
 
370
    rev_nos = dict((k, v) for v, k in cut_revs)
491
371
 
492
372
    # override the mainline to look like the revision history.
 
373
    mainline_revs = [revision_id for index, revision_id in cut_revs]
 
374
    if cut_revs[0][0] == 1:
 
375
        mainline_revs.insert(0, None)
 
376
    else:
 
377
        mainline_revs.insert(0, which_revs[start_revno-2][1])
493
378
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
494
379
 
495
380
 
557
442
    :return: A list of (revision_id, dotted_revno, merge_depth) tuples.
558
443
    """
559
444
    # find all the revisions that change the specific file
 
445
    file_weave = branch.repository.weave_store.get_weave(file_id,
 
446
                branch.repository.get_transaction())
 
447
    weave_modifed_revisions = set(file_weave.versions())
560
448
    # build the ancestry of each revision in the graph
561
449
    # - only listing the ancestors that change the specific file.
562
 
    graph = branch.repository.get_graph()
563
 
    # This asks for all mainline revisions, which means we only have to spider
564
 
    # sideways, rather than depth history. That said, its still size-of-history
565
 
    # and should be addressed.
566
 
    # mainline_revisions always includes an extra revision at the beginning, so
567
 
    # don't request it.
568
 
    parent_map = dict(((key, value) for key, value in
569
 
        graph.iter_ancestry(mainline_revisions[1:]) if value is not None))
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)
 
450
    rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
 
451
    sorted_rev_list = topo_sort(rev_graph)
573
452
    ancestry = {}
574
453
    for rev in sorted_rev_list:
575
 
        text_key = (file_id, rev)
576
 
        parents = parent_map[rev]
577
 
        if text_key not in modified_text_versions and len(parents) == 1:
 
454
        parents = rev_graph[rev]
 
455
        if rev not in weave_modifed_revisions and len(parents) == 1:
578
456
            # We will not be adding anything new, so just use a reference to
579
457
            # the parent ancestry.
580
458
            rev_ancestry = ancestry[parents[0]]
581
459
        else:
582
460
            rev_ancestry = set()
583
 
            if text_key in modified_text_versions:
 
461
            if rev in weave_modifed_revisions:
584
462
                rev_ancestry.add(rev)
585
463
            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()
591
464
                rev_ancestry = rev_ancestry.union(ancestry[parent])
592
465
        ancestry[rev] = rev_ancestry
593
466
 
594
467
    def is_merging_rev(r):
595
 
        parents = parent_map[r]
 
468
        parents = rev_graph[r]
596
469
        if len(parents) > 1:
597
470
            leftparent = parents[0]
598
471
            for rightparent in parents[1:]:
604
477
    # filter from the view the revisions that did not change or merge 
605
478
    # the specific file
606
479
    return [(r, n, d) for r, n, d in view_revs_iter
607
 
            if (file_id, r) in modified_text_versions or is_merging_rev(r)]
 
480
            if r in weave_modifed_revisions or is_merging_rev(r)]
608
481
 
609
482
 
610
483
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
620
493
        for revision_id in revision_ids:
621
494
            yield revision_id, str(rev_nos[revision_id]), 0
622
495
        return
623
 
    graph = branch.repository.get_graph()
624
 
    # This asks for all mainline revisions, which means we only have to spider
625
 
    # sideways, rather than depth history. That said, its still size-of-history
626
 
    # and should be addressed.
627
 
    # mainline_revisions always includes an extra revision at the beginning, so
628
 
    # don't request it.
629
 
    parent_map = dict(((key, value) for key, value in
630
 
        graph.iter_ancestry(mainline_revs[1:]) if value is not None))
631
 
    # filter out ghosts; merge_sort errors on ghosts.
632
 
    rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
633
 
    merge_sorted_revisions = tsort.merge_sort(
634
 
        rev_graph,
 
496
    merge_sorted_revisions = merge_sort(
 
497
        branch.repository.get_revision_graph(mainline_revs[-1]),
635
498
        mainline_revs[-1],
636
499
        mainline_revs,
637
500
        generate_revno=True)
658
521
        if val[2] == _depth:
659
522
            zd_revisions.append([val])
660
523
        else:
 
524
            assert val[2] > _depth
661
525
            zd_revisions[-1].append(val)
662
526
    for revisions in zd_revisions:
663
527
        if len(revisions) > 1:
700
564
    - supports_delta must be True if this log formatter supports delta.
701
565
        Otherwise the delta attribute may not be populated.
702
566
    - supports_merge_revisions must be True if this log formatter supports 
703
 
        merge revisions.  If not, and if supports_single_merge_revisions is
704
 
        also not True, then only mainline revisions will be passed to the 
705
 
        formatter.
706
 
    - supports_single_merge_revision must be True if this log formatter
707
 
        supports logging only a single merge revision.  This flag is
708
 
        only relevant if supports_merge_revisions is not True.
 
567
        merge revisions.  If not, only mainline revisions (those 
 
568
        with merge_depth == 0) will be passed to the formatter.
709
569
    - supports_tags must be True if this log formatter supports tags.
710
570
        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
718
571
    """
719
572
 
720
573
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
732
585
#        """
733
586
#        raise NotImplementedError('not implemented in abstract base')
734
587
 
 
588
    @deprecated_method(zero_seventeen)
 
589
    def show(self, revno, rev, delta):
 
590
        raise NotImplementedError('not implemented in abstract base')
 
591
 
735
592
    def short_committer(self, rev):
736
 
        name, address = config.parse_username(rev.committer)
737
 
        if name:
738
 
            return name
739
 
        return address
 
593
        return re.sub('<.*@.*>', '', rev.committer).strip(' ')
740
594
 
741
595
    def short_author(self, rev):
742
 
        name, address = config.parse_username(rev.get_apparent_author())
743
 
        if name:
744
 
            return name
745
 
        return address
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')
 
596
        return re.sub('<.*@.*>', '', rev.get_apparent_author()).strip(' ')
755
597
 
756
598
 
757
599
class LongLogFormatter(LogFormatter):
760
602
    supports_delta = True
761
603
    supports_tags = True
762
604
 
 
605
    @deprecated_method(zero_seventeen)
 
606
    def show(self, revno, rev, delta, tags=None):
 
607
        lr = LogRevision(rev, revno, 0, delta, tags)
 
608
        return self.log_revision(lr)
 
609
 
 
610
    @deprecated_method(zero_seventeen)
 
611
    def show_merge_revno(self, rev, merge_depth, revno, tags=None):
 
612
        """Show a merged revision rev, with merge_depth and a revno."""
 
613
        lr = LogRevision(rev, revno, merge_depth, tags=tags)
 
614
        return self.log_revision(lr)
 
615
 
763
616
    def log_revision(self, revision):
764
617
        """Log a revision, either merged or not."""
 
618
        from bzrlib.osutils import format_date
765
619
        indent = '    ' * revision.merge_depth
766
620
        to_file = self.to_file
767
621
        to_file.write(indent + '-' * 60 + '\n')
770
624
        if revision.tags:
771
625
            to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
772
626
        if self.show_ids:
773
 
            to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
 
627
            to_file.write(indent + 'revision-id:' + revision.rev.revision_id)
774
628
            to_file.write('\n')
775
629
            for parent_id in revision.rev.parent_ids:
776
630
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
777
 
        self.show_properties(revision.rev, indent)
778
631
 
779
632
        author = revision.rev.properties.get('author', None)
780
633
        if author is not None:
804
657
class ShortLogFormatter(LogFormatter):
805
658
 
806
659
    supports_delta = True
807
 
    supports_single_merge_revision = True
 
660
 
 
661
    @deprecated_method(zero_seventeen)
 
662
    def show(self, revno, rev, delta):
 
663
        lr = LogRevision(rev, revno, 0, delta)
 
664
        return self.log_revision(lr)
808
665
 
809
666
    def log_revision(self, revision):
 
667
        from bzrlib.osutils import format_date
 
668
 
810
669
        to_file = self.to_file
 
670
        date_str = format_date(revision.rev.timestamp,
 
671
                               revision.rev.timezone or 0,
 
672
                               self.show_timezone)
811
673
        is_merge = ''
812
674
        if len(revision.rev.parent_ids) > 1:
813
675
            is_merge = ' [merge]'
836
698
 
837
699
class LineLogFormatter(LogFormatter):
838
700
 
839
 
    supports_single_merge_revision = True
840
 
 
841
701
    def __init__(self, *args, **kwargs):
 
702
        from bzrlib.osutils import terminal_width
842
703
        super(LineLogFormatter, self).__init__(*args, **kwargs)
843
704
        self._max_chars = terminal_width() - 1
844
705
 
848
709
        return str[:max_len-3]+'...'
849
710
 
850
711
    def date_string(self, rev):
 
712
        from bzrlib.osutils import format_date
851
713
        return format_date(rev.timestamp, rev.timezone or 0, 
852
714
                           self.show_timezone, date_fmt="%Y-%m-%d",
853
715
                           show_offset=False)
858
720
        else:
859
721
            return rev.message
860
722
 
 
723
    @deprecated_method(zero_seventeen)
 
724
    def show(self, revno, rev, delta):
 
725
        from bzrlib.osutils import terminal_width
 
726
        self.to_file.write(self.log_string(revno, rev, terminal_width()-1))
 
727
        self.to_file.write('\n')
 
728
 
861
729
    def log_revision(self, revision):
862
730
        self.to_file.write(self.log_string(revision.revno, revision.rev,
863
731
                                              self._max_chars))
922
790
    name -- Name of the formatter to construct; currently 'long', 'short' and
923
791
        'line' are supported.
924
792
    """
 
793
    from bzrlib.errors import BzrCommandError
925
794
    try:
926
795
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
927
796
    except KeyError:
928
 
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
 
797
        raise BzrCommandError("unknown log formatter: %r" % name)
929
798
 
930
799
 
931
800
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
944
813
    :param to_file: A file to write the results to. If None, stdout will be used
945
814
    """
946
815
    if to_file is None:
947
 
        to_file = codecs.getwriter(get_terminal_encoding())(sys.stdout,
948
 
            errors='replace')
 
816
        import sys
 
817
        import codecs
 
818
        import bzrlib
 
819
        to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout,
 
820
                                                         errors='replace')
949
821
    lf = log_formatter(log_format,
950
822
                       show_ids=False,
951
823
                       to_file=to_file,
988
860
                 end_revision=len(new_rh),
989
861
                 search=None)
990
862
 
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
 
    ]