~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

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
 
from itertools import izip
 
52
import codecs
 
53
from itertools import (
 
54
    izip,
 
55
    )
53
56
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
""")
54
75
 
55
76
from bzrlib import (
56
77
    registry,
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,
 
78
    )
 
79
from bzrlib.osutils import (
 
80
    format_date,
 
81
    get_terminal_encoding,
 
82
    terminal_width,
71
83
    )
72
84
 
73
85
 
174
186
    finally:
175
187
        branch.unlock()
176
188
 
 
189
 
177
190
def _show_log(branch,
178
191
             lf,
179
192
             specific_fileid=None,
184
197
             search=None,
185
198
             limit=None):
186
199
    """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
 
 
192
200
    if not isinstance(lf, LogFormatter):
193
201
        warn("not a LogFormatter instance: %r" % lf)
194
202
 
195
203
    if specific_fileid:
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
 
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)
203
242
 
204
243
    mainline_revs, rev_nos, start_rev_id, end_rev_id = \
205
244
        _get_mainline_revs(branch, start_revision, end_revision)
206
245
    if not mainline_revs:
207
 
        return
 
246
        return []
208
247
 
209
248
    if direction == 'reverse':
210
249
        start_rev_id, end_rev_id = end_rev_id, start_rev_id
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)
 
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
232
261
    view_revs_iter = get_view_revisions(mainline_revs, rev_nos, branch,
233
262
                          direction, include_merges=generate_merge_revisions)
234
263
    view_revisions = _filter_revision_range(list(view_revs_iter),
235
264
                                            start_rev_id,
236
265
                                            end_rev_id)
 
266
    if view_revisions and generate_single_revision:
 
267
        view_revisions = view_revisions[0:1]
237
268
    if specific_fileid:
238
269
        view_revisions = _filter_revisions_touching_file_id(branch,
239
270
                                                         specific_fileid,
246
277
        min_depth = min([d for r,n,d in view_revisions])
247
278
        if min_depth != 0:
248
279
            view_revisions = [(r,n,d-min_depth) for r,n,d in view_revisions]
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:]
 
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:
 
409
                break
 
410
            yield step
273
411
            num = min(int(num * 1.5), 200)
274
412
 
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:
307
 
                break
308
 
 
309
413
 
310
414
def _get_mainline_revs(branch, start_revision, end_revision):
311
415
    """Get the mainline revisions from the branch.
324
428
 
325
429
    :return: A (mainline_revs, rev_nos, start_rev_id, end_rev_id) tuple.
326
430
    """
327
 
    which_revs = _enumerate_history(branch)
328
 
    if not which_revs:
 
431
    branch_revno, branch_last_revision = branch.last_revision_info()
 
432
    if branch_revno == 0:
329
433
        return None, None, None, None
330
434
 
331
435
    # For mainline generation, map start_revision and end_revision to 
338
442
    if start_revision is None:
339
443
        start_revno = 1
340
444
    else:
341
 
        if isinstance(start_revision,RevisionInfo):
 
445
        if isinstance(start_revision, revisionspec.RevisionInfo):
342
446
            start_rev_id = start_revision.rev_id
343
447
            start_revno = start_revision.revno or 1
344
448
        else:
347
451
    
348
452
    end_rev_id = None
349
453
    if end_revision is None:
350
 
        end_revno = len(which_revs)
 
454
        end_revno = branch_revno
351
455
    else:
352
 
        if isinstance(end_revision,RevisionInfo):
 
456
        if isinstance(end_revision, revisionspec.RevisionInfo):
353
457
            end_rev_id = end_revision.rev_id
354
 
            end_revno = end_revision.revno or len(which_revs)
 
458
            end_revno = end_revision.revno or branch_revno
355
459
        else:
356
460
            branch.check_real_revno(end_revision)
357
461
            end_revno = end_revision
358
462
 
 
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.')
359
466
    if start_revno > end_revno:
360
 
        from bzrlib.errors import BzrCommandError
361
 
        raise BzrCommandError("Start revision must be older than "
362
 
                              "the end revision.")
 
467
        raise errors.BzrCommandError("Start revision must be older than "
 
468
                                     "the end revision.")
363
469
 
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:
 
470
    if end_revno < start_revno:
367
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)
368
489
 
369
 
    # convert the revision history to a dictionary:
370
 
    rev_nos = dict((k, v) for v, k in cut_revs)
 
490
    mainline_revs.reverse()
371
491
 
372
492
    # 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])
378
493
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
379
494
 
380
495
 
442
557
    :return: A list of (revision_id, dotted_revno, merge_depth) tuples.
443
558
    """
444
559
    # 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())
448
560
    # build the ancestry of each revision in the graph
449
561
    # - only listing the ancestors that change the specific file.
450
 
    rev_graph = branch.repository.get_revision_graph(mainline_revisions[-1])
451
 
    sorted_rev_list = topo_sort(rev_graph)
 
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)
452
573
    ancestry = {}
453
574
    for rev in sorted_rev_list:
454
 
        parents = rev_graph[rev]
455
 
        if rev not in weave_modifed_revisions and len(parents) == 1:
 
575
        text_key = (file_id, rev)
 
576
        parents = parent_map[rev]
 
577
        if text_key not in modified_text_versions and len(parents) == 1:
456
578
            # We will not be adding anything new, so just use a reference to
457
579
            # the parent ancestry.
458
580
            rev_ancestry = ancestry[parents[0]]
459
581
        else:
460
582
            rev_ancestry = set()
461
 
            if rev in weave_modifed_revisions:
 
583
            if text_key in modified_text_versions:
462
584
                rev_ancestry.add(rev)
463
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()
464
591
                rev_ancestry = rev_ancestry.union(ancestry[parent])
465
592
        ancestry[rev] = rev_ancestry
466
593
 
467
594
    def is_merging_rev(r):
468
 
        parents = rev_graph[r]
 
595
        parents = parent_map[r]
469
596
        if len(parents) > 1:
470
597
            leftparent = parents[0]
471
598
            for rightparent in parents[1:]:
477
604
    # filter from the view the revisions that did not change or merge 
478
605
    # the specific file
479
606
    return [(r, n, d) for r, n, d in view_revs_iter
480
 
            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)]
481
608
 
482
609
 
483
610
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
493
620
        for revision_id in revision_ids:
494
621
            yield revision_id, str(rev_nos[revision_id]), 0
495
622
        return
496
 
    merge_sorted_revisions = merge_sort(
497
 
        branch.repository.get_revision_graph(mainline_revs[-1]),
 
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,
498
635
        mainline_revs[-1],
499
636
        mainline_revs,
500
637
        generate_revno=True)
521
658
        if val[2] == _depth:
522
659
            zd_revisions.append([val])
523
660
        else:
524
 
            assert val[2] > _depth
525
661
            zd_revisions[-1].append(val)
526
662
    for revisions in zd_revisions:
527
663
        if len(revisions) > 1:
564
700
    - supports_delta must be True if this log formatter supports delta.
565
701
        Otherwise the delta attribute may not be populated.
566
702
    - supports_merge_revisions must be True if this log formatter supports 
567
 
        merge revisions.  If not, only mainline revisions (those 
568
 
        with merge_depth == 0) will be passed to the formatter.
 
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.
569
709
    - supports_tags must be True if this log formatter supports tags.
570
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
571
718
    """
572
719
 
573
720
    def __init__(self, to_file, show_ids=False, show_timezone='original'):
585
732
#        """
586
733
#        raise NotImplementedError('not implemented in abstract base')
587
734
 
588
 
    @deprecated_method(zero_seventeen)
589
 
    def show(self, revno, rev, delta):
590
 
        raise NotImplementedError('not implemented in abstract base')
591
 
 
592
735
    def short_committer(self, rev):
593
 
        return re.sub('<.*@.*>', '', rev.committer).strip(' ')
 
736
        name, address = config.parse_username(rev.committer)
 
737
        if name:
 
738
            return name
 
739
        return address
594
740
 
595
741
    def short_author(self, rev):
596
 
        return re.sub('<.*@.*>', '', rev.get_apparent_author()).strip(' ')
 
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')
597
755
 
598
756
 
599
757
class LongLogFormatter(LogFormatter):
602
760
    supports_delta = True
603
761
    supports_tags = True
604
762
 
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
 
 
616
763
    def log_revision(self, revision):
617
764
        """Log a revision, either merged or not."""
618
 
        from bzrlib.osutils import format_date
619
765
        indent = '    ' * revision.merge_depth
620
766
        to_file = self.to_file
621
767
        to_file.write(indent + '-' * 60 + '\n')
624
770
        if revision.tags:
625
771
            to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
626
772
        if self.show_ids:
627
 
            to_file.write(indent + 'revision-id:' + revision.rev.revision_id)
 
773
            to_file.write(indent + 'revision-id: ' + revision.rev.revision_id)
628
774
            to_file.write('\n')
629
775
            for parent_id in revision.rev.parent_ids:
630
776
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
 
777
        self.show_properties(revision.rev, indent)
631
778
 
632
779
        author = revision.rev.properties.get('author', None)
633
780
        if author is not None:
657
804
class ShortLogFormatter(LogFormatter):
658
805
 
659
806
    supports_delta = 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)
 
807
    supports_single_merge_revision = True
665
808
 
666
809
    def log_revision(self, revision):
667
 
        from bzrlib.osutils import format_date
668
 
 
669
810
        to_file = self.to_file
670
 
        date_str = format_date(revision.rev.timestamp,
671
 
                               revision.rev.timezone or 0,
672
 
                               self.show_timezone)
673
811
        is_merge = ''
674
812
        if len(revision.rev.parent_ids) > 1:
675
813
            is_merge = ' [merge]'
698
836
 
699
837
class LineLogFormatter(LogFormatter):
700
838
 
 
839
    supports_single_merge_revision = True
 
840
 
701
841
    def __init__(self, *args, **kwargs):
702
 
        from bzrlib.osutils import terminal_width
703
842
        super(LineLogFormatter, self).__init__(*args, **kwargs)
704
843
        self._max_chars = terminal_width() - 1
705
844
 
709
848
        return str[:max_len-3]+'...'
710
849
 
711
850
    def date_string(self, rev):
712
 
        from bzrlib.osutils import format_date
713
851
        return format_date(rev.timestamp, rev.timezone or 0, 
714
852
                           self.show_timezone, date_fmt="%Y-%m-%d",
715
853
                           show_offset=False)
720
858
        else:
721
859
            return rev.message
722
860
 
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
 
 
729
861
    def log_revision(self, revision):
730
862
        self.to_file.write(self.log_string(revision.revno, revision.rev,
731
863
                                              self._max_chars))
790
922
    name -- Name of the formatter to construct; currently 'long', 'short' and
791
923
        'line' are supported.
792
924
    """
793
 
    from bzrlib.errors import BzrCommandError
794
925
    try:
795
926
        return log_formatter_registry.make_formatter(name, *args, **kwargs)
796
927
    except KeyError:
797
 
        raise BzrCommandError("unknown log formatter: %r" % name)
 
928
        raise errors.BzrCommandError("unknown log formatter: %r" % name)
798
929
 
799
930
 
800
931
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
813
944
    :param to_file: A file to write the results to. If None, stdout will be used
814
945
    """
815
946
    if to_file is None:
816
 
        import sys
817
 
        import codecs
818
 
        import bzrlib
819
 
        to_file = codecs.getwriter(bzrlib.user_encoding)(sys.stdout,
820
 
                                                         errors='replace')
 
947
        to_file = codecs.getwriter(get_terminal_encoding())(sys.stdout,
 
948
            errors='replace')
821
949
    lf = log_formatter(log_format,
822
950
                       show_ids=False,
823
951
                       to_file=to_file,
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
    ]